ruby_json_parser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +35 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +143 -0
  6. data/Rakefile +12 -0
  7. data/lib/ruby_json_parser/ast.rb +312 -0
  8. data/lib/ruby_json_parser/evaluator.rb +81 -0
  9. data/lib/ruby_json_parser/lexer.rb +358 -0
  10. data/lib/ruby_json_parser/parser.rb +205 -0
  11. data/lib/ruby_json_parser/result.rb +43 -0
  12. data/lib/ruby_json_parser/token.rb +171 -0
  13. data/lib/ruby_json_parser/version.rb +6 -0
  14. data/lib/ruby_json_parser.rb +77 -0
  15. data/sorbet/config +4 -0
  16. data/sorbet/rbi/annotations/.gitattributes +1 -0
  17. data/sorbet/rbi/annotations/minitest.rbi +119 -0
  18. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  19. data/sorbet/rbi/gems/.gitattributes +1 -0
  20. data/sorbet/rbi/gems/ast@2.4.2.rbi +585 -0
  21. data/sorbet/rbi/gems/erubi@1.13.0.rbi +150 -0
  22. data/sorbet/rbi/gems/json@2.7.2.rbi +1562 -0
  23. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14238 -0
  24. data/sorbet/rbi/gems/minitest@5.24.1.rbi +1563 -0
  25. data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
  26. data/sorbet/rbi/gems/parallel@1.25.1.rbi +287 -0
  27. data/sorbet/rbi/gems/parser@3.3.4.0.rbi +5519 -0
  28. data/sorbet/rbi/gems/prism@0.30.0.rbi +39212 -0
  29. data/sorbet/rbi/gems/racc@1.8.0.rbi +162 -0
  30. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
  31. data/sorbet/rbi/gems/rake@13.2.1.rbi +3028 -0
  32. data/sorbet/rbi/gems/rbi@0.1.13.rbi +3078 -0
  33. data/sorbet/rbi/gems/regexp_parser@2.9.2.rbi +3772 -0
  34. data/sorbet/rbi/gems/rexml@3.3.1.rbi +4813 -0
  35. data/sorbet/rbi/gems/rubocop-ast@1.31.3.rbi +7015 -0
  36. data/sorbet/rbi/gems/rubocop@1.65.0.rbi +58191 -0
  37. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
  38. data/sorbet/rbi/gems/spoom@1.3.3.rbi +4926 -0
  39. data/sorbet/rbi/gems/strscan@3.1.0.rbi +9 -0
  40. data/sorbet/rbi/gems/tapioca@0.15.1.rbi +3566 -0
  41. data/sorbet/rbi/gems/thor@1.3.1.rbi +4352 -0
  42. data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +66 -0
  43. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  44. data/sorbet/rbi/gems/yard@0.9.36.rbi +18221 -0
  45. data/sorbet/tapioca/config.yml +13 -0
  46. data/sorbet/tapioca/require.rb +4 -0
  47. metadata +105 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 642bf9c4ccd18cfadf48b5ebfa77838c2b27f85e22f3ad87b800709b94697344
