Merge JSON5 support into 7.0

This commit is contained in:
Andrew Johnson
2021-01-05 21:39:15 -06:00
38 changed files with 1591 additions and 528 deletions
+54
View File
@@ -17,6 +17,60 @@ should also be read to understand what has changed since earlier releases.
<!-- Insert new items immediately below here ... -->
### Support for JSON5
The YAJL parser and generator routines in libcom and in the IOC's dbStatic
parser now support the JSON5 standard. This adds various features to JSON
without altering the API for the code other than adding a new option to the
YAJL parser which can be used to disable JSON5 support if desired. The new
features include:
- The ability to handle numeric values `Infinity`, `-Infinity` and `NaN`.
- String values and map keys may be enclosed in single quotes `'`, inside which
the double-quote character `"` doesn't have to be escaped with a back-slash
`\`, although a single-quote character `'` (or apostrophy) must be escaped
inside a single-quoted string.
- Numbers may start with a plus sign, `+`.
- Integers may be expressed in hexadecimal with a leading `0x` or `0X`.
- Floating-point numbers may start or end with their decimal point `.`
(after the sign or before the exponent respectively if present).
- Map keys that match the regex `[A-Za-z_][A-Za-z_0-9]*` don't have to be
enclosed in quotes at all. The dbStatic parser adds `.+-` to the characters
allowed but will add quotes around such keys before passing them to YAJL.
- Arrays and maps allow a comma before the closing bracket/brace character.
- The YAJL parser will elide a backslash followed by a newline characters from
a string value. The dbStatic parser doesn't allow that however.
Code that must also compile against the older API can use the new C macro
`HAS_JSON5` to detect the new version. This macro is defined on including
either the `yajl_parse.h` or `yajl_gen.h` headers, which also provide the
new configuration options to turn on JSON5 support.
All APIs in the IOC that previously accepted JSON will now accept JSON5.
This includes JSON field modifiers (channel filters), JSON link addresses,
constant input link array values and database info-tag values. JSON values
that get parsed by the dbLoadRecords() routine are still more liberal than
the other uses as the ability to use unquoted strings that was called
"relaxed JSON" is still supported, whereas the JSON5 standard and the YAJL
parser only allow unquoted strings to be used for keys in a JSON map.
This also fixes [lauchpad bug #1714455](https://bugs.launchpad.net/bugs/1714455).
### Character Escape Changes
- The libCom routines `epicsStrnRawFromEscaped()` and `dbTranslateEscape()`
declared in epicsString.h no longer accept octal escaped characters such as
`\123` or `\41`.
- The routine `epicsStrnEscapedFromRaw()` now generates hex
excaped characters for unprintable characters such as `\x1f`.
- Hex escape character sequences `\xXX` must now contain exactly 2 hex digits.
- An escape sequence `\0` now generates a zero byte in the raw string, but the
other digits `1-9` should not appear after a back-slash.
These changes are to more closely follow the JSON5 standard, which doesn't
support octal character escapes or the `\a` (Bel, `\x07`) escape sequence.
### Filters in database input links
Input database links can now use channel filters, it is not necessary to
+22 -8
View File
@@ -10,6 +10,7 @@
newline "\n"
backslash "\\"
singlequote "'"
doublequote "\""
comment "#"
whitespace [ \t\r\n]
@@ -18,17 +19,30 @@ stringchar [^"\n\\]
bareword [a-zA-Z0-9_\-+:.\[\]<>;]
punctuation [:,\[\]{}]
normalchar [^"\\\0-\x1f]
normalchar [^"'\\\0-\x1f]
barechar [a-zA-Z0-9_\-+.]
escapedchar ({backslash}["\\/bfnrt])
escapedchar ({backslash}[^ux1-9])
hexdigit [0-9a-fA-F]
latinchar ({backslash}"x"{hexdigit}{2})
unicodechar ({backslash}"u"{hexdigit}{4})
jsonchar ({normalchar}|{escapedchar}|{unicodechar})
jsondqstr ({doublequote}{jsonchar}*{doublequote})
int ("-"?([0-9]|[1-9][0-9]+))
jsondqchar ({normalchar}|{singlequote}|{escapedchar}|{latinchar}|{unicodechar})
jsondqstr ({doublequote}{jsondqchar}*{doublequote})
jsonsqchar ({normalchar}|{doublequote}|{escapedchar}|{latinchar}|{unicodechar})
jsonsqstr ({singlequote}{jsonsqchar}*{singlequote})
jsonstr ({jsondqstr}|{jsonsqstr})
sign ([+-]?)
int ({sign}([0-9]|[1-9][0-9]+))
frac ("."[0-9]+)
exp ([eE][+-]?[0-9]+)
number ({int}{frac}?{exp}?)
exp ([eE]{sign}[0-9]+)
jsonnum ({int}{frac}?{exp}?)
intexp ({int}"."{exp}?)
fracexp ({sign}{frac}{exp}?)
specialnum ("NaN"|{sign}"Infinity")
zerox ("0x"|"0X")
hexint ({sign}{zerox}{hexdigit}+)
number ({jsonnum}|{intexp}|{fracexp}|{specialnum}|{hexint})
%{
#undef YY_INPUT
@@ -97,7 +111,7 @@ static int yyreset(void)
<JSON>{punctuation} return yytext[0];
<JSON>{jsondqstr} {
<JSON>{jsonstr} {
yylval.Str = dbmfStrdup((char *) yytext);
return jsonSTRING;
}
@@ -1172,12 +1172,14 @@ static void dbRecordField(char *name,char *value)
yyerror(NULL);
return;
}
if (*value == '"') {
if (*value == '"' || *value == '\'') {
/* jsonSTRING values still have their quotes */
value++;
value[strlen(value) - 1] = 0;
dbTranslateEscape(value, value); /* in-place; safe & legal */
}
dbTranslateEscape(value, value); /* in-place; safe & legal */
status = dbPutString(pdbentry,value);
if (status) {
char msg[128];
@@ -1203,12 +1205,14 @@ static void dbRecordInfo(char *name, char *value)
if (duplicate) return;
ptempListNode = (tempListNode *)ellFirst(&tempList);
pdbentry = ptempListNode->item;
if (*value == '"') {
if (*value == '"' || *value == '\'') {
/* jsonSTRING values still have their quotes */
value++;
value[strlen(value) - 1] = 0;
dbTranslateEscape(value, value); /* in-place; safe & legal */
}
dbTranslateEscape(value, value); /* yuck: in-place, but safe */
status = dbPutInfo(pdbentry,name,value);
if (status) {
epicsPrintf("Can't set \"%s\" info \"%s\" to \"%s\"\n",
+14 -3
View File
@@ -32,8 +32,8 @@ static int yyAbort = 0;
%token jsonNULL jsonTRUE jsonFALSE
%token <Str> jsonNUMBER jsonSTRING jsonBARE
%type <Str> json_value json_object json_array
%type <Str> json_members json_pair json_elements json_string
%type <Str> json_value json_string json_object json_array
%type <Str> json_members json_pair json_key json_elements
%%
@@ -299,13 +299,24 @@ json_members: json_pair
if (dbStaticDebug>2) printf("json %s\n", $$);
};
json_pair: json_string ':' json_value
json_pair: json_key ':' json_value
{
$$ = dbmfStrcat3($1, ":", $3);
dbmfFree($1); dbmfFree($3);
if (dbStaticDebug>2) printf("json %s\n", $$);
};
json_key: jsonSTRING
| jsonBARE
{
/* A key containing any of these characters must be quoted for YAJL */
if (strcspn($1, "+-.") < strlen($1)) {
$$ = dbmfStrcat3("\"", $1, "\"");
dbmfFree($1);
}
if (dbStaticDebug>2) printf("json %s\n", $$);
};
json_string: jsonSTRING
| jsonBARE
{
+42 -42
View File
@@ -612,7 +612,7 @@ MAIN(chfPluginTest)
/* tag i */
e1 = e_alloc; c1 = 0;
testOk(!(pch = dbChannelCreate(
"x.{\"alloc-fail\":{\"i\":1}}")),
"x.{'alloc-fail':{i:1}}")),
"create channel for alloc-fail: allocPvt returning NULL");
testOk(!puser1, "user part cleaned up");
if (!testOk(c1 == e1, "all expected calls happened"))
@@ -627,7 +627,7 @@ MAIN(chfPluginTest)
/* tag D (t and d) and f */
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate(
"x.{\"strict-tagged\":{\"D\":1.2e15,\"f\":false}}")),
"x.{'strict-tagged':{D:1.2e15,f:false}}")),
"create channel for strict-tagged parsing: D (t and d) and f");
testOk(checkValues(puser1, 3, 12, 0, 1.2e15, "hello", 0, 4),
"guards intact, values correct");
@@ -641,7 +641,7 @@ MAIN(chfPluginTest)
/* tag D2 (t and d) and f */
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate(
"x.{\"strict-tagged\":{\"D2\":1.2e15,\"f\":false}}")),
"x.{'strict-tagged':{D2:1.2e15,f:false}}")),
"create channel for strict-tagged parsing: D2 (t and d) and f");
testOk(checkValues(puser1, 4, 12, 0, 1.2e15, "hello", 0, 4),
"guards intact, values correct");
@@ -655,7 +655,7 @@ MAIN(chfPluginTest)
/* tag F: (t and f), d missing) */
e1 = e_alloc | e_error | e_free; c1 = 0;
testOk(!(pch = dbChannelCreate(
"x.{\"strict-tagged\":{\"F\":false}}")),
"x.{'strict-tagged':{F:false}}")),
"create channel for strict-tagged parsing: F (t and f), d missing");
testOk(!puser1, "user part cleaned up");
if (!testOk(c1 == e1, "all expected calls happened"))
@@ -663,7 +663,7 @@ MAIN(chfPluginTest)
/* tag I: (t and i) and f, d missing) */
e1 = e_alloc | e_error | e_free; c1 = 0;
testOk(!(pch = dbChannelCreate(
"x.{\"strict-tagged\":{\"I\":1,\"f\":false}}")),
"x.{'strict-tagged':{I:1,f:false}}")),
"create channel for strict-tagged parsing: I (t and i) and f, d missing");
testOk(!puser1, "user part cleaned up");
if (!testOk(c1 == e1, "all expected calls happened"))
@@ -676,7 +676,7 @@ MAIN(chfPluginTest)
/* tag i */
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate(
"x.{\"sloppy-tagged\":{\"I\":1}}")),
"x.{'sloppy-tagged':{I:1}}")),
"create channel for sloppy-tagged parsing: I");
testOk(checkValues(puser1, 1, 1, 1, 1.234e5, "hello", 0, 4),
"guards intact, values correct");
@@ -690,7 +690,7 @@ MAIN(chfPluginTest)
/* tag f */
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate(
"x.{\"sloppy-tagged\":{\"F\":false}}")),
"x.{'sloppy-tagged':{F:false}}")),
"create channel for sloppy-tagged parsing: F");
testOk(checkValues(puser1, 2, 12, 0, 1.234e5, "hello", 0, 4),
"guards intact, values correct");
@@ -704,7 +704,7 @@ MAIN(chfPluginTest)
/* tag d */
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate(
"x.{\"sloppy-tagged\":{\"D\":1.2e15}}")),
"x.{'sloppy-tagged':{D:1.2e15}}")),
"create channel for sloppy-tagged parsing: D");
testOk(checkValues(puser1, 3, 12, 1, 1.2e15, "hello", 0, 4),
"guards intact, values correct");
@@ -718,7 +718,7 @@ MAIN(chfPluginTest)
/* tag s */
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate(
"x.{\"sloppy-tagged\":{\"S\":\"bar\"}}")),
"x.{'sloppy-tagged':{S:'bar'}}")),
"create channel for sloppy-tagged parsing: S");
testOk(checkValues(puser1, 4, 12, 1, 1.234e5, "bar", 0, 4),
"guards intact, values correct");
@@ -732,7 +732,7 @@ MAIN(chfPluginTest)
/* tag c */
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate(
"x.{\"sloppy-tagged\":{\"C\":\"R\"}}")),
"x.{'sloppy-tagged':{C:'R'}}")),
"create channel for sloppy-tagged parsing: C");
testOk(checkValues(puser1, 5, 12, 1, 1.234e5, "hello", 0, 1),
"guards intact, values correct");
@@ -749,7 +749,7 @@ MAIN(chfPluginTest)
/* All perfect */
testHead("STRICT parsing: all ok");
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate("x.{\"strict\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")),
testOk(!!(pch = dbChannelCreate("x.{strict:{i:1,f:false,d:1.2e15,s:'bar',c:'R'}}")),
"create channel for strict parsing: JSON correct");
testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 1),
"guards intact, values correct");
@@ -765,35 +765,35 @@ MAIN(chfPluginTest)
testHead("STRICT parsing: any missing parameter must fail");
e1 = e_alloc | e_error | e_free; c1 = 0;
testOk(!(pch = dbChannelCreate(
"x.{\"strict\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\"}}")),
"x.{strict:{i:1,f:false,d:1.2e15,s:'bar'}}")),
"create channel for strict parsing: c missing");
testOk(!puser1, "user part cleaned up");
if (!testOk(c1 == e1, "all expected calls happened"))
testDiag("expected %#x - called %#x", e1, c1);
e1 = e_alloc | e_error | e_free; c1 = 0;
testOk(!(pch = dbChannelCreate(
"x.{\"strict\":{\"f\":false,\"i\":1,\"d\":1.2e15,\"c\":\"R\"}}")),
"x.{strict:{f:false,i:1,d:1.2e15,c:'R'}}")),
"create channel for strict parsing: s missing");
testOk(!puser1, "user part cleaned up");
if (!testOk(c1 == e1, "all expected calls happened"))
testDiag("expected %#x - called %#x", e1, c1);
e1 = e_alloc | e_error | e_free; c1 = 0;
testOk(!(pch = dbChannelCreate(
"x.{\"strict\":{\"i\":1,\"c\":\"R\",\"f\":false,\"s\":\"bar\"}}")),
"x.{strict:{i:1,c:'R',f:false,s:'bar'}}")),
"create channel for strict parsing: d missing");
testOk(!puser1, "user part cleaned up");
if (!testOk(c1 == e1, "all expected calls happened"))
testDiag("expected %#x - called %#x", e1, c1);
e1 = e_alloc | e_error | e_free; c1 = 0;
testOk(!(pch = dbChannelCreate(
"x.{\"strict\":{\"d\":1.2e15,\"c\":\"R\",\"i\":1,\"s\":\"bar\"}}")),
"x.{strict:{d:1.2e15,c:'R',i:1,s:'bar'}}")),
"create channel for strict parsing: f missing");
testOk(!puser1, "user part cleaned up");
if (!testOk(c1 == e1, "all expected calls happened"))
testDiag("expected %#x - called %#x", e1, c1);
e1 = e_alloc | e_error | e_free; c1 = 0;
testOk(!(pch = dbChannelCreate(
"x.{\"strict\":{\"c\":\"R\",\"s\":\"bar\",\"f\":false,\"d\":1.2e15}}")),
"x.{strict:{c:'R',s:'bar',f:false,d:1.2e15}}")),
"create channel for strict parsing: i missing");
testOk(!puser1, "user part cleaned up");
if (!testOk(c1 == e1, "all expected calls happened"))
@@ -805,7 +805,7 @@ MAIN(chfPluginTest)
testHead("NOCONV parsing: missing parameters get default value");
e1 = e_alloc | e_ok; c1 = 0;
testOk(!!(pch = dbChannelCreate(
"x.{\"noconv\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\"}}")),
"x.{noconv:{i:1,f:false,d:1.2e15,s:'bar'}}")),
"create channel for noconv parsing: c missing");
testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 4),
"guards intact, values correct");
@@ -819,28 +819,28 @@ MAIN(chfPluginTest)
e1 = e_any;
testOk(!!(pch = dbChannelCreate(
"x.{\"noconv\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"c\":\"R\"}}")),
"x.{noconv:{i:1,f:false,d:1.2e15,c:'R'}}")),
"create channel for noconv parsing: s missing");
testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "hello", 0, 1),
"guards intact, values correct");
if (pch) dbChannelDelete(pch);
testOk(!!(pch = dbChannelCreate(
"x.{\"noconv\":{\"i\":1,\"f\":false,\"s\":\"bar\",\"c\":\"R\"}}")),
"x.{noconv:{i:1,f:false,s:'bar',c:'R'}}")),
"create channel for noconv parsing: d missing");
testOk(checkValues(puser1, 99, 1, 0, 1.234e5, "bar", 0, 1),
"guards intact, values correct");
if (pch) dbChannelDelete(pch);
testOk(!!(pch = dbChannelCreate(
"x.{\"noconv\":{\"i\":1,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")),
"x.{noconv:{i:1,d:1.2e15,s:'bar',c:'R'}}")),
"create channel for noconv parsing: f missing");
testOk(checkValues(puser1, 99, 1, 1, 1.2e15, "bar", 0, 1),
"guards intact, values correct");
if (pch) dbChannelDelete(pch);
testOk(!!(pch = dbChannelCreate(
"x.{\"noconv\":{\"f\":false,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")),
"x.{noconv:{f:false,d:1.2e15,s:'bar',c:'R'}}")),
"create channel for noconv parsing: i missing");
testOk(checkValues(puser1, 99, 12, 0, 1.2e15, "bar", 0, 1),
"guards intact, values correct");
@@ -849,7 +849,7 @@ MAIN(chfPluginTest)
/* Reject wrong types */
#define WRONGTYPETEST(Var, Val, Typ) \
e1 = e_alloc | e_error | e_free; c1 = 0; \
testOk(!(pch = dbChannelCreate("x.{\"noconv\":{\""#Var"\":"#Val"}}")), \
testOk(!(pch = dbChannelCreate("x.{noconv:{'"#Var"':"#Val"}}")), \
"create channel for noconv parsing: wrong type "#Typ" for "#Var); \
testOk(!puser1, "user part cleaned up"); \
if (!testOk(c1 == e1, "all expected calls happened")) \
@@ -877,8 +877,8 @@ MAIN(chfPluginTest)
#define CONVTESTGOOD(Var, Val, Typ, Ival, Fval, Dval, Sval1, Sval2, Cval) \
e1 = e_alloc | e_ok; c1 = 0; \
testDiag("Calling dbChannelCreate x.{\"sloppy\":{\""#Var"\":"#Val"}}"); \
testOk(!!(pch = dbChannelCreate("x.{\"sloppy\":{\""#Var"\":"#Val"}}")), \
testDiag("Calling dbChannelCreate x.{sloppy:{"#Var":"#Val"}}"); \
testOk(!!(pch = dbChannelCreate("x.{sloppy:{"#Var":"#Val"}}")), \
"create channel for sloppy parsing: "#Typ" (good) for "#Var); \
testOk(checkValues(puser1, 99, Ival, Fval, Dval, Sval1, Sval2, Cval), \
"guards intact, values correct"); \
@@ -892,8 +892,8 @@ MAIN(chfPluginTest)
#define CONVTESTBAD(Var, Val, Typ) \
e1 = e_alloc | e_error | e_free; c1 = 0; \
testDiag("Calling dbChannelCreate x.{\"sloppy\":{\""#Var"\":"#Val"}}"); \
testOk(!(pch = dbChannelCreate("x.{\"sloppy\":{\""#Var"\":"#Val"}}")), \
testDiag("Calling dbChannelCreate x.{sloppy:{"#Var":"#Val"}}"); \
testOk(!(pch = dbChannelCreate("x.{sloppy:{"#Var":"#Val"}}")), \
"create channel for sloppy parsing: "#Typ" (bad) for "#Var); \
testOk(!puser1, "user part cleaned up"); \
if (!testOk(c1 == e1, "create channel: all expected calls happened")) \
@@ -1018,36 +1018,36 @@ MAIN(chfPluginTest)
if (!testOk(c1 == e1, "delete channel (1): all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \
if (!testOk(c2 == e2, "delete channel (2): all expected calls happened")) testDiag("expected %#x - called %#x", e2, c2);
CHAINTEST1("1 pre", "{\"pre\":{}}", e_reg_pre, e_pre | e_dtor, 1); /* One filter, pre chain */
CHAINTEST1("1 post", "{\"post\":{}}", e_reg_post, e_post | e_dtor, 1); /* One filter, post chain */
CHAINTEST1("1 both", "{\"sloppy\":{}}", e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, 2); /* One, both chains */
CHAINTEST2("2 pre", "{\"pre\":{},\"pre\":{}}", e_reg_pre, e_pre | e_dtor, e_reg_pre, e_pre, 2); /* Two filters, pre chain */
CHAINTEST2("2 post", "{\"post\":{},\"post\":{}}", e_reg_post, e_post | e_dtor, e_reg_post, e_post, 2); /* Two filters, post chain */
CHAINTEST2("2 both", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains */
CHAINTEST1("1 pre", "{pre:{}}", e_reg_pre, e_pre | e_dtor, 1); /* One filter, pre chain */
CHAINTEST1("1 post", "{post:{}}", e_reg_post, e_post | e_dtor, 1); /* One filter, post chain */
CHAINTEST1("1 both", "{sloppy:{}}", e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, 2); /* One, both chains */
CHAINTEST2("2 pre", "{pre:{},pre:{}}", e_reg_pre, e_pre | e_dtor, e_reg_pre, e_pre, 2); /* Two filters, pre chain */
CHAINTEST2("2 post", "{post:{},post:{}}", e_reg_post, e_post | e_dtor, e_reg_post, e_post, 2); /* Two filters, post chain */
CHAINTEST2("2 both", "{sloppy:{},sloppy:{}}", /* Two, both chains */
e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, e_reg_pre | e_reg_post, e_pre | e_post, 4);
CHAINTEST2("1 pre, 1 post", "{\"pre\":{},\"post\":{}}", e_reg_pre, e_pre | e_dtor, e_reg_post, e_post, 2); /* Two, pre then post */
CHAINTEST2("1 post, 1 pre", "{\"post\":{},\"pre\":{}}", e_reg_post, e_post, e_reg_pre, e_pre | e_dtor, 2); /* Two, post then pre */
CHAINTEST2("1 pre, 1 both", "{\"pre\":{},\"sloppy\":{}}", /* Two, pre then both */
CHAINTEST2("1 pre, 1 post", "{pre:{},post:{}}", e_reg_pre, e_pre | e_dtor, e_reg_post, e_post, 2); /* Two, pre then post */
CHAINTEST2("1 post, 1 pre", "{post:{},pre:{}}", e_reg_post, e_post, e_reg_pre, e_pre | e_dtor, 2); /* Two, post then pre */
CHAINTEST2("1 pre, 1 both", "{pre:{},sloppy:{}}", /* Two, pre then both */
e_reg_pre, e_pre | e_dtor, e_reg_pre | e_reg_post, e_pre | e_post, 3);
CHAINTEST2("1 both, 1 pre", "{\"sloppy\":{},\"pre\":{}}", /* Two, both then pre */
CHAINTEST2("1 both, 1 pre", "{sloppy:{},pre:{}}", /* Two, both then pre */
e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, e_reg_pre, e_pre, 3);
CHAINTEST2("1 post, 1 both", "{\"post\":{},\"sloppy\":{}}", /* Two, post then both */
CHAINTEST2("1 post, 1 both", "{post:{},sloppy:{}}", /* Two, post then both */
e_reg_post, e_post, e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, 3);
CHAINTEST2("1 both, 1 post", "{\"sloppy\":{},\"post\":{}}", /* Two, both then post */
CHAINTEST2("1 both, 1 post", "{sloppy:{},post:{}}", /* Two, both then post */
e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, e_reg_post, e_post, 3);
/* Plugins dropping updates */
drop = 0;
CHAINTEST2("2 both (drop at 0)", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains, drop at filter 0 */
CHAINTEST2("2 both (drop at 0)", "{sloppy:{},sloppy:{}}", /* Two, both chains, drop at filter 0 */
e_reg_pre | e_reg_post, e_pre, e_reg_pre | e_reg_post, 0, -1);
drop = 1;
CHAINTEST2("2 both (drop at 1)", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains, drop at filter 1 */
CHAINTEST2("2 both (drop at 1)", "{sloppy:{},sloppy:{}}", /* Two, both chains, drop at filter 1 */
e_reg_pre | e_reg_post, e_pre, e_reg_pre | e_reg_post, e_pre, -1);
drop = 2;
CHAINTEST2("2 both (drop at 2)", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains, drop at filter 2 */
CHAINTEST2("2 both (drop at 2)", "{sloppy:{},sloppy:{}}", /* Two, both chains, drop at filter 2 */
e_reg_pre | e_reg_post, e_pre | e_post, e_reg_pre | e_reg_post, e_pre, -1);
drop = 3;
CHAINTEST2("2 both (drop at 3)", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains, drop at filter 3 */
CHAINTEST2("2 both (drop at 3)", "{sloppy:{},sloppy:{}}", /* Two, both chains, drop at filter 3 */
e_reg_pre | e_reg_post, e_pre | e_post, e_reg_pre | e_reg_post, e_pre | e_post, -1);
drop = -1;
+11 -11
View File
@@ -183,7 +183,7 @@ MAIN(testDbChannel) /* dbChannelTest is an API routine... */
/* dbChannelTest() allows but ignores field modifiers */
testOk1(!dbChannelTest("x.NAME$"));
testOk1(!dbChannelTest("x.{}"));
testOk1(!dbChannelTest("x.VAL{\"json\":true}"));
testOk1(!dbChannelTest("x.VAL{json:true}"));
/* dbChannelCreate() accepts field modifiers */
testOk1(!!(pch = dbChannelCreate("x.{}")));
@@ -212,34 +212,34 @@ MAIN(testDbChannel) /* dbChannelTest is an API routine... */
testOk(!dbChannelCreate("x.NOFIELD"), "Create, bad field");
testOk(!dbChannelCreate("x.{not-json}"), "Create, bad JSON");
eltc(0);
testOk(!dbChannelCreate("x.{\"none\":null}"), "Create, bad filter");
testOk(!dbChannelCreate("x.{none:null}"), "Create, bad filter");
eltc(1);
dbRegisterFilter("any", &testIf, NULL);
/* Parser event rejection by filter */
e = e_start;
testOk1(!dbChannelCreate("x.{\"any\":null}"));
testOk1(!dbChannelCreate("x.{any:null}"));
r = e_start;
e = e_start | e_null | e_abort;
testOk1(!dbChannelCreate("x.{\"any\":null}"));
testOk1(!dbChannelCreate("x.{any:null}"));
r = e_start | e_null;
e = e_start | e_null | e_end;
testOk1(!dbChannelCreate("x.{\"any\":null}"));
testOk1(!dbChannelCreate("x.{any:null}"));
/* Successful parsing... */
r = r_any;
e = e_start | e_null | e_end;
testOk1(!!(pch = dbChannelCreate("x.{\"any\":null}")));
testOk1(!!(pch = dbChannelCreate("x.{any:null}")));
e = e_close;
if (pch) dbChannelDelete(pch);
dbRegisterFilter("scalar", &testIf, NULL);
e = e_start | e_null | e_end;
testOk1(!!(pch = dbChannelCreate("x.{\"scalar\":null}")));
testOk1(!!(pch = dbChannelCreate("x.{scalar:null}")));
e = e_report;
dbChannelShow(pch, 2, 2);
@@ -249,23 +249,23 @@ MAIN(testDbChannel) /* dbChannelTest is an API routine... */
e = e_start | e_start_array | e_boolean | e_integer | e_end_array
| e_end;
testOk1(!!(pch = dbChannelCreate("x.{\"any\":[true,1]}")));
testOk1(!!(pch = dbChannelCreate("x.{any:[true,1]}")));
e = e_close;
if (pch) dbChannelDelete(pch);
e = e_start | e_start_map | e_map_key | e_double | e_string | e_end_map
| e_end;
testOk1(!!(pch = dbChannelCreate("x.{\"any\":{\"a\":2.7183,\"b\":\"c\"}}")));
testOk1(!!(pch = dbChannelCreate("x.{any:{a:2.7183,b:'c'}}")));
e = e_close;
if (pch) dbChannelDelete(pch);
/* More event rejection */
r = r_scalar;
e = e_start | e_start_array | e_abort;
testOk1(!dbChannelCreate("x.{\"scalar\":[null]}"));
testOk1(!dbChannelCreate("x.{scalar:[null]}"));
e = e_start | e_start_map | e_abort;
testOk1(!dbChannelCreate("x.{\"scalar\":{}}"));
testOk1(!dbChannelCreate("x.{scalar:{}}"));
testIocShutdownOk();
testdbCleanup();
+9 -6
View File
@@ -67,6 +67,7 @@ static const struct testParseDataT {
{" #B111 C112 N113 @cparam", {CAMAC_IO, "cparam", 0, "BCN", {111, 112, 113}}},
{" @hello world ", {INST_IO, "hello world", 0, "", /*{}*/}},
{" {\"x\":true} ", {JSON_LINK, "{\"x\":true}", 0, "", /*{}*/}},
{" {'x':true} ", {JSON_LINK, "{'x':true}", 0, "", /*{}*/}},
{NULL}
};
@@ -255,7 +256,7 @@ typedef struct {
} testHWDataT;
static const testHWDataT testHWData[] = {
{"rJSON_LINK", JSON_LINK, "{\"x\":true}", {0}, "{\"x\":true}"},
{"rJSON_LINK", JSON_LINK, "{x:true}", {0}, "{x:true}"},
{"rVME_IO", VME_IO, "#C100 S101 @parm VME_IO", {100, 101}, "parm VME_IO"},
{"rCAMAC_IO", CAMAC_IO, "#B11 C12 N13 A14 F15 @parm CAMAC_IO", {11, 12, 13, 14, 15}, "parm CAMAC_IO"},
{"rAB_IO", AB_IO, "#L21 A22 C23 S24 @parm AB_IO", {21, 22, 23, 24}, "parm AB_IO"},
@@ -585,9 +586,11 @@ void testJLink(void)
testdbPutFieldOk("j2.PROC", DBF_LONG, 1);
testdbPutFieldOk("j3.PROC", DBF_LONG, 1);
testdbGetFieldEqual("j1.INP", DBF_STRING, "{\"z\":{\"good\":1}}");
testdbGetFieldEqual("j1.INP", DBF_STRING, "{z:{good:1}}");
testdbGetFieldEqual("j1.VAL", DBF_LONG, 1);
testdbGetFieldEqual("j2.INP", DBF_STRING, "{\"z\":{'good':2}}");
testdbGetFieldEqual("j2.VAL", DBF_LONG, 2);
testdbGetFieldEqual("j2.TSEL", DBF_STRING, "j1.TIME NPP NMS");
testdbGetFieldEqual("j3.VAL", DBF_LONG, 3);
testNumZ(6);
@@ -596,7 +599,7 @@ void testJLink(void)
testdbPutFieldOk("j1.PROC", DBF_LONG, 1);
testdbGetFieldEqual("j1.VAL", DBF_LONG, 4);
testdbPutFieldOk("j2.TSEL", DBF_STRING, "{\"z\":{\"good\":0}}");
testdbPutFieldOk("j2.TSEL", DBF_STRING, "{'z':{good:0}}");
testdbPutFieldOk("j2.PROC", DBF_LONG, 1);
testNumZ(7);
@@ -611,8 +614,8 @@ void testJLink(void)
testNumZ(7);
/* Check SDIS using a JSON link prevents processing */
testdbPutFieldOk("j1.SDIS", DBF_STRING, "{\"z\":{\"good\":1}}");
testdbPutFieldOk("j1.INP", DBF_STRING, "{\"z\":{\"good\":1}}");
testdbPutFieldOk("j1.SDIS", DBF_STRING, "{z:{good:1}}");
testdbPutFieldOk("j1.INP", DBF_STRING, "{z:{good:1}}");
testdbPutFieldOk("j1.PROC", DBF_LONG, 1);
testdbGetFieldEqual("j1.VAL", DBF_LONG, 4);
@@ -697,7 +700,7 @@ void testTSEL(void)
MAIN(dbPutLinkTest)
{
testPlan(337);
testPlan(342);
testLinkParse();
testLinkFailParse();
testCADBSet();
@@ -7,8 +7,8 @@ record(x, "j1") {
}
record(x, "j2") {
field(INP, {z:{good:2}})
field(TSEL, "j1.TIME")
field(INP, {"z":{'good':2}})
field(TSEL, 'j1.TIME')
}
record(x, "j3") {
+106 -5
View File
@@ -148,27 +148,128 @@ static void testDbVerify(const char *record)
if (dbFindRecord(&entry, record) != 0)
testAbort("Can't find record '%s'", record);
dbFindField(&entry, "UDF");
dbFindField(&entry, "C8");
verify(&entry, "0", NULL);
verify(&entry, "-128", NULL);
verify(&entry, "127", NULL);
verify(&entry, "128", "Number too large for field type");
verify(&entry, "0x7f", NULL);
verify(&entry, "0x80", "Number too large for field type");
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2345", "Extraneous characters after number");
dbFindField(&entry, "U8");
verify(&entry, "0", NULL);
verify(&entry, "128", NULL);
verify(&entry, "255", NULL);
verify(&entry, "256", "Number too large for field type");
verify(&entry, "0xff", NULL);
verify(&entry, "0x100", "Number too large for field type");
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2345", "Extraneous characters after number");
dbFindField(&entry, "PHAS");
dbFindField(&entry, "I16");
verify(&entry, "0", NULL);
verify(&entry, "-32768", NULL);
verify(&entry, "-32769", "Number too large for field type");
verify(&entry, "0x7fff", NULL);
verify(&entry, "32768", "Number too large for field type");
verify(&entry, "-0x8000", NULL);
verify(&entry, "0x7fff", NULL);
verify(&entry, "0x8000", "Number too large for field type");
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2345", "Extraneous characters after number");
dbFindField(&entry, "VAL");
dbFindField(&entry, "U16");
verify(&entry, "0", NULL);
verify(&entry, "-32768", NULL);
verify(&entry, "-65535", NULL);
verify(&entry, "-65536", "Number too large for field type");
verify(&entry, "65535", NULL);
verify(&entry, "0xffff", NULL);
verify(&entry, "0x10000", "Number too large for field type");
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2345", "Extraneous characters after number");
dbFindField(&entry, "I32");
verify(&entry, "0", NULL);
verify(&entry, "-123456789", NULL);
verify(&entry, "123456789", NULL);
verify(&entry, "-0x80000000", NULL);
verify(&entry, "-0x80000001", "Number too large for field type");
verify(&entry, "0x1234FEDC", NULL);
verify(&entry, "0x7fffffff", NULL);
verify(&entry, "0x100000000", "Number too large for field type");
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2345", "Extraneous characters after number");
dbFindField(&entry, "U32");
verify(&entry, "0", NULL);
verify(&entry, "-123456789", NULL);
verify(&entry, "123456789", NULL);
verify(&entry, "-0xffffffff", NULL);
verify(&entry, "-0x100000000", "Number too large for field type");
verify(&entry, "0x1234FEDC", NULL);
verify(&entry, "0xffffffff", NULL);
verify(&entry, "0x100000000", "Number too large for field type");
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2345", "Extraneous characters after number");
dbFindField(&entry, "I64");
verify(&entry, "0", NULL);
verify(&entry, "-1234567890123456789", NULL);
verify(&entry, "1234567890123456789", NULL);
verify(&entry, "-0x8000000000000000", NULL);
verify(&entry, "-0x8000000000000001", "Number too large for field type");
verify(&entry, "0x123456780FEDCBA9", NULL);
verify(&entry, "0x7fffffffffffffff", NULL);
verify(&entry, "0x10000000000000000", "Number too large for field type");
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2345", "Extraneous characters after number");
dbFindField(&entry, "U64");
verify(&entry, "0", NULL);
verify(&entry, "-1234567890123456789", NULL);
verify(&entry, "1234567890123456789", NULL);
verify(&entry, "-0xffffffffffffffff", NULL);
verify(&entry, "-0x10000000000000000", "Number too large for field type");
verify(&entry, "0x123456780FEDCBA9", NULL);
verify(&entry, "0x7fffffffffffffff", NULL);
verify(&entry, "0x10000000000000000", "Number too large for field type");
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2345", "Extraneous characters after number");
dbFindField(&entry, "F32");
verify(&entry, "0", NULL);
verify(&entry, "1.2345", NULL);
verify(&entry, ".12345", NULL);
verify(&entry, "-.12345", NULL);
verify(&entry, "+.12345", NULL);
verify(&entry, "1.e23", NULL);
verify(&entry, "1.e-23", NULL);
verify(&entry, "Infinity", NULL);
verify(&entry, "-Infinity", NULL);
verify(&entry, "+Infinity", NULL);
verify(&entry, "Nan", NULL);
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2e-345", "Number too small for field type");
verify(&entry, "1.2e345", "Number too large for field type");
dbFindField(&entry, "F64");
verify(&entry, "0", NULL);
verify(&entry, "1.2345", NULL);
verify(&entry, ".12345", NULL);
verify(&entry, "-.12345", NULL);
verify(&entry, "+.12345", NULL);
verify(&entry, "1.e234", NULL);
verify(&entry, "1.e-234", NULL);
verify(&entry, "Infinity", NULL);
verify(&entry, "-Infinity", NULL);
verify(&entry, "+Infinity", NULL);
verify(&entry, "Nan", NULL);
verify(&entry, "None", "Not a valid integer");
verify(&entry, "1.2e-345", "Number too small for field type");
verify(&entry, "1.2e345", "Number too large for field type");
dbFindField(&entry, "DESC");
verify(&entry, "", NULL);
verify(&entry, "abcdefghijklmnopqrstuvwxyz", NULL);
@@ -193,7 +294,7 @@ void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(dbStaticTest)
{
testPlan(223);
testPlan(310);
testdbPrepare();
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
@@ -6,3 +6,82 @@ record(x, "testrec") {
alias("testrec", "testalias2")
alias("testalias2", "testalias3")
# New number formats allowed in JSON5, show dbLex/dbYacc parsing
record(x, "t1") {
field( C8, +12)
field( U8, +123)
field(I16, +1234)
field(U16, +12345)
field(I32, +123456)
field(U32, +1234567)
field(I64, +12345678)
field(U64, +123456789)
field( C8, 0x1)
field( U8, 0x12)
field(I16, 0x123)
field(U16, 0x1234)
field(I32, 0x12345)
field(U32, 0x123456)
field(I64, 0x1234567)
field(U64, 0x12345678)
field( C8, +0x1)
field( U8, -0x12)
field(I16, +0x123)
field(U16, -0x1234)
field(I32, +0x12345)
field(U32, -0x123456)
field(I64, +0x1234567)
field(U64, -0x12345678)
field( C8, 0x7f)
field( U8, 0xff)
field(I16, 0x7fff)
field(I16, -0x8000)
field(U16, 0xffff)
field(I32, 0x7fffffff)
field(I32, -0x80000000)
field(U32, 0xffffffff)
field(I64, 0x7fffffffffffffff)
field(I64, -0x8000000000000000)
field(U64, 0xffffffffffffffff)
field(F32, .123)
field(F64, .123)
field(F32, 123.)
field(F64, 123.)
field(F32, +.123)
field(F64, +.123)
field(F32, +123.)
field(F64, +123.)
field(F32, -.123)
field(F64, -.123)
field(F32, -123.)
field(F64, -123.)
field(F32, .123e4)
field(F64, .123e4)
field(F32, 123.e4)
field(F64, 123.e4)
field(F32, 123.e+4)
field(F64, 123.e+4)
field(F32, 123.e-4)
field(F64, 123.e-4)
field(F32, +.123e4)
field(F64, +.123e4)
field(F32, +123.e4)
field(F64, +123.e4)
field(F32, -.123e4)
field(F64, -.123e4)
field(F32, -123.e4)
field(F64, -123.e4)
field(F32, Infinity)
field(F64, Infinity)
field(F32, +Infinity)
field(F64, +Infinity)
field(F32, -Infinity)
field(F64, -Infinity)
field(F32, Nan)
field(F64, Nan)
info(i1, {x:0, +x:1, -x:2, .x:3})
info(i2, Bare-word_string)
}
+30
View File
@@ -5,6 +5,36 @@ recordtype(x) {
field(VAL, DBF_LONG) {
prompt("Value")
}
field(C8, DBF_CHAR) {
prompt("Char")
}
field(U8, DBF_UCHAR) {
prompt("Byte")
}
field(I16, DBF_SHORT) {
prompt("Short")
}
field(U16, DBF_USHORT) {
prompt("UShort")
}
field(I32, DBF_LONG) {
prompt("Integer")
}
field(U32, DBF_ULONG) {
prompt("Unsigned")
}
field(I64, DBF_INT64) {
prompt("Long")
}
field(U64, DBF_UINT64) {
prompt("ULong")
}
field(F32, DBF_FLOAT) {
prompt("Float")
}
field(F64, DBF_DOUBLE) {
prompt("Double")
}
field(LNK, DBF_INLINK) {
prompt("Link")
}
@@ -178,7 +178,7 @@ static void check(short dbr_type) {
/* Default: should not change anything */
testHead("Ten %s elements from rec, increment 1, full size (default)", typname);
createAndOpen(valname, "{\"arr\":{}}", "(default)", &pch, 1);
createAndOpen(valname, "{arr:{}}", "(default)", &pch, 1);
testOk(pch->final_type == valaddr.field_type,
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type);
testOk(pch->final_no_elements == valaddr.no_elements,
@@ -188,7 +188,7 @@ static void check(short dbr_type) {
dbChannelDelete(pch);
testHead("Ten %s elements from rec, increment 1, out-of-bound start parameter", typname);
createAndOpen(valname, "{\"arr\":{\"s\":-500}}", "out-of-bound start", &pch, 1);
createAndOpen(valname, "{arr:{s:-500}}", "out-of-bound start", &pch, 1);
testOk(pch->final_type == valaddr.field_type,
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type);
testOk(pch->final_no_elements == valaddr.no_elements,
@@ -197,7 +197,7 @@ static void check(short dbr_type) {
dbChannelDelete(pch);
testHead("Ten %s elements from rec, increment 1, out-of-bound end parameter", typname);
createAndOpen(valname, "{\"arr\":{\"e\":500}}", "out-of-bound end", &pch, 1);
createAndOpen(valname, "{arr:{e:500}}", "out-of-bound end", &pch, 1);
testOk(pch->final_type == valaddr.field_type,
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type);
testOk(pch->final_no_elements == valaddr.no_elements,
@@ -206,7 +206,7 @@ static void check(short dbr_type) {
dbChannelDelete(pch);
testHead("Ten %s elements from rec, increment 1, zero increment parameter", typname);
createAndOpen(valname, "{\"arr\":{\"i\":0}}", "zero increment", &pch, 1);
createAndOpen(valname, "{arr:{i:0}}", "zero increment", &pch, 1);
testOk(pch->final_type == valaddr.field_type,
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type);
testOk(pch->final_no_elements == valaddr.no_elements,
@@ -215,7 +215,7 @@ static void check(short dbr_type) {
dbChannelDelete(pch);
testHead("Ten %s elements from rec, increment 1, invalid increment parameter", typname);
createAndOpen(valname, "{\"arr\":{\"i\":-30}}", "invalid increment", &pch, 1);
createAndOpen(valname, "{arr:{i:-30}}", "invalid increment", &pch, 1);
testOk(pch->final_type == valaddr.field_type,
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type);
testOk(pch->final_no_elements == valaddr.no_elements,
@@ -225,7 +225,7 @@ static void check(short dbr_type) {
#define TEST5(Incr, Left, Right, Type) \
testHead("Five %s elements from rec, increment " #Incr ", " Type " addressing", typname); \
createAndOpen(valname, "{\"arr\":{\"s\":" #Left ",\"e\":" #Right ",\"i\":" #Incr "}}", \
createAndOpen(valname, "{arr:{s:" #Left ",e:" #Right ",i:" #Incr "}}", \
"(" #Left ":" #Incr ":" #Right ")", &pch, 1); \
testOk(pch->final_type == valaddr.field_type, \
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); \
@@ -262,7 +262,7 @@ static void check(short dbr_type) {
#define TEST5B(Incr, Left, Right, Type) \
testHead("Five %s elements from buffer, increment " #Incr ", " Type " addressing", typname); \
createAndOpen(valname, "{\"arr\":{},\"arr\":{\"s\":" #Left ",\"e\":" #Right ",\"i\":" #Incr "}}", \
createAndOpen(valname, "{arr:{},arr:{s:" #Left ",e:" #Right ",i:" #Incr "}}", \
"(" #Left ":" #Incr ":" #Right ")", &pch, 2); \
testOk(pch->final_type == valaddr.field_type, \
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); \
+4 -4
View File
@@ -148,7 +148,7 @@ MAIN(dbndTest)
testOk(!!(plug = dbFindFilter(dbnd, strlen(dbnd))), "plugin dbnd registered correctly");
testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{}}")), "dbChannel with plugin dbnd (delta=0) created");
testOk(!!(pch = dbChannelCreate("x.VAL{dbnd:{}}")), "dbChannel with plugin dbnd (delta=0) created");
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
/* Start the free-list */
@@ -202,7 +202,7 @@ MAIN(dbndTest)
/* Delta = -1: pass any update */
testHead("Delta = -1: pass any update");
testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"d\":-1.0}}")), "dbChannel with plugin dbnd (delta=-1) created");
testOk(!!(pch = dbChannelCreate("x.VAL{dbnd:{d:-1.0}}")), "dbChannel with plugin dbnd (delta=-1) created");
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened");
pfl2 = db_create_read_log(pch);
@@ -220,7 +220,7 @@ MAIN(dbndTest)
/* Delta = absolute */
testHead("Delta = absolute");
testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"d\":3}}")), "dbChannel with plugin dbnd (delta=3) created");
testOk(!!(pch = dbChannelCreate("x.VAL{dbnd:{d:3}}")), "dbChannel with plugin dbnd (delta=3) created");
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened");
pfl2 = db_create_read_log(pch);
@@ -254,7 +254,7 @@ MAIN(dbndTest)
/* Delta = relative */
testHead("Delta = relative");
testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"m\":\"rel\",\"d\":50}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{dbnd:{m:'rel',d:50}}")),
"dbChannel with plugin dbnd (mode=rel, delta=50) created");
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened");
+8 -8
View File
@@ -149,20 +149,20 @@ MAIN(decTest)
"plugin '%s' registered correctly", myname);
/* N < 1 */
testOk(!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":-1}}")),
testOk(!(pch = dbChannelCreate("x.VAL{dec:{n:-1}}")),
"dbChannel with dec (n=-1) failed");
testOk(!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":0}}")),
testOk(!(pch = dbChannelCreate("x.VAL{dec:{n:0}}")),
"dbChannel with dec (n=0) failed");
/* Bad parms */
testOk(!(pch = dbChannelCreate("x.VAL{\"dec\":{}}")),
testOk(!(pch = dbChannelCreate("x.VAL{dec:{}}")),
"dbChannel with dec (no parm) failed");
testOk(!(pch = dbChannelCreate("x.VAL{\"dec\":{\"x\":true}}")),
testOk(!(pch = dbChannelCreate("x.VAL{dec:{x:true}}")),
"dbChannel with dec (x=true) failed");
/* No Decimation (N=1) */
testHead("No Decimation (n=1)");
testOk(!!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":1}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{dec:{n:1}}")),
"dbChannel with plugin dec (n=1) created");
/* Start the free-list */
@@ -192,7 +192,7 @@ MAIN(decTest)
/* Decimation (N=2) */
testHead("Decimation (n=2)");
testOk(!!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":2}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{dec:{n:2}}")),
"dbChannel with plugin dec (n=2) created");
checkAndOpenChannel(pch, plug);
@@ -222,7 +222,7 @@ MAIN(decTest)
/* Decimation (N=3) */
testHead("Decimation (n=3)");
testOk(!!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":3}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{dec:{n:3}}")),
"dbChannel with plugin dec (n=3) created");
checkAndOpenChannel(pch, plug);
@@ -252,7 +252,7 @@ MAIN(decTest)
/* Decimation (N=4) */
testHead("Decimation (n=4)");
testOk(!!(pch = dbChannelCreate("x.VAL{\"dec\":{\"n\":4}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{dec:{n:4}}")),
"dbChannel with plugin dec (n=4) created");
checkAndOpenChannel(pch, plug);
+9 -9
View File
@@ -225,19 +225,19 @@ MAIN(syncTest)
testOk(!!(red = dbStateCreate("red")), "state 'red' created successfully");
/* nonexisting state */
testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\",\"s\":\"blue\"}}")),
testOk(!(pch = dbChannelCreate("x.VAL{sync:{m:'while',s:'blue'}}")),
"dbChannel with sync (m='while' s='blue') (nonex state) failed");
/* missing state */
testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\"}}")),
testOk(!(pch = dbChannelCreate("x.VAL{sync:{m:'while'}}")),
"dbChannel with sync (m='while') (no state) failed");
/* missing mode */
testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"s\":\"red\"}}")),
testOk(!(pch = dbChannelCreate("x.VAL{sync:{s:'red'}}")),
"dbChannel with sync (s='red') (no mode) failed");
/* mode WHILE */
testHead("Mode WHILE (m='while', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\",\"s\":\"red\"}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{sync:{m:'while',s:'red'}}")),
"dbChannel with plugin sync (m='while' s='red') created");
/* Start the free-list */
@@ -274,7 +274,7 @@ MAIN(syncTest)
/* mode UNLESS */
testHead("Mode UNLESS (m='unless', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"unless\",\"s\":\"red\"}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{sync:{m:'unless',s:'red'}}")),
"dbChannel with plugin sync (m='unless' s='red') created");
checkAndOpenChannel(pch, plug);
@@ -306,7 +306,7 @@ MAIN(syncTest)
/* mode BEFORE */
testHead("Mode BEFORE (m='before', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"before\",\"s\":\"red\"}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{sync:{m:'before',s:'red'}}")),
"dbChannel with plugin sync (m='before' s='red') created");
checkAndOpenChannel(pch, plug);
@@ -340,7 +340,7 @@ MAIN(syncTest)
/* mode FIRST */
testHead("Mode FIRST (m='first', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"first\",\"s\":\"red\"}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{sync:{m:'first',s:'red'}}")),
"dbChannel with plugin sync (m='first' s='red') created");
checkAndOpenChannel(pch, plug);
@@ -373,7 +373,7 @@ MAIN(syncTest)
/* mode LAST */
testHead("Mode LAST (m='last', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"last\",\"s\":\"red\"}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{sync:{m:'last',s:'red'}}")),
"dbChannel with plugin sync (m='last' s='red') created");
checkAndOpenChannel(pch, plug);
@@ -407,7 +407,7 @@ MAIN(syncTest)
/* mode AFTER */
testHead("Mode AFTER (m='after', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"after\",\"s\":\"red\"}}")),
testOk(!!(pch = dbChannelCreate("x.VAL{sync:{m:'after',s:'red'}}")),
"dbChannel with plugin sync (m='after' s='red') created");
checkAndOpenChannel(pch, plug);
+1 -1
View File
@@ -74,7 +74,7 @@ MAIN(tsTest)
testOk(!!(plug = dbFindFilter(ts, strlen(ts))), "plugin ts registered correctly");
testOk(!!(pch = dbChannelCreate("x.VAL{\"ts\":{}}")), "dbChannel with plugin ts created");
testOk(!!(pch = dbChannelCreate("x.VAL{ts:{}}")), "dbChannel with plugin ts created");
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
memset(&fl, PATTERN, sizeof(fl));
+14 -14
View File
@@ -53,9 +53,9 @@ static void testCalc()
{
dbStateId red;
testPutLongStr("io.INPUT", "{\"calc\":{"
"\"expr\":\"a\","
"\"args\":[{\"state\":\"red\"}]"
testPutLongStr("io.INPUT", "{calc:{"
"expr:'a',"
"args:[{state:'red'}]"
"}}");
if (testOk1(pinp->type == JSON_LINK))
testDiag("Link was set to '%s'", pinp->value.json.string);
@@ -78,11 +78,11 @@ static void testCalc()
dbStateId minor = dbStateCreate("minor");
epicsEnum16 stat, sevr;
testPutLongStr("io.INPUT", "{\"calc\":{"
"\"expr\":\"0\","
"\"major\":\"A\","
"\"minor\":\"B\","
"\"args\":[{\"state\":\"major\"},{\"state\":\"minor\"}]"
testPutLongStr("io.INPUT", "{calc:{"
"expr:'0',"
"major:'A',"
"minor:'B',"
"args:[{state:'major'},{state:'minor'}]"
"}}");
if (testOk1(pinp->type == JSON_LINK))
testDiag("Link was set to '%s'", pinp->value.json.string);
@@ -114,12 +114,12 @@ static void testCalc()
dbStateId red = dbStateFind("red");
dbStateId out = dbStateCreate("out");
testPutLongStr("io.OUTPUT", "{\"calc\":{"
"\"expr\":\"!a\","
"\"out\":{\"state\":\"out\"},"
"\"args\":[{\"state\":\"red\"}],"
"\"units\":\"things\","
"\"prec\":3"
testPutLongStr("io.OUTPUT", "{calc:{"
"expr:'!a',"
"out:{state:'out'},"
"args:[{state:'red'}],"
"units:'things',"
"prec:3"
"}}");
if (testOk1(pout->type == JSON_LINK))
testDiag("Link was set to '%s'", pout->value.json.string);
@@ -50,7 +50,7 @@ static void testState()
red = dbStateFind("red");
testOk(!red, "No state red exists");
testdbPutFieldOk("io.INPUT", DBF_STRING, "{\"state\":\"red\"}");
testdbPutFieldOk("io.INPUT", DBF_STRING, "{state:'red'}");
if (testOk1(pinp->type == JSON_LINK))
testDiag("Link was set to '%s'", pinp->value.json.string);
red = dbStateFind("red");
@@ -64,7 +64,7 @@ static void testState()
testOk(!status, "dbGetLink succeeded (status = %ld)", status);
testOk(i16, "Got TRUE");
testdbPutFieldOk("io.INPUT", DBF_STRING, "{\"state\":\"!red\"}");
testdbPutFieldOk("io.INPUT", DBF_STRING, "{state:'!red'}");
if (testOk1(pinp->type == JSON_LINK))
testDiag("Link was set to '%s'", pinp->value.json.string);
@@ -72,7 +72,7 @@ static void testState()
testOk(!status, "dbGetLink succeeded (status = %ld)", status);
testOk(!i16, "Got FALSE");
testdbPutFieldOk("io.OUTPUT", DBF_STRING, "{\"state\":\"red\"}");
testdbPutFieldOk("io.OUTPUT", DBF_STRING, "{state:'red'}");
if (testOk1(pout->type == JSON_LINK))
testDiag("Link was set to '%s'", pout->value.json.string);
@@ -106,7 +106,7 @@ static void testState()
testOk(!status, "dbPutLink %g succeeded (status = %ld)", f64, status);
testOk(dbStateGet(red), "state was set");
testdbPutFieldOk("io.OUTPUT", DBF_STRING, "{\"state\":\"!red\"}");
testdbPutFieldOk("io.OUTPUT", DBF_STRING, "{state:'!red'}");
if (testOk1(pout->type == JSON_LINK))
testDiag("Link was set to '%s'", pout->value.json.string);
@@ -180,7 +180,7 @@ MAIN(analogMonitorTest)
for (irec = 0; irec < NO_OF_RECORD_TYPES; irec++) {
strcpy(cval, t_Record[irec]);
strcpy(chan, cval);
strcat(chan, ".VAL{\"test\":{}}");
strcat(chan, ".VAL{test:{}}");
if ((strcmp(t_Record[irec], "sel") == 0)
|| (strcmp(t_Record[irec], "calc") == 0)
|| (strcmp(t_Record[irec], "calcout") == 0)) {
@@ -18,7 +18,7 @@ record(stringout, "rec:link2") {
record(ai, "rec:j1") {
field(INP, {calc:{
expr:"A+5",
expr:'A+5',
args:5
}})
field(PINI, "YES")
@@ -75,18 +75,18 @@ static void testRetargetJLink(void)
testdbGetFieldEqual("rec:j1", DBF_DOUBLE, 10.0);
/* minimal args */
testLongStrEq("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":5}}");
testLongStrEq("rec:j1.INP$", "{calc:{expr:'A+5',args:5}}");
/* with [] */
testPutLongStr("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[7]}}");
testPutLongStr("rec:j1.INP$", "{calc:{expr:'A+5',args:[7]}}");
testdbPutFieldOk("rec:j1.PROC", DBF_LONG, 1);
/* with const */
testPutLongStr("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[{\"const\":7}]}}");
testPutLongStr("rec:j1.INP$", "{calc:{expr:'A+5',args:[{const:7}]}}");
testdbPutFieldOk("rec:j1.PROC", DBF_LONG, 1);
testdbGetFieldEqual("rec:j1", DBF_DOUBLE, 12.0);
testLongStrEq("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[{\"const\":7}]}}");
testLongStrEq("rec:j1.INP$", "{calc:{expr:'A+5',args:[{const:7}]}}");
}
MAIN(linkRetargetLinkTest)
+1 -3
View File
@@ -71,7 +71,7 @@ void testArrayLength1(void)
}
/*
* https://bugs.launchpad.net/epics-base/+bug/1699445
* https://bugs.launchpad.net/epics-base/+bug/1699445 and 1887981
*/
static
void testHexConstantLinks(void)
@@ -81,7 +81,6 @@ void testHexConstantLinks(void)
testdbGetFieldEqual("ai1", DBR_LONG, 0x10);
testdbGetFieldEqual("li1", DBR_LONG, 0x10);
testdbGetFieldEqual("mi1", DBR_LONG, 0x10);
testTodoBegin("Needs JSON5 for hex arrays");
testdbGetFieldEqual("as1.A", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.B", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.C", DBR_LONG, 0x10);
@@ -90,7 +89,6 @@ void testHexConstantLinks(void)
testdbGetFieldEqual("as1.F", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.G", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.H", DBR_LONG, 0x10);
testTodoEnd();
testIocShutdownOk();
testdbCleanup();
+25 -45
View File
@@ -66,55 +66,34 @@ int epicsStrnRawFromEscaped(char *dst, size_t dstlen, const char *src,
case '\\': OUT('\\'); break;
case '\'': OUT('\''); break;
case '\"': OUT('\"'); break;
case '0' :case '1' :case '2' :case '3' :
case '4' :case '5' :case '6' :case '7' :
{ /* \ooo */
unsigned int u = c - '0';
if (!srclen-- || !(c = *src++)) {
OUT(u); goto done;
}
if (c < '0' || c > '7') {
OUT(u); goto input;
}
u = u << 3 | (c - '0');
if (!srclen-- || !(c = *src++)) {
OUT(u); goto done;
}
if (c < '0' || c > '7') {
OUT(u); goto input;
}
u = u << 3 | (c - '0');
if (u > 0377) {
/* Undefined behaviour! */
}
OUT(u);
}
break;
case '0': OUT('\0'); break;
case 'x' :
{ /* \xXXX... */
{ /* \xXX */
unsigned int u = 0;
if (!srclen-- || !(c = *src++ & 0xff))
goto done;
while (isxdigit(c)) {
u = u << 4 | ((c > '9') ? toupper(c) - 'A' + 10 : c - '0');
if (u > 0xff) {
/* Undefined behaviour! */
}
if (!srclen-- || !(c = *src++ & 0xff)) {
OUT(u);
goto done;
}
if (!isxdigit(c))
goto input;
u = u << 4 | ((c > '9') ? toupper(c) - 'A' + 10 : c - '0');
if (!srclen-- || !(c = *src++ & 0xff)) {
OUT(u);
goto done;
}
if (!isxdigit(c)) {
OUT(u);
goto input;
}
u = u << 4 | ((c > '9') ? toupper(c) - 'A' + 10 : c - '0');
OUT(u);
goto input;
}
break;
default:
OUT(c);
@@ -137,6 +116,7 @@ int epicsStrnEscapedFromRaw(char *dst, size_t dstlen, const char *src,
return -1;
while (srclen--) {
static const char hex[] = "0123456789abcdef";
int c = *src++;
#define OUT(chr) ndst++; if (--rem > 0) *dst++ = chr
@@ -151,15 +131,15 @@ int epicsStrnEscapedFromRaw(char *dst, size_t dstlen, const char *src,
case '\\': OUT('\\'); OUT('\\'); break;
case '\'': OUT('\\'); OUT('\''); break;
case '\"': OUT('\\'); OUT('\"'); break;
case '\0': OUT('\\'); OUT('0'); break;
default:
if (isprint(c & 0xff)) {
OUT(c);
break;
}
OUT('\\');
OUT('0' + ((c & 0300) >> 6));
OUT('0' + ((c & 0070) >> 3));
OUT('0' + (c & 0007));
OUT('\\'); OUT('x');
OUT(hex[(c >> 4) & 0x0f]);
OUT(hex[ c & 0x0f]);
}
#undef OUT
}
@@ -178,7 +158,7 @@ size_t epicsStrnEscapedFromRawSize(const char *src, size_t srclen)
switch (c) {
case '\a': case '\b': case '\f': case '\n':
case '\r': case '\t': case '\v': case '\\':
case '\'': case '\"':
case '\'': case '\"': case '\0':
ndst++;
break;
default:
@@ -258,7 +238,7 @@ int epicsStrPrintEscaped(FILE *fp, const char *s, size_t len)
if (isprint(0xff & (int)c))
nout += fprintf(fp, "%c", c);
else
nout += fprintf(fp, "\\%03o", (unsigned char)c);
nout += fprintf(fp, "\\x%02x", (unsigned char)c);
break;
}
}
+10 -5
View File
@@ -74,7 +74,7 @@ yajl_alloc(const yajl_callbacks * callbacks,
hand->lexer = NULL;
hand->bytesConsumed = 0;
hand->decodeBuf = yajl_buf_alloc(&(hand->alloc));
hand->flags = 0;
hand->flags = yajl_allow_json5 | yajl_allow_comments;
yajl_bs_init(hand->stateStack, &(hand->alloc));
yajl_bs_push(hand->stateStack, yajl_state_start);
@@ -82,13 +82,16 @@ yajl_alloc(const yajl_callbacks * callbacks,
}
int
yajl_config(yajl_handle h, yajl_option opt, ...)
yajl_config(yajl_handle h, int option, ...)
{
yajl_option opt = option; /* UB to use an enum in va_start */
int rv = 1;
va_list ap;
va_start(ap, opt);
va_start(ap, option);
switch(opt) {
case yajl_allow_json5:
opt |= yajl_allow_comments; /* JSON5 allows comments */
case yajl_allow_comments:
case yajl_dont_validate_strings:
case yajl_allow_trailing_garbage:
@@ -127,7 +130,8 @@ yajl_parse(yajl_handle hand, const unsigned char * jsonText,
if (hand->lexer == NULL) {
hand->lexer = yajl_lex_alloc(&(hand->alloc),
hand->flags & yajl_allow_comments,
!(hand->flags & yajl_dont_validate_strings));
!(hand->flags & yajl_dont_validate_strings),
hand->flags & yajl_allow_json5);
}
if (hand->lexer == NULL) {
return yajl_status_error;
@@ -150,7 +154,8 @@ yajl_complete_parse(yajl_handle hand)
if (hand->lexer == NULL) {
hand->lexer = yajl_lex_alloc(&(hand->alloc),
hand->flags & yajl_allow_comments,
!(hand->flags & yajl_dont_validate_strings));
!(hand->flags & yajl_dont_validate_strings),
hand->flags & yajl_allow_json5);
}
return yajl_do_finish(hand);
+26 -10
View File
@@ -37,18 +37,34 @@ extern "C" {
* YAJL 1.0.12 version that was
* bundled with EPICS Base 3.15.0.1
*
* YAJL 2.1.0
* ***YAJL 2.1.0***
* \li Changes argument type for yajl_integer() from \c int to \c long \c long
* \li Changes argument type for yajl_string() and yajl_map_key() from \c unsigned to \c size_t
* \li Changes argument type for yajl_string() and yajl_map_key() from
* \c unsigned to \c size_t
* \li Replacement of struct yajl_parser_config with yajl_config()
* \li Replacement of yajl_parse_complete() with yajl_complete_parse()
*
* ***YAJL 2.2.0***
* This version adds support for JSON5 parsing and generation.
* The upstream YAJL repository is no longer being maintained and the
* original author no longer responds to messsages, so while this code
* has been offered for merging into the upstream it is unlikely that
* will ever happen. The version number here is thus an EPICS-specific
* construct.
*/
#define EPICS_YAJL_VERSION VERSION_INT(2,1,0,0)
#define EPICS_YAJL_VERSION VERSION_INT(2,2,0,0)
/** Maximum generation depth for YAJL's JSON generation routines */
/** A macro to make it easy to conditionally compile code that supports
* older releases.
*/
#define HAS_JSON5
/** A limit used by the generator API, YAJL_MAX_DEPTH is the maximum
* depth to which arrays and maps may be nested.
*/
#define YAJL_MAX_DEPTH 128
/** Symbol decoration for Microsoft builds */
/** Marks a yajl routine for export from the DLL/shared library. */
#define YAJL_API LIBCOM_API
/** Pointer to a malloc() function, supporting client overriding memory
@@ -62,18 +78,18 @@ typedef void (*yajl_free_func)(void *ctx, void * ptr);
/** Pointer to a realloc() function which can resize an allocation. */
typedef void * (*yajl_realloc_func)(void *ctx, void * ptr, size_t sz);
/** A structure which can be passed to yajl_*_alloc routines to allow the
/** A structure which can be passed to yajl_*_alloc() routines to allow the
* client to specify memory allocation functions to be used. */
typedef struct
{
/** Pointer to a function that can allocate uninitialized memory */
/** Pointer to a function that can allocate uninitialized memory. */
yajl_malloc_func malloc;
/** Pointer to a function that can resize memory allocations */
/** Pointer to a function that can resize memory allocations. */
yajl_realloc_func realloc;
/** Pointer to a function that can free memory allocated using
* reallocFunction or mallocFunction */
* the above realloc or malloc functions. */
yajl_free_func free;
/** A context pointer that will be passed to above allocation routines */
/** A context pointer that will be passed to above allocation routines. */
void * ctx;
} yajl_alloc_funcs;
+78 -11
View File
@@ -33,13 +33,22 @@ yajl_string_encode(const yajl_print_t print,
void * ctx,
const unsigned char * str,
size_t len,
int escape_solidus)
int escape_solidus,
int output_json5)
{
size_t beg = 0;
size_t end = 0;
char hexBuf[7];
hexBuf[0] = '\\'; hexBuf[1] = 'u'; hexBuf[2] = '0'; hexBuf[3] = '0';
hexBuf[6] = 0;
char *hexAt;
if (output_json5) {
hexBuf[0] = '\\'; hexBuf[1] = 'x';
hexBuf[4] = 0;
hexAt = &hexBuf[2];
} else {
hexBuf[0] = '\\'; hexBuf[1] = 'u'; hexBuf[2] = '0'; hexBuf[3] = '0';
hexBuf[6] = 0;
hexAt = &hexBuf[4];
}
while (end < len) {
const char * escaped = NULL;
@@ -57,9 +66,20 @@ yajl_string_encode(const yajl_print_t print,
case '\f': escaped = "\\f"; break;
case '\b': escaped = "\\b"; break;
case '\t': escaped = "\\t"; break;
case '\0':
if (output_json5) {
escaped = "\\0"; break;
}
goto ashex;
case '\v':
if (output_json5) {
escaped = "\\v"; break;
}
goto ashex;
default:
if ((unsigned char) str[end] < 32) {
CharToHex(str[end], hexBuf + 4);
ashex:
CharToHex(str[end], hexAt);
escaped = hexBuf;
}
break;
@@ -75,10 +95,10 @@ yajl_string_encode(const yajl_print_t print,
print(ctx, (const char *) (str + beg), end - beg);
}
static void hexToDigit(unsigned int * val, const unsigned char * hex)
static void hexToDigit(unsigned int * val, unsigned int len, const unsigned char * hex)
{
unsigned int i;
for (i=0;i<4;i++) {
for (i=0;i<len;i++) {
unsigned char c = hex[i];
if (c >= 'A') c = (c & ~0x20) - 7;
c -= '0';
@@ -128,21 +148,19 @@ void yajl_string_decode(yajl_buf buf, const unsigned char * str,
case 'r': unescaped = "\r"; break;
case 'n': unescaped = "\n"; break;
case '\\': unescaped = "\\"; break;
case '/': unescaped = "/"; break;
case '"': unescaped = "\""; break;
case 'f': unescaped = "\f"; break;
case 'b': unescaped = "\b"; break;
case 't': unescaped = "\t"; break;
case 'u': {
unsigned int codepoint = 0;
hexToDigit(&codepoint, str + ++end);
hexToDigit(&codepoint, 4, str + ++end);
end+=3;
/* check if this is a surrogate */
if ((codepoint & 0xFC00) == 0xD800) {
end++;
if (str[end] == '\\' && str[end + 1] == 'u') {
unsigned int surrogate = 0;
hexToDigit(&surrogate, str + end + 2);
hexToDigit(&surrogate, 4, str + end + 2);
codepoint =
(((codepoint & 0x3F) << 10) |
((((codepoint >> 6) & 0xF) + 1) << 16) |
@@ -165,8 +183,33 @@ void yajl_string_decode(yajl_buf buf, const unsigned char * str,
break;
}
/* The following escapes are only valid when parsing JSON5.
* The lexer catches them when allowJson5 is not set.
*/
case '\n': beg = ++end; continue;
case '\r':
if (str[++end] == '\n') ++end;
beg = end;
continue;
case '0':
utf8Buf[0] = '\0';
yajl_buf_append(buf, utf8Buf, 1);
beg = ++end;
continue;
case 'v': unescaped = "\v"; break;
case 'x': {
unsigned int codepoint = 0;
hexToDigit(&codepoint, 2, str + ++end);
end++;
utf8Buf[0] = (char) codepoint;
yajl_buf_append(buf, utf8Buf, 1);
beg = ++end;
continue;
}
default:
assert("this should never happen" == NULL);
utf8Buf[0] = str[end];
utf8Buf[1] = 0;
unescaped = utf8Buf;
}
yajl_buf_append(buf, unescaped, (unsigned int)strlen(unescaped));
beg = ++end;
@@ -218,3 +261,27 @@ int yajl_string_validate_utf8(const unsigned char * s, size_t len)
return 1;
}
int yajl_string_validate_identifier(const unsigned char * str, size_t len)
{
const unsigned char * s = str;
int c;
if (!len || !str) return 0;
c = *s++; /* First character [$_A-Za-z] */
if ((c != '$' && c < 'A') ||
(c > 'Z' && c != '_' && c < 'a') ||
(c > 'z'))
return 0;
while (--len) {
c = *s++; /* Remaining characters [$_A-Za-z0-9] */
if ((c != '$' && c < '0') ||
(c > '9' && c < 'A') ||
(c > 'Z' && c != '_' && c < 'a') ||
(c > 'z'))
return 0;
}
return 1;
}
+4 -1
View File
@@ -28,13 +28,16 @@ void yajl_string_encode(const yajl_print_t printer,
void * ctx,
const unsigned char * str,
size_t length,
int escape_solidus);
int escape_solidus,
int output_json5);
void yajl_string_decode(yajl_buf buf, const unsigned char * str,
size_t length);
int yajl_string_validate_utf8(const unsigned char * s, size_t len);
int yajl_string_validate_identifier(const unsigned char * str, size_t len);
#ifdef __cplusplus
}
#endif
+56 -21
View File
@@ -48,15 +48,17 @@ struct yajl_gen_t
};
int
yajl_gen_config(yajl_gen g, yajl_gen_option opt, ...)
yajl_gen_config(yajl_gen g, int option, ...)
{
yajl_gen_option opt = option; /* UB to use an enum in va_start */
int rv = 1;
va_list ap;
va_start(ap, opt);
va_start(ap, option);
switch(opt) {
case yajl_gen_beautify:
case yajl_gen_validate_utf8:
case yajl_gen_json5:
if (va_arg(ap, int)) g->flags |= opt;
else g->flags &= ~opt;
break;
@@ -124,6 +126,14 @@ yajl_gen_alloc(const yajl_alloc_funcs * afs)
return g;
}
void
yajl_gen_reset(yajl_gen g, const char * sep)
{
g->depth = 0;
memset((void *) &(g->state), 0, sizeof(g->state));
if (sep != NULL) g->print(g->ctx, sep, strlen(sep));
}
void
yajl_gen_free(yajl_gen g)
{
@@ -132,17 +142,17 @@ yajl_gen_free(yajl_gen g)
}
#define INSERT_SEP \
if (g->state[g->depth] == yajl_gen_map_key || \
g->state[g->depth] == yajl_gen_in_array) { \
g->print(g->ctx, ",", 1); \
if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, "\n", 1); \
} else if (g->state[g->depth] == yajl_gen_map_val) { \
g->print(g->ctx, ":", 1); \
if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, " ", 1); \
}
if (g->state[g->depth] == yajl_gen_map_key || \
g->state[g->depth] == yajl_gen_in_array) { \
g->print(g->ctx, ",", 1); \
if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, "\n", 1); \
} else if (g->state[g->depth] == yajl_gen_map_val) { \
g->print(g->ctx, ":", 1); \
if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, " ", 1); \
}
#define INSERT_WHITESPACE \
if ((g->flags & yajl_gen_beautify)) { \
#define INSERT_WHITESPACE \
if ((g->flags & yajl_gen_beautify)) { \
if (g->state[g->depth] != yajl_gen_map_val) { \
unsigned int _i; \
for (_i=0;_i<g->depth;_i++) \
@@ -161,8 +171,8 @@ yajl_gen_free(yajl_gen g)
/* check that we're not complete, or in error state. in a valid state
* to be generating */
#define ENSURE_VALID_STATE \
if (g->state[g->depth] == yajl_gen_error) { \
return yajl_gen_in_error_state;\
if (g->state[g->depth] == yajl_gen_error) { \
return yajl_gen_in_error_state; \
} else if (g->state[g->depth] == yajl_gen_complete) { \
return yajl_gen_generation_complete; \
}
@@ -192,8 +202,9 @@ yajl_gen_free(yajl_gen g)
break; \
} \
#define FINAL_NEWLINE \
if ((g->flags & yajl_gen_beautify) && g->state[g->depth] == yajl_gen_complete) \
#define FINAL_NEWLINE \
if ((g->flags & yajl_gen_beautify) && \
g->state[g->depth] == yajl_gen_complete) \
g->print(g->ctx, "\n", 1);
yajl_gen_status
@@ -212,10 +223,24 @@ yajl_gen_status
yajl_gen_double(yajl_gen g, double number)
{
char i[32];
int special = 1;
ENSURE_VALID_STATE; ENSURE_NOT_KEY;
if (isnan(number) || isinf(number)) return yajl_gen_invalid_number;
if (isnan(number)) {
strcpy(i, "NaN");
}
else if (isinf(number)) {
sprintf(i, "%cInfinity", number < 0 ? '-' : '+');
}
else {
special = 0;
sprintf(i, "%.17g", number);
if (strspn(i, "0123456789-") == strlen(i)) {
strcat(i, ".0");
}
}
if (special && !(g->flags & yajl_gen_json5))
return yajl_gen_invalid_number;
INSERT_SEP; INSERT_WHITESPACE;
sprintf(i, "%.20g", number);
g->print(g->ctx, i, (unsigned int)strlen(i));
APPENDED_ATOM;
FINAL_NEWLINE;
@@ -245,9 +270,19 @@ yajl_gen_string(yajl_gen g, const unsigned char * str,
}
}
ENSURE_VALID_STATE; INSERT_SEP; INSERT_WHITESPACE;
g->print(g->ctx, "\"", 1);
yajl_string_encode(g->print, g->ctx, str, len, g->flags & yajl_gen_escape_solidus);
g->print(g->ctx, "\"", 1);
if (g->flags & yajl_gen_json5 &&
(g->state[g->depth] == yajl_gen_map_key ||
g->state[g->depth] == yajl_gen_map_start) &&
yajl_string_validate_identifier(str, len)) {
/* No need to quote this key */
g->print(g->ctx, (const char *) str, len);
}
else {
g->print(g->ctx, "\"", 1);
yajl_string_encode(g->print, g->ctx, str, len, g->flags & yajl_gen_escape_solidus,
g->flags & yajl_gen_json5);
g->print(g->ctx, "\"", 1);
}
APPENDED_ATOM;
FINAL_NEWLINE;
return yajl_gen_status_ok;
+100 -43
View File
@@ -28,34 +28,34 @@
#ifdef __cplusplus
extern "C" {
#endif
/** Generator status codes */
/** Generator status codes. */
typedef enum {
/** No error */
/** No error. */
yajl_gen_status_ok = 0,
/** At a point where a map key is generated, a function other than
* yajl_gen_string() was called */
* yajl_gen_string() was called. */
yajl_gen_keys_must_be_strings,
/** YAJL's maximum generation depth was exceeded. see
/** YAJL's maximum generation depth was exceeded. See
* \ref YAJL_MAX_DEPTH */
yajl_max_depth_exceeded,
/** A generator function (yajl_gen_XXX) was called while in an error
* state */
/** A yajl_gen_XXX() generator function was called while in an error
* state. */
yajl_gen_in_error_state,
/** A complete JSON document has been generated */
/** A complete JSON document has been generated. */
yajl_gen_generation_complete,
/** yajl_gen_double() was passed an invalid floating point value
* (infinity or NaN). */
* (infinity or NaN) without \ref yajl_gen_json5 set. */
yajl_gen_invalid_number,
/** A print callback was passed in, so there is no internal
* buffer to get from */
* buffer to get from. */
yajl_gen_no_buf,
/** Returned from yajl_gen_string() when the yajl_gen_validate_utf8()
* option is enabled and an invalid was passed by client code.
/** Returned from yajl_gen_string() when the \ref yajl_gen_validate_utf8
* option is enabled and an invalid UTF8 code was passed by client.
*/
yajl_gen_invalid_string
} yajl_gen_status;
/** An opaque handle to a generator */
/** An opaque handle to a generator. */
typedef struct yajl_gen_t * yajl_gen;
/** A callback used for "printing" the results. */
@@ -64,67 +64,111 @@ extern "C" {
size_t len);
/** Configuration parameters for the parser, these may be passed to
* yajl_gen_config() along with option specific argument(s). In general,
* yajl_gen_config() along with option-specific argument(s). In general,
* all configuration parameters default to *off*. */
typedef enum {
/** Generate indented (beautiful) output */
/** Generate indented (beautiful) output.
*
* yajl_gen_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_gen_config(g, yajl_gen_beautify, 1); // Human format please
* \endcode
*/
yajl_gen_beautify = 0x01,
/**
* Set an indent string which is used when yajl_gen_beautify()
* is enabled. Maybe something like \\t or some number of
* spaces. The default is four spaces ' '.
* Set the indent string which is used when \ref yajl_gen_beautify
* is enabled, which may only contain whitespace characters such as
* \c \\t or some number of spaces. The default is four spaces ' '.
*
* yajl_gen_config() argument type: const char *
*
* Example: \code{.cpp}
* yajl_gen_config(g, yajl_gen_indent_string, " "); // 2 spaces
* \endcode
*/
yajl_gen_indent_string = 0x02,
/**
* Set a function and context argument that should be used to
* output generated json. the function should conform to the
* yajl_print_t() prototype while the context argument is a
* output the generated json. The function should conform to the
* \ref yajl_print_t prototype while the context argument may be any
* void * of your choosing.
*
* yajl_gen_config() arguments: \ref yajl_print_t, void *
*
* Example: \code{.cpp}
* yajl_gen_config(g, yajl_gen_print_callback, myFunc, myVoidPtr);
* \endcode
* yajl_gen_config(g, yajl_gen_print_callback, myFunc, myVoidPtr);
* \endcode
*/
yajl_gen_print_callback = 0x04,
/**
* Normally the generator does not validate that strings you
* pass to it via yajl_gen_string() are valid UTF8. Enabling
* pass to it via yajl_gen_string() are valid UTF8. Enabling
* this option will cause it to do so.
*
* yajl_gen_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_gen_config(g, yajl_gen_validate_utf8, 1); // Check UTF8
* \endcode
*/
yajl_gen_validate_utf8 = 0x08,
/**
* The forward solidus (slash or '/' in human) is not required to be
* escaped in json text. By default, YAJL will not escape it in the
* iterest of saving bytes. Setting this flag will cause YAJL to
* escaped in JSON text. By default, YAJL will not escape it in the
* interest of saving bytes. Setting this flag will cause YAJL to
* always escape '/' in generated JSON strings.
*
* yajl_gen_config() argument type: int (boolean)
*/
yajl_gen_escape_solidus = 0x10
yajl_gen_escape_solidus = 0x10,
/**
* JSON5 is an updated version of JSON with additional capabilities.
* Special numbers such as NaN and Infinity cannot be represented in
* the original JSON, but are permitted in JSON5. Setting this flag
* allows YAJL to output the JSON5 representation of these special
* numbers instead of returning with an error, and to emit map keys
* that are valid javascript identifiers without quotes.
*
* yajl_gen_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_gen_config(g, yajl_gen_json5, 1); // Output JSON5
* \endcode
*/
yajl_gen_json5 = 0x20,
} yajl_gen_option;
/** Allow the modification of generator options subsequent to handle
* allocation (via yajl_alloc())
* \returns zero in case of errors, non-zero otherwise
/** Set generator options associated with a generator handle. See the
* \ref yajl_gen_option documentation for details of the available
* options and their arguments.
* \returns Zero in case of error, non-zero otherwise.
*/
YAJL_API int yajl_gen_config(yajl_gen g, yajl_gen_option opt, ...);
YAJL_API int yajl_gen_config(yajl_gen hand, int option, ...);
/** Allocate a generator handle
* \param allocFuncs An optional pointer to a structure which allows
* the client to overide the memory allocation
* used by yajl. May be NULL, in which case
* malloc(), free() and realloc() will be used.
/** Allocate a generator handle.
* \param allocFuncs An optional pointer to a structure which allows the
* client to provide memory allocation functions for
* use by yajl. May be \c NULL to use the C runtime
* library's malloc(), free() and realloc().
*
* \returns an allocated handle on success, NULL on failure (bad params)
* \returns An allocated handle on success, \c NULL on failure (bad params)
*/
YAJL_API yajl_gen yajl_gen_alloc(const yajl_alloc_funcs * allocFuncs);
/** Free a generator handle */
/** Free a generator handle. */
YAJL_API void yajl_gen_free(yajl_gen handle);
/** Generate an integer number. */
YAJL_API yajl_gen_status yajl_gen_integer(yajl_gen hand, long long int number);
/** Generate a floating point number. \p number may not be infinity or
* NaN, as these have no representation in JSON. In these cases the
* generator will return \ref yajl_gen_invalid_number */
/** Generate a floating point number.
* \param hand The generator handle.
* \param number The value to output. The values \c Infinity or \c NaN are
* only accepted if the \ref yajl_gen_json5 option is set,
* as these values have no legal representation in JSON;
* the generator will return \ref yajl_gen_invalid_number
* otherwise.
*/
YAJL_API yajl_gen_status yajl_gen_double(yajl_gen hand, double number);
/** Generate a number from the string given in \p num. */
YAJL_API yajl_gen_status yajl_gen_number(yajl_gen hand,
@@ -149,18 +193,31 @@ extern "C" {
/** Finish generating a JSON array. */
YAJL_API yajl_gen_status yajl_gen_array_close(yajl_gen hand);
/** Access the null terminated generator buffer. If incrementally
/** Access the zero-terminated generator buffer. If incrementally
* outputing JSON, one should call yajl_gen_clear() to clear the
* buffer. This allows stream generation. */
* buffer. This allows stream generation. */
YAJL_API yajl_gen_status yajl_gen_get_buf(yajl_gen hand,
const unsigned char ** buf,
size_t * len);
/** Clear yajl's output buffer, but maintain all internal generation
* state. This function will not "reset" the generator state, and is
* intended to enable incremental JSON outputing. */
* state. This function will not reset the generator state, and is
* intended to enable incremental JSON output. */
YAJL_API void yajl_gen_clear(yajl_gen hand);
/** Reset the generator state. Allows a client to generate multiple
* JSON entities in a stream.
* \param hand The generator handle.
* \param sep This string will be inserted to separate the previously
* generated output from the following; passing \c NULL means
* *no separation* of entites (beware that generating
* multiple JSON numbers without a separator creates
* ambiguous output).
*
* Note: This call does not clear yajl's output buffer, which must be
* accomplished explicitly by calling yajl_gen_clear(). */
YAJL_API void yajl_gen_reset(yajl_gen hand, const char * sep);
#ifdef __cplusplus
}
#endif
+310 -81
View File
@@ -87,6 +87,9 @@ struct yajl_lexer_t {
/* shall we allow comments? */
unsigned int allowComments;
/* are we parsing JSON5? */
unsigned int allowJson5;
/* shall we validate utf8 inside strings? */
unsigned int validateUTF8;
@@ -102,7 +105,8 @@ struct yajl_lexer_t {
yajl_lexer
yajl_lex_alloc(yajl_alloc_funcs * alloc,
unsigned int allowComments, unsigned int validateUTF8)
unsigned int allowComments, unsigned int validateUTF8,
unsigned int allowJson5)
{
yajl_lexer lxr = (yajl_lexer) YA_MALLOC(alloc, sizeof(struct yajl_lexer_t));
if (lxr == NULL) {
@@ -113,6 +117,7 @@ yajl_lex_alloc(yajl_alloc_funcs * alloc,
lxr->buf = yajl_buf_alloc(alloc);
lxr->allowComments = allowComments;
lxr->validateUTF8 = validateUTF8;
lxr->allowJson5 = !!allowJson5;
lxr->alloc = alloc;
return lxr;
}
@@ -125,19 +130,21 @@ yajl_lex_free(yajl_lexer lxr)
return;
}
/* a lookup table which lets us quickly determine three things:
/* a lookup table which lets us quickly determine various things:
* VEC - valid escaped control char
* note. the solidus '/' may be escaped or not.
* Note: the solidus '/' may be escaped or not.
* IJC - invalid json char
* VHC - valid hex char
* NFP - needs further processing (from a string scanning perspective)
* NUC - needs utf8 checking when enabled (from a string scanning perspective)
* VIC - valid identifier char (after the first char)
*/
#define VEC 0x01
#define IJC 0x02
#define VHC 0x04
#define NFP 0x08
#define NUC 0x10
#define VIC 0x20
static const char charLookupTable[256] =
{
@@ -146,20 +153,20 @@ static const char charLookupTable[256] =
/*10*/ IJC , IJC , IJC , IJC , IJC , IJC , IJC , IJC ,
/*18*/ IJC , IJC , IJC , IJC , IJC , IJC , IJC , IJC ,
/*20*/ 0 , 0 , NFP|VEC|IJC, 0 , 0 , 0 , 0 , 0 ,
/*20*/ 0 , 0 , NFP|VEC, 0 , VIC , 0 , 0 , NFP|VEC,
/*28*/ 0 , 0 , 0 , 0 , 0 , 0 , 0 , VEC ,
/*30*/ VHC , VHC , VHC , VHC , VHC , VHC , VHC , VHC ,
/*38*/ VHC , VHC , 0 , 0 , 0 , 0 , 0 , 0 ,
/*30*/ VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC,
/*38*/ VHC|VIC, VHC|VIC, 0 , 0 , 0 , 0 , 0 , 0 ,
/*40*/ 0 , VHC , VHC , VHC , VHC , VHC , VHC , 0 ,
/*48*/ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/*50*/ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/*58*/ 0 , 0 , 0 , 0 , NFP|VEC|IJC, 0 , 0 , 0 ,
/*40*/ 0 , VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC, VIC ,
/*48*/ VIC , VIC , VIC , VIC , VIC , VIC , VIC , VIC ,
/*50*/ VIC , VIC , VIC , VIC , VIC , VIC , VIC , VIC ,
/*58*/ VIC , VIC , VIC , 0 , NFP|VEC|IJC, 0 , 0 , VIC ,
/*60*/ 0 , VHC , VEC|VHC, VHC , VHC , VHC , VEC|VHC, 0 ,
/*68*/ 0 , 0 , 0 , 0 , 0 , 0 , VEC , 0 ,
/*70*/ 0 , 0 , VEC , 0 , VEC , 0 , 0 , 0 ,
/*78*/ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/*60*/ 0 , VHC|VIC, VEC|VHC|VIC, VHC|VIC, VHC|VIC, VHC|VIC, VEC|VHC|VIC, VIC,
/*68*/ VIC , VIC , VIC , VIC , VIC , VIC , VEC|VIC, VIC ,
/*70*/ VIC , VIC , VEC|VIC, VIC , VEC|VIC, VIC , VIC , VIC ,
/*78*/ VIC , VIC , VIC , 0 , 0 , 0 , 0 , 0 ,
NUC , NUC , NUC , NUC , NUC , NUC , NUC , NUC ,
NUC , NUC , NUC , NUC , NUC , NUC , NUC , NUC ,
@@ -193,7 +200,7 @@ static const char charLookupTable[256] =
*
* NOTE: on error the offset will point to the first char of the
* invalid utf8 */
#define UTF8_CHECK_EOF if (*offset >= jsonTextLen) { return yajl_tok_eof; }
#define UTF8_CHECK_EOF if (*offset >= jsonTextLen) return yajl_tok_eof;
static yajl_tok
yajl_lex_utf8_char(yajl_lexer lexer, const unsigned char * jsonText,
@@ -270,7 +277,7 @@ yajl_string_scan(const unsigned char * buf, size_t len, int utf8check)
static yajl_tok
yajl_lex_string(yajl_lexer lexer, const unsigned char * jsonText,
size_t jsonTextLen, size_t * offset)
size_t jsonTextLen, size_t * offset, const char quote)
{
yajl_tok tok = yajl_tok_error;
int hasEscapes = 0;
@@ -305,7 +312,7 @@ yajl_lex_string(yajl_lexer lexer, const unsigned char * jsonText,
curChar = readChar(lexer, jsonText, offset);
/* quote terminates */
if (curChar == '"') {
if (curChar == quote) {
tok = yajl_tok_string;
break;
}
@@ -325,16 +332,38 @@ yajl_lex_string(yajl_lexer lexer, const unsigned char * jsonText,
if (!(charLookupTable[curChar] & VHC)) {
/* back up to offending char */
unreadChar(lexer, offset);
lexer->error = yajl_lex_string_invalid_hex_char;
lexer->error = yajl_lex_string_invalid_hex_u_char;
goto finish_string_lex;
}
}
} else if (!(charLookupTable[curChar] & VEC)) {
}
else if (lexer->allowJson5 && curChar == 'x') {
unsigned int i = 0;
for (i=0;i<2;i++) {
STR_CHECK_EOF;
curChar = readChar(lexer, jsonText, offset);
if (!(charLookupTable[curChar] & VHC)) {
/* back up to offending char */
unreadChar(lexer, offset);
lexer->error = yajl_lex_string_invalid_hex_x_char;
goto finish_string_lex;
}
}
}
else if (lexer->allowJson5 ? (curChar >= '1' && curChar <= '9')
: !(charLookupTable[curChar] & VEC)) {
/* back up to offending char */
unreadChar(lexer, offset);
lexer->error = yajl_lex_string_invalid_escaped_char;
goto finish_string_lex;
}
else if (lexer->allowJson5 && curChar == '\r') {
STR_CHECK_EOF;
curChar = readChar(lexer, jsonText, offset);
if (curChar != '\n')
unreadChar(lexer, offset);
}
}
/* when not validating UTF8 it's a simple table lookup to determine
* if the present character is invalid */
@@ -371,36 +400,83 @@ yajl_lex_string(yajl_lexer lexer, const unsigned char * jsonText,
#define RETURN_IF_EOF if (*offset >= jsonTextLen) return yajl_tok_eof;
/* For both identifiers and numbers, we always have to lex one
* character too many to know when they are complete.
*/
static yajl_tok
yajl_lex_identifier(yajl_lexer lexer, const unsigned char * jsonText,
size_t jsonTextLen, size_t * offset)
{
unsigned char c;
do {
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
} while (charLookupTable[c] & VIC);
/* we always go "one too far" */
unreadChar(lexer, offset);
return yajl_tok_identifier;
}
static yajl_tok
yajl_lex_number(yajl_lexer lexer, const unsigned char * jsonText,
size_t jsonTextLen, size_t * offset)
{
/** XXX: numbers are the only entities in json that we must lex
* _beyond_ in order to know that they are complete. There
* is an ambiguous case for integers at EOF. */
const char hexDigits[] = "0123456789abcdefABCDEF";
unsigned char c;
int numRd = 0;
yajl_tok tok = yajl_tok_integer;
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
/* optional leading minus */
if (c == '-') {
/* optional leading plus/minus */
if (c == '-' || (lexer->allowJson5 && c == '+')) {
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
}
/* a single zero, or a series of integers */
if (c == '0') {
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
} else if (c >= '1' && c <= '9') {
if (c == 'I') {
const char * want = "nfinity";
do {
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
if (c != *want) {
unreadChar(lexer, offset);
lexer->error = yajl_lex_invalid_string;
return yajl_tok_error;
}
} while (*(++want));
if (!lexer->allowJson5) {
unreadChar(lexer, offset);
lexer->error = yajl_lex_unallowed_special_number;
return yajl_tok_error;
}
return yajl_tok_double;
}
/* a single zero, hex number, or a series of decimal digits */
if (c == '0') {
numRd++;
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
if (c == 'x' || c == 'X') {
if (lexer->allowJson5) goto got_hex;
lexer->error = yajl_lex_unallowed_hex_integer;
return yajl_tok_error;
}
} else if (c >= '1' && c <= '9') {
do {
numRd++;
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
} while (c >= '0' && c <= '9');
} else if (lexer->allowJson5 && c == '.') {
goto got_decimal;
} else {
unreadChar(lexer, offset);
lexer->error = yajl_lex_missing_integer_after_minus;
@@ -409,10 +485,10 @@ yajl_lex_number(yajl_lexer lexer, const unsigned char * jsonText,
/* optional fraction (indicates this is floating point) */
if (c == '.') {
int numRd = 0;
got_decimal:
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
if (!lexer->allowJson5) numRd = 0;
while (c >= '0' && c <= '9') {
numRd++;
@@ -452,6 +528,25 @@ yajl_lex_number(yajl_lexer lexer, const unsigned char * jsonText,
tok = yajl_tok_double;
}
goto end_number;
got_hex:
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
if (strchr(hexDigits, c)) {
do {
RETURN_IF_EOF;
c = readChar(lexer, jsonText, offset);
} while (strchr(hexDigits, c));
}
else {
unreadChar(lexer, offset);
lexer->error = yajl_lex_missing_hex_digit_after_0x;
return yajl_tok_error;
}
end_number:
/* we always go "one too far" */
unreadChar(lexer, offset);
@@ -499,6 +594,23 @@ yajl_lex_comment(yajl_lexer lexer, const unsigned char * jsonText,
return tok;
}
/* Macro to reduce code duplication in yajl_lex_lex() */
#define LEX_WANT(tring) \
const char * want = tring; \
do { \
if (*offset >= jsonTextLen) { \
tok = yajl_tok_eof; \
goto lexed; \
} \
c = readChar(lexer, jsonText, offset); \
if (c != *want) { \
unreadChar(lexer, offset); \
lexer->error = yajl_lex_invalid_string; \
tok = yajl_tok_error; \
goto lexed; \
} \
} while (*(++want))
yajl_tok
yajl_lex_lex(yajl_lexer lexer, const unsigned char * jsonText,
size_t jsonTextLen, size_t * offset,
@@ -544,71 +656,58 @@ yajl_lex_lex(yajl_lexer lexer, const unsigned char * jsonText,
startOffset++;
break;
case 't': {
const char * want = "rue";
do {
if (*offset >= jsonTextLen) {
tok = yajl_tok_eof;
goto lexed;
}
c = readChar(lexer, jsonText, offset);
if (c != *want) {
unreadChar(lexer, offset);
lexer->error = yajl_lex_invalid_string;
tok = yajl_tok_error;
goto lexed;
}
} while (*(++want));
LEX_WANT("rue");
tok = yajl_tok_bool;
goto lexed;
}
case 'f': {
const char * want = "alse";
do {
if (*offset >= jsonTextLen) {
tok = yajl_tok_eof;
goto lexed;
}
c = readChar(lexer, jsonText, offset);
if (c != *want) {
unreadChar(lexer, offset);
lexer->error = yajl_lex_invalid_string;
tok = yajl_tok_error;
goto lexed;
}
} while (*(++want));
LEX_WANT("alse");
tok = yajl_tok_bool;
goto lexed;
}
case 'n': {
const char * want = "ull";
do {
if (*offset >= jsonTextLen) {
tok = yajl_tok_eof;
goto lexed;
}
c = readChar(lexer, jsonText, offset);
if (c != *want) {
unreadChar(lexer, offset);
lexer->error = yajl_lex_invalid_string;
tok = yajl_tok_error;
goto lexed;
}
} while (*(++want));
LEX_WANT("ull");
tok = yajl_tok_null;
goto lexed;
}
case '"': {
tok = yajl_lex_string(lexer, (const unsigned char *) jsonText,
jsonTextLen, offset);
case 'I': {
LEX_WANT("nfinity");
if (!lexer->allowJson5) {
unreadChar(lexer, offset);
lexer->error = yajl_lex_unallowed_special_number;
tok = yajl_tok_error;
} else {
tok = yajl_tok_double;
}
goto lexed;
}
case 'N': {
LEX_WANT("aN");
if (!lexer->allowJson5) {
unreadChar(lexer, offset);
lexer->error = yajl_lex_unallowed_special_number;
tok = yajl_tok_error;
} else {
tok = yajl_tok_double;
}
goto lexed;
}
case '\'':
if (!lexer->allowJson5) goto invalid;
/* Fall through... */
case '"': {
tok = yajl_lex_string(lexer, jsonText, jsonTextLen, offset, c);
goto lexed;
}
case '+': case '.':
if (!lexer->allowJson5)
goto invalid;
case '-':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': {
/* integer parsing wants to start from the beginning */
unreadChar(lexer, offset);
tok = yajl_lex_number(lexer, (const unsigned char *) jsonText,
jsonTextLen, offset);
tok = yajl_lex_number(lexer, jsonText, jsonTextLen, offset);
goto lexed;
}
case '/':
@@ -640,6 +739,7 @@ yajl_lex_lex(yajl_lexer lexer, const unsigned char * jsonText,
/* hit error or eof, bail */
goto lexed;
default:
invalid:
lexer->error = yajl_lex_invalid_char;
tok = yajl_tok_error;
goto lexed;
@@ -674,6 +774,126 @@ yajl_lex_lex(yajl_lexer lexer, const unsigned char * jsonText,
*outLen -= 2;
}
#ifdef YAJL_LEXER_DEBUG
if (tok == yajl_tok_error) {
printf("lexical error: %s\n",
yajl_lex_error_to_string(yajl_lex_get_error(lexer)));
} else if (tok == yajl_tok_eof) {
printf("EOF hit\n");
} else {
printf("lexed %s: '", tokToStr(tok));
fwrite(*outBuf, 1, *outLen, stdout);
printf("'\n");
}
#endif
return tok;
}
yajl_tok yajl_lex_key(yajl_lexer lexer, const unsigned char * jsonText,
size_t jsonTextLen, size_t * offset,
const unsigned char ** outBuf, size_t * outLen)
{
yajl_tok tok = yajl_tok_error;
unsigned char c;
size_t startOffset = *offset;
*outBuf = NULL;
*outLen = 0;
for (;;) {
assert(*offset <= jsonTextLen);
if (*offset >= jsonTextLen) {
tok = yajl_tok_eof;
goto lexed;
}
c = readChar(lexer, jsonText, offset);
switch (c) {
case '\t': case '\n': case '\v': case '\f': case '\r': case ' ':
startOffset++;
break;
case '}':
tok = yajl_tok_right_brace;
goto lexed;
case '\'':
if (!lexer->allowJson5) goto invalid;
/* Fall through... */
case '"': {
tok = yajl_lex_string(lexer, jsonText, jsonTextLen, offset, c);
goto lexed;
}
case '/':
/* If comments are disabled this is an error. */
if (!lexer->allowComments) {
unreadChar(lexer, offset);
lexer->error = yajl_lex_unallowed_comment;
tok = yajl_tok_error;
goto lexed;
}
/* Comments are enabled, so lex it.
* Possible outcomes are:
* - successful lex (tok_comment, which means continue),
* - malformed comment opening (slash not followed by
* '*' or '/') (tok_error)
* - eof hit. (tok_eof) */
tok = yajl_lex_comment(lexer, jsonText, jsonTextLen, offset);
if (tok == yajl_tok_comment) {
/* "error" is silly, but that's the initial
* state of tok. guilty until proven innocent. */
tok = yajl_tok_error;
yajl_buf_clear(lexer->buf);
lexer->bufInUse = 0;
startOffset = *offset;
break;
}
/* hit error or eof, bail */
goto lexed;
default:
if (lexer->allowJson5 && (c == '$' || c == '_' ||
(c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
tok = yajl_lex_identifier(lexer, jsonText, jsonTextLen, offset);
}
else {
invalid:
lexer->error = yajl_lex_invalid_char;
tok = yajl_tok_error;
}
goto lexed;
}
}
lexed:
/* need to append to buffer if the buffer is in use or
* if it's an EOF token */
if (tok == yajl_tok_eof || lexer->bufInUse) {
if (!lexer->bufInUse) yajl_buf_clear(lexer->buf);
lexer->bufInUse = 1;
yajl_buf_append(lexer->buf, jsonText + startOffset, *offset - startOffset);
lexer->bufOff = 0;
if (tok != yajl_tok_eof) {
*outBuf = yajl_buf_data(lexer->buf);
*outLen = yajl_buf_len(lexer->buf);
lexer->bufInUse = 0;
}
} else if (tok != yajl_tok_error) {
*outBuf = jsonText + startOffset;
*outLen = *offset - startOffset;
}
/* For strings skip the quotes. */
if (tok == yajl_tok_string ||
tok == yajl_tok_string_with_escapes) {
assert(*outLen >= 2);
(*outBuf)++;
*outLen -= 2;
}
else if (tok == yajl_tok_identifier) {
tok = yajl_tok_string;
}
#ifdef YAJL_LEXER_DEBUG
if (tok == yajl_tok_error) {
@@ -704,9 +924,12 @@ yajl_lex_error_to_string(yajl_lex_error error)
"which it may not.";
case yajl_lex_string_invalid_json_char:
return "invalid character inside string.";
case yajl_lex_string_invalid_hex_char:
case yajl_lex_string_invalid_hex_u_char:
return "invalid (non-hex) character occurs after '\\u' inside "
"string.";
case yajl_lex_string_invalid_hex_x_char:
return "invalid (non-hex) character occurs after '\\x' inside "
"string.";
case yajl_lex_invalid_char:
return "invalid char in json text.";
case yajl_lex_invalid_string:
@@ -718,10 +941,16 @@ yajl_lex_error_to_string(yajl_lex_error error)
"decimal point.";
case yajl_lex_missing_integer_after_minus:
return "malformed number, a digit is required after the "
"minus sign.";
"plus/minus sign.";
case yajl_lex_unallowed_comment:
return "probable comment found in input text, comments are "
"not enabled.";
case yajl_lex_missing_hex_digit_after_0x:
return "malformed number, a hex digit is required after the 0x/0X.";
case yajl_lex_unallowed_hex_integer:
return "probable hex number found, JSON5 is not enabled.";
case yajl_lex_unallowed_special_number:
return "special number Infinity or NaN found, JSON5 is not enabled.";
}
return "unknown error code";
}
+22 -4
View File
@@ -41,7 +41,12 @@ typedef enum {
yajl_tok_string,
yajl_tok_string_with_escapes,
/* comment tokens are not currently returned to the parser, ever */
/* These tokens are used within the lexer and never seen by the parser: */
/* An unquoted map key, for JSON5 only, returned as yajl_tok_string */
yajl_tok_identifier,
/* A comment token, never returned */
yajl_tok_comment
} yajl_tok;
@@ -53,7 +58,8 @@ extern "C" {
yajl_lexer yajl_lex_alloc(yajl_alloc_funcs * alloc,
unsigned int allowComments,
unsigned int validateUTF8);
unsigned int validateUTF8,
unsigned int allowJson5);
void yajl_lex_free(yajl_lexer lexer);
@@ -83,6 +89,14 @@ yajl_tok yajl_lex_lex(yajl_lexer lexer, const unsigned char * jsonText,
size_t jsonTextLen, size_t * offset,
const unsigned char ** outBuf, size_t * outLen);
/**
* A specialized version of yajl_lex_lex for use when the next token is
* a map key, which the parser knows.
*/
yajl_tok yajl_lex_key(yajl_lexer lexer, const unsigned char * jsonText,
size_t jsonTextLen, size_t * offset,
const unsigned char ** outBuf, size_t * outLen);
/** have a peek at the next token, but don't move the lexer forward */
yajl_tok yajl_lex_peek(yajl_lexer lexer, const unsigned char * jsonText,
size_t jsonTextLen, size_t offset);
@@ -93,13 +107,17 @@ typedef enum {
yajl_lex_string_invalid_utf8,
yajl_lex_string_invalid_escaped_char,
yajl_lex_string_invalid_json_char,
yajl_lex_string_invalid_hex_char,
yajl_lex_string_invalid_hex_u_char,
yajl_lex_string_invalid_hex_x_char,
yajl_lex_invalid_char,
yajl_lex_invalid_string,
yajl_lex_missing_integer_after_decimal,
yajl_lex_missing_integer_after_exponent,
yajl_lex_missing_integer_after_minus,
yajl_lex_unallowed_comment
yajl_lex_unallowed_comment,
yajl_lex_missing_hex_digit_after_0x,
yajl_lex_unallowed_hex_integer,
yajl_lex_unallowed_special_number,
} yajl_lex_error;
const char * yajl_lex_error_to_string(yajl_lex_error error);
+112 -76
View File
@@ -28,42 +28,42 @@
#ifdef __cplusplus
extern "C" {
#endif
/** Error codes returned from this interface */
/** Error codes returned from this interface. */
typedef enum {
/** No error was encountered */
/** No error was encountered. */
yajl_status_ok,
/** A client callback returned zero, stopping the parse */
/** A client callback returned zero, stopping the parse. */
yajl_status_client_canceled,
/** An error occured during the parse. Call yajl_get_error() for
* more information about the encountered error */
/** An error occured during the parse. Call yajl_get_error() for
* more information about the encountered error. */
yajl_status_error
} yajl_status;
/** Attain a human readable, english, string for an error */
/** Return a human readable, english, string for an error. */
YAJL_API const char * yajl_status_to_string(yajl_status code);
/** An opaque handle to a parser */
/** An opaque handle to a parser. */
typedef struct yajl_handle_t * yajl_handle;
/** YAJL is an event driven parser. This means as json elements are
* parsed, you are called back to do something with the data. The
/** YAJL is an event driven parser. This means as json elements are
* parsed, you are called back to do something with the data. The
* functions in this table indicate the various events for which
* you will be called back. Each callback accepts a "context"
* pointer, this is a void * that is passed into the yajl_parse()
* pointer, this is a \c void \c * that is passed into the yajl_parse()
* function which the client code may use to pass around context.
*
* All callbacks return an integer. If non-zero, the parse will
* continue. If zero, the parse will be canceled and
* yajl_status_client_canceled() will be returned from the parse.
* All callbacks return an integer. If non-zero, the parse will
* continue. If zero, the parse will be canceled and
* \c yajl_status_client_canceled will be returned from the parse.
*
* \attention
* A note about the handling of numbers:
* yajl will only convert numbers that can be represented in a
* YAJL will only convert numbers that can be represented in a
* double or a 64 bit (long long) int. All other numbers will
* be passed to the client in string form using the yajl_number()
* callback. Furthermore, if yajl_number() is not NULL, it will
* always be used to return numbers, that is yajl_integer() and
* yajl_double() will be ignored. If yajl_number() is NULL but one
* yajl_double() will be ignored. If yajl_number() is NULL but one
* of yajl_integer() or yajl_double() are defined, parsing of a
* number larger than is representable in a double or 64 bit
* integer will result in a parse error.
@@ -74,12 +74,12 @@ extern "C" {
int (* yajl_integer)(void * ctx, long long integerVal);
int (* yajl_double)(void * ctx, double doubleVal);
/** A callback which passes the string representation of the number
* back to the client. Will be used for all numbers when present */
* back to the client. Will be used for all numbers when present. */
int (* yajl_number)(void * ctx, const char * numberVal,
size_t numberLen);
/** Strings are returned as pointers into the JSON text when,
* possible, as a result, they are _not_ null padded */
/** Strings are returned as pointers into the JSON text when
* possible. As a result they are _not_ zero-terminated. */
int (* yajl_string)(void * ctx, const unsigned char * stringVal,
size_t stringLen);
@@ -92,117 +92,153 @@ extern "C" {
int (* yajl_end_array)(void * ctx);
} yajl_callbacks;
/** Allocate a parser handle
* \param callbacks A yajl callbacks structure specifying the
/** Allocate a parser handle.
* \param callbacks A \c yajl_callbacks structure specifying the
* functions to call when different JSON entities
* are encountered in the input text. May be NULL,
* are encountered in the input text. May be \c NULL,
* which is only useful for validation.
* \param afs memory allocation functions, may be NULL for to use
* C runtime library routines (malloc and friends)
* \param ctx a context pointer that will be passed to callbacks.
* \param afs Memory allocation functions, pass \c NULL to use the
* C runtime library routines (malloc() and friends).
* \param ctx A context pointer that will be passed to callbacks.
*/
YAJL_API yajl_handle yajl_alloc(const yajl_callbacks * callbacks,
yajl_alloc_funcs * afs,
void * ctx);
/** Configuration parameters for the parser, these may be passed to
* yajl_config() along with option specific argument(s). In general,
* all configuration parameters default to *off*. */
/** Configuration parameters for the parser. These should be passed to
* yajl_config() followed by any option specific argument(s). In general,
* all boolean configuration parameters default to *off*. */
typedef enum {
/** Ignore javascript style comments present in
* JSON input. Non-standard, but rather fun
* arguments: toggled off with integer zero, on otherwise.
/**
* Ignore javascript style comments present in the input. These are
* not standard in JSON but can be enabled with this. Turning on the
* \ref yajl_allow_json5 option enables this option automatically.
*
* Example: \code{.cpp}
* yajl_config(h, yajl_allow_comments, 1); // turn comment support on
* \endcode
* yajl_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_config(h, yajl_allow_comments, 1); // turn comment support on
* \endcode
*/
yajl_allow_comments = 0x01,
/**
* When set the parser will verify that all strings in JSON input are
* valid UTF8 and will emit a parse error if this is not so. When set,
* this option makes parsing slightly more expensive (~7% depending
* on processor and compiler in use)
* valid UTF8, and will emit a parse error if this is not so. When set,
* this option makes parsing slightly more expensive (~7% depending on
* the processor and compiler in use)
*
* yajl_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_config(h, yajl_dont_validate_strings, 1); // disable utf8 checking
* \endcode
* yajl_config(h, yajl_dont_validate_strings, 1); // disable utf8 checking
* \endcode
*/
yajl_dont_validate_strings = 0x02,
/**
* By default, upon calls to yajl_complete_parse(), yajl will
* ensure the entire input text was consumed and will raise an error
* otherwise. Enabling this flag will cause yajl to disable this
* check. This can be useful when parsing json out of a that contains more
* than a single JSON document.
* By default, upon calls to yajl_complete_parse(), yajl will ensure
* the entire input text was consumed and will raise an error
* otherwise. Turning this flag on causes YAJL to disable the garbage
* check. This can be useful when parsing JSON out of an input stream
* that contains more than a single JSON document.
*
* yajl_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_config(h, yajl_allow_trailing_garbage, 1); // non-JSON follows
* \endcode
*/
yajl_allow_trailing_garbage = 0x04,
/**
* Allow multiple values to be parsed by a single handle. The
* entire text must be valid JSON, and values can be seperated
* by any kind of whitespace. This flag will change the
* behavior of the parser, and cause it continue parsing after
* a value is parsed, rather than transitioning into a
* complete state. This option can be useful when parsing multiple
* values from an input stream.
* Allow multiple values to be parsed by a single handle. The entire
* text must be valid JSON, and values can be seperated by any kind of
* whitespace. This flag will change the behavior of the parser, and
* cause it to continue parsing after a value is parsed, rather than
* transitioning into a complete state. This option can be useful when
* parsing multiple values from an input stream.
*
* yajl_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_config(h, yajl_allow_multiple_values, 1); // multi-doc stream
* \endcode
*/
yajl_allow_multiple_values = 0x08,
/**
* When yajl_complete_parse() is called the parser will
* check that the top level value was completely consumed. I.E.,
* if called whilst in the middle of parsing a value
* yajl will enter an error state (premature EOF). Setting this
* flag suppresses that check and the corresponding error.
* When yajl_complete_parse() is called the parser will check that the
* top level value was completely consumed. If called whilst in the
* middle of parsing a value, yajl will enter an error state (premature
* EOF). Setting this flag suppresses that check and the corresponding
* error.
*
* yajl_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_config(h, yajl_allow_partial_values, 1); // might stop early
* \endcode
*/
yajl_allow_partial_values = 0x10
yajl_allow_partial_values = 0x10,
/**
* The JSON5 standard allows additional formats for numbers, strings
* and object keys which are not permitted by the JSON standard.
* Setting this flag tells yajl to accept JSON5 standard input.
* This flag also enables \c yajl_allow_comments since comments are
* part of the JSON5 standard. In the EPICS build this option is
* enabled by default, it must be turned off to disable JSON5.
*
* yajl_config() argument type: int (boolean)
*
* Example: \code{.cpp}
* yajl_config(h, yajl_allow_json5, 1); // We accept JSON5!
* \endcode
*/
yajl_allow_json5 = 0x20,
} yajl_option;
/** Allow the modification of parser options subsequent to handle
* allocation (via yajl_alloc())
* \returns zero in case of errors, non-zero otherwise
/** Set parser options associated with a parser handle. See the
* \ref yajl_option documentation for details of the available options
* and their arguments.
* \returns Zero in case of error, non-zero otherwise.
*/
YAJL_API int yajl_config(yajl_handle h, yajl_option opt, ...);
YAJL_API int yajl_config(yajl_handle hand, int option, ...);
/** Free a parser handle */
YAJL_API void yajl_free(yajl_handle handle);
/** Free a parser handle. */
YAJL_API void yajl_free(yajl_handle hand);
/** Parse some json!
* \param hand - a handle to the json parser allocated with yajl_alloc()
* \param jsonText - a pointer to the UTF8 json text to be parsed
* \param jsonTextLength - the length, in bytes, of input text
* \param hand A handle to the json parser allocated with yajl_alloc().
* \param jsonText A pointer to the UTF8 json text to be parsed.
* \param jsonTextLength The length, in bytes, of input text.
*/
YAJL_API yajl_status yajl_parse(yajl_handle hand,
const unsigned char * jsonText,
size_t jsonTextLength);
/** Parse any remaining buffered json.
*
* Since yajl is a stream-based parser, without an explicit end of
* input, yajl sometimes can't decide if content at the end of the
* stream is valid or not. For example, if "1" has been fed in,
* stream is valid or not. For example, if "1" has been received,
* yajl can't know whether another digit is next or some character
* that would terminate the integer token.
*
* \param hand - a handle to the json parser allocated with yajl_alloc()
* \param hand a handle to the json parser allocated with yajl_alloc().
*/
YAJL_API yajl_status yajl_complete_parse(yajl_handle hand);
/** Get an error string describing the state of the
* parse.
/** Get an error string describing the state of the parse.
*
* If verbose is non-zero, the message will include the JSON
* text where the error occured, along with an arrow pointing to
* the specific char.
* If verbose is non-zero, the message will include the JSON text where
* the error occured, along with an arrow pointing to the specific char.
*
* \returns A dynamically allocated string will be returned which should
* be freed with yajl_free_error()
* be freed with yajl_free_error().
*/
YAJL_API unsigned char * yajl_get_error(yajl_handle hand, int verbose,
const unsigned char * jsonText,
size_t jsonTextLength);
/**
* Get the amount of data consumed from the last chunk passed to YAJL.
/** Get the amount of data consumed from the last chunk passed to YAJL.
*
* In the case of a successful parse this can help you understand if
* the entire buffer was consumed (which will allow you to handle
@@ -215,7 +251,7 @@ extern "C" {
*/
YAJL_API size_t yajl_get_bytes_consumed(yajl_handle hand);
/** Free an error returned from yajl_get_error() */
/** Free an error string returned from yajl_get_error(). */
YAJL_API void yajl_free_error(yajl_handle hand, unsigned char * str);
#ifdef __cplusplus
+41 -15
View File
@@ -34,29 +34,52 @@
#define LLONG_MIN (-0x7FFFFFFFFFFFFFFFLL - 1)
#endif
#define MAX_VALUE_TO_MULTIPLY ((LLONG_MAX / 10) + (LLONG_MAX % 10))
/* same semantics as strtol */
long long
yajl_parse_integer(const unsigned char *number, size_t length)
{
long long ret = 0;
long sign = 1;
long base = 10;
long long max = LLONG_MAX / base;
const unsigned char *pos = number;
if (*pos == '-') { pos++; sign = -1; }
if (*pos == '+') { pos++; }
const unsigned char *end = number + length;
while (pos < number + length) {
if ( ret > MAX_VALUE_TO_MULTIPLY ) {
if (*pos == '-') {
pos++;
sign = -1;
}
else if (*pos == '+') {
pos++;
}
if (*pos == '0' &&
(pos[1] == 'x' || pos[1] == 'X')) {
base = 16;
max = LLONG_MAX / base;
pos += 2;
}
while (pos < end) {
int digit;
if (ret > max) {
errno = ERANGE;
return sign == 1 ? LLONG_MAX : LLONG_MIN;
}
ret *= 10;
if (LLONG_MAX - ret < (*pos - '0')) {
ret *= base;
digit = *pos++ - '0';
/* Don't have to check for non-digit characters,
* the lexer has already rejected any bad digits.
*/
if (digit > 9)
digit = (digit - ('A' - '0') + 10) & 0xf;
if (LLONG_MAX - ret < digit) {
errno = ERANGE;
return sign == 1 ? LLONG_MAX : LLONG_MIN;
}
ret += (*pos++ - '0');
ret += digit;
}
return sign * ret;
@@ -332,7 +355,8 @@ yajl_do_parse(yajl_handle hand, const unsigned char * jsonText,
case yajl_tok_right_bracket: {
yajl_state s = yajl_bs_current(hand->stateStack);
if (s == yajl_state_array_start ||
s == yajl_state_array_need_val)
((hand->flags & yajl_allow_json5) &&
(s == yajl_state_array_need_val)))
{
if (hand->callbacks &&
hand->callbacks->yajl_end_array)
@@ -377,8 +401,8 @@ yajl_do_parse(yajl_handle hand, const unsigned char * jsonText,
case yajl_state_map_need_key: {
/* only difference between these two states is that in
* start '}' is valid, whereas in need_key, we've parsed
* a comma, and a string key _must_ follow */
tok = yajl_lex_lex(hand->lexer, jsonText, jsonTextLen,
* a comma, so unless this is JSON5 a key _must_ follow. */
tok = yajl_lex_key(hand->lexer, jsonText, jsonTextLen,
offset, &buf, &bufLen);
switch (tok) {
case yajl_tok_eof:
@@ -404,7 +428,8 @@ yajl_do_parse(yajl_handle hand, const unsigned char * jsonText,
case yajl_tok_right_brace: {
yajl_state s = yajl_bs_current(hand->stateStack);
if (s == yajl_state_map_start ||
s == yajl_state_map_need_key) {
((hand->flags & yajl_allow_json5) &&
(s == yajl_state_map_need_key))) {
if (hand->callbacks && hand->callbacks->yajl_end_map) {
_CC_CHK(hand->callbacks->yajl_end_map(hand->ctx));
}
@@ -414,7 +439,8 @@ yajl_do_parse(yajl_handle hand, const unsigned char * jsonText,
}
default:
yajl_bs_set(hand->stateStack, yajl_state_parse_error);
hand->parseError =
hand->parseError = hand->flags & yajl_allow_json5 ?
"invalid object key (must be a string or identifier)" :
"invalid object key (must be a string)";
goto around_again;
}
+10 -50
View File
@@ -88,7 +88,7 @@ MAIN(epicsStringTest)
char *s;
int status;
testPlan(406);
testPlan(387);
testChars();
@@ -159,7 +159,7 @@ MAIN(epicsStringTest)
status = epicsStrnEscapedFromRawSize(ABCD, 4);
testOk(status == 4, "size(\"ABCD\", 4) -> %d (exp. 4)", status);
status = epicsStrnEscapedFromRawSize(ABCD, 5);
testOk(status == 8, "size(\"ABCD\", 5) -> %d (exp. 8)", status);
testOk(status == 6, "size(\"ABCD\", 5) -> %d (exp. 8)", status);
testDiag("Testing esc = epicsStrnEscapedFromRaw(out, 4, ...)");
@@ -177,7 +177,7 @@ MAIN(epicsStringTest)
memset(result, 'x', sizeof(result));
status = epicsStrnEscapedFromRaw(result, 4, ABCD, 5);
testOk(status == 8, "esc(\"ABCD\", 5) -> %d (exp. 8)", status);
testOk(status == 6, "esc(\"ABCD\", 5) -> %d (exp. 8)", status);
testOk(result[3] == 0, " 0-terminated");
testOk(result[4] == 'x', " No overrun");
@@ -234,49 +234,6 @@ MAIN(epicsStringTest)
testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\123", 1);
testOk(status == 0, "raw(\"\\123\", 1) -> %d (exp. 0)", status);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\123", 2);
testOk(status == 1, "raw(\"\\123\", 2) -> %d (exp. 1)", status);
testOk(result[0] == 1, " Octal escape (got \\%03o)", result[0]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\123", 3);
testOk(status == 1, "raw(\"\\123\", 3) -> %d (exp. 1)", status);
testOk(result[0] == 012, " Octal escape (got \\%03o)", result[0]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\123", 4);
testOk(status == 1, "raw(\"\\123\", 4) -> %d (exp. 1)", status);
testOk(result[0] == 0123, " Octal escape (got \\%03o)", result[0]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\812", 2);
testOk(status == 1, "raw(\"\\812\", 2) -> %d (exp. 1)", status);
testOk(result[0] == '8', " Escaped '%c')", result[0]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\182", 3);
testOk(status == 2, "raw(\"\\182\", 3) -> %d (exp. 2)", status);
testOk(result[0] == 1, " Octal escape (got \\%03o)", result[0]);
testOk(result[1] == '8', " Terminated with '%c'", result[1]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\128", 4);
testOk(status == 2, "raw(\"\\128\", 4) -> %d (exp. 2)", status);
testOk(result[0] == 012, " Octal escape (got \\%03o)", result[0]);
testOk(result[1] == '8', " Terminator char got '%c'", result[1]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\x12", 1);
testOk(status == 0, "raw(\"\\x12\", 1) -> %d (exp. 0)", status);
@@ -307,14 +264,17 @@ MAIN(epicsStringTest)
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\x012", 5);
testOk(status == 1, "raw(\"\\x012\", 5) -> %d (exp. 1)", status);
testOk(result[0] == 0x12," Hex escape (got \\x%x)", result[0]);
testOk(status == 2, "raw(\"\\x012\", 5) -> %d (exp. 2)", status);
testOk(result[0] == 0x1," Hex escape (got \\x%x)", result[0]);
testOk(result[1] == '2', " Terminator char got '%c'", result[1]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
status = epicsStrnRawFromEscaped(result, 4, "\\x0012", 6);
testOk(status == 1, "raw(\"\\x0012\", 6) -> %d (exp. 1)", status);
testOk(result[0] == 0x12," Hex escape (got \\x%x)", result[0]);
testOk(status == 3, "raw(\"\\x0012\", 6) -> %d (exp. 3)", status);
testOk(result[0] == 0," Hex escape (got \\x%x)", result[0]);
testOk(result[1] == '1', " Terminator char got '%c'", result[1]);
testOk(result[2] == '2', " Following char got '%c'", result[2]);
testOk(result[status] == 0, " 0-terminated");
memset(result, 'x', sizeof(result));
+343 -24
View File
@@ -7,6 +7,300 @@
sub cases {
my $VAR1 = [
{
name => "codepoints_from_hex",
opts => [
-5
],
input => [
"\"\\x0a\\x07\\x21\\x40\\x7c\"",
""
],
gives => [
"string: '",
"\a!\@|'",
"memory leaks:\t0"
]
},
{
name => "doubles",
opts => [
-5
],
input => [
"[ .1e2, 10., +3.141569, -.1e4, NaN, Infinity, +Infinity, -Infinity ]",
""
],
gives => [
"array open '['",
"double: 10",
"double: 10",
"double: 3.14157",
"double: -1000",
"double: NaN",
"double: Infinity",
"double: Infinity",
"double: -Infinity",
"array close ']'",
"memory leaks:\t0"
]
},
{
name => "integers",
opts => [
-5
],
input => [
"[ +1,+2,+3,+4,+5,+6,+7,+8,+9,",
" 0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,",
" 0xa,0xb,0xc,0xd,0xe,0xf,",
" 0xA,0xB,0xC,0xD,0xE,0xF,",
" +0xfedcba98, -0x6789ABCD,",
" +123456789 , -123456789,",
" +2147483647, -2147483648,",
" 0x7fffFFFFffffFFFF, -0x7FFFffffFFFFffff,",
" 9223372036854775807, -9223372036854775807",
"]",
""
],
gives => [
"array open '['",
"integer: 1",
"integer: 2",
"integer: 3",
"integer: 4",
"integer: 5",
"integer: 6",
"integer: 7",
"integer: 8",
"integer: 9",
"integer: 1",
"integer: 2",
"integer: 3",
"integer: 4",
"integer: 5",
"integer: 6",
"integer: 7",
"integer: 8",
"integer: 9",
"integer: 10",
"integer: 11",
"integer: 12",
"integer: 13",
"integer: 14",
"integer: 15",
"integer: 10",
"integer: 11",
"integer: 12",
"integer: 13",
"integer: 14",
"integer: 15",
"integer: 4275878552",
"integer: -1737075661",
"integer: 123456789",
"integer: -123456789",
"integer: 2147483647",
"integer: -2147483648",
"integer: 9223372036854775807",
"integer: -9223372036854775807",
"integer: 9223372036854775807",
"integer: -9223372036854775807",
"array close ']'",
"memory leaks:\t0"
]
},
{
name => "invalid_hex_char",
opts => [
-5
],
input => [
"\"yabba dabba do \\x1g !!\"",
""
],
gives => [
"lexical error: invalid (non-hex) character occurs after '\\x' inside string.",
"memory leaks:\t0"
]
},
{
name => "map_identifiers",
opts => [
-5
],
input => [
"{",
" \$:1,",
" _:2,",
" A:3,",
" Z:4,",
" a:5,",
" z:6,",
" \$1:7,",
" _zz:8,",
" ZZ9\$Zalpha:9",
"}",
""
],
gives => [
"map open '{'",
"key: '\$'",
"integer: 1",
"key: '_'",
"integer: 2",
"key: 'A'",
"integer: 3",
"key: 'Z'",
"integer: 4",
"key: 'a'",
"integer: 5",
"key: 'z'",
"integer: 6",
"key: '\$1'",
"integer: 7",
"key: '_zz'",
"integer: 8",
"key: 'ZZ9\$Zalpha'",
"integer: 9",
"map close '}'",
"memory leaks:\t0"
]
},
{
name => "simple_with_comments",
opts => [
-5
],
input => [
"{",
" \"this\": \"is\", // ignore this",
" \"really\": \"simple\",",
" /* ignore",
"this",
"too * / ",
"** //",
"(/",
"******/",
" \"json\": \"right?\"",
"}",
""
],
gives => [
"map open '{'",
"key: 'this'",
"string: 'is'",
"key: 'really'",
"string: 'simple'",
"key: 'json'",
"string: 'right?'",
"map close '}'",
"memory leaks:\t0"
]
},
{
name => "spec_example",
opts => [
-5
],
input => [
"{",
" // comments",
" unquoted: 'and you can quote me on that',",
" singleQuotes: 'I can use \"double quotes\" here',",
" lineBreaks: \"Look, Mom! \\",
"No \\\\n's!\",",
" hexadecimal: 0xdecaf,",
" leadingDecimalPoint: .8675309, andTrailing: 8675309.,",
" positiveSign: +1,",
" trailingComma: 'in objects', andIn: ['arrays',],",
" \"backwardsCompatible\": \"with JSON\",",
"}",
""
],
gives => [
"map open '{'",
"key: 'unquoted'",
"string: 'and you can quote me on that'",
"key: 'singleQuotes'",
"string: 'I can use \"double quotes\" here'",
"key: 'lineBreaks'",
"string: 'Look, Mom! No \\n's!'",
"key: 'hexadecimal'",
"integer: 912559",
"key: 'leadingDecimalPoint'",
"double: 0.867531",
"key: 'andTrailing'",
"double: 8.67531e+06",
"key: 'positiveSign'",
"integer: 1",
"key: 'trailingComma'",
"string: 'in objects'",
"key: 'andIn'",
"array open '['",
"string: 'arrays'",
"array close ']'",
"key: 'backwardsCompatible'",
"string: 'with JSON'",
"map close '}'",
"memory leaks:\t0"
]
},
{
name => "strings",
opts => [
-5
],
input => [
"[",
" 'Hello\\!',",
" \"\\\"Evenin\\',\\\" said the barman.\",",
" // The following string has 3 different escaped line-endings,",
" // LF, CR, and CR+LF, which all disappear from the final string.",
" \"Well \\",
"hi \\\rthere \\\r",
"y'all!\",",
" \"\\b\\f\\n\\r\\t\\v\\\\\",",
" '\\A\\C\\/\\D\\C',",
"]",
""
],
gives => [
"array open '['",
"string: 'Hello!'",
"string: '\"Evenin',\" said the barman.'",
"string: 'Well hi there y'all!'",
"string: '\b\f",
"\r\t\13\\'",
"string: 'AC/DC'",
"array close ']'",
"memory leaks:\t0"
]
},
{
name => "trailing_commas",
opts => [
-5
],
input => [
"{\"array\":[1,2,],\"map\":{\"a\":1,},}",
""
],
gives => [
"map open '{'",
"key: 'array'",
"array open '['",
"integer: 1",
"integer: 2",
"array close ']'",
"key: 'map'",
"map open '{'",
"key: 'a'",
"integer: 1",
"map close '}'",
"map close '}'",
"memory leaks:\t0"
]
},
{
name => "difficult_json_c_test_case_with_comments",
opts => [
@@ -2636,6 +2930,18 @@ sub cases {
"memory leaks:\t0"
]
},
{
name => "hex",
opts => [],
input => [
"0x1",
""
],
gives => [
"lexical error: probable hex number found, JSON5 is not enabled.",
"memory leaks:\t0"
]
},
{
name => "high_overflow",
opts => [],
@@ -2647,6 +2953,18 @@ sub cases {
"memory leaks:\t0"
]
},
{
name => "infinity",
opts => [],
input => [
"Infinity",
""
],
gives => [
"lexical error: special number Infinity or NaN found, JSON5 is not enabled.",
"memory leaks:\t0"
]
},
{
name => "integers",
opts => [],
@@ -2733,7 +3051,7 @@ sub cases {
"string: 'blue'",
"string: 'baby where are you?'",
"string: 'oh boo hoo!'",
"lexical error: malformed number, a digit is required after the minus sign.",
"lexical error: malformed number, a digit is required after the plus/minus sign.",
"memory leaks:\t0"
]
},
@@ -2771,6 +3089,18 @@ sub cases {
"memory leaks:\t0"
]
},
{
name => "minus_infinity",
opts => [],
input => [
"-Infinity",
""
],
gives => [
"lexical error: special number Infinity or NaN found, JSON5 is not enabled.",
"memory leaks:\t0"
]
},
{
name => "missing_integer_after_decimal_point",
opts => [],
@@ -2811,6 +3141,18 @@ sub cases {
"memory leaks:\t0"
]
},
{
name => "nan",
opts => [],
input => [
"NaN",
""
],
gives => [
"lexical error: special number Infinity or NaN found, JSON5 is not enabled.",
"memory leaks:\t0"
]
},
{
name => "non_utf8_char_in_string",
opts => [],
@@ -3000,29 +3342,6 @@ sub cases {
"memory leaks:\t0"
]
},
{
name => "trailing_commas",
opts => [],
input => [
"{\"array\":[1,2,],\"map\":{\"a\":1,},}",
""
],
gives => [
"map open '{'",
"key: 'array'",
"array open '['",
"integer: 1",
"integer: 2",
"array close ']'",
"key: 'map'",
"map open '{'",
"key: 'a'",
"integer: 1",
"map close '}'",
"map close '}'",
"memory leaks:\t0"
]
},
{
name => "true",
opts => [],
+5 -2
View File
@@ -22,14 +22,17 @@ my $caseFile = 'yajlTestCases.pm';
my @cases;
for my $file (@files) {
$file =~ m|/([afn][cgmp]_)?([^/]*)\.json$|;
$file =~ m|/([afn][5cgmp]_)?([^/]*)\.json$|;
my $allow = $1;
my $name = $2;
next if $name eq '';
my $case = { name => $name };
if ($allow eq 'ac_') {
if ($allow eq 'a5_') {
$case->{opts} = ['-5'];
}
elsif ($allow eq 'ac_') {
$case->{opts} = ['-c'];
}
elsif ($allow eq 'ag_') {
+17 -2
View File
@@ -17,6 +17,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <epicsMath.h>
#include <yajl_parse.h>
#include <yajl_gen.h>
@@ -85,7 +86,15 @@ static int test_yajl_integer(void *ctx, long long integerVal)
static int test_yajl_double(void *ctx, double doubleVal)
{
printf("double: %g\n", doubleVal);
if (isnan(doubleVal)) {
printf("double: NaN\n");
}
else if (isinf(doubleVal)) {
printf("double: %sInfinity\n", doubleVal > 0 ? "" : "-");
}
else {
printf("double: %g\n", doubleVal);
}
return 1;
}
@@ -154,6 +163,7 @@ static void usage(const char * progname)
"usage: %s [options]\n"
"Parse input from stdin as JSON and ouput parsing details "
"to stdout\n"
" -5 allow JSON5\n"
" -b set the read buffer size\n"
" -c allow comments\n"
" -g allow *g*arbage after valid JSON text\n"
@@ -195,9 +205,14 @@ main(int argc, char ** argv)
/* allocate the parser */
hand = yajl_alloc(&callbacks, &allocFuncs, NULL);
/* turn off JSON5 (the EPICS default) */
yajl_config(hand, yajl_allow_json5, 0);
/* check arguments. We expect exactly one! */
for (i=1;i<argc;i++) {
if (!strcmp("-c", argv[i])) {
if (!strcmp("-5", argv[i])) {
yajl_config(hand, yajl_allow_json5, 1);
} else if (!strcmp("-c", argv[i])) {
yajl_config(hand, yajl_allow_comments, 1);
} else if (!strcmp("-b", argv[i])) {
if (++i >= argc) usage(argv[0]);