Skip to content

Commit 49d3156

Browse files
committed
add tests & fixes
1 parent 907cd24 commit 49d3156

24 files changed

Lines changed: 177 additions & 39 deletions

src/HJSON/HJSONParser.php

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,21 @@ public function parse($source, $options=[])
2929

3030
$this->keepWsc = $options && $options['keepWsc'];
3131
$this->text = $source;
32-
$this->at = 0;
33-
$this->ch = ' ';
32+
$this->resetAt();
3433
$result = $this->rootValue();
3534
$this->white();
36-
35+
3736
if ($this->ch) throw new HJSONException("Syntax error, found trailing characters");
3837

3938
return $result;
4039
}
4140

41+
function resetAt()
42+
{
43+
$this->at = 0;
44+
$this->ch = ' ';
45+
}
46+
4247
public function parseWsc($source, $options=[])
4348
{
4449
return $this->parse($source, array_merge($options, ['keepWsc' => true]));
@@ -53,12 +58,16 @@ private function rootValue()
5358
case '[': return $this->_array();
5459
}
5560

56-
// look if we are dealing with a single JSON value (true/false/null/num/"")
57-
// if it is multiline we assume it's a Hjson object without root braces.
58-
for ($i = 0; $i < mb_strlen($this->text); $i++)
59-
if ($this->text[$i] === "\n") return $this->object(true);
60-
61-
return $this->value();
61+
try {
62+
// assume we have a root object without braces
63+
return $this->object(true);
64+
}
65+
catch (HJSONException $e) {
66+
// test if we are dealing with a single JSON value instead (true/false/null/num/"")
67+
$this->resetAt();
68+
try { return $this->value(); }
69+
catch (HJSONException $e2) { throw $e; } // throw original error
70+
}
6271
}
6372

6473
private function value()
@@ -111,7 +120,7 @@ private function _array()
111120
// assumeing ch === '['
112121

113122
$array = []; $kw = null; $wat = null;
114-
123+
115124
if ($this->keepWsc) {
116125
$array['__WSC__'] = [];
117126
$kw = &$array['__WSC__'];
@@ -318,7 +327,8 @@ private function keyname()
318327
return $name;
319328
}
320329
else if ($this->ch <= ' ') {
321-
if ($space < 0) $space = mb_strlen($name);
330+
if (!$this->ch) $this->error("Found EOF while looking for a key name (check your syntax)");
331+
else if ($space < 0) $space = mb_strlen($name);
322332
}
323333
else if ($this->ch === '{' || $this->ch === '}' || $this->ch === '[' || $this->ch === ']' || $this->ch === ',') {
324334
$this->error("Found '{$this->ch}' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)");
@@ -333,10 +343,11 @@ private function tfnns()
333343
// Hjson strings can be quoteless
334344
// returns string, true, false, or null.
335345
$value = $this->ch;
336-
while ($this->next() !== null) {
346+
while (true) {
347+
$isEol = $this->next() === null;
337348
if (mb_strlen($value) === 3 && $value === "'''") return $this->mlString();
338-
$isEol = $this->ch === "\r" || $this->ch === "\n";
339-
349+
$isEol = $isEol || $this->ch === "\r" || $this->ch === "\n";
350+
340351
if ($isEol || $this->ch === ',' ||
341352
$this->ch === '}' || $this->ch === ']' ||
342353
$this->ch === '#' ||
@@ -360,24 +371,22 @@ private function tfnns()
360371
}
361372
$value .= $this->ch;
362373
}
363-
364-
$this->error("End of input while parsing a value");
365374
}
366375

367376
private function getComment($wat)
368377
{
369378
$i; $wat--;
370379
// remove trailing whitespace
371380
for ($i = $this->at - 2; $i > $wat && $this->text[$i] <= ' ' && $this->text[$i] !== "\n"; $i--);
372-
381+
373382
// but only up to EOL
374383
if ($this->text[$i] === "\n") $i--;
375384
if ($this->text[$i] === "\r") $i--;
376-
385+
377386
$res = mb_substr($this->text, $wat, $i-$wat+1);
378387
for ($i = 0; $i < mb_strlen($res); $i++)
379388
if ($res[$i] > ' ') return $res;
380-
389+
381390
return "";
382391
}
383392
}

src/HJSON/HJSONStringifier.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace HJSON;
44

