mirror of
https://github.com/taigrr/arduinolibs
synced 2025-01-18 04:33:12 -08:00
254 lines
7.5 KiB
C++
254 lines
7.5 KiB
C++
/*
|
|
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "json-reader.h"
|
|
#include <stdarg.h>
|
|
|
|
/* Note: This is not a complete JSON parser. It has enough support
|
|
to process the Noise test vector format only */
|
|
|
|
#define JSON_STR_MAX 8192
|
|
|
|
/**
|
|
* \brief Initializes a JSON reader object.
|
|
*
|
|
* \param reader The reader to initialize.
|
|
* \param stream The stream to read from.
|
|
*/
|
|
void json_init(JSONReader *reader, const char *filename, FILE *stream)
|
|
{
|
|
reader->stream = stream;
|
|
reader->token = JSON_TOKEN_START;
|
|
reader->str_value = 0;
|
|
reader->filename = filename;
|
|
reader->line_number = 1;
|
|
reader->saw_eof = 0;
|
|
reader->errors = 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Frees a JSON reader object.
|
|
*
|
|
* \param reader The reader to free.
|
|
*/
|
|
void json_free(JSONReader *reader)
|
|
{
|
|
reader->stream = 0;
|
|
reader->token = JSON_TOKEN_END;
|
|
reader->filename = 0;
|
|
reader->line_number = 1;
|
|
reader->saw_eof = 1;
|
|
reader->errors = 0;
|
|
if (reader->str_value) {
|
|
free(reader->str_value);
|
|
reader->str_value = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Recognizes a named token.
|
|
*
|
|
* \param reader The reader.
|
|
* \param token The token code that we expect to recognize.
|
|
* \param name The name of the token. The first character is assumed
|
|
* to have already been recognized.
|
|
*/
|
|
static void json_named_token
|
|
(JSONReader *reader, JSONToken token, const char *name)
|
|
{
|
|
const char *n = name + 1;
|
|
int ch;
|
|
for (;;) {
|
|
ch = getc(reader->stream);
|
|
if (*n == '\0') {
|
|
if (ch == ',' || ch == ' ' || ch == '\t' ||
|
|
ch == '\r' || ch == '\n') {
|
|
ungetc(ch, reader->stream);
|
|
reader->token = token;
|
|
return;
|
|
} else if (ch == EOF) {
|
|
reader->token = token;
|
|
reader->saw_eof = 1;
|
|
return;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (ch == EOF || ch != *n)
|
|
break;
|
|
++n;
|
|
}
|
|
json_error(reader, "Could not recognize '%s' token", name);
|
|
reader->token = JSON_TOKEN_END;
|
|
}
|
|
|
|
/**
|
|
* \brief Reads the next token from the input stream.
|
|
*
|
|
* \param reader The reader.
|
|
*
|
|
* \return The token code.
|
|
*/
|
|
JSONToken json_next_token(JSONReader *reader)
|
|
{
|
|
int ch;
|
|
|
|
/* Bail out if we already reached the end of the stream */
|
|
if (reader->token == JSON_TOKEN_END)
|
|
return JSON_TOKEN_END;
|
|
if (reader->saw_eof) {
|
|
reader->token = JSON_TOKEN_END;
|
|
return JSON_TOKEN_END;
|
|
}
|
|
|
|
/* Free the previous token's string value */
|
|
if (reader->str_value) {
|
|
free(reader->str_value);
|
|
reader->str_value = 0;
|
|
}
|
|
|
|
/* Skip whitespace */
|
|
for (;;) {
|
|
ch = getc(reader->stream);
|
|
if (ch == EOF) {
|
|
reader->token = JSON_TOKEN_END;
|
|
reader->saw_eof = 1;
|
|
return reader->token;
|
|
} else if (ch == '\n') {
|
|
++(reader->line_number);
|
|
} else if (ch != ' ' && ch != '\t' && ch != '\r') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Parse the next token */
|
|
if (ch == '{') {
|
|
reader->token = JSON_TOKEN_LBRACE;
|
|
} else if (ch == '}') {
|
|
reader->token = JSON_TOKEN_RBRACE;
|
|
} else if (ch == '[') {
|
|
reader->token = JSON_TOKEN_LSQUARE;
|
|
} else if (ch == ']') {
|
|
reader->token = JSON_TOKEN_RSQUARE;
|
|
} else if (ch == ',') {
|
|
reader->token = JSON_TOKEN_COMMA;
|
|
} else if (ch == ':') {
|
|
reader->token = JSON_TOKEN_COLON;
|
|
} else if (ch == 't') {
|
|
json_named_token(reader, JSON_TOKEN_TRUE, "true");
|
|
} else if (ch == 'f') {
|
|
json_named_token(reader, JSON_TOKEN_FALSE, "false");
|
|
} else if (ch == 'n') {
|
|
json_named_token(reader, JSON_TOKEN_FALSE, "null");
|
|
} else if (ch == '"') {
|
|
/* Recognize very simple strings with no escaping */
|
|
char buffer[JSON_STR_MAX];
|
|
size_t posn = 0;
|
|
for (;;) {
|
|
ch = getc(reader->stream);
|
|
if (ch == '"') {
|
|
break;
|
|
} else if (ch == '\r' || ch == '\n' || ch == EOF) {
|
|
json_error(reader, "Unterminated string");
|
|
reader->token = JSON_TOKEN_END;
|
|
return reader->token;
|
|
} else if (ch == '\\') {
|
|
json_error(reader, "String escapes are not supported");
|
|
reader->token = JSON_TOKEN_END;
|
|
return reader->token;
|
|
} else {
|
|
if (posn >= (sizeof(buffer) - 1)) {
|
|
json_error(reader, "String is too long");
|
|
reader->token = JSON_TOKEN_END;
|
|
return reader->token;
|
|
}
|
|
buffer[posn++] = (char)ch;
|
|
}
|
|
}
|
|
buffer[posn] = '\0';
|
|
reader->str_value = (char *)malloc(posn + 1);
|
|
if (!(reader->str_value)) {
|
|
json_error(reader, "Out of memory");
|
|
reader->token = JSON_TOKEN_END;
|
|
return reader->token;
|
|
}
|
|
strcpy(reader->str_value, buffer);
|
|
reader->token = JSON_TOKEN_STRING;
|
|
} else {
|
|
/* Unknown character. Note: numbers are not yet supported. */
|
|
json_error(reader, "Unknown character 0x%02x", ch);
|
|
reader->token = JSON_TOKEN_END;
|
|
}
|
|
|
|
return reader->token;
|
|
}
|
|
|
|
/**
|
|
* \brief Matches the current token against a specific field name.
|
|
*
|
|
* \param reader The reader.
|
|
* \param name The name of the field.
|
|
*
|
|
* \return Returns 1 if the name matches, 0 if not.
|
|
*/
|
|
int json_is_name(JSONReader *reader, const char *name)
|
|
{
|
|
if (reader->token != JSON_TOKEN_STRING || !reader->str_value)
|
|
return 0;
|
|
return !strcmp(reader->str_value, name);
|
|
}
|
|
|
|
/**
|
|
* \brief Reports an error on the current line of the input.
|
|
*
|
|
* \param reader The reader.
|
|
* \param format The printf-style format to use.
|
|
*/
|
|
void json_error(JSONReader *reader, const char *format, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, format);
|
|
fprintf(stderr, "%s:%ld: ", reader->filename, reader->line_number);
|
|
vfprintf(stderr, format, va);
|
|
putc('\n', stderr);
|
|
va_end(va);
|
|
++(reader->errors);
|
|
}
|
|
|
|
/**
|
|
* \brief Reports an error on a specific line of the input.
|
|
*
|
|
* \param reader The reader.
|
|
* \param line The line number to report in the error message.
|
|
* \param format The printf-style format to use.
|
|
*/
|
|
void json_error_on_line(JSONReader *reader, long line, const char *format, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, format);
|
|
fprintf(stderr, "%s:%ld: ", reader->filename, line);
|
|
vfprintf(stderr, format, va);
|
|
putc('\n', stderr);
|
|
va_end(va);
|
|
++(reader->errors);
|
|
}
|