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.
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