]> git.rm.cloudns.org Git - xonotic/darkplaces.git/commitdiff
json: Crude (but working) initial implementation of json parser (lexer only for now)
authorCloudwalk <cloudwalk009@gmail.com>
Sun, 28 Mar 2021 04:45:22 +0000 (00:45 -0400)
committerCloudwalk <cloudwalk009@gmail.com>
Sun, 28 Mar 2021 04:45:22 +0000 (00:45 -0400)
host.c
json.c [new file with mode: 0644]
makefile.inc

diff --git a/host.c b/host.c
index 1dc4b0d5b8d2c65dd569bad28d0f6d1b302e98bf..d3e5fb6f3f0baa610bd8433912545553a67315f3 100644 (file)
--- a/host.c
+++ b/host.c
@@ -189,6 +189,7 @@ Host_InitLocal
 */
 void Host_SaveConfig_f(cmd_state_t *cmd);
 void Host_LoadConfig_f(cmd_state_t *cmd);
+void Json_Test_f(cmd_state_t *cmd);
 extern cvar_t sv_writepicture_quality;
 extern cvar_t r_texture_jpeg_fastpicmip;
 static void Host_InitLocal (void)
@@ -198,6 +199,7 @@ static void Host_InitLocal (void)
        Cmd_AddCommand(CF_SHARED, "saveconfig", Host_SaveConfig_f, "save settings to config.cfg (or a specified filename) immediately (also automatic when quitting)");
        Cmd_AddCommand(CF_SHARED, "loadconfig", Host_LoadConfig_f, "reset everything and reload configs");
        Cmd_AddCommand(CF_SHARED, "sendcvar", SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
+       Cmd_AddCommand(CF_SHARED, "json_test", Json_Test_f, "test the json parser");
        Cvar_RegisterVariable (&cl_maxphysicsframesperserverframe);
        Cvar_RegisterVariable (&host_framerate);
        Cvar_RegisterCallback (&host_framerate, Host_Framerate_c);
diff --git a/json.c b/json.c
new file mode 100644 (file)
index 0000000..c12dea4
--- /dev/null
+++ b/json.c
@@ -0,0 +1,379 @@
+/*
+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 "quakedef.h"
+#include <setjmp.h>
+
+// taken from json's wikipedia article
+const char json_test_string[] =
+{
+       "{\n"
+       "\t\"firstName\": \"John\",\n"
+       "\t\"lastName\": \"Smith\",\n"
+       "\t\"isAlive\": true,\n"
+       "\t\"age\": 27,\n"
+       "\t\"address\": {\n"
+       "\t\t\"streetAddress\": \"21 2nd Street\",\n"
+       "\t\t\"city\": \"New York\",\n"
+       "\t\t\"state\": \"NY\",\n"
+       "\t\t\"postalCode\": \"10021-3100\"\n"
+       "\t},\n"
+       "\t\"phoneNumbers\": [\n"
+       "\t\t{\n"
+       "\t\t\t\"type\": \"home\",\n"
+       "\t\t\t\"number\": \"212 555-1234\"\n"
+       "\t\t},\n"
+       "\t\t{\n"
+       "\t\t\t\"type\": \"office\",\n"
+       "\t\t\t\"number\": \"646 555-4567\"\n"
+       "\t\t}\n"
+       "\t],\n"
+       "\t\"children\": [],\n"
+       "\t\"spouse\": null\n"
+       "}\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,
+       JSON_TYPE_OBJECT = 1,
+       JSON_TYPE_ARRAY = 2,
+       JSON_TYPE_STRING = 3,
+       JSON_TYPE_PRIMITIVE = 4
+} qjson_type_t;
+
+typedef struct qjson_token_s
+{
+       qjson_type_t type;
+       struct qjson_token_s *next; // if an array, next will be a NULL terminated array
+       char *string; // ASCII only for now
+} qjson_token_t;
+
+struct qjson_state_s
+{
+       qjson_token_t *head, *cur;
+       const char *buf;
+       const char *pos;
+       int line, col;
+};
+
+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)
+{
+       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;
+}
+
+// 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)
+{
+       if(*state->pos == '/')
+       {
+               if(*state->pos++ == '/')
+                       Json_Parse_SkipLine(state);
+               else if(*state->pos == '*')
+               {
+                       while(*state->pos++ != '*' && *state->pos + 1 != '/')
+                               continue;
+               }
+               else
+                       Json_Parse_Error(state, JSON_ERR_INVAL);
+               return true;
+       }
+       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)
+{
+       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));
+}
+
+// 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)
+{
+       do {
+               Json_Parse_Next(state, 1);
+               if(*state->pos == '\\')
+                       Json_Parse_Next(state, 1);
+       } while(*state->pos != '"');
+
+       Json_Parse_Next(state, 1);
+}
+
+// Handles numbers. Json numbers can be either an integer or a double.
+static qbool Json_Parse_Number(struct qjson_state_s *state)
+{
+       int i, numsize;
+       const char *in = state->pos;
+       //char out[128];
+       qbool is_float = false;
+       qbool is_exp = false;
+
+       for(i = 0, numsize = 0; isdigit(in[i]); i++, numsize++)
+       {
+               //out[numsize] = in[numsize];
+
+               if(in[i] == '.')
+               {
+                       if(is_float || is_exp)
+                               Json_Parse_Error(state, JSON_ERR_INVAL);
+                       is_float = true;
+                       i++;
+                       continue;
+               }
+
+               if(in[i] == 'e' || in[i] == 'E')
+               {
+                       if(is_exp)
+                               Json_Parse_Error(state, JSON_ERR_INVAL);
+                       if(in[i+1] == '+' || in[i+1] == '-')
+                               i++;
+                       is_exp = true;
+                       i++;
+                       continue;
+               }
+       }
+       // TODO: use strtod()
+       Json_Parse_Next(state, i);
+       return true;
+}
+
+// Parse a keyword.
+static qbool Json_Parse_Keyword(struct qjson_state_s *state, const char *keyword)
+{
+       size_t keyword_size = strlen(keyword);
+       if(!strncmp(keyword, state->pos, keyword_size))
+       {
+               Json_Parse_Next(state, keyword_size);
+               return true;
+       }
+       return false;
+}
+
+// Parse a value.
+static void Json_Parse_Value(struct qjson_state_s *state)
+{
+       Json_Parse_Next(state, 1);
+
+       switch(Json_Parse_NextToken(state))
+       {
+       case '"': // string
+               Json_Parse_String(state);
+               break;
+       case '{': // object
+               Json_Parse_Object(state);
+               break;
+       case '[': // array
+               Json_Parse_Array(state);
+               break;
+       case '-':
+               Json_Parse_Number(state);
+               break;
+       default:
+               if(Json_Parse_Keyword(state, "true"))
+                       break;
+               if(Json_Parse_Keyword(state, "false"))
+                       break;
+               if(Json_Parse_Keyword(state, "null"))
+                       break;
+               if(isdigit(*state->pos))
+                       Json_Parse_Number(state);
+       }
+}
+
+// Parse an object.
+static void Json_Parse_Object(struct qjson_state_s *state)
+{
+       /*
+        * 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 the key
+               if(Json_Parse_NextToken(state) == '"')
+                       Json_Parse_String(state);
+               else
+                       goto fail;
+               
+               // And its value
+               if(Json_Parse_NextToken(state) == ':')
+                       Json_Parse_Value(state);
+               else
+                       goto fail;
+       } while (Json_Parse_NextToken(state) == ',');
+
+       if(Json_Parse_NextToken(state) == '}')
+               return;
+fail:
+       Json_Parse_Error(state, JSON_ERR_INVAL);
+}
+
+// Parse an array.
+static void Json_Parse_Array(struct qjson_state_s *state)
+{
+       /*
+        * 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) == ',');
+
+       if(Json_Parse_NextToken(state) == ']')
+               return;
+       else
+               Json_Parse_Error(state, JSON_ERR_INVAL);
+}
+
+// Main function for the parser.
+qjson_token_t *Json_Parse(const char *data)
+{
+       struct qjson_state_s state =
+       {
+               .head = NULL,
+               .buf = data,
+               .pos = &data[0],
+               .line = 1,
+               .col = 1
+       };
+
+       if(data == NULL)
+       {
+               Con_Printf(CON_ERROR "Json_Parse: Empty json file\n");
+               return NULL;
+       }
+
+       if(setjmp(json_error))
+       {
+               // actually not sure about this
+               return NULL;
+       }
+
+       if(Json_Parse_NextToken(&state) == '{')
+               Json_Parse_Object(&state);
+       else
+       {
+               Con_Printf(CON_ERROR "Json_Parse: Not a json file\n");
+               return NULL;
+       }
+
+       // Success!
+       // TODO: Actually parse.
+       Con_Printf("Hmm, yes. This json is made of json\n");
+
+       return state.head;
+}
+
+void Json_Test_f(cmd_state_t *cmd)
+{
+       Json_Parse(json_test_string);
+}
index 2fdc966a2d3c1c612d6b5b6deba187e40f165965..eefcf8ee3c1bffde5fce335be9b87b82ea02b523 100644 (file)
@@ -124,6 +124,7 @@ OBJ_COMMON= \
        image.o \
        image_png.o \
        jpeg.o \
+       json.o \
        keys.o \
        lhnet.o \
        libcurl.o \