423 lines
12 KiB
C
423 lines
12 KiB
C
/**
|
|
rjp
|
|
Copyright (C) 2018 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 <string.h> //strncpy
|
|
#include <stdlib.h> //malloc, calloc, free
|
|
#include <stdio.h> //fprintf, stderr
|
|
|
|
#ifdef __GNUC__
|
|
#define MAYBE_UNUSED __attribute__((unused))
|
|
#else
|
|
#define MAYBE_UNUSED
|
|
#endif
|
|
|
|
#ifdef RJP_DIAGNOSTICS
|
|
#define DIAG_PRINT(...) fprintf(__VA_ARGS__)
|
|
#else
|
|
#define DIAG_PRINT(...)
|
|
#endif
|
|
|
|
//types of searches in the text
|
|
typedef enum json_search_target{
|
|
json_key,
|
|
json_colon,
|
|
json_comma,
|
|
json_value,
|
|
json_none
|
|
}json_search_target;
|
|
|
|
//Determine if the character is valid whitespace
|
|
static int _rjp__is_whitespace(char c){
|
|
return c == ' ' || c == '\n' || c == '\r' || c == '\t';
|
|
}
|
|
//add an element to an array
|
|
void _rjp__add_element(RJP_array* j){
|
|
++j->num_elements;
|
|
if(!j->elements){
|
|
j->elements = rjp_calloc(1, sizeof(RJP_array_element));
|
|
j->last = j->elements;
|
|
}else{
|
|
j->last->next = rjp_calloc(1, sizeof(RJP_array_element));
|
|
j->last = j->last->next;
|
|
}
|
|
}
|
|
//create member of the object as a linked list member and assign a name with name allocation
|
|
void _rjp__add_member(RJP_object* j, char* str, size_t len){
|
|
++j->num_members;
|
|
if(!j->members){
|
|
j->members = rjp_calloc(1, sizeof(RJP_object_member));
|
|
j->last = j->members;
|
|
}else{
|
|
j->last->next = rjp_calloc(1, sizeof(RJP_object_member));
|
|
j->last = j->last->next;
|
|
}
|
|
j->last->name.value = rjp_alloc(len + 1);
|
|
strncpy(j->last->name.value, str, len);
|
|
j->last->name.value[len] = 0;
|
|
j->last->name.length = len;
|
|
}
|
|
void _rjp__add_member_no_alloc(RJP_object* j, char* str, size_t len){
|
|
++j->num_members;
|
|
if(!j->members){
|
|
j->members = rjp_calloc(1, sizeof(RJP_object_member));
|
|
j->last = j->members;
|
|
}else{
|
|
j->last->next = rjp_calloc(1, sizeof(RJP_object_member));
|
|
j->last = j->last->next;
|
|
}
|
|
j->last->name.value = str;
|
|
j->last->name.length = len;
|
|
}
|
|
|
|
static RJP_value* _rjp__add_value(RJP_value* curr, RJP_value* new_val, int* target){
|
|
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;
|
|
}
|
|
//Assign object characteristics to previously allocated RJP_value
|
|
static RJP_value* _rjp__add_object(RJP_value* curr, int* target){
|
|
RJP_value new_object = {.type = json_object, .integer = 0, .parent = curr};
|
|
return _rjp__add_value(curr, &new_object, target);
|
|
}
|
|
//Assign array characteristics to previously allocated RJP_value
|
|
static RJP_value* _rjp__add_array(RJP_value* curr, int* target){
|
|
RJP_value new_array = {.type = json_array, .array = {.num_elements = 0, .elements = NULL}, .parent = curr};
|
|
return _rjp__add_value(curr, &new_array, target);
|
|
}
|
|
//Assign string characteristics to previously allocated RJP_value with no string allocation
|
|
static RJP_value* _rjp__add_string_no_alloc(RJP_value* curr, char* str, int len, int* target){
|
|
RJP_value new_string = {.type = json_string, .string = {.value = str, .length = len}, .parent = curr};
|
|
return _rjp__add_value(curr, &new_string, target);
|
|
}
|
|
//Assign double characteristics to previously allocated RJP_value
|
|
static RJP_value* _rjp__add_dfloat(RJP_value* curr, double value, int* target){
|
|
RJP_value new_double = {.type = json_dfloat, .dfloat = value, .parent = curr};
|
|
return _rjp__add_value(curr, &new_double, target);
|
|
}
|
|
//Assign integer characteristics to previously allocated RJP_value
|
|
static RJP_value* _rjp__add_integer(RJP_value* curr, long value, int* target){
|
|
RJP_value new_integer = {.type = json_integer, .integer = value, .parent = curr};
|
|
return _rjp__add_value(curr, &new_integer, target);
|
|
}
|
|
static RJP_value* _rjp__add_boolean(RJP_value* curr, int value, int* target){
|
|
RJP_value new_boolean = {.type = json_boolean, .boolean = value, .parent = curr};
|
|
return _rjp__add_value(curr, &new_boolean, target);
|
|
}
|
|
static RJP_value* _rjp__add_null(RJP_value* curr, int* target){
|
|
RJP_value new_null = {.type = json_null, .integer = 0, .parent = curr};
|
|
return _rjp__add_value(curr, &new_null, target);
|
|
}
|
|
|
|
static void _rjp__free_object_recurse(RJP_value* root);
|
|
|
|
static void _rjp__free_array(RJP_value* root){
|
|
RJP_array_element* arr = root->array.elements;
|
|
for(RJP_array_element* i = arr;i != NULL;i = arr){
|
|
arr = arr->next;
|
|
if(i->value.type == json_object){
|
|
_rjp__free_object_recurse(&i->value);
|
|
}else if(i->value.type == json_array){
|
|
_rjp__free_array(&i->value);
|
|
}else if(i->value.type == json_string){
|
|
free(i->value.string.value);
|
|
}
|
|
free(i);
|
|
}
|
|
}
|
|
//Recursively free JSON objects
|
|
static void _rjp__free_object_recurse(RJP_value* root){
|
|
RJP_object_member* next;
|
|
for(RJP_object_member* m = root->object.members;m;m = next){
|
|
next = m->next;
|
|
if(m->value.type == json_object)
|
|
_rjp__free_object_recurse(&m->value);
|
|
else if(m->value.type == json_string)
|
|
free(m->value.string.value);
|
|
else if(m->value.type == json_array)
|
|
_rjp__free_array(&m->value);
|
|
if(m->name.value)
|
|
free(m->name.value);
|
|
free(m);
|
|
}
|
|
}
|
|
void rjp_free_value_json(char* json){
|
|
free(json);
|
|
}
|
|
//Same as recurse but also frees root node
|
|
void rjp_free_value(RJP_value* root){
|
|
if(!root)
|
|
return;
|
|
|
|
if((root->type) == json_object)
|
|
_rjp__free_object_recurse(root);
|
|
else if((root->type) == json_array)
|
|
_rjp__free_array(root);
|
|
|
|
free(root);
|
|
}
|
|
|
|
MAYBE_UNUSED static int _rjp__is_array_empty(RJP_value* curr){
|
|
if(curr->object.members->value.type != json_array)
|
|
return 0;
|
|
return curr->object.members->value.array.num_elements == 0;
|
|
}
|
|
|
|
#define syntax_error(msg, row, column)\
|
|
do{DIAG_PRINT(stderr, "Syntax error! %s (%i:%i)\n", msg, row, column);rjp_free_value(root);return NULL;}while(0)
|
|
|
|
#define MAX_DEPTH 16
|
|
RJP_value* rjp_parse(const char* str){
|
|
RJP_value* root = 0;
|
|
RJP_value* curr = 0;
|
|
int row = 1, column = 0;
|
|
int in_line_comment = 0;
|
|
int in_block_comment = 0;
|
|
|
|
//keep track of where we are in a given subobject
|
|
int state_stack[MAX_DEPTH] = {0},*top = state_stack;
|
|
|
|
//initially search for the root object
|
|
*top = json_value;
|
|
|
|
for(;*str != '\0';++str){
|
|
char c = *str;
|
|
|
|
//keep track of position in input file
|
|
if(c == '\n'){
|
|
++row;
|
|
column = 0;
|
|
}else{
|
|
++column;
|
|
}
|
|
|
|
//Handle comments
|
|
if(in_line_comment){
|
|
if(c == '\n')
|
|
in_line_comment = 0;
|
|
}
|
|
else if(in_block_comment){
|
|
if(c == '*' && *(str+1) == '/'){
|
|
in_block_comment = 0;
|
|
++str;
|
|
}
|
|
}
|
|
else if(c == '/' && *(str+1) == '/'){
|
|
in_line_comment = 1;
|
|
++str;
|
|
}
|
|
else if(c == '/' && *(str+1) == '*'){
|
|
in_block_comment = 1;
|
|
++str;
|
|
}
|
|
|
|
else if(*top == json_key){
|
|
//start of key
|
|
if(c == '"'){
|
|
if(curr == NULL)
|
|
syntax_error("Key found outside of object definition!", row, column);
|
|
|
|
int keylen;
|
|
char* new_string = _rjp__parse_string(root, ++str, &keylen, &row, &column);
|
|
if(!new_string){
|
|
if(!keylen)
|
|
syntax_error("Cannot have empty key name!", row, column);
|
|
return NULL;
|
|
}
|
|
_rjp__add_member_no_alloc(&curr->object, new_string, keylen);
|
|
str += keylen;
|
|
*top = json_colon;
|
|
//end of this object (object is empty)
|
|
}else if(c == '}'){
|
|
curr = curr->parent;
|
|
if(top != state_stack)
|
|
--top;
|
|
|
|
//unrecognized character
|
|
}else if(!_rjp__is_whitespace(c)){
|
|
syntax_error("Unexpected character, expected '\"'!", row, column);
|
|
}
|
|
}
|
|
else if(*top == json_colon){
|
|
//colon after a key
|
|
if(c == ':'){
|
|
*top = json_value;
|
|
//unrecognized character
|
|
}else if(!_rjp__is_whitespace(c)){
|
|
syntax_error( "Unexpected character, expected ':'!", row, column);
|
|
}
|
|
}
|
|
else if(*top == json_comma){
|
|
//comma separating keys in an object or values in an array
|
|
if(c == ','){
|
|
*top = (curr->type == json_array ? json_value : json_key);
|
|
|
|
//end of object
|
|
}else if(c == '}'){
|
|
if(curr->type == json_array){
|
|
syntax_error("Unexpected end of object within array!", row, column);
|
|
}
|
|
curr = curr->parent;
|
|
if(top != state_stack)
|
|
--top;
|
|
//end of array
|
|
}else if(c == ']' && curr->type == json_array){
|
|
curr = curr->parent;
|
|
//unrecognized character
|
|
}else if(!_rjp__is_whitespace(c)){
|
|
syntax_error("Unexpected character, expected ','!", row, column);
|
|
}
|
|
}
|
|
else if(*top == json_value){
|
|
//object
|
|
if(c == '{'){
|
|
if(!root){
|
|
root = _rjp__add_object(NULL, top);
|
|
curr = root;
|
|
*top = json_key;
|
|
}else{
|
|
curr = _rjp__add_object(curr, top);
|
|
*top = json_comma;
|
|
++top;
|
|
*top = json_key;
|
|
}
|
|
}
|
|
else if(c == '['){
|
|
if(!root){
|
|
root = _rjp__add_array(NULL, top);
|
|
curr = root;
|
|
|
|
}else{
|
|
curr = _rjp__add_array(curr, top);
|
|
}
|
|
}
|
|
else if(c == ']' && curr->type == json_array){ //empty array
|
|
*top = json_comma;
|
|
curr = curr->parent;
|
|
}
|
|
//strings
|
|
else if(c == '"'){
|
|
int vallen;
|
|
++str;
|
|
char* new_string = _rjp__parse_string(root, str, &vallen, &row, &column);
|
|
if(!new_string){
|
|
if(vallen == 0){
|
|
new_string = rjp_calloc(1, 1);
|
|
}else{
|
|
return NULL;
|
|
}
|
|
}
|
|
_rjp__add_string_no_alloc(curr, new_string, vallen, top);
|
|
str += vallen;
|
|
*top = json_comma;
|
|
}
|
|
//numbers
|
|
else if((c >= '0' && c <= '9') || c == '-'){
|
|
if(!curr)
|
|
*top = json_none;
|
|
else
|
|
*top = json_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!", row, column);
|
|
}
|
|
floating = 1;
|
|
}
|
|
if(*(str+numlen) == '\0' && curr){ //hit EOF early
|
|
syntax_error("Unexpected EOF before end of object!", row, column);
|
|
}
|
|
if(c == '-' && numlen == 1){ //only have a '-' with no numbers
|
|
syntax_error("Missing numerals ofter '-' sign!", row, column);
|
|
}
|
|
if(floating){
|
|
if(!root){
|
|
root = curr = _rjp__add_dfloat(NULL, strtod(str, NULL), top);
|
|
}else{
|
|
_rjp__add_dfloat(curr, strtod(str, NULL), top);
|
|
}
|
|
}else{
|
|
if(!root){
|
|
root = curr = _rjp__add_integer(NULL, strtol(str, NULL, 10), top);
|
|
}else{
|
|
_rjp__add_integer(curr, strtol(str, NULL, 10), top);
|
|
}
|
|
}
|
|
str += (numlen-1);
|
|
column += numlen;
|
|
}
|
|
//booleans and null
|
|
else if(!strncmp(str, "true", 4)){
|
|
if(!curr){
|
|
*top = json_none;
|
|
root = curr = _rjp__add_boolean(curr, 1, top);
|
|
}else{
|
|
*top = json_comma;
|
|
_rjp__add_boolean(curr, 1, top);
|
|
}
|
|
str += 3;column += 3;
|
|
}else if(!strncmp(str, "false", 5)){
|
|
if(!curr){
|
|
*top = json_none;
|
|
root = curr = _rjp__add_boolean(curr, 0, top);
|
|
}else{
|
|
*top = json_comma;
|
|
_rjp__add_boolean(curr, 0, top);
|
|
}
|
|
str += 4;column += 4;
|
|
}else if(!strncmp(str, "null", 4)){
|
|
if(!curr){
|
|
*top = json_none;
|
|
root = curr = _rjp__add_null(curr, top);
|
|
}else{
|
|
*top = json_comma;
|
|
_rjp__add_null(curr, top);
|
|
}
|
|
str += 3;column += 3;
|
|
}
|
|
//unrecognized character
|
|
else if(!_rjp__is_whitespace(c)){
|
|
syntax_error("Unexpected character!", row, column);
|
|
}
|
|
}else if(*top == json_none && !_rjp__is_whitespace(c)){
|
|
syntax_error("Unexpected character!", row, column);
|
|
}
|
|
}
|
|
return root;
|
|
}
|
|
|
|
#undef syntax_error
|
|
|
|
|