4
+ data.tar.gz: e56908db728c244bd31105f6feb8093d5f5f2b04f56d4d0d04d43c140fdc5077
5
+ SHA512:
6
+ metadata.gz: 3488592ef9a18ab9e2dbc155c7e9f7c00fab7e523b41de5b9a0624c9462285a0d1b5239f2fc38b7d1723e2945ec4735731ffc52dae554d5146dcc09f54b76e56
7
+ data.tar.gz: 39da06576d98f82b3d4f023b10e815c4f2f79684b13264414ba62f762d184327dcc0eca7327e8523fa379a957dc275c22b1186ad87134955e4e3b9ce8a31e41c
data/.rubocop.yml ADDED
@@ -0,0 +1,35 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ inherit_gem:
5
+ rubocop-espago: rubocop.yml
6
+
7
+ Lint/BooleanSymbol:
8
+ Enabled: false
9
+
10
+ Lint/MissingSuper:
11
+ Enabled: false
12
+
13
+ Style/NumericPredicate:
14
+ Enabled: false
15
+
16
+ Metrics/MethodLength:
17
+ Enabled: false
18
+
19
+ Metrics/AbcSize:
20
+ Enabled: false
21
+
22
+ Style/YodaExpression:
23
+ Enabled: false
24
+
25
+ Metrics/CyclomaticComplexity:
26
+ Enabled: false
27
+
28
+ Metrics/BlockLength:
29
+ Enabled: false
30
+
31
+ Metrics/ClassLength:
32
+ Enabled: false
33
+
34
+ Style/RaiseArgs:
35
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-11-26
4
+
5
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mateusz Drewniak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # RubyJsonParser
2
+
3
+ This library implements a JSON lexer, parser and evaluator in pure Ruby 💎.
4
+
5
+ It has been built for educational purposes, to serve as a simple example of what makes parsers tick.
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add ruby_json_parser
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ $ gem install ruby_json_parser
16
+
17
+ ## Usage
18
+
19
+ ### Lexer
20
+
21
+ This library implements a streaming JSON lexer.
22
+ You can use it by creating an instance of `RubyJsonParser::Lexer` passing in a string
23
+ with JSON source.
24
+
25
+ You can call the `next` method to receive the next token.
26
+ Once the lexing is complete a token of type `:end_of_file` gets returned.
27
+
28
+ ```rb
29
+ require 'ruby_json_parser'
30
+
31
+ lexer = RubyJsonParser::Lexer.new('{ "some": ["json", 2e-29, "text"] }')
32
+ lexer.next #=> Token(:lbrace)
33
+ lexer.next #=> Token(:string, "some")
34
+ lexer.next #=> Token(:colon)
35
+ lexer.next #=> Token(:lbracket)
36
+ lexer.next #=> Token(:string, "json")
37
+ # ...
38
+ lexer.next #=> Token(:end_of_file)
39
+ ```
40
+
41
+ There is a simplified API that lets you generate an array of all tokens.
42
+
43
+ ```rb
44
+ require 'ruby_json_parser'
45
+
46
+ RubyJsonParser.lex('{ "some": ["json", 2e-29, "text"] }')
47
+ #=> [Token(:lbrace), Token(:string, "some"), Token(:colon), Token(:lbracket), Token(:string, "json"), Token(:comma), Token(:number, "2e-29"), Token(:comma), Token(:string, "text"), Token(:rbracket), Token(:rbrace)]
48
+ ```
49
+
50
+ ### Parser
51
+
52
+ This library implements a JSON parser.
53
+ You can use it by calling `RubyJsonParser.parse` passing in a string
54
+ with JSON source.
55
+
56
+ It returns `RubyJsonParser::Result` which contains the produced AST (Abstract Syntax Tree) and the list of encountered errors.
57
+
58
+ ```rb
59
+ require 'ruby_json_parser'
60
+
61
+ RubyJsonParser.parse('{ "some": ["json", 2e-29, "text"] }')
62
+ #=> <RubyJsonParser::Result>
63
+ # AST:
64
+ # (object
65
+ # (pair
66
+ # "some"
67
+ # (array
68
+ # "json"
69
+ # 2e-29
70
+ # "text")))
71
+
72
+ result = RubyJsonParser.parse('[1, 2')
73
+ #=> <RubyJsonParser::Result>
74
+ # !Errors!
75
+ # - unexpected `END_OF_FILE`, expected `]`
76
+ #
77
+ # AST:
78
+ # (array
79
+ # 1
80
+ # 2)
81
+
82
+ result.ast # get the AST
83
+ result.err? # check if there are any errors
84
+ result.errors # get the list of errors
85
+ ```
86
+
87
+ All AST nodes are implemented as classes under the `RubyJsonParser::AST` module.
88
+ AST nodes have an `inspect` method that presents their structure in the [S-expression](https://en.wikipedia.org/wiki/S-expression) format.
89
+ You can also use `#to_s` to convert them to a JSON-like human readable format.
90
+
91
+ ```rb
92
+ result = RubyJsonParser.parse('{"some" :[ "json",2e-29 , "text" ]}')
93
+ ast = result.ast
94
+
95
+ puts ast.inspect # S-expression format
96
+ # (object
97
+ # (pair
98
+ # "some"
99
+ # (array
100
+ # "json"
101
+ # 2e-29
102
+ # "text")))
103
+
104
+ puts ast.to_s # JSON-like format
105
+ # {"some": ["json", 2e-29, "text"]}
106
+
107
+ ast.class #=> RubyJsonParser::AST::ObjectLiteralNode
108
+
109
+ ast.pairs[0].key #=> RubyJsonParser::AST::StringLiteralNode("some")
110
+ ast.pairs[0].value.elements[2] #=> RubyJsonParser::AST::NumberLiteralNode("2e-29")
111
+ ```
112
+
113
+ ### Evaluator
114
+
115
+ This library implements a JSON evaluator.
116
+ It interprets a JSON source string as builtin Ruby data structures.
117
+
118
+ You can use it by calling `RubyJsonParser.eval` passing in a string
119
+ with JSON source.
120
+
121
+ It throws `RubyJsonParser::SyntaxError` when the string cannot be parsed.
122
+
123
+ ```rb
124
+ RubyJsonParser.eval('{ "some": ["json", 2e-29, "text"] }')
125
+ #=> {"some"=>["json", 2.0e-29, "text"]}
126
+
127
+ RubyJsonParser.eval('{ "some" }')
128
+ #! RubyJsonParser::SyntaxError: missing key in object literal for value: `"some"`
129
+ ```
130
+
131
+ ## Development
132
+
133
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
134
+
135
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
136
+
137
+ ## Contributing
138
+
139
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Verseth/ruby_json_parser.
140
+
141
+ ## License
142
+
143
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'minitest/test_task'
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,312 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ class RubyJsonParser
5
+ # Contains the definitions of all AST (Abstract Syntax Tree) nodes.
6
+ # AST is the data structure that is returned by the parser.
7
+ module AST
8
+ # A string that represents a single level of indentation
9
+ # in S-expressions
10
+ INDENT_UNIT = ' '
11
+
12
+ # Abstract class representing an AST node.
13
+ class Node
14
+ extend T::Sig
15
+ extend T::Helpers
16
+
17
+ abstract!
18
+
19
+ # Get the JSON-like representation of the AST
20
+ sig { abstract.returns(String) }
21
+ def to_s; end
22
+
23
+ # Inspect the AST in the S-expression format
24
+ sig { abstract.params(indent: Integer).returns(String) }
25
+ def inspect(indent = 0); end
26
+ end
27
+
28
+ # Represents an invalid node
29
+ class InvalidNode < Node
30
+ sig { returns(Token) }
31
+ attr_reader :token
32
+
33
+ sig { params(token: Token).void }
34
+ def initialize(token)
35
+ @token = token
36
+ end
37
+
38
+ sig { params(other: Object).returns(T::Boolean) }
39
+ def ==(other)
40
+ return false unless other.is_a?(InvalidNode)
41
+
42
+ token == other.token
43
+ end
44
+
45
+ sig { override.returns(String) }
46
+ def to_s
47
+ "<invalid: `#{token}`>"
48
+ end
49
+
50
+ sig { override.params(indent: Integer).returns(String) }
51
+ def inspect(indent = 0)
52
+ "#{INDENT_UNIT * indent}(invalid `#{token}`)"
53
+ end
54
+ end
55
+
56
+ # Represents a false literal eg. `false`
57
+ class FalseLiteralNode < Node
58
+ sig { params(other: Object).returns(T::Boolean) }
59
+ def ==(other)
60
+ other.is_a?(FalseLiteralNode)
61
+ end
62
+
63
+ sig { override.returns(String) }
64
+ def to_s
65
+ 'false'
66
+ end
67
+
68
+ sig { override.params(indent: Integer).returns(String) }
69
+ def inspect(indent = 0)
70
+ "#{INDENT_UNIT * indent}false"
71
+ end
72
+ end
73
+
74
+ # Represents a true literal eg. `true`
75
+ class TrueLiteralNode < Node
76
+ sig { params(other: Object).returns(T::Boolean) }
77
+ def ==(other)
78
+ other.is_a?(TrueLiteralNode)
79
+ end
80
+
81
+ sig { override.returns(String) }
82
+ def to_s
83
+ 'true'
84
+ end
85
+
86
+ sig { override.params(indent: Integer).returns(String) }
87
+ def inspect(indent = 0)
88
+ "#{INDENT_UNIT * indent}true"
89
+ end
90
+ end
91
+
92
+ # Represents a true literal eg. `null`
93
+ class NullLiteralNode < Node
94
+ sig { params(other: Object).returns(T::Boolean) }
95
+ def ==(other)
96
+ other.is_a?(NullLiteralNode)
97
+ end
98
+
99
+ sig { override.returns(String) }
100
+ def to_s
101
+ 'null'
102
+ end
103
+
104
+ sig { override.params(indent: Integer).returns(String) }
105
+ def inspect(indent = 0)
106
+ "#{INDENT_UNIT * indent}null"
107
+ end
108
+ end
109
+
110
+ # Represents a number literal eg. `123.5`
111
+ class NumberLiteralNode < Node
112
+ sig { returns(String) }
113
+ attr_reader :value
114
+
115
+ sig { params(value: String).void }
116
+ def initialize(value)
117
+ @value = value
118
+ end
119
+
120
+ sig { params(other: Object).returns(T::Boolean) }
121
+ def ==(other)
122
+ return false unless other.is_a?(NumberLiteralNode)
123
+
124
+ value == other.value
125
+ end
126
+
127
+ sig { override.returns(String) }
128
+ def to_s
129
+ value
130
+ end
131
+
132
+ sig { override.params(indent: Integer).returns(String) }
133
+ def inspect(indent = 0)
134
+ "#{INDENT_UNIT * indent}#{value}"
135
+ end
136
+ end
137
+
138
+ # Represents a string literal eg. `"foo"`
139
+ class StringLiteralNode < Node
140
+ sig { returns(String) }
141
+ attr_reader :value
142
+
143
+ sig { params(value: String).void }
144
+ def initialize(value)
145
+ @value = value
146
+ end
147
+
148
+ sig { params(other: Object).returns(T::Boolean) }
149
+ def ==(other)
150
+ return false unless other.is_a?(StringLiteralNode)
151
+
152
+ value == other.value
153
+ end
154
+
155
+ sig { override.returns(String) }
156
+ def to_s
157
+ value.inspect
158
+ end
159
+
160
+ sig { override.params(indent: Integer).returns(String) }
161
+ def inspect(indent = 0)
162
+ "#{INDENT_UNIT * indent}#{value.inspect}"
163
+ end
164
+ end
165
+
166
+ # Represents an object literal eg. `{ "foo": 123 }`
167
+ class ObjectLiteralNode < Node
168
+ sig { returns(T::Array[KeyValuePairNode]) }
169
+ attr_reader :pairs
170
+
171
+ sig { params(pairs: T::Array[KeyValuePairNode]).void }
172
+ def initialize(pairs)
173
+ @pairs = pairs
174
+ end
175
+
176
+ sig { params(other: Object).returns(T::Boolean) }
177
+ def ==(other)
178
+ return false unless other.is_a?(ObjectLiteralNode)
179
+
180
+ pairs == other.pairs
181
+ end
182
+
183
+ sig { override.returns(String) }
184
+ def to_s
185
+ buff = String.new
186
+ buff << '{'
187
+
188
+ @pairs.each.with_index do |pair, i|
189
+ buff << ', ' if i > 0
190
+ buff << pair.to_s
191
+ end
192
+
193
+ buff << '}'
194
+ buff
195
+ end
196
+
197
+ sig { override.params(indent: Integer).returns(String) }
198
+ def inspect(indent = 0)
199
+ buff = String.new
200
+
201
+ buff << "#{INDENT_UNIT * indent}(object"
202
+ @pairs.each do |pair|
203
+ buff << "\n"
204
+ buff << pair.inspect(indent + 1)
205
+ end
206
+ buff << ')'
207
+ buff
208
+ end
209
+ end
210
+
211
+ # Represents a key-value pair eg. `"foo": 123`
212
+ class KeyValuePairNode < Node
213
+ sig { returns(T.nilable(Node)) }
214
+ attr_reader :key
215
+
216
+ sig { returns(T.nilable(Node)) }
217
+ attr_reader :value
218
+
219
+ sig { params(key: T.nilable(Node), value: T.nilable(Node)).void }
220
+ def initialize(key, value)
221
+ @key = key
222
+ @value = value
223
+ end
224
+
225
+ sig { params(other: Object).returns(T::Boolean) }
226
+ def ==(other)
227
+ return false unless other.is_a?(KeyValuePairNode)
228
+
229
+ key == other.key && value == other.value
230
+ end
231
+
232
+ sig { override.returns(String) }
233
+ def to_s
234
+ return value.to_s unless key
235
+
236
+ "#{key}: #{value}"
237
+ end
238
+
239
+ sig { override.params(indent: Integer).returns(String) }
240
+ def inspect(indent = 0)
241
+ buff = String.new
242
+ buff << "#{INDENT_UNIT * indent}(pair"
243
+
244
+ k = key
245
+ buff << "\n"
246
+ buff <<
247
+ if k
248
+ k.inspect(indent + 1)
249
+ else
250
+ "#{INDENT_UNIT * (indent + 1)}<nil>"
251
+ end
252
+
253
+ v = value
254
+ buff << "\n"
255
+ buff <<
256
+ if v
257
+ v.inspect(indent + 1)
258
+ else
259
+ "#{INDENT_UNIT * (indent + 1)}<nil>"
260
+ end
261
+
262
+ buff << ')'
263
+ buff
264
+ end
265
+ end
266
+
267
+ # Represents an object literal eg. `[1, "foo"]`
268
+ class ArrayLiteralNode < Node
269
+ sig { returns(T::Array[Node]) }
270
+ attr_reader :elements
271
+
272
+ sig { params(elements: T::Array[Node]).void }
273
+ def initialize(elements)
274
+ @elements = elements
275
+ end
276
+
277
+ sig { params(other: Object).returns(T::Boolean) }
278
+ def ==(other)
279
+ return false unless other.is_a?(ArrayLiteralNode)
280
+
281
+ elements == other.elements
282
+ end
283
+
284
+ sig { override.returns(String) }
285
+ def to_s
286
+ buff = String.new
287
+ buff << '['
288
+
289
+ @elements.each.with_index do |element, i|
290
+ buff << ', ' if i > 0
291
+ buff << element.to_s
292
+ end
293
+
294
+ buff << ']'
295
+ buff
296
+ end
297
+
298
+ sig { override.params(indent: Integer).returns(String) }
299
+ def inspect(indent = 0)
300
+ buff = String.new
301
+
302
+ buff << "#{INDENT_UNIT * indent}(array"
303
+ @elements.each do |element|
304
+ buff << "\n"
305
+ buff << element.inspect(indent + 1)
306
+ end
307
+ buff << ')'
308
+ buff
309
+ end
310
+ end
311
+ end
312
+ end
@@ -0,0 +1,81 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'token'
5
+
6
+ class RubyJsonParser
7
+ # An evaluator for JSON.
8
+ # Creates Ruby structures based on an JSON AST.
9
+ module Evaluator
10
+ # Eval error
11
+ class Error < StandardError; end
12
+
13
+ class << self
14
+ extend T::Sig
15
+
16
+ sig { params(source: String).returns(Object) }
17
+ def eval(source)
18
+ result = RubyJsonParser.parse(source)
19
+ raise SyntaxError.new(result.errors) if result.err?
20
+
21
+ eval_node(result.ast)
22
+ end
23
+
24
+ sig { params(node: AST::Node).returns(Object) }
25
+ def eval_node(node)
26
+ case node
27
+ when AST::NullLiteralNode
28
+ nil
29
+ when AST::FalseLiteralNode
30
+ false
31
+ when AST::TrueLiteralNode
32
+ true
33
+ when AST::StringLiteralNode
34
+ eval_string(node)
35
+ when AST::NumberLiteralNode
36
+ eval_number(node)
37
+ when AST::ArrayLiteralNode
38
+ eval_array(node)
39
+ when AST::ObjectLiteralNode
40
+ eval_object(node)
41
+ else
42
+ raise Error, "invalid AST node: #{node.class}"
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ sig { params(node: AST::StringLiteralNode).returns(String) }
49
+ def eval_string(node)
50
+ node.value
51
+ end
52
+
53
+ sig { params(node: AST::NumberLiteralNode).returns(T.any(Integer, Float)) }
54
+ def eval_number(node)
55
+ Integer(node.value)
56
+ rescue ArgumentError
57
+ node.value.to_f
58
+ end
59
+
60
+ sig { params(node: AST::ArrayLiteralNode).returns(T::Array[Object]) }
61
+ def eval_array(node)
62
+ node.elements.map do |element|
63
+ eval_node(element)
64
+ end
65
+ end
66
+
67
+ sig { params(node: AST::ObjectLiteralNode).returns(T::Hash[String, Object]) }
68
+ def eval_object(node)
69
+ result = {}
70
+ node.pairs.each do |pair|
71
+ key = T.cast(pair.key, AST::StringLiteralNode)
72
+ val = T.must pair.value
73
+ result[eval_string(key)] = eval_node(val)
74
+ end
75
+
76
+ result
77
+ end
78
+ end
79
+
80
+ end
81
+ end