From: drh Date: Sat, 15 Aug 2015 21:25:36 +0000 (+0000) Subject: Add a prototype JSON parser. X-Git-Tag: version-3.9.0~204^2~18 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e9c37f32e57ba56b4ba14b5445a65ebaecd641e2;p=thirdparty%2Fsqlite.git Add a prototype JSON parser. FossilOrigin-Name: 789ba487000aa73621a41d115ad5de455ea8ea31 --- diff --git a/ext/misc/json.c b/ext/misc/json.c index 4f3e1c02bb..2fce79610c 100644 --- a/ext/misc/json.c +++ b/ext/misc/json.c @@ -77,6 +77,7 @@ SQLITE_EXTENSION_INIT1 #include #include +#include /* Unsigned integer types */ typedef sqlite3_uint64 u64; @@ -93,20 +94,41 @@ struct Json { u64 nAlloc; /* Bytes of storage available in zBuf[] */ u64 nUsed; /* Bytes of zBuf[] currently used */ u8 bStatic; /* True if zBuf is static space */ - u8 mallocFailed; /* True if an OOM has been encountered */ + u8 oom; /* True if an OOM has been encountered */ char zSpace[100]; /* Initial static space */ }; -/* JSONB type values +/* JSON type values */ -#define JSONB_NULL 0 -#define JSONB_TRUE 1 -#define JSONB_FALSE 2 -#define JSONB_INT 3 -#define JSONB_REAL 4 -#define JSONB_STRING 5 -#define JSONB_ARRAY 6 -#define JSONB_OBJECT 7 +#define JSON_NULL 0 +#define JSON_TRUE 1 +#define JSON_FALSE 2 +#define JSON_INT 3 +#define JSON_REAL 4 +#define JSON_STRING 5 +#define JSON_ARRAY 6 +#define JSON_OBJECT 7 + +/* A single node of parsed JSON +*/ +typedef struct JsonNode JsonNode; +struct JsonNode { + u32 eType; /* One of the JSON_ type values */ + u32 n; /* Bytes of content, or number of sub-nodes */ + const char *zContent; /* Content for JSON_INT, JSON_REAL, or JSON_STRING */ +}; + +/* A completely parsed JSON string +*/ +typedef struct JsonParse JsonParse; +struct JsonParse { + u32 nNode; /* Number of slots of aNode[] used */ + u32 nAlloc; /* Number of slots of aNode[] allocated */ + JsonNode *aNode; /* Array of nodes containing the parse */ + const char *zJson; /* Original JSON string */ + u8 oom; /* Set to true if out of memory */ +}; + #if 0 /* @@ -177,7 +199,7 @@ static void jsonZero(Json *p){ */ static void jsonInit(Json *p, sqlite3_context *pCtx){ p->pCtx = pCtx; - p->mallocFailed = 0; + p->oom = 0; jsonZero(p); } @@ -194,7 +216,7 @@ static void jsonReset(Json *p){ /* Report an out-of-memory (OOM) condition */ static void jsonOom(Json *p){ - p->mallocFailed = 1; + p->oom = 1; sqlite3_result_error_nomem(p->pCtx); jsonReset(p); } @@ -206,7 +228,7 @@ static int jsonGrow(Json *p, u32 N){ u64 nTotal = NnAlloc ? p->nAlloc*2 : p->nAlloc+N+100; char *zNew; if( p->bStatic ){ - if( p->mallocFailed ) return SQLITE_NOMEM; + if( p->oom ) return SQLITE_NOMEM; zNew = sqlite3_malloc64(nTotal); if( zNew==0 ){ jsonOom(p); @@ -235,6 +257,12 @@ static void jsonAppendRaw(Json *p, const char *zIn, u32 N){ p->nUsed += N; } +/* Append the zero-terminated string zIn +*/ +static void jsonAppend(Json *p, const char *zIn){ + jsonAppendRaw(p, zIn, (u32)strlen(zIn)); +} + /* Append the N-byte string in zIn to the end of the Json string ** under construction. Enclose the string in "..." and escape ** any double-quotes or backslash characters contained within the @@ -343,7 +371,7 @@ static void jsonAppendVarint(Json *p, u64 X){ /* Make the JSON in p the result of the SQL function. */ static void jsonResult(Json *p){ - if( p->mallocFailed==0 ){ + if( p->oom==0 ){ sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, p->bStatic ? SQLITE_TRANSIENT : sqlite3_free, SQLITE_UTF8); @@ -355,7 +383,7 @@ static void jsonResult(Json *p){ /* Make the JSONB in p the result of the SQL function. */ static void jsonbResult(Json *p){ - if( p->mallocFailed==0 ){ + if( p->oom==0 ){ sqlite3_result_blob(p->pCtx, p->zBuf, p->nUsed, p->bStatic ? SQLITE_TRANSIENT : sqlite3_free); jsonZero(p); @@ -428,7 +456,7 @@ static void jsonbArrayFunc( for(i=0; iaNode[] of the +** new node, or -1 if a memory allocation fails. +*/ +static int jsonParseAddNode( + JsonParse *pParse, /* Append the node to this object */ + u32 eType, /* Node type */ + u32 n, /* Content size or sub-node count */ + const char *zContent /* Content */ +){ + JsonNode *p; + if( pParse->nNode>=pParse->nAlloc ){ + u32 nNew; + JsonNode *pNew; + if( pParse->oom ) return -1; + nNew = pParse->nAlloc*2 + 10; + if( nNew<=pParse->nNode ){ + pParse->oom = 1; + return -1; + } + pNew = sqlite3_realloc(pParse->aNode, sizeof(JsonNode)*nNew); + if( pNew==0 ){ + pParse->oom = 1; + return -1; + } + pParse->nAlloc = nNew; + pParse->aNode = pNew; + } + p = &pParse->aNode[pParse->nNode]; + p->eType = eType; + p->n = n; + p->zContent = zContent; + return pParse->nNode++; +} + +/* +** Parse a single JSON value which begins at pParse->zJson[i]. Return the +** index of the first character past the end of the value parsed. +** +** Return negative for a syntax error. Special cases: return -2 if the +** first non-whitespace character is '}' and return -3 if the first +** non-whitespace character is ']'. +*/ +static int jsonParseValue(JsonParse *pParse, u32 i){ + char c; + u32 j; + u32 iThis; + int x; + while( isspace(pParse->zJson[i]) ){ i++; } + if( (c = pParse->zJson[i])==0 ) return 0; + if( c=='{' ){ + /* Parse object */ + iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); + if( iThis<0 ) return -1; + for(j=i+1;;j++){ + while( isspace(pParse->zJson[j]) ){ j++; } + x = jsonParseValue(pParse, j); + if( x<0 ){ + if( x==(-2) && pParse->nNode==iThis+1 ) return j+1; + return -1; + } + if( pParse->aNode[pParse->nNode-1].eType!=JSON_STRING ) return -1; + j = x; + while( isspace(pParse->zJson[j]) ){ j++; } + if( pParse->zJson[j]!=':' ) return -1; + j++; + x = jsonParseValue(pParse, j); + if( x<0 ) return -1; + j = x; + while( isspace(pParse->zJson[j]) ){ j++; } + c = pParse->zJson[j]; + if( c==',' ) continue; + if( c!='}' ) return -1; + break; + } + pParse->aNode[iThis].n = pParse->nNode - iThis - 1; + return j+1; + }else if( c=='[' ){ + /* Parse array */ + iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); + if( iThis<0 ) return -1; + for(j=i+1;;j++){ + while( isspace(pParse->zJson[j]) ){ j++; } + x = jsonParseValue(pParse, j); + if( x<0 ){ + if( x==(-3) && pParse->nNode==iThis+1 ) return j+1; + return -1; + } + j = x; + while( isspace(pParse->zJson[j]) ){ j++; } + c = pParse->zJson[j]; + if( c==',' ) continue; + if( c!=']' ) return -1; + break; + } + pParse->aNode[iThis].n = pParse->nNode - iThis - 1; + return j+1; + }else if( c=='"' ){ + /* Parse string */ + j = i+1; + for(;;){ + c = pParse->zJson[j]; + if( c==0 ) return -1; + if( c=='\\' ){ + c = pParse->zJson[++j]; + if( c==0 ) return -1; + }else if( c=='"' ){ + break; + } + j++; + } + jsonParseAddNode(pParse, JSON_STRING, j+1-i, &pParse->zJson[i]); + return j+1; + }else if( c=='n' + && strncmp(pParse->zJson+i,"null",4)==0 + && !isalnum(pParse->zJson[i+5]) ){ + jsonParseAddNode(pParse, JSON_NULL, 0, 0); + return i+4; + }else if( c=='t' + && strncmp(pParse->zJson+i,"true",4)==0 + && !isalnum(pParse->zJson[i+5]) ){ + jsonParseAddNode(pParse, JSON_TRUE, 0, 0); + return i+4; + }else if( c=='f' + && strncmp(pParse->zJson+i,"false",5)==0 + && !isalnum(pParse->zJson[i+6]) ){ + jsonParseAddNode(pParse, JSON_FALSE, 0, 0); + return i+5; + }else if( c=='-' || (c>='0' && c<='9') ){ + /* Parse number */ + u8 seenDP = 0; + u8 seenE = 0; + j = i+1; + for(;; j++){ + c = pParse->zJson[j]; + if( c>='0' && c<='9' ) continue; + if( c=='.' ){ + if( pParse->zJson[j-1]=='-' ) return -1; + if( seenDP ) return -1; + seenDP = 1; + continue; + } + if( c=='e' || c=='E' ){ + if( pParse->zJson[j-1]<'0' ) return -1; + if( seenE ) return -1; + seenDP = seenE = 1; + c = pParse->zJson[j+1]; + if( c=='+' || c=='-' ) j++; + continue; + } + break; + } + if( pParse->zJson[j-1]<'0' ) return -1; + jsonParseAddNode(pParse, seenDP ? JSON_REAL : JSON_INT, + j - i, &pParse->zJson[i]); + return j; + }else if( c=='}' ){ + return -2; /* End of {...} */ + }else if( c==']' ){ + return -3; /* End of [...] */ + }else{ + return -1; /* Syntax error */ + } +} + +/* +** Parse a complete JSON string. Return 0 on success or non-zero if there +** are any errors. If an error occurs, free all memory associated with +** pParse. +** +** pParse is uninitialized when this routine is called. +*/ +static int jsonParse(JsonParse *pParse, const char *zJson){ + int i; + if( zJson==0 ) return 1; + memset(pParse, 0, sizeof(*pParse)); + pParse->zJson = zJson; + i = jsonParseValue(pParse, 0); + if( i>0 ){ + while( isspace(zJson[i]) ) i++; + if( zJson[i] ) i = -1; + } + if( i<0 ){ + sqlite3_free(pParse->aNode); + pParse->aNode = 0; + pParse->nNode = 0; + pParse->nAlloc = 0; + return 1; + } + return 0; +} + +/* +** The json_debug(JSON) function returns a string which describes +** a parse of the JSON provided. Or it returns NULL if JSON is not +** well-formed. +*/ +static void jsonDebugFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Json s; /* Output string - not real JSON */ + JsonParse x; /* The parse */ + u32 i; + char zBuf[50]; + static const char *azType[] = { + "NULL", "TRUE", "FALSE", "INT", "REAL", "STRING", "ARRAY", "OBJECT" + }; + + assert( argc==1 ); + if( jsonParse(&x, (const char*)sqlite3_value_text(argv[0])) ) return; + jsonInit(&s, context); + for(i=0; i=JSON_INT ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, " n: %u\n", x.aNode[i].n); + jsonAppend(&s, zBuf); + } + if( x.aNode[i].zContent!=0 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, " ofst: %u\n", + (u32)(x.aNode[i].zContent - x.zJson)); + jsonAppend(&s, zBuf); + jsonAppendRaw(&s, " text: ", 8); + jsonAppendRaw(&s, x.aNode[i].zContent, x.aNode[i].n); + jsonAppendRaw(&s, "\n", 1); + } + } + sqlite3_free(x.aNode); + jsonResult(&s); +} + #ifdef _WIN32 __declspec(dllexport) #endif @@ -540,6 +805,7 @@ int sqlite3_json_init( { "json_array", -1, jsonArrayFunc }, { "jsonb_array", -1, jsonbArrayFunc }, { "json_object", -1, jsonObjectFunc }, + { "json_debug", 1, jsonDebugFunc }, }; SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; /* Unused parameter */ diff --git a/manifest b/manifest index 1d41c76aaf..acc6cbf136 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Experimental\scode\s(untested)\sfor\sa\sJSONB\sdatatype. -D 2015-08-13T13:54:59.587 +C Add\sa\sprototype\sJSON\sparser. +D 2015-08-15T21:25:37.000 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 7669f34c487f5b328de6b508f374ee1e56558bb0 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -192,7 +192,7 @@ F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2 F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f F ext/misc/fuzzer.c 4c84635c71c26cfa7c2e5848cf49fe2d2cfcd767 F ext/misc/ieee754.c b0362167289170627659e84173f5d2e8fee8566e -F ext/misc/json.c 748c5bffa8840f1b0bdc4b5ff672949c31c7f436 +F ext/misc/json.c f4fa58cb8c6a7e13ae9a357e8c5783f43c60f595 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 F ext/misc/percentile.c bcbee3c061b884eccb80e21651daaae8e1e43c63 F ext/misc/regexp.c af92cdaa5058fcec1451e49becc7ba44dba023dc @@ -1374,7 +1374,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 414a95f3b79359f167858b2325fd2be223569c66 -R 45e5df2bce81d8e555551bffb25d0cb6 +P e3596ac7b1dd5bde3f9cae5781a6806d8d9f7166 +R 45c6bd1bef56b998d07e7b3125f794b3 U drh -Z 62911abad9701038f8848b5004ef4c38 +Z e2e8ec51bb6080feb16e3eda35039c7d diff --git a/manifest.uuid b/manifest.uuid index 58f3872eaf..7cd175e6dd 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e3596ac7b1dd5bde3f9cae5781a6806d8d9f7166 \ No newline at end of file +789ba487000aa73621a41d115ad5de455ea8ea31 \ No newline at end of file