| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| |
| #include <proton/parser.h> |
| #include <proton/scanner.h> |
| #include <proton/error.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include "platform.h" |
| |
| struct pn_parser_t { |
| pn_scanner_t *scanner; |
| char *atoms; |
| size_t size; |
| size_t capacity; |
| int error_code; |
| }; |
| |
| pn_parser_t *pn_parser() |
| { |
| pn_parser_t *parser = (pn_parser_t *) malloc(sizeof(pn_parser_t)); |
| if (parser != NULL) { |
| parser->scanner = pn_scanner(); |
| parser->atoms = NULL; |
| parser->size = 0; |
| parser->capacity = 0; |
| } |
| return parser; |
| } |
| |
| static void pni_parser_ensure(pn_parser_t *parser, size_t size) |
| { |
| while (parser->capacity - parser->size < size) { |
| parser->capacity = parser->capacity ? 2 * parser->capacity : 1024; |
| parser->atoms = (char *) realloc(parser->atoms, parser->capacity); |
| } |
| } |
| |
| int pn_parser_err(pn_parser_t *parser, int code, const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| int err = pn_scanner_verr(parser->scanner, code, fmt, ap); |
| va_end(ap); |
| return err; |
| } |
| |
| int pn_parser_errno(pn_parser_t *parser) |
| { |
| return pn_scanner_errno(parser->scanner); |
| } |
| |
| const char *pn_parser_error(pn_parser_t *parser) |
| { |
| return pn_scanner_error(parser->scanner); |
| } |
| |
| void pn_parser_free(pn_parser_t *parser) |
| { |
| if (parser) { |
| pn_scanner_free(parser->scanner); |
| free(parser->atoms); |
| free(parser); |
| } |
| } |
| |
| static int pni_parser_shift(pn_parser_t *parser) |
| { |
| return pn_scanner_shift(parser->scanner); |
| } |
| |
| static pn_token_t pni_parser_token(pn_parser_t *parser) |
| { |
| return pn_scanner_token(parser->scanner); |
| } |
| |
| static int pni_parser_value(pn_parser_t *parser, pn_data_t *data); |
| |
| static int pni_parser_descriptor(pn_parser_t *parser, pn_data_t *data) |
| { |
| if (pni_parser_token(parser).type == PN_TOK_AT) { |
| int err = pni_parser_shift(parser); |
| if (err) return err; |
| |
| err = pn_data_put_described(data); |
| if (err) return pn_parser_err(parser, err, "error writing described"); |
| pn_data_enter(data); |
| for (int i = 0; i < 2; i++) { |
| err = pni_parser_value(parser, data); |
| if (err) return err; |
| } |
| pn_data_exit(data); |
| return 0; |
| } else { |
| return pn_parser_err(parser, PN_ERR, "expecting '@'"); |
| } |
| } |
| |
| static int pni_parser_map(pn_parser_t *parser, pn_data_t *data) |
| { |
| if (pni_parser_token(parser).type == PN_TOK_LBRACE) { |
| int err = pni_parser_shift(parser); |
| if (err) return err; |
| |
| err = pn_data_put_map(data); |
| if (err) return pn_parser_err(parser, err, "error writing map"); |
| |
| pn_data_enter(data); |
| |
| if (pni_parser_token(parser).type != PN_TOK_RBRACE) { |
| while (true) { |
| err = pni_parser_value(parser, data); |
| if (err) return err; |
| |
| if (pni_parser_token(parser).type == PN_TOK_EQUAL) { |
| err = pni_parser_shift(parser); |
| if (err) return err; |
| } else { |
| return pn_parser_err(parser, PN_ERR, "expecting '='"); |
| } |
| |
| err = pni_parser_value(parser, data); |
| if (err) return err; |
| |
| if (pni_parser_token(parser).type == PN_TOK_COMMA) { |
| err = pni_parser_shift(parser); |
| if (err) return err; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| pn_data_exit(data); |
| |
| if (pni_parser_token(parser).type == PN_TOK_RBRACE) { |
| return pni_parser_shift(parser); |
| } else { |
| return pn_parser_err(parser, PN_ERR, "expecting '}'"); |
| } |
| } else { |
| return pn_parser_err(parser, PN_ERR, "expecting '{'"); |
| } |
| } |
| |
| static int pni_parser_list(pn_parser_t *parser, pn_data_t *data) |
| { |
| int err; |
| |
| if (pni_parser_token(parser).type == PN_TOK_LBRACKET) { |
| err = pni_parser_shift(parser); |
| if (err) return err; |
| |
| err = pn_data_put_list(data); |
| if (err) return pn_parser_err(parser, err, "error writing list"); |
| |
| pn_data_enter(data); |
| |
| if (pni_parser_token(parser).type != PN_TOK_RBRACKET) { |
| while (true) { |
| err = pni_parser_value(parser, data); |
| if (err) return err; |
| |
| if (pni_parser_token(parser).type == PN_TOK_COMMA) { |
| err = pni_parser_shift(parser); |
| if (err) return err; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| pn_data_exit(data); |
| |
| if (pni_parser_token(parser).type == PN_TOK_RBRACKET) { |
| return pni_parser_shift(parser); |
| } else { |
| return pn_parser_err(parser, PN_ERR, "expecting ']'"); |
| } |
| } else { |
| return pn_parser_err(parser, PN_ERR, "expecting '['"); |
| } |
| } |
| |
| static void pni_parser_append_tok(pn_parser_t *parser, char *dst, int *idx) |
| { |
| memcpy(dst + *idx, pni_parser_token(parser).start, pni_parser_token(parser).size); |
| *idx += pni_parser_token(parser).size; |
| } |
| |
| static int pni_parser_number(pn_parser_t *parser, pn_data_t *data) |
| { |
| bool dbl = false; |
| char number[1024]; |
| int idx = 0; |
| int err; |
| |
| bool negate = false; |
| |
| if (pni_parser_token(parser).type == PN_TOK_NEG || pni_parser_token(parser).type == PN_TOK_POS) { |
| if (pni_parser_token(parser).type == PN_TOK_NEG) |
| negate = !negate; |
| err = pni_parser_shift(parser); |
| if (err) return err; |
| } |
| |
| if (pni_parser_token(parser).type == PN_TOK_FLOAT || pni_parser_token(parser).type == PN_TOK_INT) { |
| dbl = pni_parser_token(parser).type == PN_TOK_FLOAT; |
| pni_parser_append_tok(parser, number, &idx); |
| err = pni_parser_shift(parser); |
| if (err) return err; |
| } else { |
| return pn_parser_err(parser, PN_ERR, "expecting FLOAT or INT"); |
| } |
| |
| number[idx] = '\0'; |
| |
| if (dbl) { |
| double value = atof(number); |
| if (negate) { |
| value = -value; |
| } |
| err = pn_data_put_double(data, value); |
| if (err) return pn_parser_err(parser, err, "error writing double"); |
| } else { |
| int64_t value = pn_i_atoll(number); |
| if (negate) { |
| value = -value; |
| } |
| err = pn_data_put_long(data, value); |
| if (err) return pn_parser_err(parser, err, "error writing long"); |
| } |
| |
| return 0; |
| } |
| |
| static int pni_parser_unquote(pn_parser_t *parser, char *dst, const char *src, size_t *n) |
| { |
| size_t idx = 0; |
| bool escape = false; |
| int start, end; |
| if (src[0] != '"') { |
| if (src[1] == '"') { |
| start = 2; |
| end = *n - 1; |
| } else { |
| start = 1; |
| end = *n; |
| } |
| } else { |
| start = 1; |
| end = *n - 1; |
| } |
| for (int i = start; i < end; i++) |
| { |
| char c = src[i]; |
| if (escape) { |
| switch (c) { |
| case '"': |
| case '\\': |
| case '/': |
| dst[idx++] = c; |
| escape = false; |
| break; |
| case 'b': |
| dst[idx++] = '\b'; |
| break; |
| case 'f': |
| dst[idx++] = '\f'; |
| break; |
| case 'n': |
| dst[idx++] = '\n'; |
| break; |
| case 'r': |
| dst[idx++] = '\r'; |
| break; |
| case 't': |
| dst[idx++] = '\t'; |
| break; |
| case 'x': |
| { |
| char n1 = toupper(src[i+1]); |
| char n2 = n1 ? toupper(src[i+2]) : 0; |
| if (!n2) { |
| return pn_parser_err(parser, PN_ERR, "truncated escape code"); |
| } |
| int d1 = isdigit(n1) ? n1 - '0' : n1 - 'A' + 10; |
| int d2 = isdigit(n2) ? n2 - '0' : n2 - 'A' + 10; |
| dst[idx++] = d1*16 + d2; |
| i += 2; |
| } |
| break; |
| // XXX: need to handle unicode escapes: 'u' |
| default: |
| return pn_parser_err(parser, PN_ERR, "unrecognized escape code"); |
| } |
| escape = false; |
| } else { |
| switch (c) |
| { |
| case '\\': |
| escape = true; |
| break; |
| default: |
| dst[idx++] = c; |
| break; |
| } |
| } |
| } |
| dst[idx++] = '\0'; |
| *n = idx; |
| return 0; |
| } |
| |
| static int pni_parser_value(pn_parser_t *parser, pn_data_t *data) |
| { |
| int err; |
| size_t n; |
| char *dst; |
| |
| pn_token_t tok = pni_parser_token(parser); |
| |
| switch (tok.type) |
| { |
| case PN_TOK_AT: |
| return pni_parser_descriptor(parser, data); |
| case PN_TOK_LBRACE: |
| return pni_parser_map(parser, data); |
| case PN_TOK_LBRACKET: |
| return pni_parser_list(parser, data); |
| case PN_TOK_BINARY: |
| case PN_TOK_SYMBOL: |
| case PN_TOK_STRING: |
| n = tok.size; |
| pni_parser_ensure(parser, n); |
| dst = parser->atoms + parser->size; |
| err = pni_parser_unquote(parser, dst, tok.start, &n); |
| if (err) return err; |
| parser->size += n; |
| switch (tok.type) { |
| case PN_TOK_BINARY: |
| err = pn_data_put_binary(data, pn_bytes(n - 1, dst)); |
| break; |
| case PN_TOK_STRING: |
| err = pn_data_put_string(data, pn_bytes(n - 1, dst)); |
| break; |
| case PN_TOK_SYMBOL: |
| err = pn_data_put_symbol(data, pn_bytes(n - 1, dst)); |
| break; |
| default: |
| return pn_parser_err(parser, PN_ERR, "internal error"); |
| } |
| if (err) return pn_parser_err(parser, err, "error writing string/binary/symbol"); |
| return pni_parser_shift(parser); |
| case PN_TOK_POS: |
| case PN_TOK_NEG: |
| case PN_TOK_FLOAT: |
| case PN_TOK_INT: |
| return pni_parser_number(parser, data); |
| case PN_TOK_TRUE: |
| err = pn_data_put_bool(data, true); |
| if (err) return pn_parser_err(parser, err, "error writing boolean"); |
| return pni_parser_shift(parser); |
| case PN_TOK_FALSE: |
| err = pn_data_put_bool(data, false); |
| if (err) return pn_parser_err(parser, err, "error writing boolean"); |
| return pni_parser_shift(parser); |
| case PN_TOK_NULL: |
| err = pn_data_put_null(data); |
| if (err) return pn_parser_err(parser, err, "error writing null"); |
| return pni_parser_shift(parser); |
| default: |
| return pn_parser_err(parser, PN_ERR, "expecting one of '[', '{', STRING, " |
| "SYMBOL, BINARY, true, false, null, NUMBER"); |
| } |
| } |
| |
| static int pni_parser_parse_r(pn_parser_t *parser, pn_data_t *data) |
| { |
| while (true) { |
| int err; |
| switch (pni_parser_token(parser).type) |
| { |
| case PN_TOK_EOS: |
| return 0; |
| case PN_TOK_ERR: |
| return PN_ERR; |
| default: |
| err = pni_parser_value(parser, data); |
| if (err) return err; |
| } |
| } |
| } |
| |
| int pn_parser_parse(pn_parser_t *parser, const char *str, pn_data_t *data) |
| { |
| int err = pn_scanner_start(parser->scanner, str); |
| if (err) return err; |
| parser->size = 0; |
| return pni_parser_parse_r(parser, data); |
| } |