diff --git a/doc/examples/.gitignore b/doc/examples/.gitignore index e1b5dc5..c265f50 100644 --- a/doc/examples/.gitignore +++ b/doc/examples/.gitignore @@ -1,3 +1,4 @@ output parse +parse_file *.o diff --git a/doc/examples/parse.c b/doc/examples/parse.c index 8a00816..1ce13c3 100644 --- a/doc/examples/parse.c +++ b/doc/examples/parse.c @@ -8,7 +8,7 @@ int main(int argc, char** argv){ } //Read in json argument allowing all RJP extensions (comments, trailing comma) - RJP_value* root = rjp_parse(argv[1], RJP_PARSE_ALL_EXT); + RJP_value* root = rjp_parse(argv[1], RJP_PARSE_ALL_EXT, NULL); //returns NULL on error if(!root){ diff --git a/doc/examples/parse_file.c b/doc/examples/parse_file.c index b2b3075..5e4cecf 100644 --- a/doc/examples/parse_file.c +++ b/doc/examples/parse_file.c @@ -22,7 +22,7 @@ int main(int argc, char** argv){ fclose(fp); //Read in json argument allowing all RJP extensions (comments, trailing comma) - RJP_value* root = rjp_parse(buffer, RJP_PARSE_ALL_EXT); + RJP_value* root = rjp_parse(buffer, RJP_PARSE_ALL_EXT, NULL); //returns NULL on error if(!root){ diff --git a/include/rjp.h b/include/rjp.h index 08c3b85..c73acaa 100644 --- a/include/rjp.h +++ b/include/rjp.h @@ -108,7 +108,11 @@ typedef struct RJP_parse_callback{ int(*read)(char*,int,void*); void* data; }RJP_parse_callback; - +typedef struct RJP_parse_error{ + void* parsestate; + int errcode; + int row, column; +}RJP_parse_error; /***************** NON OBJECT OPERATIONS *******************/ void* rjp_alloc(RJP_index nbytes); @@ -121,10 +125,14 @@ RJP_index rjp_escape_strlen(const char* str); RJP_string rjp_escape(const char* src); /***************** GENERIC OPERATIONS *******************/ -RJP_value* rjp_parse(const char* str, int flags); -RJP_value* rjp_parse_cback(int flags, RJP_parse_callback* cbacks); +RJP_value* rjp_simple_parse(const char* str); +RJP_value* rjp_parse(const char* str, int flags, RJP_parse_error* err); +RJP_value* rjp_parse_cback(int flags, RJP_parse_callback* cbacks, RJP_parse_error* err); char* rjp_to_json(const RJP_value* root, int pretty); +char* rjp_parse_error_to_string(RJP_parse_error* err); +void rjp_delete_parse_error(RJP_parse_error* err); + RJP_value* rjp_new_null(void); RJP_value* rjp_new_int(RJP_int val); RJP_value* rjp_new_float(RJP_float val); diff --git a/include/rjp_lex.h b/include/rjp_lex.h index 7baa4d5..7706fb6 100644 --- a/include/rjp_lex.h +++ b/include/rjp_lex.h @@ -24,6 +24,9 @@ #define RJP_LEX_CBACK_BUFFER_SIZE 512 #define RJP_LEX_CBACK_STR_SIZE 64 +#define RJP_LEX_TYPE_NORMAL 0 +#define RJP_LEX_TYPE_CBACK 1 + #define rjp_lex_accept 1 //DFA states. odd numbers are accepting states typedef enum RJP_lex_category{ @@ -79,9 +82,12 @@ typedef struct RJP_lex_state{ RJP_lex_category node; //tracks current dfa state RJP_index length; //length of current token which parser will utilize RJP_index offset; //offset in the str buffer that the parser should start from. must be 0 in callback lexer + int type; }RJP_lex_state; void irjp_init_lex_cback_state(RJP_lex_state* state); +void irjp_init_lex_state(RJP_lex_state* state); +void irjp_delete_lex_state(RJP_lex_state* state); RJP_lex_category irjp_lex(RJP_lex_state* state); RJP_lex_category irjp_lex_cback(RJP_lex_state* state, RJP_parse_callback* cbacks); diff --git a/rjp++/include/parse.hpp b/rjp++/include/parse.hpp index 1d4e157..e54cec5 100644 --- a/rjp++/include/parse.hpp +++ b/rjp++/include/parse.hpp @@ -30,7 +30,9 @@ namespace rjp{ string to_json(const value& val, int format = RJP_FORMAT_PRETTY); value parse_json(const rexy::string_base& str, RJP_parse_flag = RJP_PARSE_NONE); + value parse_json(const rexy::string_base& str, RJP_parse_flag, RJP_parse_error&); value parse_json(const char* str, RJP_parse_flag = RJP_PARSE_NONE); + value parse_json(const char* str, RJP_parse_flag, RJP_parse_error&); namespace detail{ template struct sequence_tup{}; @@ -66,6 +68,7 @@ namespace rjp{ return ph(c, size, typename sequence_gen::type{}); } }; + RJP_value* parse_cback(RJP_parse_flag f, RJP_parse_callback* cb, RJP_parse_error& err); RJP_value* parse_cback(RJP_parse_flag f, RJP_parse_callback* cb); int irjp_parse_callback(char* dest, int size, void* userdata); } @@ -77,6 +80,14 @@ namespace rjp{ cb.read = detail::irjp_parse_callback; return value(detail::parse_cback(f, &cb), true); } + template + value parse_json(RJP_parse_flag f, RJP_parse_error& err, Func&& func, Args&&... args){ + RJP_parse_callback cb; + detail::invoker_impl inv(std::forward(func), std::forward(args)...); + cb.data = static_cast(&inv); + cb.read = detail::irjp_parse_callback; + return value(detail::parse_cback(f, &cb, err), true); + } } diff --git a/rjp++/src/rjp.cpp b/rjp++/src/rjp.cpp index c115e8d..9fb84fc 100644 --- a/rjp++/src/rjp.cpp +++ b/rjp++/src/rjp.cpp @@ -32,19 +32,28 @@ namespace rjp{ s.reset(rjp_to_json(val.raw(), format)); return s; } + value parse_json(const rexy::string_base& str, RJP_parse_flag flags, RJP_parse_error& err){ + return parse_json(str.get(), flags, err); + } value parse_json(const rexy::string_base& str, RJP_parse_flag flags){ - return value(rjp_parse(str.get(), flags), true); + return parse_json(str.get(), flags); } value parse_json(const char* str, RJP_parse_flag flags){ - return value(rjp_parse(str, flags), true); + return value(rjp_parse(str, flags, NULL), true); + } + value parse_json(const char* str, RJP_parse_flag flags, RJP_parse_error& err){ + return value(rjp_parse(str, flags, &err), true); } namespace detail{ int irjp_parse_callback(char* dest, int size, void* userdata){ invoker* inv = static_cast(userdata); return inv->run(dest, size); } + RJP_value* parse_cback(RJP_parse_flag f, RJP_parse_callback* cb, RJP_parse_error& err){ + return rjp_parse_cback(f, cb, &err); + } RJP_value* parse_cback(RJP_parse_flag f, RJP_parse_callback* cb){ - return rjp_parse_cback(f, cb); + return rjp_parse_cback(f, cb, NULL); } template<> diff --git a/rjp++/tests/parse.cpp b/rjp++/tests/parse.cpp index 9f78a16..31782e8 100644 --- a/rjp++/tests/parse.cpp +++ b/rjp++/tests/parse.cpp @@ -19,6 +19,8 @@ int read_callback(char* c, int size, const char* data, int datalen, int& datapos int handle_res(const rjp::value& res){ if(res.valid()){ fprintf(stderr, "Accepted\n"); + }else{ + fprintf(stderr, "Rejected\n"); } return !res.valid(); } diff --git a/src/rjp_lex.c b/src/rjp_lex.c index e2e91d0..6d7022a 100644 --- a/src/rjp_lex.c +++ b/src/rjp_lex.c @@ -27,7 +27,18 @@ void irjp_init_lex_cback_state(RJP_lex_state* state){ state->strcap = RJP_LEX_CBACK_STR_SIZE; state->buff = rjp_alloc(RJP_LEX_CBACK_BUFFER_SIZE); state->buffcap = RJP_LEX_CBACK_BUFFER_SIZE; + state->type = RJP_LEX_TYPE_CBACK; } +void irjp_init_lex_state(RJP_lex_state* state){ + state->type = RJP_LEX_TYPE_NORMAL; +} +void irjp_delete_lex_state(RJP_lex_state* state){ + if(state->type == RJP_LEX_TYPE_CBACK){ + rjp_free(state->str); + rjp_free(state->buff); + } +} + static RJP_lex_category irjp_lex_accept(RJP_lex_category val, RJP_lex_state* state){ state->node = rjp_lex_start; return val; diff --git a/src/rjp_parse.c b/src/rjp_parse.c index 1668ac2..5776617 100644 --- a/src/rjp_parse.c +++ b/src/rjp_parse.c @@ -28,8 +28,19 @@ #define RJP_INITIAL_PARSE_DEPTH 16 -#define RJP_PARSE_STATUS_ERR 1 -#define RJP_PARSE_STATUS_SUC 2 + +typedef enum RJP_parse_status{ + RJP_PARSE_STATUS_SUC, + RJP_PARSE_STATUS_ERR, + RJP_PARSE_STATUS_MISSING_VALUE, + RJP_PARSE_STATUS_MISSING_COMMA, + RJP_PARSE_STATUS_INVALID, + RJP_PARSE_STATUS_NO_ROOT_VALUE, + RJP_PARSE_STATUS_MISSING_KEY, + RJP_PARSE_STATUS_MISSING_COLON, + RJP_PARSE_STATUS_EXCESS_DATA, + RJP_PARSE_STATUS_MISSING_CLOSE_BRACE, +}RJP_parse_status; typedef enum RJP_parse_target{ @@ -57,8 +68,8 @@ typedef struct RJP_parse_state{ RJP_value* lastadded; RJP_lex_state lexstate; int row, column; - const _Bool allow_comments; - const _Bool allow_trail_comma; + _Bool allow_comments; + _Bool allow_trail_comma; }RJP_parse_state; static void irjp_init_parse_stack(RJP_parse_stack* s){ @@ -175,15 +186,14 @@ static void irjp_init_parse_state(RJP_parse_state* state, const char* str){ } static void irjp_delete_parse_state(RJP_parse_state* state){ irjp_delete_parse_stack(&state->target_stack); + irjp_delete_lex_state(&state->lexstate); +} +static void irjp_delete_parse_state_no_preserve_root(RJP_parse_state* state){ + irjp_delete_parse_state(state); rjp_free_value(state->root); state->root = NULL; } -static inline int irjp_parse_error(RJP_parse_state* state, const char* str){ - DIAG_PRINT(stderr, "%s: %d:%d\n", str, state->column, state->row); - return RJP_PARSE_STATUS_ERR; -} - static int irjp_parse_handle_lexcat(RJP_lex_category cat, RJP_parse_state* state){ if(cat == rjp_lex_line_comment || cat == rjp_lex_block_comment) cat = irjp_convert_comment(state->allow_comments); @@ -196,14 +206,14 @@ static int irjp_parse_handle_lexcat(RJP_lex_category cat, RJP_parse_state* state return RJP_PARSE_STATUS_SUC; } if(cat == rjp_lex_invalid) - return irjp_parse_error(state, "Invalid token"); + return RJP_PARSE_STATUS_INVALID; switch(irjp_parse_stack_current(&state->target_stack)){ case rjp_parse_start: irjp_parse_stack_set(&state->target_stack, rjp_parse_end); if(irjp_init_value(state->root, cat, state)){ - return irjp_parse_error(state, "Expected value"); + return RJP_PARSE_STATUS_NO_ROOT_VALUE; } break; case rjp_parse_first_mem_key: @@ -216,10 +226,10 @@ static int irjp_parse_handle_lexcat(RJP_lex_category cat, RJP_parse_state* state if(cat == rjp_lex_string){ irjp_parse_stack_set(&state->target_stack, rjp_parse_key_colon); if(!irjp_add_value_to_object(state, state->lexstate.str+state->lexstate.offset, state->lexstate.length)){ - return irjp_parse_error(state, "Expected member key"); + return RJP_PARSE_STATUS_MISSING_KEY; } }else{ - return irjp_parse_error(state, "Expected member key"); + return RJP_PARSE_STATUS_MISSING_KEY; } } break; @@ -232,19 +242,19 @@ static int irjp_parse_handle_lexcat(RJP_lex_category cat, RJP_parse_state* state case rjp_parse_arr_value: irjp_parse_stack_set(&state->target_stack, rjp_parse_arr_comma); if(!irjp_add_value_to_array(cat, state)) - return irjp_parse_error(state, "Expected value"); + return RJP_PARSE_STATUS_MISSING_VALUE; } break; case rjp_parse_key_colon: if(cat != rjp_lex_colon) - return irjp_parse_error(state, "Expected member key"); + return RJP_PARSE_STATUS_MISSING_COLON; irjp_parse_stack_set(&state->target_stack, rjp_parse_obj_value); break; case rjp_parse_obj_value: irjp_parse_stack_set(&state->target_stack, rjp_parse_obj_comma); if(irjp_init_value(state->lastadded, cat, state)){ - return irjp_parse_error(state, "Expected value"); + return RJP_PARSE_STATUS_MISSING_VALUE; } break; case rjp_parse_obj_comma: @@ -254,7 +264,7 @@ static int irjp_parse_handle_lexcat(RJP_lex_category cat, RJP_parse_state* state irjp_parse_stack_pop(&state->target_stack); state->curr = state->curr->parent; }else{ - return irjp_parse_error(state, "Expected comma"); + return RJP_PARSE_STATUS_MISSING_COMMA; } break; @@ -265,13 +275,13 @@ static int irjp_parse_handle_lexcat(RJP_lex_category cat, RJP_parse_state* state irjp_parse_stack_pop(&state->target_stack); state->curr = state->curr->parent; }else{ - return irjp_parse_error(state, "Expected comma"); + return RJP_PARSE_STATUS_MISSING_COMMA; } break; case rjp_parse_end: if(state->lexstate.str[state->lexstate.offset] != 0) - return irjp_parse_error(state, "Excess data after end of JSON"); + return RJP_PARSE_STATUS_EXCESS_DATA; }; return RJP_PARSE_STATUS_SUC; } @@ -279,18 +289,19 @@ static int irjp_parse_handle_lexcat(RJP_lex_category cat, RJP_parse_state* state //parse loop. it is a successful state though as it just indicates end of input. static int irjp_handle_final_parse_token(RJP_parse_state* state, RJP_lex_category cat){ if(state->target_stack.position != 0) - return irjp_parse_error(state, "Missing closing brace"); + return RJP_PARSE_STATUS_MISSING_CLOSE_BRACE; if(cat == rjp_lex_end) return RJP_PARSE_STATUS_SUC; - return irjp_parse_error(state, "Invalid Token"); + return RJP_PARSE_STATUS_INVALID; } //Basic parse loop static int irjp_parse(RJP_parse_state* state){ RJP_lex_category cat; + RJP_parse_status status; for(cat = irjp_lex(&state->lexstate);cat & rjp_lex_accept;cat = irjp_lex(&state->lexstate),state->row += state->lexstate.length){ - if(irjp_parse_handle_lexcat(cat, state) != RJP_PARSE_STATUS_SUC) - return RJP_PARSE_STATUS_ERR; + if((status = irjp_parse_handle_lexcat(cat, state)) != RJP_PARSE_STATUS_SUC) + return status; } return irjp_handle_final_parse_token(state, cat); } @@ -298,43 +309,126 @@ static int irjp_parse(RJP_parse_state* state){ //Callback parse loop static int irjp_parse_cback(RJP_parse_state* state, RJP_parse_callback* cback){ RJP_lex_category cat; + RJP_parse_status status; for(cat = irjp_lex_cback(&state->lexstate, cback);cat & rjp_lex_accept;cat = irjp_lex_cback(&state->lexstate, cback),state->row += state->lexstate.length){ - if(irjp_parse_handle_lexcat(cat, state) != RJP_PARSE_STATUS_SUC) - return RJP_PARSE_STATUS_ERR; + if((status = irjp_parse_handle_lexcat(cat, state)) != RJP_PARSE_STATUS_SUC) + return status; } return irjp_handle_final_parse_token(state, cat); } -RJP_value* rjp_parse(const char* str, int flags){ - RJP_parse_state state = {.allow_comments = (flags & RJP_PARSE_ALLOW_COMMENTS), - .allow_trail_comma = (flags & RJP_PARSE_ALLOW_TRAILING_COMMA) + +char* rjp_parse_error_to_string(RJP_parse_error* err){ + RJP_parse_state* state = (RJP_parse_state*)err->parsestate; + RJP_parse_status status = err->errcode; + char* buffer = NULL; + const char* format = NULL; + switch(status){ + case RJP_PARSE_STATUS_MISSING_VALUE: + format = "Expected value before '%.*s'"; + buffer = rjp_alloc(snprintf(NULL, 0, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)) + 1); + sprintf(buffer, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)); + break; + case RJP_PARSE_STATUS_MISSING_COMMA: + format = "Expected comma before '%.*s'"; + buffer = rjp_alloc(snprintf(NULL, 0, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)) + 1); + sprintf(buffer, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)); + break; + case RJP_PARSE_STATUS_INVALID: + format = "Invalid lex token '%.*s'"; + buffer = rjp_alloc(snprintf(NULL, 0, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)) + 1); + sprintf(buffer, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)); + break; + case RJP_PARSE_STATUS_NO_ROOT_VALUE: + format = "Missing root JSON value"; + buffer = rjp_alloc(snprintf(NULL, 0, format) + 1); + sprintf(buffer, format); + break; + case RJP_PARSE_STATUS_MISSING_KEY: + format = "Expected key before '%.*s'"; + buffer = rjp_alloc(snprintf(NULL, 0, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)) + 1); + sprintf(buffer, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)); + break; + case RJP_PARSE_STATUS_MISSING_COLON: + format = "Expected colon before '%.*s'"; + buffer = rjp_alloc(snprintf(NULL, 0, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)) + 1); + sprintf(buffer, format, (int)state->lexstate.length, (state->lexstate.str + state->lexstate.offset)); + break; + case RJP_PARSE_STATUS_EXCESS_DATA: + format = "Excess data after JSON"; + buffer = rjp_alloc(snprintf(NULL, 0, format) + 1); + sprintf(buffer, format); + break; + case RJP_PARSE_STATUS_MISSING_CLOSE_BRACE: + format = "Missing closing brace"; + buffer = rjp_alloc(snprintf(NULL, 0, format) + 1); + sprintf(buffer, format); + break; + default: + break; }; - irjp_init_parse_state(&state, str); - int status = irjp_parse(&state); + return buffer; +} +void rjp_delete_parse_error(RJP_parse_error* err){ + irjp_delete_parse_state_no_preserve_root((RJP_parse_state*)err->parsestate); + rjp_free(err->parsestate); +} + +RJP_value* rjp_simple_parse(const char* str){ + return rjp_parse(str, RJP_PARSE_NONE, NULL); +} + +RJP_value* rjp_parse(const char* str, int flags, RJP_parse_error* err){ + RJP_parse_state* state = rjp_calloc(sizeof(RJP_parse_state), 1); + state->allow_comments = (flags & RJP_PARSE_ALLOW_COMMENTS); + state->allow_trail_comma = (flags & RJP_PARSE_ALLOW_TRAILING_COMMA); + + irjp_init_parse_state(state, str); + irjp_init_lex_state(&state->lexstate); + int status = irjp_parse(state); if(status == RJP_PARSE_STATUS_SUC){ - irjp_delete_parse_stack(&state.target_stack); - return state.root; + irjp_delete_parse_state(state); + RJP_value* root = state->root; + rjp_free(state); + return root; }else{ - irjp_delete_parse_state(&state); + if(err){ + err->parsestate = state; + err->errcode = status; + err->row = state->column; + err->column = state->row; + }else{ + irjp_delete_parse_state_no_preserve_root(state); + rjp_free(state); + } return NULL; } } //Callback based parse. Runs identical to normal parsing except sets up callback //lex state and calls callback lex function -RJP_value* rjp_parse_cback(int flags, RJP_parse_callback* cback){ - RJP_parse_state state = {.allow_comments = (flags & RJP_PARSE_ALLOW_COMMENTS), - .allow_trail_comma = (flags & RJP_PARSE_ALLOW_TRAILING_COMMA) - }; - irjp_init_parse_state(&state, NULL); - irjp_init_lex_cback_state(&state.lexstate); - int status = irjp_parse_cback(&state, cback); - rjp_free(state.lexstate.str); - rjp_free(state.lexstate.buff); +RJP_value* rjp_parse_cback(int flags, RJP_parse_callback* cback, RJP_parse_error* err){ + RJP_parse_state* state = rjp_calloc(sizeof(RJP_parse_state), 1); + state->allow_comments = (flags & RJP_PARSE_ALLOW_COMMENTS); + state->allow_trail_comma = (flags & RJP_PARSE_ALLOW_TRAILING_COMMA); + + irjp_init_parse_state(state, NULL); + irjp_init_lex_cback_state(&state->lexstate); + int status = irjp_parse_cback(state, cback); if(status == RJP_PARSE_STATUS_SUC){ - irjp_delete_parse_stack(&state.target_stack); - return state.root; + irjp_delete_parse_state(state); + RJP_value* root = state->root; + rjp_free(state); + return root; }else{ - irjp_delete_parse_state(&state); + if(err){ + err->parsestate = state; + err->errcode = status; + err->row = state->column; + err->column = state->row; + }else{ + irjp_delete_parse_state_no_preserve_root(state); + rjp_free(state); + } return NULL; } } diff --git a/tests/parse.c b/tests/parse.c index 76c4cf2..7880a00 100644 --- a/tests/parse.c +++ b/tests/parse.c @@ -25,15 +25,16 @@ int read_callback(char* c, int size, void* userdata){ return i; } -int handle_res(RJP_value* res){ +int handle_res(RJP_value* res, RJP_parse_error* err){ if(res){ fprintf(stderr, "Accepted\n"); } -#ifndef RJP_ENABLE_DIAGNOSTICS else{ - fprintf(stderr, "Failed\n"); + char* buf = rjp_parse_error_to_string(err); + rjp_delete_parse_error(err); + fprintf(stderr, "%s\n", buf); + rjp_free(buf); } -#endif int failed = res == NULL; rjp_free_value(res); return failed; @@ -44,14 +45,16 @@ int test_cbacks(const char* str, RJP_parse_flag flags){ cbacks.read = read_callback; cbacks.data = &cback_data; RJP_value* res; - res = rjp_parse_cback(flags, &cbacks); - return handle_res(res); + RJP_parse_error err; + res = rjp_parse_cback(flags, &cbacks, &err); + return handle_res(res, &err); } int test(const char* str, RJP_parse_flag flags){ RJP_value* res; - res = rjp_parse(str, flags); - return handle_res(res); + RJP_parse_error err; + res = rjp_parse(str, flags, &err); + return handle_res(res, &err); } struct parse_pair{