json5-ruby 0.1.0

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.
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSON5
4
+ class DocumentParser
5
+ MemberParts = Struct.new(:name, :value, :colon, :comma)
6
+ ElementParts = Struct.new(:elements, :commas)
7
+
8
+ def initialize(source, limits:, diagnostics:, filename:, preserve_trivia: true, collected_diagnostics: [])
9
+ @source = source
10
+ @limits = limits
11
+ @input = Input.new(source, limits: limits, filename: filename)
12
+ @lexer = Lexer.new(
13
+ @input,
14
+ limits: limits,
15
+ diagnostics: diagnostics,
16
+ filename: filename,
17
+ preserve_trivia: preserve_trivia
18
+ )
19
+ @filename = filename
20
+ @diagnostics = collected_diagnostics
21
+ @value_tokens = {}
22
+ @eof_token = nil
23
+ end
24
+
25
+ def parse
26
+ root = GeneratedParser.new(@lexer, self).parse_tokens
27
+ Document.new(
28
+ source: @source,
29
+ root: root,
30
+ diagnostics: @diagnostics,
31
+ trailing_trivia: @eof_token&.leading_trivia || []
32
+ )
33
+ rescue JSON5::Error => error
34
+ raise enrich_error(error)
35
+ rescue Ibex::Runtime::ParseError => error
36
+ raise enrich_error(translate_parse_error(error))
37
+ end
38
+
39
+ def observe_token(token)
40
+ @last_token = token
41
+ @value_tokens[token.value.object_id] = token if token.value
42
+ @eof_token = token if token.kind == :EOF
43
+ end
44
+
45
+ def retain_punctuation_tokens?
46
+ true
47
+ end
48
+
49
+ def string(value)
50
+ scalar_string(take_value_token(value))
51
+ end
52
+
53
+ def number(value)
54
+ scalar_number(take_value_token(value))
55
+ end
56
+
57
+ def literal(value, result)
58
+ scalar_literal(take_value_token(value), result)
59
+ end
60
+
61
+ def member_name(value)
62
+ token = take_value_token(value)
63
+ return scalar_string(token) if token.kind == :STRING
64
+
65
+ code_units = if value.is_a?(ECMAString)
66
+ value
67
+ else
68
+ ECMAString.new(value.raw.each_codepoint.to_a)
69
+ end
70
+ IdentifierNameNode.new(
71
+ code_units: code_units,
72
+ source: @source,
73
+ start_byte: token.start_byte,
74
+ end_byte: token.end_byte,
75
+ leading_trivia: token.leading_trivia
76
+ )
77
+ end
78
+
79
+ def member(name, colon, value)
80
+ MemberParts.new(name, value, colon, nil)
81
+ end
82
+
83
+ def object(members, open, close, trailing_comma = nil)
84
+ members.last.comma = trailing_comma if trailing_comma && !members.empty?
85
+ nodes = members.map do |parts|
86
+ MemberNode.new(
87
+ name: parts.name,
88
+ value: parts.value,
89
+ colon_start_byte: parts.colon.start_byte,
90
+ colon_end_byte: parts.colon.end_byte,
91
+ source: @source,
92
+ start_byte: parts.name.start_byte,
93
+ end_byte: parts.value.end_byte,
94
+ trailing_trivia: parts.comma ? parts.comma.leading_trivia : [],
95
+ colon_leading_trivia: parts.colon.leading_trivia
96
+ )
97
+ end
98
+ ObjectNode.new(
99
+ members: nodes,
100
+ source: @source,
101
+ start_byte: open.start_byte,
102
+ end_byte: close.end_byte,
103
+ leading_trivia: open.leading_trivia,
104
+ trailing_trivia: close.leading_trivia
105
+ )
106
+ end
107
+
108
+ def array(parts, open, close, trailing_comma = nil)
109
+ element_parts = parts.is_a?(ElementParts) ? parts : ElementParts.new(parts, [])
110
+ element_parts.commas << trailing_comma if trailing_comma
111
+ trailing = element_parts.elements.each_index.map do |index|
112
+ comma = element_parts.commas[index]
113
+ comma ? comma.leading_trivia : []
114
+ end
115
+ ArrayNode.new(
116
+ elements: element_parts.elements,
117
+ element_trailing_trivia: trailing,
118
+ source: @source,
119
+ start_byte: open.start_byte,
120
+ end_byte: close.end_byte,
121
+ leading_trivia: open.leading_trivia,
122
+ trailing_trivia: close.leading_trivia
123
+ )
124
+ end
125
+
126
+ def member_list(member)
127
+ enforce_entry_limit(1)
128
+ [member]
129
+ end
130
+
131
+ def append_member(members, member, comma)
132
+ enforce_entry_limit(members.length + 1)
133
+ members.last.comma = comma
134
+ members << member
135
+ end
136
+
137
+ def element_list(element)
138
+ enforce_entry_limit(1)
139
+ ElementParts.new([element], [])
140
+ end
141
+
142
+ def append_element(parts, element, comma)
143
+ enforce_entry_limit(parts.elements.length + 1)
144
+ parts.commas << comma
145
+ parts.elements << element
146
+ parts
147
+ end
148
+
149
+ private
150
+
151
+ def take_value_token(value)
152
+ @value_tokens.delete(value.object_id) || raise("missing scalar token")
153
+ end
154
+
155
+ def scalar_string(token)
156
+ StringNode.new(
157
+ code_units: token.value,
158
+ quote: @source.getbyte(token.start_byte),
159
+ escape_spans: string_escape_spans(token),
160
+ source: @source,
161
+ start_byte: token.start_byte,
162
+ end_byte: token.end_byte,
163
+ leading_trivia: token.leading_trivia
164
+ )
165
+ end
166
+
167
+ def scalar_number(token)
168
+ value = token.value.is_a?(NumberValue) ? token.value : NumberValue.new(token.value)
169
+ classification = %i[integer float].include?(value.kind) ? :decimal : value.kind
170
+ NumberNode.new(
171
+ classification: classification,
172
+ sign: value.raw.start_with?("-") ? :- : value.raw.start_with?("+") ? :+ : nil,
173
+ source: @source,
174
+ start_byte: token.start_byte,
175
+ end_byte: token.end_byte,
176
+ leading_trivia: token.leading_trivia
177
+ )
178
+ end
179
+
180
+ def scalar_literal(token, value)
181
+ LiteralNode.new(
182
+ value: value,
183
+ source: @source,
184
+ start_byte: token.start_byte,
185
+ end_byte: token.end_byte,
186
+ leading_trivia: token.leading_trivia
187
+ )
188
+ end
189
+
190
+ def string_escape_spans(token)
191
+ spans = []
192
+ index = token.start_byte + 1
193
+ content_end = token.end_byte - 1
194
+ while index < content_end
195
+ unless @source.getbyte(index) == 0x5c
196
+ index += utf8_width(@source.getbyte(index))
197
+ next
198
+ end
199
+
200
+ start_byte = index
201
+ index = escape_end_byte(index + 1)
202
+ spans << ByteSpan.new(start_byte, index)
203
+ end
204
+ spans
205
+ end
206
+
207
+ def escape_end_byte(marker_byte)
208
+ marker = @source.getbyte(marker_byte)
209
+ return marker_byte + 3 if marker == 0x78
210
+ return marker_byte + 5 if marker == 0x75
211
+ return marker_byte + 2 if marker == 0x0d && @source.getbyte(marker_byte + 1) == 0x0a
212
+
213
+ marker_byte + utf8_width(marker)
214
+ end
215
+
216
+ def utf8_width(byte)
217
+ return 1 if byte < 0x80
218
+ return 2 if byte < 0xe0
219
+ return 3 if byte < 0xf0
220
+
221
+ 4
222
+ end
223
+
224
+ def enforce_entry_limit(count)
225
+ return unless @limits.max_container_entries && count > @limits.max_container_entries
226
+
227
+ token = @last_token
228
+ offset = token&.start_byte || @input.index
229
+ raise LimitError.new(
230
+ "container exceeds max_container_entries",
231
+ code: "maximum_container_entries_exceeded",
232
+ filename: @filename,
233
+ byte_offset: offset,
234
+ end_byte_offset: token&.end_byte || offset,
235
+ line: token&.line || @input.line,
236
+ column: token&.column || @input.column,
237
+ excerpt: @input.excerpt_at(offset)
238
+ )
239
+ end
240
+
241
+ def translate_parse_error(error)
242
+ internal_expected = error.respond_to?(:expected_tokens) ? contextual_expected(error.expected_tokens) : nil
243
+ internal_unexpected = error.respond_to?(:token_name) ? error.token_name : nil
244
+ trailing_tokens = internal_unexpected != "$eof" && internal_expected&.include?("$eof") &&
245
+ @lexer.last_token_nesting_depth == 0
246
+ expected = trailing_tokens ? ["end of input"] : PublicTokenNames.expected(internal_expected)
247
+ unexpected = PublicTokenNames.name(internal_unexpected || "token")
248
+ token = @lexer.last_token
249
+ message = "unexpected #{unexpected}"
250
+ message += "; expected #{expected.join(', ')}" if expected && !expected.empty?
251
+ ParseError.new(
252
+ message,
253
+ code: trailing_tokens ? "trailing_tokens" : "unexpected_token",
254
+ filename: @filename,
255
+ byte_offset: token&.start_byte || @input.index,
256
+ end_byte_offset: token&.end_byte || @input.index,
257
+ line: token&.line || @input.line,
258
+ column: token&.column || @input.column,
259
+ unexpected: unexpected,
260
+ expected: expected
261
+ )
262
+ end
263
+
264
+ def contextual_expected(expected_tokens)
265
+ expected = Array(expected_tokens).dup
266
+ case @lexer.last_token_container
267
+ when "{"
268
+ expected -= ["$eof", "']'"]
269
+ when "["
270
+ expected -= ["$eof", "'}'"]
271
+ end
272
+ expected
273
+ end
274
+
275
+ def enrich_error(error)
276
+ return error if error.byte_offset && error.end_byte_offset && error.line && error.column && error.excerpt
277
+
278
+ token = @lexer.last_token
279
+ offset = error.byte_offset || token&.start_byte || @input.index
280
+ finish = error.end_byte_offset || token&.end_byte || @input.index
281
+ error.with_context(
282
+ filename: error.filename || @filename,
283
+ byte_offset: offset,
284
+ end_byte_offset: finish,
285
+ line: error.line || token&.line || @input.line,
286
+ column: error.column || token&.column || @input.column,
287
+ excerpt: error.excerpt || @input.excerpt_at(offset)
288
+ )
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSON5
4
+ class ECMAString
5
+ attr_reader :code_units
6
+
7
+ def initialize(code_units)
8
+ unless code_units.all? { |unit| unit.is_a?(Integer) && unit.between?(0, 0xffff) }
9
+ raise ArgumentError, "code units must be UTF-16 integers"
10
+ end
11
+ @code_units = code_units.dup.freeze
12
+ freeze
13
+ end
14
+
15
+ def each_code_unit(&block)
16
+ return enum_for(__method__) unless block
17
+ code_units.each(&block)
18
+ end
19
+
20
+ def length
21
+ code_units.length
22
+ end
23
+
24
+ def empty?
25
+ code_units.empty?
26
+ end
27
+
28
+ def eql?(other)
29
+ other.is_a?(ECMAString) && code_units == other.code_units
30
+ end
31
+ alias == eql?
32
+
33
+ def hash
34
+ code_units.hash
35
+ end
36
+
37
+ def lone_surrogate?
38
+ index = 0
39
+ while index < code_units.length
40
+ unit = code_units[index]
41
+ if surrogate_pair_at?(index)
42
+ index += 2
43
+ next
44
+ end
45
+ return true if high_surrogate?(unit) || low_surrogate?(unit)
46
+
47
+ index += 1
48
+ end
49
+ false
50
+ end
51
+
52
+ def to_utf8(lone_surrogate: :error)
53
+ result = String.new(encoding: Encoding::UTF_8)
54
+ index = 0
55
+ while index < code_units.length
56
+ unit = code_units[index]
57
+ if surrogate_pair_at?(index)
58
+ codepoint = 0x10000 + ((unit - 0xd800) << 10) + code_units[index + 1] - 0xdc00
59
+ append_codepoint(result, codepoint)
60
+ index += 2
61
+ next
62
+ end
63
+
64
+ if high_surrogate?(unit) || low_surrogate?(unit)
65
+ case lone_surrogate
66
+ when :error
67
+ raise ConversionError.new(
68
+ "lone surrogate cannot be represented as UTF-8",
69
+ code: "lone_surrogate"
70
+ )
71
+ when :replace
72
+ append_codepoint(result, 0xfffd)
73
+ else
74
+ raise ArgumentError, "unknown lone_surrogate policy: #{lone_surrogate.inspect}"
75
+ end
76
+ else
77
+ append_codepoint(result, unit)
78
+ end
79
+ index += 1
80
+ end
81
+ result
82
+ end
83
+
84
+ def to_s
85
+ to_utf8
86
+ end
87
+
88
+ private
89
+
90
+ def high_surrogate?(unit)
91
+ unit.between?(0xd800, 0xdbff)
92
+ end
93
+
94
+ def low_surrogate?(unit)
95
+ unit.between?(0xdc00, 0xdfff)
96
+ end
97
+
98
+ def surrogate_pair_at?(index)
99
+ high_surrogate?(code_units[index]) && index + 1 < code_units.length && low_surrogate?(code_units[index + 1])
100
+ end
101
+
102
+ def append_codepoint(result, codepoint)
103
+ result << [codepoint].pack("U")
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSON5
4
+ class Error < StandardError
5
+ attr_reader :code, :filename, :byte_offset, :end_byte_offset, :line,
6
+ :column, :unexpected, :expected, :excerpt
7
+
8
+ def initialize(message = nil, code: "parse_error", filename: nil,
9
+ byte_offset: nil, end_byte_offset: nil, line: nil,
10
+ column: nil, unexpected: nil, expected: nil, excerpt: nil)
11
+ @code = code.to_s.freeze
12
+ @filename = filename
13
+ @byte_offset = byte_offset
14
+ @end_byte_offset = end_byte_offset
15
+ @line = line
16
+ @column = column
17
+ @unexpected = unexpected
18
+ @expected = expected&.map(&:to_s)&.freeze
19
+ @excerpt = excerpt
20
+ super(message || @code)
21
+ end
22
+
23
+ def to_h
24
+ {
25
+ code: code,
26
+ message: message,
27
+ filename: filename,
28
+ byte_offset: byte_offset,
29
+ end_byte_offset: end_byte_offset,
30
+ line: line,
31
+ column: column,
32
+ unexpected: unexpected,
33
+ expected: expected,
34
+ excerpt: excerpt
35
+ }
36
+ end
37
+
38
+ def with_context(filename: self.filename, byte_offset: self.byte_offset,
39
+ end_byte_offset: self.end_byte_offset, line: self.line,
40
+ column: self.column, excerpt: self.excerpt)
41
+ return self if self.byte_offset && self.end_byte_offset && self.line && self.column && self.excerpt
42
+
43
+ enriched = self.class.new(
44
+ message,
45
+ code: code,
46
+ filename: filename,
47
+ byte_offset: byte_offset,
48
+ end_byte_offset: end_byte_offset,
49
+ line: line,
50
+ column: column,
51
+ unexpected: unexpected,
52
+ expected: expected,
53
+ excerpt: excerpt
54
+ )
55
+ enriched.set_backtrace(backtrace) if backtrace
56
+ enriched
57
+ end
58
+ end
59
+
60
+ class EncodingError < Error; end
61
+ class LexError < Error; end
62
+ class ParseError < Error; end
63
+ class DuplicateKeyError < ParseError; end
64
+ class DepthError < Error; end
65
+ class LimitError < Error; end
66
+ class ConversionError < Error; end
67
+ end
@@ -0,0 +1,196 @@
1
+ {
2
+ "ibex_manifest": "generation",
3
+ "schema_version": 1,
4
+ "input": {
5
+ "root": "grammar/json5.y",
6
+ "sha256": "bf1302cb95d92012fa9d462837b4bb7b0f165d13cea80e1e1724d98c383ee230",
7
+ "files": [
8
+ {
9
+ "path": "grammar/json5.y",
10
+ "sha256": "ca1490daccedcc6067afc95d175173bd3453c416007555938ed934d74cbd7183",
11
+ "bytesize": 2345
12
+ }
13
+ ]
14
+ },
15
+ "options": {
16
+ "algorithm": "lalr",
17
+ "automaton_ir": {
18
+ "algorithm": "lalr1",
19
+ "construction_authority": "grammar_contract",
20
+ "entry_construction": "shared",
21
+ "schema_version": 1
22
+ },
23
+ "counterexample_max_configurations": 50000,
24
+ "counterexample_max_tokens": 32,
25
+ "cst_trivia": "leading",
26
+ "effective_configuration": [
27
+ {
28
+ "key": "actions.omit_calls",
29
+ "value": null,
30
+ "owner": "grammar-contract",
31
+ "policy": "fixed",
32
+ "origin": {
33
+ "kind": "builtin"
34
+ },
35
+ "explicit": false,
36
+ "canonical": true
37
+ },
38
+ {
39
+ "key": "build.debug",
40
+ "value": false,
41
+ "owner": "project-build",
42
+ "policy": "build",
43
+ "origin": {
44
+ "kind": "builtin"
45
+ },
46
+ "explicit": false,
47
+ "canonical": true
48
+ },
49
+ {
50
+ "key": "build.executable",
51
+ "value": null,
52
+ "owner": "project-build",
53
+ "policy": "build",
54
+ "origin": {
55
+ "kind": "builtin"
56
+ },
57
+ "explicit": false,
58
+ "canonical": true
59
+ },
60
+ {
61
+ "key": "build.frozen_strings",
62
+ "value": true,
63
+ "owner": "project-build",
64
+ "policy": "build",
65
+ "origin": {
66
+ "kind": "cli"
67
+ },
68
+ "explicit": true,
69
+ "canonical": true
70
+ },
71
+ {
72
+ "key": "cst.trivia",
73
+ "value": "leading",
74
+ "owner": "grammar-contract",
75
+ "policy": "fixed",
76
+ "origin": {
77
+ "kind": "builtin"
78
+ },
79
+ "explicit": false,
80
+ "canonical": true
81
+ },
82
+ {
83
+ "key": "grammar.mode",
84
+ "value": "default",
85
+ "owner": "grammar-contract",
86
+ "policy": "fixed",
87
+ "origin": {
88
+ "kind": "builtin"
89
+ },
90
+ "explicit": false,
91
+ "canonical": true
92
+ },
93
+ {
94
+ "key": "parser.algorithm",
95
+ "value": "lalr",
96
+ "owner": "grammar-contract",
97
+ "policy": "fixed",
98
+ "origin": {
99
+ "kind": "cli"
100
+ },
101
+ "explicit": true,
102
+ "canonical": true
103
+ },
104
+ {
105
+ "key": "parser.entries",
106
+ "value": "shared",
107
+ "owner": "grammar-contract",
108
+ "policy": "fixed",
109
+ "origin": {
110
+ "kind": "builtin"
111
+ },
112
+ "explicit": false,
113
+ "canonical": true
114
+ },
115
+ {
116
+ "key": "parser.superclass",
117
+ "value": null,
118
+ "owner": "grammar-contract",
119
+ "policy": "fixed",
120
+ "origin": {
121
+ "kind": "builtin"
122
+ },
123
+ "explicit": false,
124
+ "canonical": true
125
+ },
126
+ {
127
+ "key": "runtime.embedded",
128
+ "value": true,
129
+ "owner": "project-build",
130
+ "policy": "build",
131
+ "origin": {
132
+ "kind": "cli"
133
+ },
134
+ "explicit": true,
135
+ "canonical": true
136
+ },
137
+ {
138
+ "key": "source.line_mapping",
139
+ "value": "actions",
140
+ "owner": "project-build",
141
+ "policy": "build",
142
+ "origin": {
143
+ "kind": "builtin"
144
+ },
145
+ "explicit": false,
146
+ "canonical": true
147
+ },
148
+ {
149
+ "key": "table.representation",
150
+ "value": "compact",
151
+ "owner": "project-build",
152
+ "policy": "build",
153
+ "origin": {
154
+ "kind": "builtin"
155
+ },
156
+ "explicit": false,
157
+ "canonical": true
158
+ }
159
+ ],
160
+ "embedded": true,
161
+ "emit": "ruby",
162
+ "frozen": true,
163
+ "grammar_ir": {
164
+ "digest": "sha256:ad3f3c01a5895b10f17bd4c512aa8f073f651d9b7a623a6df9a4b5536c5d1a18",
165
+ "parser_contract": {
166
+ "algorithm": {
167
+ "explicit": false,
168
+ "loc": null,
169
+ "value": null
170
+ },
171
+ "cst_trivia": {
172
+ "explicit": false,
173
+ "loc": null,
174
+ "value": null
175
+ },
176
+ "entries": {
177
+ "explicit": false,
178
+ "loc": null,
179
+ "value": null
180
+ }
181
+ },
182
+ "schema_version": 1
183
+ },
184
+ "line_convert": true,
185
+ "mode": "default",
186
+ "table": "compact"
187
+ },
188
+ "artifacts": [
189
+ {
190
+ "kind": "parser",
191
+ "path": "lib/json5/generated_parser.rb",
192
+ "sha256": "2ae44411e7a2f97a35d9b3a1083921baa47615f7e69e0a94f1751866d762b722",
193
+ "bytesize": 392768
194
+ }
195
+ ]
196
+ }