json5 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +60 -0
  7. data/Rakefile +1 -0
  8. data/json5.gemspec +25 -0
  9. data/lib/json5.rb +13 -0
  10. data/lib/json5/parser.rb +461 -0
  11. data/lib/json5/stringifier.rb +242 -0
  12. data/lib/json5/version.rb +3 -0
  13. data/spec/json5/parse_spec.rb +71 -0
  14. data/spec/parse-cases/arrays/empty-array.json +1 -0
  15. data/spec/parse-cases/arrays/leading-comma-array.js +3 -0
  16. data/spec/parse-cases/arrays/lone-trailing-comma-array.js +3 -0
  17. data/spec/parse-cases/arrays/no-comma-array.txt +4 -0
  18. data/spec/parse-cases/arrays/regular-array.json +5 -0
  19. data/spec/parse-cases/arrays/trailing-comma-array.json5 +3 -0
  20. data/spec/parse-cases/comments/block-comment-following-array-element.json5 +6 -0
  21. data/spec/parse-cases/comments/block-comment-following-top-level-value.json5 +5 -0
  22. data/spec/parse-cases/comments/block-comment-in-string.json +1 -0
  23. data/spec/parse-cases/comments/block-comment-preceding-top-level-value.json5 +5 -0
  24. data/spec/parse-cases/comments/block-comment-with-asterisks.json5 +7 -0
  25. data/spec/parse-cases/comments/inline-comment-following-array-element.json5 +3 -0
  26. data/spec/parse-cases/comments/inline-comment-following-top-level-value.json5 +1 -0
  27. data/spec/parse-cases/comments/inline-comment-in-string.json +1 -0
  28. data/spec/parse-cases/comments/inline-comment-preceding-top-level-value.json5 +2 -0
  29. data/spec/parse-cases/comments/top-level-block-comment.txt +4 -0
  30. data/spec/parse-cases/comments/top-level-inline-comment.txt +1 -0
  31. data/spec/parse-cases/comments/unterminated-block-comment.txt +5 -0
  32. data/spec/parse-cases/misc/empty.txt +0 -0
  33. data/spec/parse-cases/misc/npm-package.json +106 -0
  34. data/spec/parse-cases/misc/npm-package.json5 +106 -0
  35. data/spec/parse-cases/misc/readme-example.json5 +25 -0
  36. data/spec/parse-cases/numbers/binary-coffeescript.txt +1 -0
  37. data/spec/parse-cases/numbers/float-leading-decimal-point.json5 +1 -0
  38. data/spec/parse-cases/numbers/float-leading-zero.json +1 -0
  39. data/spec/parse-cases/numbers/float-trailing-decimal-point-with-integer-exponent.json5.todo +1 -0
  40. data/spec/parse-cases/numbers/float-trailing-decimal-point.json5 +1 -0
  41. data/spec/parse-cases/numbers/float-with-integer-exponent.json +1 -0
  42. data/spec/parse-cases/numbers/float.json +1 -0
  43. data/spec/parse-cases/numbers/hexadecimal-empty.txt +1 -0
  44. data/spec/parse-cases/numbers/hexadecimal-lowercase-letter.json5 +1 -0
  45. data/spec/parse-cases/numbers/hexadecimal-uppercase-x.json5 +1 -0
  46. data/spec/parse-cases/numbers/hexadecimal-with-integer-exponent.json5 +1 -0
  47. data/spec/parse-cases/numbers/hexadecimal.json5 +1 -0
  48. data/spec/parse-cases/numbers/infinity.json5 +1 -0
  49. data/spec/parse-cases/numbers/integer-with-float-exponent.txt +1 -0
  50. data/spec/parse-cases/numbers/integer-with-hexadecimal-exponent.txt +1 -0
  51. data/spec/parse-cases/numbers/integer-with-integer-exponent.json +1 -0
  52. data/spec/parse-cases/numbers/integer-with-negative-float-exponent.txt +1 -0
  53. data/spec/parse-cases/numbers/integer-with-negative-hexadecimal-exponent.txt +1 -0
  54. data/spec/parse-cases/numbers/integer-with-negative-integer-exponent.json +1 -0
  55. data/spec/parse-cases/numbers/integer-with-negative-zero-integer-exponent.json +1 -0
  56. data/spec/parse-cases/numbers/integer-with-positive-float-exponent.txt +1 -0
  57. data/spec/parse-cases/numbers/integer-with-positive-hexadecimal-exponent.txt +1 -0
  58. data/spec/parse-cases/numbers/integer-with-positive-integer-exponent.json +1 -0
  59. data/spec/parse-cases/numbers/integer-with-positive-zero-integer-exponent.json +1 -0
  60. data/spec/parse-cases/numbers/integer-with-zero-integer-exponent.json +1 -0
  61. data/spec/parse-cases/numbers/integer.json +1 -0
  62. data/spec/parse-cases/numbers/lone-decimal-point.txt +1 -0
  63. data/spec/parse-cases/numbers/nan.json5 +1 -0
  64. data/spec/parse-cases/numbers/negative-binary-coffeescript.txt +1 -0
  65. data/spec/parse-cases/numbers/negative-float-leading-decimal-point.json5 +1 -0
  66. data/spec/parse-cases/numbers/negative-float-leading-zero.json +1 -0
  67. data/spec/parse-cases/numbers/negative-float-trailing-decimal-point.json5 +1 -0
  68. data/spec/parse-cases/numbers/negative-float.json +1 -0
  69. data/spec/parse-cases/numbers/negative-hexadecimal.js +1 -0
  70. data/spec/parse-cases/numbers/negative-infinity.json5 +1 -0
  71. data/spec/parse-cases/numbers/negative-integer.json +1 -0
  72. data/spec/parse-cases/numbers/negative-noctal.js +1 -0
  73. data/spec/parse-cases/numbers/negative-octal-coffeescript.txt +1 -0
  74. data/spec/parse-cases/numbers/negative-octal.txt +1 -0
  75. data/spec/parse-cases/numbers/negative-zero-binary-coffeescript.txt +1 -0
  76. data/spec/parse-cases/numbers/negative-zero-float-leading-decimal-point.json5 +1 -0
  77. data/spec/parse-cases/numbers/negative-zero-float-trailing-decimal-point.json5 +1 -0
  78. data/spec/parse-cases/numbers/negative-zero-float.json +1 -0
  79. data/spec/parse-cases/numbers/negative-zero-hexadecimal.js +1 -0
  80. data/spec/parse-cases/numbers/negative-zero-integer.json +1 -0
  81. data/spec/parse-cases/numbers/negative-zero-octal-coffeescript.txt +1 -0
  82. data/spec/parse-cases/numbers/negative-zero-octal.txt +1 -0
  83. data/spec/parse-cases/numbers/noctal-with-leading-octal-digit.js +1 -0
  84. data/spec/parse-cases/numbers/noctal.js +1 -0
  85. data/spec/parse-cases/numbers/octal-coffeescript.txt +1 -0
  86. data/spec/parse-cases/numbers/octal.txt +1 -0
  87. data/spec/parse-cases/numbers/positive-binary-coffeescript.txt +1 -0
  88. data/spec/parse-cases/numbers/positive-float-leading-decimal-point.json5 +1 -0
  89. data/spec/parse-cases/numbers/positive-float-leading-zero.json5 +1 -0
  90. data/spec/parse-cases/numbers/positive-float-trailing-decimal-point.json5 +1 -0
  91. data/spec/parse-cases/numbers/positive-float.json5 +1 -0
  92. data/spec/parse-cases/numbers/positive-hexadecimal.js +1 -0
  93. data/spec/parse-cases/numbers/positive-infinity.json5 +1 -0
  94. data/spec/parse-cases/numbers/positive-integer.json5 +1 -0
  95. data/spec/parse-cases/numbers/positive-noctal.js +1 -0
  96. data/spec/parse-cases/numbers/positive-octal-coffeescript.txt +1 -0
  97. data/spec/parse-cases/numbers/positive-octal.txt +1 -0
  98. data/spec/parse-cases/numbers/positive-zero-binary-coffeescript.txt +1 -0
  99. data/spec/parse-cases/numbers/positive-zero-float-leading-decimal-point.json5 +1 -0
  100. data/spec/parse-cases/numbers/positive-zero-float-trailing-decimal-point.json5 +1 -0
  101. data/spec/parse-cases/numbers/positive-zero-float.json5 +1 -0
  102. data/spec/parse-cases/numbers/positive-zero-hexadecimal.js +1 -0
  103. data/spec/parse-cases/numbers/positive-zero-integer.json5 +1 -0
  104. data/spec/parse-cases/numbers/positive-zero-octal-coffeescript.txt +1 -0
  105. data/spec/parse-cases/numbers/positive-zero-octal.txt +1 -0
  106. data/spec/parse-cases/numbers/zero-binary-coffeescript.txt +1 -0
  107. data/spec/parse-cases/numbers/zero-float-leading-decimal-point.json5 +1 -0
  108. data/spec/parse-cases/numbers/zero-float-trailing-decimal-point.json5 +1 -0
  109. data/spec/parse-cases/numbers/zero-float.json +1 -0
  110. data/spec/parse-cases/numbers/zero-hexadecimal.json5 +1 -0
  111. data/spec/parse-cases/numbers/zero-integer-with-integer-exponent.json +1 -0
  112. data/spec/parse-cases/numbers/zero-integer.json +1 -0
  113. data/spec/parse-cases/numbers/zero-octal-coffeescript.txt +1 -0
  114. data/spec/parse-cases/numbers/zero-octal.txt +1 -0
  115. data/spec/parse-cases/objects/empty-object.json +1 -0
  116. data/spec/parse-cases/objects/illegal-unquoted-key-number.txt +3 -0
  117. data/spec/parse-cases/objects/illegal-unquoted-key-symbol.txt +3 -0
  118. data/spec/parse-cases/objects/leading-comma-object.txt +3 -0
  119. data/spec/parse-cases/objects/lone-trailing-comma-object.txt +3 -0
  120. data/spec/parse-cases/objects/no-comma-object.txt +4 -0
  121. data/spec/parse-cases/objects/reserved-unquoted-key.json5 +3 -0
  122. data/spec/parse-cases/objects/single-quoted-key.json5 +3 -0
  123. data/spec/parse-cases/objects/trailing-comma-object.json5 +3 -0
  124. data/spec/parse-cases/objects/unquoted-keys.json5 +8 -0
  125. data/spec/parse-cases/strings/escaped-single-quoted-string.json5 +1 -0
  126. data/spec/parse-cases/strings/multi-line-string.json5 +2 -0
  127. data/spec/parse-cases/strings/single-quoted-string.json5 +1 -0
  128. data/spec/parse-cases/strings/unescaped-multi-line-string.txt +2 -0
  129. data/spec/parse-cases/todo/unicode-escaped-unquoted-key.json5.todo +3 -0
  130. data/spec/parse-cases/todo/unicode-unquoted-key.json5.todo +3 -0
  131. data/spec/spec_helper.rb +15 -0
  132. metadata +363 -0
@@ -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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1 @@
1
+ 2.1.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in json5.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -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
@@ -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