From 53d04464b13b12d0d8aabd35021b1d03bc7942e7 Mon Sep 17 00:00:00 2001 From: Cloudwalk Date: Sat, 24 Apr 2021 10:56:26 -0400 Subject: [PATCH] Move general purpose parsing functions to a new helper library --- darkplaces-sdl2-vs2017.vcxproj | 3 + darkplaces-sdl2-vs2019.vcxproj | 2 + json.c | 245 ++++++++++++--------------------- makefile.inc | 1 + parser.c | 138 +++++++++++++++++++ parser.h | 51 +++++++ 6 files changed, 284 insertions(+), 156 deletions(-) create mode 100644 parser.c create mode 100644 parser.h diff --git a/darkplaces-sdl2-vs2017.vcxproj b/darkplaces-sdl2-vs2017.vcxproj index eb3ac72a..b0d6df69 100644 --- a/darkplaces-sdl2-vs2017.vcxproj +++ b/darkplaces-sdl2-vs2017.vcxproj @@ -247,6 +247,7 @@ + @@ -264,6 +265,7 @@ + @@ -381,6 +383,7 @@ + diff --git a/darkplaces-sdl2-vs2019.vcxproj b/darkplaces-sdl2-vs2019.vcxproj index a44a9231..e8bc3460 100644 --- a/darkplaces-sdl2-vs2019.vcxproj +++ b/darkplaces-sdl2-vs2019.vcxproj @@ -248,6 +248,7 @@ + @@ -382,6 +383,7 @@ + diff --git a/json.c b/json.c index c12dea4f..4f4770ae 100644 --- a/json.c +++ b/json.c @@ -18,8 +18,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include "quakedef.h" -#include +#include "darkplaces.h" +#include "parser.h" // taken from json's wikipedia article const char json_test_string[] = @@ -50,16 +50,6 @@ const char json_test_string[] = "}\n\000" }; -static jmp_buf json_error; - -typedef enum qjson_err_e -{ - JSON_ERR_SUCCESS = 0, - JSON_ERR_INVAL = 1, - JSON_ERR_EOF = 2, - JSON_ERR_EMPTY = 3 -} qjson_err_t; - typedef enum qjson_type_e { JSON_TYPE_UNDEFINED = 0, @@ -76,142 +66,74 @@ typedef struct qjson_token_s char *string; // ASCII only for now } qjson_token_t; -struct qjson_state_s +typedef struct qjson_state_s { qjson_token_t *head, *cur; - const char *buf; - const char *pos; - int line, col; -}; + qparser_state_t state; +} qjson_state_t; static void Json_Parse_Object(struct qjson_state_s *state); static void Json_Parse_Array(struct qjson_state_s *state); -// Tell the user that their json is broken, why it's broken, and where it's broken, so hopefully they fix it. -static void Json_Parse_Error(struct qjson_state_s *state, qjson_err_t error) -{ - if(!error) - return; - else - { - switch (error) - { - case JSON_ERR_INVAL: - Con_Printf(CON_ERROR "Json Error: Unexpected token '%c', line %i, column %i\n", *state->pos, state->line, state->col); - break; - case JSON_ERR_EOF: - Con_Printf(CON_ERROR "Json Error: Unexpected end-of-file\n"); - break; - default: - return; - } - } - longjmp(json_error, 1); -} - -// Skips newlines, and handles different line endings. -static qbool Json_Parse_Newline(struct qjson_state_s *state) +// Checks for C/C++-style comments and ignores them. This is not standard json. +static qbool Json_Parse_Comment_SingleLine(struct qparser_state_s *state) { - if(*state->pos == '\n') - goto newline; - if(*state->pos == '\r') + if(*state->pos == '/') { - if(*state->pos + 1 == '\n') - state->pos++; - goto newline; + // FIXME: Let the parser interface increment this? + if(*state->pos++ == '/') + return true; + else + Parse_Error(state, PARSE_ERR_INVAL); } return false; -newline: - state->col = 1; - state->line++; - state->pos++; - return true; } -// Skips the current line. Only useful for comments. -static void Json_Parse_SkipLine(struct qjson_state_s *state) -{ - while(!Json_Parse_Newline(state)) - state->pos++; -} - -// Checks for C/C++-style comments and ignores them. This is not standard json. -static qbool Json_Parse_Comment(struct qjson_state_s *state) +static qbool Json_Parse_CheckComment_Multiline_Start(struct qparser_state_s *state) { if(*state->pos == '/') { - if(*state->pos++ == '/') - Json_Parse_SkipLine(state); - else if(*state->pos == '*') - { - while(*state->pos++ != '*' && *state->pos + 1 != '/') - continue; - } + // FIXME: Let the parser interface increment this? + if(*state->pos++ == '*') + return true; else - Json_Parse_Error(state, JSON_ERR_INVAL); - return true; + Parse_Error(state, PARSE_ERR_INVAL); } return false; } -// Advance forward in the stream as many times as 'count', cleanly. -static void Json_Parse_Next(struct qjson_state_s *state, size_t count) +static qbool Json_Parse_CheckComment_Multiline_End(struct qparser_state_s *state) { - state->col = state->col + count; - state->pos = state->pos + count; - - if(!*state->pos) - Json_Parse_Error(state, JSON_ERR_EOF); -} - -// Skip all whitespace, as we normally know it. -static void Json_Parse_Whitespace(struct qjson_state_s *state) -{ - while(*state->pos == ' ' || *state->pos == '\t') - Json_Parse_Next(state, 1); -} - -// Skip all whitespace, as json defines it. -static void Json_Parse_Skip(struct qjson_state_s *state) -{ - /* - * Repeat this until we run out of whitespace, newlines, and comments. - * state->pos should be left on non-whitespace when this returns. - */ - do { - Json_Parse_Whitespace(state); - } while (Json_Parse_Comment(state) || Json_Parse_Newline(state)); + if(*state->pos == '*') + { + // FIXME: Let the parser interface increment this? + if(*state->pos++ == '/') + return true; + } + return false; } -// Skip to the next token that isn't whitespace. Hopefully a valid one. -static char Json_Parse_NextToken(struct qjson_state_s *state) -{ - /* - * This assumes state->pos is already on whitespace. Most of the time this - * doesn't happen automatically, but advancing the pointer here would break - * comment and newline handling when it does happen automatically. - */ - Json_Parse_Skip(state); - return *state->pos; -} // TODO: handle escape sequences -static void Json_Parse_String(struct qjson_state_s *state) +static void Json_Parse_String(struct qjson_state_s *json) { do { - Json_Parse_Next(state, 1); - if(*state->pos == '\\') - Json_Parse_Next(state, 1); - } while(*state->pos != '"'); + Parse_Next(&json->state, 1); + if(*json->state.pos == '\\') + { + Parse_Next(&json->state, 1); + continue; + } + } while(*json->state.pos != '"'); - Json_Parse_Next(state, 1); + Parse_Next(&json->state, 1); } // Handles numbers. Json numbers can be either an integer or a double. -static qbool Json_Parse_Number(struct qjson_state_s *state) +static qbool Json_Parse_Number(struct qjson_state_s *json) { int i, numsize; - const char *in = state->pos; + const char *in = json->state.pos; //char out[128]; qbool is_float = false; qbool is_exp = false; @@ -223,7 +145,7 @@ static qbool Json_Parse_Number(struct qjson_state_s *state) if(in[i] == '.') { if(is_float || is_exp) - Json_Parse_Error(state, JSON_ERR_INVAL); + Parse_Error(&json->state, PARSE_ERR_INVAL); is_float = true; i++; continue; @@ -232,7 +154,7 @@ static qbool Json_Parse_Number(struct qjson_state_s *state) if(in[i] == 'e' || in[i] == 'E') { if(is_exp) - Json_Parse_Error(state, JSON_ERR_INVAL); + Parse_Error(&json->state, PARSE_ERR_INVAL); if(in[i+1] == '+' || in[i+1] == '-') i++; is_exp = true; @@ -241,109 +163,120 @@ static qbool Json_Parse_Number(struct qjson_state_s *state) } } // TODO: use strtod() - Json_Parse_Next(state, i); + Parse_Next(&json->state, i); return true; } // Parse a keyword. -static qbool Json_Parse_Keyword(struct qjson_state_s *state, const char *keyword) +static qbool Json_Parse_Keyword(struct qjson_state_s *json, const char *keyword) { size_t keyword_size = strlen(keyword); - if(!strncmp(keyword, state->pos, keyword_size)) + if(!strncmp(keyword, json->state.pos, keyword_size)) { - Json_Parse_Next(state, keyword_size); + Parse_Next(&json->state, keyword_size); return true; } return false; } // Parse a value. -static void Json_Parse_Value(struct qjson_state_s *state) +static void Json_Parse_Value(struct qjson_state_s *json) { - Json_Parse_Next(state, 1); + Parse_Next(&json->state, 1); - switch(Json_Parse_NextToken(state)) + switch(Parse_NextToken(&json->state)) { case '"': // string - Json_Parse_String(state); + Json_Parse_String(json); break; case '{': // object - Json_Parse_Object(state); + Json_Parse_Object(json); break; case '[': // array - Json_Parse_Array(state); + Json_Parse_Array(json); break; case '-': - Json_Parse_Number(state); + Json_Parse_Number(json); break; default: - if(Json_Parse_Keyword(state, "true")) + if(Json_Parse_Keyword(json, "true")) break; - if(Json_Parse_Keyword(state, "false")) + if(Json_Parse_Keyword(json, "false")) break; - if(Json_Parse_Keyword(state, "null")) + if(Json_Parse_Keyword(json, "null")) break; - if(isdigit(*state->pos)) - Json_Parse_Number(state); + if(isdigit(*json->state.pos)) + Json_Parse_Number(json); } } // Parse an object. -static void Json_Parse_Object(struct qjson_state_s *state) +static void Json_Parse_Object(struct qjson_state_s *json) { /* * Json objects are basically a data map; key-value pairs. * They end in a comma or a closing curly brace. */ do { - Json_Parse_Next(state, 1); + Parse_Next(&json->state, 1); // Parse the key - if(Json_Parse_NextToken(state) == '"') - Json_Parse_String(state); + if(Parse_NextToken(&json->state) == '"') + Json_Parse_String(json); else goto fail; // And its value - if(Json_Parse_NextToken(state) == ':') - Json_Parse_Value(state); + if(Parse_NextToken(&json->state) == ':') + Json_Parse_Value(json); else goto fail; - } while (Json_Parse_NextToken(state) == ','); + } while (Parse_NextToken(&json->state) == ','); - if(Json_Parse_NextToken(state) == '}') + if(Parse_NextToken(&json->state) == '}') return; fail: - Json_Parse_Error(state, JSON_ERR_INVAL); + Parse_Error(&json->state, PARSE_ERR_INVAL); } // Parse an array. -static void Json_Parse_Array(struct qjson_state_s *state) +static void Json_Parse_Array(struct qjson_state_s *json) { /* * Json arrays are basically lists. They can contain * any value, comma-separated, and end with a closing square bracket. */ do { - Json_Parse_Value(state); - } while (Json_Parse_NextToken(state) == ','); + Json_Parse_Value(json); + } while (Parse_NextToken(&json->state) == ','); - if(Json_Parse_NextToken(state) == ']') + if(Parse_NextToken(&json->state) == ']') return; else - Json_Parse_Error(state, JSON_ERR_INVAL); + Parse_Error(&json->state, PARSE_ERR_INVAL); } // Main function for the parser. qjson_token_t *Json_Parse(const char *data) { - struct qjson_state_s state = + struct qjson_state_s json = { .head = NULL, - .buf = data, - .pos = &data[0], - .line = 1, - .col = 1 + .cur = NULL, + .state = + { + .name = "json", + .buf = data, + .pos = &data[0], + .line = 1, + .col = 1, + .callback = + { + .CheckComment_SingleLine = Json_Parse_Comment_SingleLine, + .CheckComment_Multiline_Start = Json_Parse_CheckComment_Multiline_Start, + .CheckComment_Multiline_End = Json_Parse_CheckComment_Multiline_End + } + } }; if(data == NULL) @@ -352,14 +285,14 @@ qjson_token_t *Json_Parse(const char *data) return NULL; } - if(setjmp(json_error)) + if(setjmp(parse_error)) { // actually not sure about this return NULL; } - if(Json_Parse_NextToken(&state) == '{') - Json_Parse_Object(&state); + if(Parse_NextToken(&(json.state)) == '{') + Json_Parse_Object(&json); else { Con_Printf(CON_ERROR "Json_Parse: Not a json file\n"); @@ -370,7 +303,7 @@ qjson_token_t *Json_Parse(const char *data) // TODO: Actually parse. Con_Printf("Hmm, yes. This json is made of json\n"); - return state.head; + return NULL; } void Json_Test_f(cmd_state_t *cmd) diff --git a/makefile.inc b/makefile.inc index 6223aa69..5967a45e 100644 --- a/makefile.inc +++ b/makefile.inc @@ -139,6 +139,7 @@ OBJ_COMMON= \ model_sprite.o \ netconn.o \ palette.o \ + parser.o \ polygon.o \ portals.o \ protocol.o \ diff --git a/parser.c b/parser.c new file mode 100644 index 00000000..534b236c --- /dev/null +++ b/parser.c @@ -0,0 +1,138 @@ +/* +Copyright (C) 2021 David Knapp (Cloudwalk) + +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 2 +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, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "darkplaces.h" +#include "parser.h" + +jmp_buf parse_error; + +// Tell the user that their stuff is broken, why it's broken, and where it's broken, so hopefully they fix it. +void Parse_Error(struct qparser_state_s *state, qparser_err_t error) +{ + if(!error) + return; + else + { + switch (error) + { + case PARSE_ERR_INVAL: + Con_Printf(CON_ERROR "Parse Error: %s: Unexpected token '%c', line %i, column %i\n", state->name, *state->pos, state->line, state->col); + break; + case PARSE_ERR_EOF: + Con_Printf(CON_ERROR "Parse Error: %s: Unexpected end-of-file\n", state->name); + break; + default: + return; + } + } + + longjmp(parse_error, 1); +} + +// Advance forward in the stream as many times as 'count', cleanly. +void Parse_Next(struct qparser_state_s *state, size_t count) +{ + state->col += count; + state->pos += count; + + if(!*state->pos) + Parse_Error(state, PARSE_ERR_EOF); +} + +// Skips newlines, and handles different line endings. +static qbool Parse_Newline(struct qparser_state_s *state) +{ + if(*state->pos == '\n') + goto newline; + if(*state->pos == '\r') + { + if(*state->pos + 1 == '\n') + state->pos++; + goto newline; + } + return false; +newline: + state->col = 1; + state->line++; + state->pos++; + return true; +} + +// Skip all whitespace, as we normally know it. +static inline void Parse_Whitespace(struct qparser_state_s *state) +{ + // TODO: Some languages enforce indentation style. Add a callback to override this. + while(*state->pos == ' ' || *state->pos == '\t') + Parse_Next(state, 1); +} + +// Skips the current line. Only useful for comments. +static inline void Parse_SkipLine(struct qparser_state_s *state) +{ + while(!Parse_Newline(state)) + Parse_Next(state, 1); +} + +static inline qbool Parse_Skip_Comments(struct qparser_state_s *state) +{ + // Make sure these are both defined (or both not defined) + if((state->callback.CheckComment_Multiline_Start != NULL) ^ (state->callback.CheckComment_Multiline_End != NULL)) + Sys_Error("Parse_Skip_Comments: CheckComment_Multiline_Start (or _End) == NULL"); + + // Assume language doesn't support the respective comment types if one of these are NULL. + if(state->callback.CheckComment_SingleLine && state->callback.CheckComment_SingleLine(state)) + Parse_SkipLine(state); + else if(state->callback.CheckComment_Multiline_Start && state->callback.CheckComment_Multiline_Start(state)) + { + do + { + Parse_Next(state, 1); + Parse_Newline(state); + } while (state->callback.CheckComment_Multiline_End(state)); + } + else + return false; + + return true; +} + +// Skip all whitespace. +static inline void Parse_Skip(struct qparser_state_s *state) +{ + /* + * Repeat this until we run out of whitespace, newlines, and comments. + * state->pos should be left on non-whitespace when this returns. + */ + do { + Parse_Whitespace(state); + } while (Parse_Skip_Comments(state) || Parse_Newline(state)); +} + +// Skip to the next token that isn't whitespace. Hopefully a valid one. +char Parse_NextToken(struct qparser_state_s *state) +{ + /* + * This assumes state->pos is already on whitespace. Most of the time this + * doesn't happen automatically, but advancing the pointer here would break + * comment and newline handling when it does happen automatically. + */ + Parse_Skip(state); + return *state->pos; +} diff --git a/parser.h b/parser.h new file mode 100644 index 00000000..9179d3c7 --- /dev/null +++ b/parser.h @@ -0,0 +1,51 @@ +/* +Copyright (C) 2021 David Knapp (Cloudwalk) + +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 2 +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, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "qtypes.h" +#include + +typedef enum qparser_err_e +{ + PARSE_ERR_SUCCESS = 0, + PARSE_ERR_INVAL = 1, + PARSE_ERR_EOF = 2, + PARSE_ERR_EMPTY = 3 +} qparser_err_t; + +typedef struct qparser_state_s +{ + const char *name; + const char *buf; + const char *pos; + int line, col; + + struct + { + qbool (*CheckComment_SingleLine)(struct qparser_state_s *); + qbool (*CheckComment_Multiline_Start)(struct qparser_state_s *); + qbool (*CheckComment_Multiline_End)(struct qparser_state_s *); + } callback; +} qparser_state_t; + +extern jmp_buf parse_error; + +void Parse_Error(struct qparser_state_s *state, qparser_err_t error); +void Parse_Next(struct qparser_state_s *state, size_t count); +char Parse_NextToken(struct qparser_state_s *state); \ No newline at end of file -- 2.39.2