json5 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +60 -0
- data/Rakefile +1 -0
- data/json5.gemspec +25 -0
- data/lib/json5.rb +13 -0
- data/lib/json5/parser.rb +461 -0
- data/lib/json5/stringifier.rb +242 -0
- data/lib/json5/version.rb +3 -0
- data/spec/json5/parse_spec.rb +71 -0
- data/spec/parse-cases/arrays/empty-array.json +1 -0
- data/spec/parse-cases/arrays/leading-comma-array.js +3 -0
- data/spec/parse-cases/arrays/lone-trailing-comma-array.js +3 -0
- data/spec/parse-cases/arrays/no-comma-array.txt +4 -0
- data/spec/parse-cases/arrays/regular-array.json +5 -0
- data/spec/parse-cases/arrays/trailing-comma-array.json5 +3 -0
- data/spec/parse-cases/comments/block-comment-following-array-element.json5 +6 -0
- data/spec/parse-cases/comments/block-comment-following-top-level-value.json5 +5 -0
- data/spec/parse-cases/comments/block-comment-in-string.json +1 -0
- data/spec/parse-cases/comments/block-comment-preceding-top-level-value.json5 +5 -0
- data/spec/parse-cases/comments/block-comment-with-asterisks.json5 +7 -0
- data/spec/parse-cases/comments/inline-comment-following-array-element.json5 +3 -0
- data/spec/parse-cases/comments/inline-comment-following-top-level-value.json5 +1 -0
- data/spec/parse-cases/comments/inline-comment-in-string.json +1 -0
- data/spec/parse-cases/comments/inline-comment-preceding-top-level-value.json5 +2 -0
- data/spec/parse-cases/comments/top-level-block-comment.txt +4 -0
- data/spec/parse-cases/comments/top-level-inline-comment.txt +1 -0
- data/spec/parse-cases/comments/unterminated-block-comment.txt +5 -0
- data/spec/parse-cases/misc/empty.txt +0 -0
- data/spec/parse-cases/misc/npm-package.json +106 -0
- data/spec/parse-cases/misc/npm-package.json5 +106 -0
- data/spec/parse-cases/misc/readme-example.json5 +25 -0
- data/spec/parse-cases/numbers/binary-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/float-leading-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/float-leading-zero.json +1 -0
- data/spec/parse-cases/numbers/float-trailing-decimal-point-with-integer-exponent.json5.todo +1 -0
- data/spec/parse-cases/numbers/float-trailing-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/float-with-integer-exponent.json +1 -0
- data/spec/parse-cases/numbers/float.json +1 -0
- data/spec/parse-cases/numbers/hexadecimal-empty.txt +1 -0
- data/spec/parse-cases/numbers/hexadecimal-lowercase-letter.json5 +1 -0
- data/spec/parse-cases/numbers/hexadecimal-uppercase-x.json5 +1 -0
- data/spec/parse-cases/numbers/hexadecimal-with-integer-exponent.json5 +1 -0
- data/spec/parse-cases/numbers/hexadecimal.json5 +1 -0
- data/spec/parse-cases/numbers/infinity.json5 +1 -0
- data/spec/parse-cases/numbers/integer-with-float-exponent.txt +1 -0
- data/spec/parse-cases/numbers/integer-with-hexadecimal-exponent.txt +1 -0
- data/spec/parse-cases/numbers/integer-with-integer-exponent.json +1 -0
- data/spec/parse-cases/numbers/integer-with-negative-float-exponent.txt +1 -0
- data/spec/parse-cases/numbers/integer-with-negative-hexadecimal-exponent.txt +1 -0
- data/spec/parse-cases/numbers/integer-with-negative-integer-exponent.json +1 -0
- data/spec/parse-cases/numbers/integer-with-negative-zero-integer-exponent.json +1 -0
- data/spec/parse-cases/numbers/integer-with-positive-float-exponent.txt +1 -0
- data/spec/parse-cases/numbers/integer-with-positive-hexadecimal-exponent.txt +1 -0
- data/spec/parse-cases/numbers/integer-with-positive-integer-exponent.json +1 -0
- data/spec/parse-cases/numbers/integer-with-positive-zero-integer-exponent.json +1 -0
- data/spec/parse-cases/numbers/integer-with-zero-integer-exponent.json +1 -0
- data/spec/parse-cases/numbers/integer.json +1 -0
- data/spec/parse-cases/numbers/lone-decimal-point.txt +1 -0
- data/spec/parse-cases/numbers/nan.json5 +1 -0
- data/spec/parse-cases/numbers/negative-binary-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/negative-float-leading-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/negative-float-leading-zero.json +1 -0
- data/spec/parse-cases/numbers/negative-float-trailing-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/negative-float.json +1 -0
- data/spec/parse-cases/numbers/negative-hexadecimal.js +1 -0
- data/spec/parse-cases/numbers/negative-infinity.json5 +1 -0
- data/spec/parse-cases/numbers/negative-integer.json +1 -0
- data/spec/parse-cases/numbers/negative-noctal.js +1 -0
- data/spec/parse-cases/numbers/negative-octal-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/negative-octal.txt +1 -0
- data/spec/parse-cases/numbers/negative-zero-binary-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/negative-zero-float-leading-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/negative-zero-float-trailing-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/negative-zero-float.json +1 -0
- data/spec/parse-cases/numbers/negative-zero-hexadecimal.js +1 -0
- data/spec/parse-cases/numbers/negative-zero-integer.json +1 -0
- data/spec/parse-cases/numbers/negative-zero-octal-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/negative-zero-octal.txt +1 -0
- data/spec/parse-cases/numbers/noctal-with-leading-octal-digit.js +1 -0
- data/spec/parse-cases/numbers/noctal.js +1 -0
- data/spec/parse-cases/numbers/octal-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/octal.txt +1 -0
- data/spec/parse-cases/numbers/positive-binary-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/positive-float-leading-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/positive-float-leading-zero.json5 +1 -0
- data/spec/parse-cases/numbers/positive-float-trailing-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/positive-float.json5 +1 -0
- data/spec/parse-cases/numbers/positive-hexadecimal.js +1 -0
- data/spec/parse-cases/numbers/positive-infinity.json5 +1 -0
- data/spec/parse-cases/numbers/positive-integer.json5 +1 -0
- data/spec/parse-cases/numbers/positive-noctal.js +1 -0
- data/spec/parse-cases/numbers/positive-octal-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/positive-octal.txt +1 -0
- data/spec/parse-cases/numbers/positive-zero-binary-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/positive-zero-float-leading-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/positive-zero-float-trailing-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/positive-zero-float.json5 +1 -0
- data/spec/parse-cases/numbers/positive-zero-hexadecimal.js +1 -0
- data/spec/parse-cases/numbers/positive-zero-integer.json5 +1 -0
- data/spec/parse-cases/numbers/positive-zero-octal-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/positive-zero-octal.txt +1 -0
- data/spec/parse-cases/numbers/zero-binary-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/zero-float-leading-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/zero-float-trailing-decimal-point.json5 +1 -0
- data/spec/parse-cases/numbers/zero-float.json +1 -0
- data/spec/parse-cases/numbers/zero-hexadecimal.json5 +1 -0
- data/spec/parse-cases/numbers/zero-integer-with-integer-exponent.json +1 -0
- data/spec/parse-cases/numbers/zero-integer.json +1 -0
- data/spec/parse-cases/numbers/zero-octal-coffeescript.txt +1 -0
- data/spec/parse-cases/numbers/zero-octal.txt +1 -0
- data/spec/parse-cases/objects/empty-object.json +1 -0
- data/spec/parse-cases/objects/illegal-unquoted-key-number.txt +3 -0
- data/spec/parse-cases/objects/illegal-unquoted-key-symbol.txt +3 -0
- data/spec/parse-cases/objects/leading-comma-object.txt +3 -0
- data/spec/parse-cases/objects/lone-trailing-comma-object.txt +3 -0
- data/spec/parse-cases/objects/no-comma-object.txt +4 -0
- data/spec/parse-cases/objects/reserved-unquoted-key.json5 +3 -0
- data/spec/parse-cases/objects/single-quoted-key.json5 +3 -0
- data/spec/parse-cases/objects/trailing-comma-object.json5 +3 -0
- data/spec/parse-cases/objects/unquoted-keys.json5 +8 -0
- data/spec/parse-cases/strings/escaped-single-quoted-string.json5 +1 -0
- data/spec/parse-cases/strings/multi-line-string.json5 +2 -0
- data/spec/parse-cases/strings/single-quoted-string.json5 +1 -0
- data/spec/parse-cases/strings/unescaped-multi-line-string.txt +2 -0
- data/spec/parse-cases/todo/unicode-escaped-unquoted-key.json5.todo +3 -0
- data/spec/parse-cases/todo/unicode-unquoted-key.json5.todo +3 -0
- data/spec/spec_helper.rb +15 -0
- metadata +363 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 8a950e3a30d3e7d3ceeb109f30f0f247d2725002
|
|
4
|
+
data.tar.gz: 2e8013df8d7e26bdccce69748eb06a5175fc0e59
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 78e86edefcdc4f982da34f3f7eca1ad46897446b1e71ac54931e28b9ea6650a1e0044d4f0927efe317dabecc26e7f123e8a93fe89ebed23000b60f903564bb19
|
|
7
|
+
data.tar.gz: e9d4f224531965c9d7054b9022d7488a3750f1730797204f181fea58b31920ab44ba1d216f3635977c25716fe468b0a677f0467236485944fbded421a6483748
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.1.1
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2014 Bartosz Kopiński
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# JSON5
|
|
2
|
+
|
|
3
|
+
JSON5 parser for Ruby
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'json5'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install json5
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
JSON5.parse(<<-JSON5)
|
|
23
|
+
{
|
|
24
|
+
foo: 'bar',
|
|
25
|
+
while: true,
|
|
26
|
+
|
|
27
|
+
this: 'is a \
|
|
28
|
+
multi-line string',
|
|
29
|
+
|
|
30
|
+
// this is an inline comment
|
|
31
|
+
here: 'is another', // inline comment
|
|
32
|
+
|
|
33
|
+
/* this is a block comment
|
|
34
|
+
that continues on another line */
|
|
35
|
+
|
|
36
|
+
hex: 0xDEADbeef,
|
|
37
|
+
half: .5,
|
|
38
|
+
delta: +10,
|
|
39
|
+
to: Infinity, // and beyond!
|
|
40
|
+
|
|
41
|
+
finally: 'a trailing comma',
|
|
42
|
+
oh: [
|
|
43
|
+
"we shouldn't forget",
|
|
44
|
+
'arrays can have',
|
|
45
|
+
'trailing commas too',
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
JSON5
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
See http://json5.org/ for details
|
|
53
|
+
|
|
54
|
+
## Contributing
|
|
55
|
+
|
|
56
|
+
1. Fork it ( http://github.com/bartoszkopinski/json5/fork )
|
|
57
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
58
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
59
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
60
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/json5.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'json5/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "json5"
|
|
8
|
+
spec.version = JSON5::VERSION
|
|
9
|
+
spec.authors = ["Bartosz Kopinski"]
|
|
10
|
+
spec.email = ["bartosz.kopinski@gmail.com"]
|
|
11
|
+
spec.summary = %q{JSON5 parser in Ruby}
|
|
12
|
+
spec.homepage = "http://json5.org/"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files`.split($/)
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
|
21
|
+
spec.add_development_dependency "rake"
|
|
22
|
+
spec.add_development_dependency "rspec"
|
|
23
|
+
spec.add_development_dependency "oj"
|
|
24
|
+
spec.add_development_dependency "therubyracer"
|
|
25
|
+
end
|
data/lib/json5.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'json5/version'
|
|
2
|
+
require 'json5/parser'
|
|
3
|
+
require 'json5/stringifier'
|
|
4
|
+
|
|
5
|
+
module JSON5
|
|
6
|
+
def self.parse source, reviver = nil
|
|
7
|
+
Parser.new(source, reviver).run
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.stringify obj, replacer, space
|
|
11
|
+
Stringifier.new(obj, replacer, space).run
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/json5/parser.rb
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
module JSON5
|
|
2
|
+
class Parser
|
|
3
|
+
def error(m)
|
|
4
|
+
error = SyntaxError.new("#{m} at #{@at}: #{@text}")
|
|
5
|
+
raise error
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def get_next c = nil
|
|
9
|
+
if (c && c != @ch)
|
|
10
|
+
error("Expected '" + c + "' instead of '" + @ch + "'")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Get the next character. When there are no more characters,
|
|
14
|
+
# return the empty string.
|
|
15
|
+
|
|
16
|
+
# ch = text.charAt(at)
|
|
17
|
+
@ch = @text[@at]
|
|
18
|
+
@at += 1
|
|
19
|
+
return @ch
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
ESCAPEE = {
|
|
23
|
+
"'" => "'",
|
|
24
|
+
'"' => '"',
|
|
25
|
+
'\\' => '\\',
|
|
26
|
+
'/' => '/',
|
|
27
|
+
"\n" => '', # Replace escaped newlines in strings w/ empty string
|
|
28
|
+
'b' => '\b',
|
|
29
|
+
'f' => '\f',
|
|
30
|
+
'n' => "\n",
|
|
31
|
+
'r' => '\r',
|
|
32
|
+
't' => '\t',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def identifier
|
|
37
|
+
# Parse an identifier. Normally, reserved words are disallowed here, but we
|
|
38
|
+
# only use this for unquoted object keys, where reserved words are allowed,
|
|
39
|
+
# so we don't check for those here. References:
|
|
40
|
+
# - http://es5.github.com/#x7.6
|
|
41
|
+
# - https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables
|
|
42
|
+
# - http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm
|
|
43
|
+
# TODO Identifiers can have Unicode "letters" in them; add support for those.
|
|
44
|
+
|
|
45
|
+
key = @ch
|
|
46
|
+
|
|
47
|
+
# Identifiers must start with a letter, _ or $.
|
|
48
|
+
if ((@ch != '_' && @ch != '$') && (@ch < 'a' || @ch > 'z') && (@ch < 'A' || @ch > 'Z'))
|
|
49
|
+
error("Bad identifier")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Subsequent characters can contain digits.
|
|
53
|
+
while (get_next && (
|
|
54
|
+
@ch == '_' || @ch == '$' ||
|
|
55
|
+
(@ch >= 'a' && @ch <= 'z') ||
|
|
56
|
+
(@ch >= 'A' && @ch <= 'Z') ||
|
|
57
|
+
(@ch >= '0' && @ch <= '9'))) do
|
|
58
|
+
key += @ch
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return key
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def number
|
|
65
|
+
# Parse a number value.
|
|
66
|
+
number = nil
|
|
67
|
+
sign = ''
|
|
68
|
+
string = ''
|
|
69
|
+
base = 10
|
|
70
|
+
|
|
71
|
+
if (@ch == '-' || @ch == '+')
|
|
72
|
+
sign = string = @ch
|
|
73
|
+
get_next(@ch)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# support for Infinity (could tweak to allow other words):
|
|
77
|
+
if (@ch == 'I')
|
|
78
|
+
number = word
|
|
79
|
+
if (number.is_a?(Integer))
|
|
80
|
+
error('Unexpected word for number')
|
|
81
|
+
end
|
|
82
|
+
return (sign == '-') ? -number : number
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# support for NaN
|
|
86
|
+
if (@ch == 'N' )
|
|
87
|
+
number = word
|
|
88
|
+
if (!isNaN(number))
|
|
89
|
+
error('expected word to be NaN')
|
|
90
|
+
end
|
|
91
|
+
# ignore sign as -NaN also is NaN
|
|
92
|
+
return number
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if (@ch == '0')
|
|
96
|
+
string += @ch
|
|
97
|
+
get_next
|
|
98
|
+
if (@ch == 'x' || @ch == 'X')
|
|
99
|
+
string += @ch
|
|
100
|
+
get_next
|
|
101
|
+
base = 16
|
|
102
|
+
elsif (@ch >= '0' && @ch <= '9')
|
|
103
|
+
error('Octal literal')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# https://github.com/aseemk/json5/issues/36
|
|
108
|
+
if (base == 16 && !sign.empty?)
|
|
109
|
+
error('Signed hexadecimal literal')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
case (base)
|
|
113
|
+
when 10
|
|
114
|
+
while (@ch >= '0' && @ch <= '9' ) do
|
|
115
|
+
string += @ch
|
|
116
|
+
get_next
|
|
117
|
+
end
|
|
118
|
+
if (@ch == '.')
|
|
119
|
+
string += '.'
|
|
120
|
+
while (get_next && @ch >= '0' && @ch <= '9') do
|
|
121
|
+
string += @ch
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
if (@ch == 'e' || @ch == 'E')
|
|
125
|
+
string += @ch
|
|
126
|
+
get_next
|
|
127
|
+
if (@ch == '-' || @ch == '+')
|
|
128
|
+
string += @ch
|
|
129
|
+
get_next
|
|
130
|
+
end
|
|
131
|
+
while (@ch >= '0' && @ch <= '9') do
|
|
132
|
+
string += @ch
|
|
133
|
+
get_next
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
when 16
|
|
137
|
+
while (@ch >= '0' && @ch <= '9' || @ch >= 'A' && @ch <= 'F' || @ch >= 'a' && @ch <= 'f') do
|
|
138
|
+
string += @ch
|
|
139
|
+
get_next
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
string.chomp!('.')
|
|
144
|
+
begin
|
|
145
|
+
if string.include?('.') || string.include?('e-')
|
|
146
|
+
Float(string).to_f
|
|
147
|
+
else
|
|
148
|
+
Float(string).to_i
|
|
149
|
+
end
|
|
150
|
+
rescue
|
|
151
|
+
error("Bad number")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def string
|
|
156
|
+
# Parse a string value.
|
|
157
|
+
|
|
158
|
+
hex = nil
|
|
159
|
+
i = nil
|
|
160
|
+
string = ''
|
|
161
|
+
delim = nil # double quote or single quote
|
|
162
|
+
uffff = nil
|
|
163
|
+
|
|
164
|
+
# When parsing for string values, we must look for ' or " and \ characters
|
|
165
|
+
|
|
166
|
+
if (@ch == '"' || @ch == "'")
|
|
167
|
+
delim = @ch
|
|
168
|
+
while (get_next) do
|
|
169
|
+
if (@ch == delim)
|
|
170
|
+
get_next
|
|
171
|
+
return string
|
|
172
|
+
elsif (@ch == '\\')
|
|
173
|
+
get_next
|
|
174
|
+
if (@ch == 'u')
|
|
175
|
+
uffff = 0
|
|
176
|
+
# for (i = 0; i < 4; i += 1) do
|
|
177
|
+
for i in 0...4 do
|
|
178
|
+
hex = parseInt(get_next, 16)
|
|
179
|
+
if (!isFinite(hex))
|
|
180
|
+
break
|
|
181
|
+
end
|
|
182
|
+
uffff = uffff * 16 + hex
|
|
183
|
+
end
|
|
184
|
+
string += String.fromCharCode(uffff)
|
|
185
|
+
elsif ESCAPEE.has_key? @ch
|
|
186
|
+
string += ESCAPEE[@ch]
|
|
187
|
+
else
|
|
188
|
+
break
|
|
189
|
+
end
|
|
190
|
+
elsif (@ch == "\n")
|
|
191
|
+
# unescaped newlines are invalid; see:
|
|
192
|
+
# https://github.com/aseemk/json5/issues/24
|
|
193
|
+
# TODO this feels special-cased; are there other
|
|
194
|
+
# invalid unescaped chars?
|
|
195
|
+
break
|
|
196
|
+
else
|
|
197
|
+
string += @ch
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
error("Bad string")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def inlineComment
|
|
205
|
+
# Skip an inline comment, assuming this is one. The current character should
|
|
206
|
+
# be the second / character in the # pair that begins this inline comment.
|
|
207
|
+
# To finish the inline comment, we look for a newline or the end of the text.
|
|
208
|
+
|
|
209
|
+
if (@ch != '/')
|
|
210
|
+
error("Not an inline comment")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
begin
|
|
214
|
+
get_next
|
|
215
|
+
if (@ch == "\n")
|
|
216
|
+
get_next("\n")
|
|
217
|
+
return
|
|
218
|
+
end
|
|
219
|
+
end while (@ch)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def blockComment
|
|
223
|
+
|
|
224
|
+
# Skip a block comment, assuming this is one. The current @character should be
|
|
225
|
+
# the * @character in the /* pair that begins this block comment.
|
|
226
|
+
# To finish the block comment, we look for an ending */ pair of @characters,
|
|
227
|
+
# but we also wat@ch for the end of text before the comment is terminated.
|
|
228
|
+
|
|
229
|
+
if (@ch != '*')
|
|
230
|
+
error("Not a block comment")
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
begin
|
|
234
|
+
get_next
|
|
235
|
+
while (@ch == '*') do
|
|
236
|
+
get_next('*')
|
|
237
|
+
if (@ch == '/')
|
|
238
|
+
get_next('/')
|
|
239
|
+
return
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end while (@ch)
|
|
243
|
+
|
|
244
|
+
error("Unterminated block comment")
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def comment
|
|
248
|
+
# Skip a comment, whether inline or block-level, assuming this is one.
|
|
249
|
+
# Comments always begin with a / @character.
|
|
250
|
+
|
|
251
|
+
if (@ch != '/')
|
|
252
|
+
error("Not a comment")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
get_next('/')
|
|
256
|
+
|
|
257
|
+
if (@ch == '/')
|
|
258
|
+
inlineComment
|
|
259
|
+
elsif (@ch == '*')
|
|
260
|
+
blockComment
|
|
261
|
+
else
|
|
262
|
+
error("Unrecognized comment")
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def white
|
|
267
|
+
# Skip whitespace and comments.
|
|
268
|
+
# Note that we're detecting comments by only a single / @character.
|
|
269
|
+
# This works since regular expressions are not valid JSON(5), but this will
|
|
270
|
+
# break if there are other valid values that begin with a / @character
|
|
271
|
+
|
|
272
|
+
while (@ch) do
|
|
273
|
+
if (@ch == '/')
|
|
274
|
+
comment
|
|
275
|
+
elsif (@ch <= ' ')
|
|
276
|
+
get_next
|
|
277
|
+
else
|
|
278
|
+
return
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def word
|
|
284
|
+
# true, false, or null.
|
|
285
|
+
|
|
286
|
+
case (@ch)
|
|
287
|
+
when 't'
|
|
288
|
+
get_next('t')
|
|
289
|
+
get_next('r')
|
|
290
|
+
get_next('u')
|
|
291
|
+
get_next('e')
|
|
292
|
+
return true
|
|
293
|
+
when 'f'
|
|
294
|
+
get_next('f')
|
|
295
|
+
get_next('a')
|
|
296
|
+
get_next('l')
|
|
297
|
+
get_next('s')
|
|
298
|
+
get_next('e')
|
|
299
|
+
return false
|
|
300
|
+
when 'n'
|
|
301
|
+
get_next('n')
|
|
302
|
+
get_next('u')
|
|
303
|
+
get_next('l')
|
|
304
|
+
get_next('l')
|
|
305
|
+
return nil
|
|
306
|
+
when 'I'
|
|
307
|
+
get_next('I')
|
|
308
|
+
get_next('n')
|
|
309
|
+
get_next('f')
|
|
310
|
+
get_next('i')
|
|
311
|
+
get_next('n')
|
|
312
|
+
get_next('i')
|
|
313
|
+
get_next('t')
|
|
314
|
+
get_next('y')
|
|
315
|
+
return Float::INFINITY
|
|
316
|
+
when 'N'
|
|
317
|
+
get_next( 'N' )
|
|
318
|
+
get_next( 'a' )
|
|
319
|
+
get_next( 'N' )
|
|
320
|
+
return Float::NAN
|
|
321
|
+
end
|
|
322
|
+
error("Unexpected '" + @ch + "'")
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def array
|
|
326
|
+
# Parse an array value.
|
|
327
|
+
|
|
328
|
+
array = []
|
|
329
|
+
|
|
330
|
+
if (@ch == '[')
|
|
331
|
+
get_next('[')
|
|
332
|
+
white
|
|
333
|
+
while (@ch) do
|
|
334
|
+
if (@ch == ']')
|
|
335
|
+
get_next(']')
|
|
336
|
+
return array; # Potentially empty array
|
|
337
|
+
end
|
|
338
|
+
# ES5 allows omitting elements in arrays, e.g. [,] and
|
|
339
|
+
# [,null]. We don't allow this in JSON5.
|
|
340
|
+
if (@ch == ',')
|
|
341
|
+
error("Missing array element")
|
|
342
|
+
else
|
|
343
|
+
array.push(value)
|
|
344
|
+
end
|
|
345
|
+
white
|
|
346
|
+
# If there's no comma after this value, this needs t
|
|
347
|
+
# be the end of the array.
|
|
348
|
+
if (@ch != ',')
|
|
349
|
+
get_next(']')
|
|
350
|
+
return array
|
|
351
|
+
end
|
|
352
|
+
get_next(',')
|
|
353
|
+
white
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
error("Bad array")
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def object
|
|
360
|
+
# Parse an object value.
|
|
361
|
+
key = nil
|
|
362
|
+
object = {}
|
|
363
|
+
|
|
364
|
+
if (@ch == '{')
|
|
365
|
+
get_next('{')
|
|
366
|
+
white
|
|
367
|
+
while (@ch) do
|
|
368
|
+
if (@ch == '}')
|
|
369
|
+
get_next('}')
|
|
370
|
+
return object; # Potentially empty object
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Keys can be unquoted. If they are, they need to b
|
|
374
|
+
# valid JS identifiers.
|
|
375
|
+
if (@ch == '"' || @ch == "'")
|
|
376
|
+
key = string
|
|
377
|
+
else
|
|
378
|
+
key = identifier
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
white
|
|
382
|
+
get_next(':')
|
|
383
|
+
|
|
384
|
+
if (object.has_key? key)
|
|
385
|
+
error('Duplicate key "' + key + '"')
|
|
386
|
+
end
|
|
387
|
+
object[key] = value
|
|
388
|
+
white
|
|
389
|
+
# If there's no comma after this pair, this needs to b
|
|
390
|
+
# the end of the object.
|
|
391
|
+
if (@ch != ',')
|
|
392
|
+
get_next('}')
|
|
393
|
+
return object
|
|
394
|
+
end
|
|
395
|
+
get_next(',')
|
|
396
|
+
white
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
error("Bad object")
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def value
|
|
403
|
+
# Parse a JSON value. It could be an object, an array, a string, a number,
|
|
404
|
+
# or a word.
|
|
405
|
+
|
|
406
|
+
white
|
|
407
|
+
case (@ch)
|
|
408
|
+
when '{'
|
|
409
|
+
return object
|
|
410
|
+
when '['
|
|
411
|
+
return array
|
|
412
|
+
when '"', "'"
|
|
413
|
+
return string
|
|
414
|
+
when '-', '+', '.'
|
|
415
|
+
return number
|
|
416
|
+
else
|
|
417
|
+
return @ch >= '0' && @ch <= '9' ? number : word
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def initialize source, reviver = nil
|
|
422
|
+
@text = source
|
|
423
|
+
@at = 0
|
|
424
|
+
@ch = ' '
|
|
425
|
+
@reviver = reviver
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def run
|
|
429
|
+
result = value
|
|
430
|
+
white
|
|
431
|
+
if (@ch)
|
|
432
|
+
error("Syntax error")
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# If there is a reviver function, we recursively walk the new structure,
|
|
436
|
+
# passing each name/value pair to the reviver function for possible
|
|
437
|
+
# transformation, starting with a temporary root object that holds the result
|
|
438
|
+
# in an empty key. If there is not a reviver function, we simply return the
|
|
439
|
+
# result.
|
|
440
|
+
|
|
441
|
+
walk = ->(holder, key) do
|
|
442
|
+
k, v, value = holder[key]
|
|
443
|
+
if (value)
|
|
444
|
+
value.each do |k|
|
|
445
|
+
if (Object.prototype.hasOwnProperty.call(value, k))
|
|
446
|
+
v = walk(value, k)
|
|
447
|
+
if (v != undefined)
|
|
448
|
+
value[k] = v
|
|
449
|
+
else
|
|
450
|
+
delete value[k]
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
return @reviver.call(holder, key, value)
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
return @reviver ? walk.call({ '' => result }, '') : result
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|