rjp/src/input.c
2018-12-15 05:33:44 -08:00

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