5-
function mb_str_split( $string ) {
6-
return preg_split('/(?<!^)(?!$)/u', $string );
5+
function mb_str_split( $string ) {
6+
return preg_split('/(?<!^)(?!$)/u', $string );
77
}
88

99
class HJSONStringifier {
@@ -12,10 +12,10 @@ class HJSONStringifier {
1212
private $needsQuotes = '/[\x00-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}\x]/u'; // like needsEscape but without \\ and \"
1313
private $needsEscapeML = '/\'\'\'|[\x00-\x09\x0b\x0c\x0e-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}\x]/u'; // ''' || (needsQuotes but without \n and \r)
1414
private $startsWithKeyword = '/^(true|false|null)\s*((,|\]|\}|#|\/\/|\/\*).*)?$/';
15-
private $needsEscapeName = '/[,\{\[\}\]\s]/';
15+
private $needsEscapeName = '/[,\{\[\}\]\s:#"]|\/\/|\/\*|\'\'\'/';
1616
private $gap = '';
1717
private $indent = ' ';
18-
18+
1919
// options
2020
private $eol;
2121
private $keepWsc;
@@ -111,6 +111,7 @@ private function quote($string=null, $gap=null, $hasComment=null, $isRootObject=
111111
if ($doEscape ||
112112
$this->isWhite($first) ||
113113
$first === '"' ||
114+
$first === "'" && $string[1] === "'" && $string[2] === "'" ||
114115
$first === '#' ||
115116
$first === '/' && ($string[1] === '*' || $string[1] === '/') ||
116117
$first === '{' ||
@@ -128,11 +129,11 @@ private function quote($string=null, $gap=null, $hasComment=null, $isRootObject=
128129
if (!preg_match($this->needsEscape, $string)) {
129130
return '"' . $string . '"';
130131
}
131-
132+
132133
else if (!preg_match($this->needsEscapeML, $string) && !$isRootObject) {
133134
return $this->mlString($string, $gap);
134135
}
135-
136+
136137
else {
137138
return '"' . $this->quoteReplace($string) . '"';
138139
}
@@ -214,9 +215,9 @@ private function str($value, $hasComment=null, $noIndent=null, $isRootObject=nul
214215

215216
case 'object':
216217
case 'array':
217-
218+
218219
$isArray = is_array($value);
219-
220+
220221
$kw = null; $kwl = null; // whitespace & comments
221222
if ($this->keepWsc) {
222223
if ($isArray) $kw = @$value['__WSC__'];

tests/HJSONParserTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public function testAll()
6666
if (count($name) < 2) continue;
6767
$isJson = $name[1] === "json";
6868
$name = $name[0];
69+
70+
// skip empty test
71+
if ($name === "empty") continue;
72+
6973
$this->runEach($name, $file, $isJson, false, false);
7074
$this->runEach($name, $file, $isJson, false, true);
7175
$this->runEach($name, $file, $isJson, true, false);

tests/assets/comments_test.hjson

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
// test
2+
# all
3+
// comment
4+
/*
5+
styles
6+
*/
7+
# with lf
8+
9+
10+
11+
# !
12+
113
{
214
# hjson style comment
315
foo1: This is a string value. # part of the string

tests/assets/empty_result.hjson

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"": empty
3+
}

tests/assets/empty_result.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"": "empty"
3+
}

tests/assets/empty_test.hjson

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"": empty
3+
}

tests/assets/keys_result.hjson

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,29 @@
66
.key: test
77
trailing: test
88
trailing2: test
9+
"#c1": test
10+
"foo#bar": test
11+
"//bar": test
12+
"foo//bar": test
13+
"/*foo*/": test
14+
"foo/*foo*/bar": test
15+
"/*": test
16+
"foo/*bar": test
17+
"\"": test
18+
"foo\"bar": test
19+
"'''": test
20+
"foo'''bar": test
21+
":": test
22+
"foo:bar": test
23+
"{": test
24+
"foo{bar": test
25+
"}": test
26+
"foo}bar": test
27+
"[": test
28+
"foo[bar": test
29+
"]": test
30+
"foo]bar": test
31+
nl1: test
32+
nl2: test
33+
nl3: test
934
}

tests/assets/keys_result.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,30 @@
55
"-test": "test",
66
".key": "test",
77
"trailing": "test",
8-
"trailing2": "test"
8+
"trailing2": "test",
9+
"#c1": "test",
10+
"foo#bar": "test",
11+
"//bar": "test",
12+
"foo//bar": "test",
13+
"/*foo*/": "test",
14+
"foo/*foo*/bar": "test",
15+
"/*": "test",
16+
"foo/*bar": "test",
17+
"\"": "test",
18+
"foo\"bar": "test",
19+
"'''": "test",
20+
"foo'''bar": "test",
21+
":": "test",
22+
"foo:bar": "test",
23+
"{": "test",
24+
"foo{bar": "test",
25+
"}": "test",
26+
"foo}bar": "test",
27+
"[": "test",
28+
"foo[bar": "test",
29+
"]": "test",
30+
"foo]bar": "test",
31+
"nl1": "test",
32+
"nl2": "test",
33+
"nl3": "test"
934
}

tests/assets/keys_test.hjson

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,41 @@
88
# trailing spaces in key names are ignored
99
trailing : test
1010
trailing2 : test
11+
# comment char in key name
12+
"#c1": test
13+
"foo#bar": test
14+
"//bar": test
15+
"foo//bar": test
16+
"/*foo*/": test
17+
"foo/*foo*/bar": test
18+
"/*": test
19+
"foo/*bar": test
20+
# quotes in key name
21+
"\"": test
22+
"foo\"bar": test
23+
"'''": test
24+
"foo'''bar": test
25+
# control char in key name
26+
":": test
27+
"foo:bar": test
28+
"{": test
29+
"foo{bar": test
30+
"}": test
31+
"foo}bar": test
32+
"[": test
33+
"foo[bar": test
34+
"]": test
35+
"foo]bar": test
36+
# newline
37+
nl1:
38+
test
39+
nl2
40+
:
41+
test
42+
43+
nl3
44+
45+
:
46+
47+
test
1148
}

0 commit comments

Comments
 (0)