376 lines
9.7 KiB
C
376 lines
9.7 KiB
C
/**
|
|
rjp
|
|
Copyright (C) 2018-2019 rexy712
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
//TODO: Scientific notation
|
|
|
|
#include "rjp.h"
|
|
#include "rjp_internal.h"
|
|
#include "strings.h"
|
|
#include "memory.h"
|
|
#include <stdlib.h> //strtod, strtol
|
|
#include <stdio.h> //fprintf, stderr
|
|
#include <string.h> //memset
|
|
|
|
//types of searches in the text
|
|
typedef enum json_search_target{
|
|
json_target_key,
|
|
json_target_colon,
|
|
json_target_comma,
|
|
json_target_value,
|
|
json_target_string,
|
|
json_target_numeral,
|
|
json_target_none
|
|
}json_search_target;
|
|
|
|
static RJP_value* _rjp__add_value(RJP_value* curr, RJP_value new_val){
|
|
new_val.parent = curr;
|
|
if(!curr){
|
|
curr = rjp_calloc(1, sizeof(RJP_value));
|
|
*curr = new_val;
|
|
return curr;
|
|
}
|
|
if(curr->type == json_array){
|
|
_rjp__add_element(&curr->array);
|
|
curr->array.last->value = new_val;
|
|
return &curr->array.last->value;
|
|
}
|
|
curr->object.last->value = new_val;
|
|
return &curr->object.last->value;
|
|
}
|
|
#define MAX_DEPTH 16
|
|
|
|
typedef struct RJP_string_state{
|
|
int escaped;
|
|
int in_utf_sequence;
|
|
char* buffer; //store partial string here only when chunked reading and chunk ends mid string
|
|
}RJP_string_state;
|
|
|
|
typedef struct RJP_numeral_state{
|
|
int numlen;
|
|
char* buffer; //store partial number string here only when chunked reading and chunk ends mid number
|
|
}RJP_numeral_state;
|
|
|
|
typedef struct RJP_parse_state{
|
|
RJP_value* root;
|
|
RJP_value* curr;
|
|
union{
|
|
RJP_string_state str_state;
|
|
RJP_numeral_state num_state;
|
|
};
|
|
int row, column;
|
|
int in_line_comment;
|
|
int in_block_comment;
|
|
int target_stack[MAX_DEPTH];
|
|
int* target;
|
|
}RJP_parse_state;
|
|
|
|
void _rjp__init_parse_state(RJP_parse_state* state){
|
|
state->root = NULL;
|
|
state->curr = NULL;
|
|
state->row = state->column = 0;
|
|
state->in_line_comment = 0;
|
|
state->in_block_comment = 0;
|
|
memset(state->target_stack, 0, MAX_DEPTH*sizeof(int));
|
|
state->target = state->target_stack;
|
|
}
|
|
|
|
static void syntax_error(const char* msg, RJP_parse_state* state){
|
|
DIAG_PRINT(stderr, "Syntax error! %s (%i:%i)\n", msg, state->row, state->column);
|
|
rjp_free_value(state->root);
|
|
}
|
|
|
|
//Return number of characters handled while processing comment
|
|
int _rjp__handle_comment(const char* str, RJP_parse_state* state){
|
|
char c = *str;
|
|
if(state->in_line_comment){
|
|
if(c == '\n')
|
|
state->in_line_comment = 0;
|
|
return 1;
|
|
}else if(state->in_block_comment){
|
|
if(c == '*' && *(str+1) == '/'){
|
|
state->in_block_comment = 0;
|
|
return 2;
|
|
}
|
|
return 1;
|
|
}else if(c == '/' && *(str+1) == '/'){
|
|
state->in_block_comment = 1;
|
|
return 2;
|
|
}else if(c == '/' && *(str+1) == '/'){
|
|
state->in_line_comment = 1;
|
|
return 2;
|
|
}
|
|
return 0;
|
|
}
|
|
int _rjp__handle_key(const char* str, RJP_parse_state* state){
|
|
char c = *str;
|
|
//start of key
|
|
if(c == '"'){
|
|
if(state->curr == NULL){
|
|
syntax_error("Key found outside of object definition!", state);
|
|
return -1;
|
|
}
|
|
|
|
int keylen;
|
|
int inclen;
|
|
char* new_string = _rjp__parse_string(state->root, str+1, &inclen, &keylen, &state->row, &state->column);
|
|
if(!new_string){
|
|
if(!keylen)
|
|
syntax_error("Cannot have empty key name!", state);
|
|
return -1;
|
|
}
|
|
_rjp__add_member_no_alloc(&(state->curr->object), new_string, keylen);
|
|
*state->target = json_target_colon;
|
|
return inclen+2;
|
|
//end of this object (object is empty)
|
|
}else if(c == '}'){
|
|
state->curr = state->curr->parent;
|
|
if(state->target != state->target_stack)
|
|
--state->target;
|
|
return 1;
|
|
|
|
//unrecognized character
|
|
}else if(!_rjp__is_whitespace(c)){
|
|
syntax_error("Unexpected character, expected '\"'!", state);
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int _rjp__handle_colon(const char* str, RJP_parse_state* state){
|
|
char c = *str;
|
|
//colon after a key
|
|
if(c == ':'){
|
|
*state->target = json_target_value;
|
|
//unrecognized character
|
|
}else if(!_rjp__is_whitespace(c)){
|
|
syntax_error( "Unexpected character, expected ':'!", state);
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
int _rjp__handle_comma(const char* str, RJP_parse_state* state){
|
|
char c = *str;
|
|
//comma separating keys in an object or values in an array
|
|
if(c == ','){
|
|
*state->target = (state->curr->type == json_array ? json_target_value : json_target_key);
|
|
|
|
//end of object
|
|
}else if(c == '}'){
|
|
if(state->curr->type == json_array){
|
|
syntax_error("Unexpected end of object within array!", state);
|
|
return -1;
|
|
}
|
|
state->curr = state->curr->parent;
|
|
if(state->target != state->target_stack)
|
|
--state->target;
|
|
//end of array
|
|
}else if(c == ']' && state->curr->type == json_array){
|
|
state->curr = state->curr->parent;
|
|
//unrecognized character
|
|
}else if(!_rjp__is_whitespace(c)){
|
|
syntax_error("Unexpected character, expected ','!", state);
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int _rjp__handle_value(const char* str, RJP_parse_state* state){
|
|
//object
|
|
char c = *str;
|
|
if(c == '{'){
|
|
if(!state->root){
|
|
state->root = _rjp__add_value(NULL, rjp_object());
|
|
state->curr = state->root;
|
|
*state->target = json_target_key;
|
|
}else{
|
|
state->curr = _rjp__add_value(state->curr, rjp_object());
|
|
*state->target = json_target_comma;
|
|
++state->target;
|
|
*state->target = json_target_key;
|
|
}
|
|
return 1;
|
|
}
|
|
else if(c == '['){
|
|
if(!state->root){
|
|
state->root = _rjp__add_value(NULL, rjp_array());
|
|
state->curr = state->root;
|
|
|
|
}else{
|
|
state->curr = _rjp__add_value(state->curr, rjp_array());
|
|
}
|
|
return 1;
|
|
}
|
|
else if(c == ']' && state->curr->type == json_array){ //empty array
|
|
*state->target = json_target_comma;
|
|
state->curr = state->curr->parent;
|
|
return 1;
|
|
}
|
|
//strings
|
|
else if(c == '"'){
|
|
int vallen, inclen;
|
|
char* new_string = _rjp__parse_string(state->root, str+1, &inclen, &vallen, &state->row, &state->column);
|
|
if(!new_string){
|
|
if(vallen == 0){
|
|
new_string = rjp_calloc(1, 1);
|
|
}else{
|
|
return -1;
|
|
}
|
|
}
|
|
_rjp__add_value(state->curr, rjp_string(new_string, vallen));
|
|
*state->target = json_target_comma;
|
|
return inclen+2;
|
|
}
|
|
//numbers
|
|
else if((c >= '0' && c <= '9') || c == '-'){
|
|
if(!state->curr)
|
|
*state->target = json_target_none;
|
|
else
|
|
*state->target = json_target_comma;
|
|
int numlen;
|
|
int floating = 0; //is an int or a double
|
|
for(numlen = 1;*(str+numlen) >= '0' && *(str+numlen) <= '9';++numlen);
|
|
if(*(str+numlen) == '.'){ //if we have a decimal, make it a double and continue parsing as a number
|
|
int i = ++numlen;
|
|
for(;*(str+numlen) >= '0' && *(str+numlen) <= '9';++numlen);
|
|
if(i == numlen){ //no number after decimal
|
|
syntax_error("Missing numerals after decimal place!", state);
|
|
return -1;
|
|
}
|
|
floating = 1;
|
|
}
|
|
if(*(str+numlen) == '\0' && state->curr){ //hit EOF early
|
|
syntax_error("Unexpected EOF before end of object!", state);
|
|
return -1;
|
|
}
|
|
if(c == '-' && numlen == 1){ //only have a '-' with no numbers
|
|
syntax_error("Missing numerals after '-' sign!", state);
|
|
return -1;
|
|
}
|
|
if(floating){
|
|
if(!state->root){
|
|
state->root = state->curr = _rjp__add_value(NULL, rjp_dfloat(strtod(str, NULL)));
|
|
}else{
|
|
_rjp__add_value(state->curr, rjp_dfloat(strtod(str, NULL)));
|
|
}
|
|
}else{
|
|
if(!state->root){
|
|
state->root = state->curr = _rjp__add_value(NULL, rjp_integer(strtoll(str, NULL, 10)));
|
|
}else{
|
|
_rjp__add_value(state->curr, rjp_integer(strtoll(str, NULL, 10)));
|
|
}
|
|
}
|
|
state->column += numlen;
|
|
return numlen;
|
|
}
|
|
//booleans and null
|
|
else if(!strncmp(str, "true", 4)){
|
|
if(!state->curr){
|
|
*state->target = json_target_none;
|
|
state->root = state->curr = _rjp__add_value(state->curr, rjp_boolean(1));
|
|
}else{
|
|
*state->target = json_target_comma;
|
|
_rjp__add_value(state->curr, rjp_boolean(1));
|
|
}
|
|
state->column += 3;
|
|
return 4;
|
|
}else if(!strncmp(str, "false", 5)){
|
|
if(!state->curr){
|
|
*state->target = json_target_none;
|
|
state->root = state->curr = _rjp__add_value(state->curr, rjp_boolean(0));
|
|
}else{
|
|
*state->target = json_target_comma;
|
|
_rjp__add_value(state->curr, rjp_boolean(0));
|
|
}
|
|
state->column += 4;
|
|
return 5;
|
|
}else if(!strncmp(str, "null", 4)){
|
|
if(!state->curr){
|
|
*state->target = json_target_none;
|
|
state->root = state->curr = _rjp__add_value(state->curr, rjp_null());
|
|
}else{
|
|
*state->target = json_target_comma;
|
|
_rjp__add_value(state->curr, rjp_null());
|
|
}
|
|
state->column += 3;
|
|
return 4;
|
|
}
|
|
//unrecognized character
|
|
else if(!_rjp__is_whitespace(c)){
|
|
syntax_error("Unexpected character!", state);
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
RJP_value* rjp_parse(const char* str){
|
|
RJP_parse_state state;
|
|
_rjp__init_parse_state(&state);
|
|
|
|
//initially search for the root object
|
|
*state.target = json_target_value;
|
|
|
|
int inc = 0;
|
|
for(;*str != '\0';str += inc){
|
|
char c = *str;
|
|
|
|
//keep track of position in input file
|
|
if(c == '\n'){
|
|
++state.row;
|
|
state.column = 0;
|
|
}else{
|
|
++state.column;
|
|
}
|
|
|
|
if((inc = _rjp__handle_comment(str, &state))){
|
|
continue;
|
|
}
|
|
|
|
switch(*state.target){
|
|
case json_target_key:
|
|
inc = _rjp__handle_key(str, &state);
|
|
break;
|
|
case json_target_colon:
|
|
inc = _rjp__handle_colon(str, &state);
|
|
break;
|
|
case json_target_comma:
|
|
inc = _rjp__handle_comma(str, &state);
|
|
break;
|
|
case json_target_value:
|
|
inc = _rjp__handle_value(str, &state);
|
|
break;
|
|
case json_target_none:
|
|
if(!_rjp__is_whitespace(*str)){
|
|
syntax_error("Unexpected character!", &state);
|
|
return NULL;
|
|
}
|
|
inc = 1;
|
|
break;
|
|
default:
|
|
inc = 1;
|
|
break;
|
|
};
|
|
}
|
|
return state.root;
|
|
}
|
|
RJP_value* rjp_parse_chunked(const char* str, RJP_value* prev_chunk){
|
|
if(!prev_chunk){
|
|
return rjp_parse(str);
|
|
}
|
|
return NULL;
|
|
}
|