graphqlite 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +46 -0
- data/LICENSE +21 -0
- data/README.md +339 -0
- data/lib/graphqlite/errors.rb +7 -0
- data/lib/graphqlite/executor.rb +380 -0
- data/lib/graphqlite/introspection.rb +222 -0
- data/lib/graphqlite/lexer.rb +266 -0
- data/lib/graphqlite/parser.rb +354 -0
- data/lib/graphqlite/schema.rb +238 -0
- data/lib/graphqlite/types.rb +336 -0
- data/lib/graphqlite/validator.rb +183 -0
- data/lib/graphqlite/version.rb +3 -0
- data/lib/graphqlite.rb +18 -0
- metadata +85 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
module GraphQLite
|
|
2
|
+
# Lexer tokenizes GraphQL documents according to the spec
|
|
3
|
+
class Lexer
|
|
4
|
+
TOKEN_TYPES = %i[
|
|
5
|
+
name int float string boolean null
|
|
6
|
+
lparen rparen lbracket rbracket lbrace rbrace
|
|
7
|
+
colon pipe equals bang dollar at spread
|
|
8
|
+
eof
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
Token = Struct.new(:type, :value, :line, :column)
|
|
12
|
+
|
|
13
|
+
attr_reader :source, :pos, :line, :column
|
|
14
|
+
|
|
15
|
+
def initialize(source)
|
|
16
|
+
@source = source
|
|
17
|
+
@pos = 0
|
|
18
|
+
@line = 1
|
|
19
|
+
@column = 1
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def tokenize
|
|
23
|
+
tokens = []
|
|
24
|
+
loop do
|
|
25
|
+
token = next_token
|
|
26
|
+
tokens << token
|
|
27
|
+
break if token.type == :eof
|
|
28
|
+
end
|
|
29
|
+
tokens
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def next_token
|
|
33
|
+
skip_ignored
|
|
34
|
+
return Token.new(:eof, nil, @line, @column) if @pos >= @source.length
|
|
35
|
+
|
|
36
|
+
char = current_char
|
|
37
|
+
start_line = @line
|
|
38
|
+
start_column = @column
|
|
39
|
+
|
|
40
|
+
token = case char
|
|
41
|
+
when '{'
|
|
42
|
+
advance
|
|
43
|
+
Token.new(:lbrace, '{', start_line, start_column)
|
|
44
|
+
when '}'
|
|
45
|
+
advance
|
|
46
|
+
Token.new(:rbrace, '}', start_line, start_column)
|
|
47
|
+
when '('
|
|
48
|
+
advance
|
|
49
|
+
Token.new(:lparen, '(', start_line, start_column)
|
|
50
|
+
when ')'
|
|
51
|
+
advance
|
|
52
|
+
Token.new(:rparen, ')', start_line, start_column)
|
|
53
|
+
when '['
|
|
54
|
+
advance
|
|
55
|
+
Token.new(:lbracket, '[', start_line, start_column)
|
|
56
|
+
when ']'
|
|
57
|
+
advance
|
|
58
|
+
Token.new(:rbracket, ']', start_line, start_column)
|
|
59
|
+
when ':'
|
|
60
|
+
advance
|
|
61
|
+
Token.new(:colon, ':', start_line, start_column)
|
|
62
|
+
when '|'
|
|
63
|
+
advance
|
|
64
|
+
Token.new(:pipe, '|', start_line, start_column)
|
|
65
|
+
when '='
|
|
66
|
+
advance
|
|
67
|
+
Token.new(:equals, '=', start_line, start_column)
|
|
68
|
+
when '!'
|
|
69
|
+
advance
|
|
70
|
+
Token.new(:bang, '!', start_line, start_column)
|
|
71
|
+
when '$'
|
|
72
|
+
advance
|
|
73
|
+
Token.new(:dollar, '$', start_line, start_column)
|
|
74
|
+
when '@'
|
|
75
|
+
advance
|
|
76
|
+
Token.new(:at, '@', start_line, start_column)
|
|
77
|
+
when '.'
|
|
78
|
+
if peek(1) == '.' && peek(2) == '.'
|
|
79
|
+
advance(3)
|
|
80
|
+
Token.new(:spread, '...', start_line, start_column)
|
|
81
|
+
else
|
|
82
|
+
raise ParseError, "Unexpected character '.' at #{start_line}:#{start_column}"
|
|
83
|
+
end
|
|
84
|
+
when '"'
|
|
85
|
+
read_string(start_line, start_column)
|
|
86
|
+
when '-', '0'..'9'
|
|
87
|
+
read_number(start_line, start_column)
|
|
88
|
+
when 'a'..'z', 'A'..'Z', '_'
|
|
89
|
+
read_name(start_line, start_column)
|
|
90
|
+
else
|
|
91
|
+
raise ParseError, "Unexpected character '#{char}' at #{start_line}:#{start_column}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
token
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def current_char
|
|
100
|
+
@source[@pos]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def peek(offset = 1)
|
|
104
|
+
@source[@pos + offset]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def advance(count = 1)
|
|
108
|
+
count.times do
|
|
109
|
+
if current_char == "\n"
|
|
110
|
+
@line += 1
|
|
111
|
+
@column = 1
|
|
112
|
+
else
|
|
113
|
+
@column += 1
|
|
114
|
+
end
|
|
115
|
+
@pos += 1
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def skip_ignored
|
|
120
|
+
loop do
|
|
121
|
+
break if @pos >= @source.length
|
|
122
|
+
char = current_char
|
|
123
|
+
|
|
124
|
+
case char
|
|
125
|
+
when ' ', "\t", "\r", "\n", ','
|
|
126
|
+
advance
|
|
127
|
+
when '#'
|
|
128
|
+
skip_comment
|
|
129
|
+
else
|
|
130
|
+
break
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def skip_comment
|
|
136
|
+
advance while @pos < @source.length && current_char != "\n"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def read_string(start_line, start_column)
|
|
140
|
+
advance # skip opening quote
|
|
141
|
+
value = ""
|
|
142
|
+
|
|
143
|
+
loop do
|
|
144
|
+
raise ParseError, "Unterminated string at #{start_line}:#{start_column}" if @pos >= @source.length
|
|
145
|
+
|
|
146
|
+
char = current_char
|
|
147
|
+
|
|
148
|
+
if char == '"'
|
|
149
|
+
advance
|
|
150
|
+
break
|
|
151
|
+
elsif char == '\\'
|
|
152
|
+
advance
|
|
153
|
+
escape_char = current_char
|
|
154
|
+
value += case escape_char
|
|
155
|
+
when '"', '\\', '/'
|
|
156
|
+
escape_char
|
|
157
|
+
when 'n'
|
|
158
|
+
"\n"
|
|
159
|
+
when 't'
|
|
160
|
+
"\t"
|
|
161
|
+
when 'r'
|
|
162
|
+
"\r"
|
|
163
|
+
when 'b'
|
|
164
|
+
"\b"
|
|
165
|
+
when 'f'
|
|
166
|
+
"\f"
|
|
167
|
+
when 'u'
|
|
168
|
+
# Unicode escape sequence \uXXXX
|
|
169
|
+
advance
|
|
170
|
+
hex = @source[@pos, 4]
|
|
171
|
+
raise ParseError, "Invalid unicode escape at #{@line}:#{@column}" unless hex =~ /\A[0-9a-fA-F]{4}\z/
|
|
172
|
+
advance(3)
|
|
173
|
+
[hex.to_i(16)].pack('U')
|
|
174
|
+
else
|
|
175
|
+
raise ParseError, "Invalid escape sequence \\#{escape_char} at #{@line}:#{@column}"
|
|
176
|
+
end
|
|
177
|
+
advance
|
|
178
|
+
else
|
|
179
|
+
value += char
|
|
180
|
+
advance
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
Token.new(:string, value, start_line, start_column)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def read_number(start_line, start_column)
|
|
188
|
+
value = ""
|
|
189
|
+
is_float = false
|
|
190
|
+
|
|
191
|
+
# Optional negative sign
|
|
192
|
+
if current_char == '-'
|
|
193
|
+
value += current_char
|
|
194
|
+
advance
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Integer part
|
|
198
|
+
if current_char == '0'
|
|
199
|
+
value += current_char
|
|
200
|
+
advance
|
|
201
|
+
else
|
|
202
|
+
while current_char =~ /[0-9]/
|
|
203
|
+
value += current_char
|
|
204
|
+
advance
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Fractional part
|
|
209
|
+
if current_char == '.'
|
|
210
|
+
is_float = true
|
|
211
|
+
value += current_char
|
|
212
|
+
advance
|
|
213
|
+
|
|
214
|
+
raise ParseError, "Invalid number at #{start_line}:#{start_column}" unless current_char =~ /[0-9]/
|
|
215
|
+
|
|
216
|
+
while current_char =~ /[0-9]/
|
|
217
|
+
value += current_char
|
|
218
|
+
advance
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Exponent part
|
|
223
|
+
if current_char =~ /[eE]/
|
|
224
|
+
is_float = true
|
|
225
|
+
value += current_char
|
|
226
|
+
advance
|
|
227
|
+
|
|
228
|
+
if current_char =~ /[+-]/
|
|
229
|
+
value += current_char
|
|
230
|
+
advance
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
raise ParseError, "Invalid number at #{start_line}:#{start_column}" unless current_char =~ /[0-9]/
|
|
234
|
+
|
|
235
|
+
while current_char =~ /[0-9]/
|
|
236
|
+
value += current_char
|
|
237
|
+
advance
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
type = is_float ? :float : :int
|
|
242
|
+
Token.new(type, value, start_line, start_column)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def read_name(start_line, start_column)
|
|
246
|
+
value = ""
|
|
247
|
+
|
|
248
|
+
while current_char =~ /[a-zA-Z0-9_]/
|
|
249
|
+
value += current_char
|
|
250
|
+
advance
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Check for keywords
|
|
254
|
+
type = case value
|
|
255
|
+
when "true", "false"
|
|
256
|
+
:boolean
|
|
257
|
+
when "null"
|
|
258
|
+
:null
|
|
259
|
+
else
|
|
260
|
+
:name
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
Token.new(type, value, start_line, start_column)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
module GraphQLite
|
|
2
|
+
# Parser builds an AST from tokens
|
|
3
|
+
class Parser
|
|
4
|
+
# AST Node types
|
|
5
|
+
Document = Struct.new(:definitions)
|
|
6
|
+
OperationDefinition = Struct.new(:operation_type, :name, :variable_definitions, :directives, :selection_set)
|
|
7
|
+
FragmentDefinition = Struct.new(:name, :type_condition, :directives, :selection_set)
|
|
8
|
+
VariableDefinition = Struct.new(:variable, :type, :default_value, :directives)
|
|
9
|
+
SelectionSet = Struct.new(:selections)
|
|
10
|
+
Field = Struct.new(:alias, :name, :arguments, :directives, :selection_set)
|
|
11
|
+
FragmentSpread = Struct.new(:name, :directives)
|
|
12
|
+
InlineFragment = Struct.new(:type_condition, :directives, :selection_set)
|
|
13
|
+
Argument = Struct.new(:name, :value)
|
|
14
|
+
Directive = Struct.new(:name, :arguments)
|
|
15
|
+
Variable = Struct.new(:name)
|
|
16
|
+
|
|
17
|
+
# Values
|
|
18
|
+
IntValue = Struct.new(:value)
|
|
19
|
+
FloatValue = Struct.new(:value)
|
|
20
|
+
StringValue = Struct.new(:value)
|
|
21
|
+
BooleanValue = Struct.new(:value)
|
|
22
|
+
NullValue = Struct.new(:value)
|
|
23
|
+
EnumValue = Struct.new(:value)
|
|
24
|
+
ListValue = Struct.new(:values)
|
|
25
|
+
ObjectValue = Struct.new(:fields)
|
|
26
|
+
ObjectField = Struct.new(:name, :value)
|
|
27
|
+
|
|
28
|
+
# Types
|
|
29
|
+
NamedType = Struct.new(:name)
|
|
30
|
+
ListType = Struct.new(:type)
|
|
31
|
+
NonNullType = Struct.new(:type)
|
|
32
|
+
|
|
33
|
+
attr_reader :tokens, :pos
|
|
34
|
+
|
|
35
|
+
def initialize(source)
|
|
36
|
+
@lexer = Lexer.new(source)
|
|
37
|
+
@tokens = @lexer.tokenize
|
|
38
|
+
@pos = 0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def parse
|
|
42
|
+
definitions = []
|
|
43
|
+
|
|
44
|
+
while current_token.type != :eof
|
|
45
|
+
definitions << parse_definition
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Document.new(definitions)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def current_token
|
|
54
|
+
@tokens[@pos]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def peek_token(offset = 1)
|
|
58
|
+
@tokens[@pos + offset] || @tokens.last
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def advance
|
|
62
|
+
@pos += 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def expect(type)
|
|
66
|
+
token = current_token
|
|
67
|
+
raise ParseError, "Expected #{type} but got #{token.type} at #{token.line}:#{token.column}" unless token.type == type
|
|
68
|
+
advance
|
|
69
|
+
token
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def skip_token(type)
|
|
73
|
+
advance if current_token.type == type
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def parse_definition
|
|
77
|
+
token = current_token
|
|
78
|
+
|
|
79
|
+
case token.type
|
|
80
|
+
when :name
|
|
81
|
+
case token.value
|
|
82
|
+
when 'query', 'mutation', 'subscription'
|
|
83
|
+
parse_operation_definition
|
|
84
|
+
when 'fragment'
|
|
85
|
+
parse_fragment_definition
|
|
86
|
+
else
|
|
87
|
+
# Short-hand query (no operation keyword)
|
|
88
|
+
parse_operation_definition
|
|
89
|
+
end
|
|
90
|
+
when :lbrace
|
|
91
|
+
# Short-hand query
|
|
92
|
+
parse_operation_definition
|
|
93
|
+
else
|
|
94
|
+
raise ParseError, "Unexpected token #{token.type} at #{token.line}:#{token.column}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def parse_operation_definition
|
|
99
|
+
operation_type = 'query' # default
|
|
100
|
+
name = nil
|
|
101
|
+
variable_definitions = []
|
|
102
|
+
directives = []
|
|
103
|
+
|
|
104
|
+
if current_token.type == :name && %w[query mutation subscription].include?(current_token.value)
|
|
105
|
+
operation_type = current_token.value
|
|
106
|
+
advance
|
|
107
|
+
|
|
108
|
+
# Operation name (optional)
|
|
109
|
+
if current_token.type == :name
|
|
110
|
+
name = current_token.value
|
|
111
|
+
advance
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Variable definitions (optional)
|
|
115
|
+
if current_token.type == :lparen
|
|
116
|
+
variable_definitions = parse_variable_definitions
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Directives (optional)
|
|
120
|
+
directives = parse_directives
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Selection set (required)
|
|
124
|
+
selection_set = parse_selection_set
|
|
125
|
+
|
|
126
|
+
OperationDefinition.new(operation_type, name, variable_definitions, directives, selection_set)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def parse_fragment_definition
|
|
130
|
+
expect(:name) # 'fragment'
|
|
131
|
+
name = expect(:name).value
|
|
132
|
+
expect(:name) # 'on'
|
|
133
|
+
type_condition = parse_named_type
|
|
134
|
+
directives = parse_directives
|
|
135
|
+
selection_set = parse_selection_set
|
|
136
|
+
|
|
137
|
+
FragmentDefinition.new(name, type_condition, directives, selection_set)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_variable_definitions
|
|
141
|
+
expect(:lparen)
|
|
142
|
+
definitions = []
|
|
143
|
+
|
|
144
|
+
while current_token.type != :rparen
|
|
145
|
+
definitions << parse_variable_definition
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
expect(:rparen)
|
|
149
|
+
definitions
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def parse_variable_definition
|
|
153
|
+
expect(:dollar)
|
|
154
|
+
variable_name = expect(:name).value
|
|
155
|
+
expect(:colon)
|
|
156
|
+
type = parse_type
|
|
157
|
+
|
|
158
|
+
default_value = nil
|
|
159
|
+
if current_token.type == :equals
|
|
160
|
+
advance
|
|
161
|
+
default_value = parse_value_literal
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
directives = parse_directives
|
|
165
|
+
|
|
166
|
+
VariableDefinition.new(Variable.new(variable_name), type, default_value, directives)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def parse_type
|
|
170
|
+
type = if current_token.type == :lbracket
|
|
171
|
+
advance
|
|
172
|
+
inner_type = parse_type
|
|
173
|
+
expect(:rbracket)
|
|
174
|
+
ListType.new(inner_type)
|
|
175
|
+
else
|
|
176
|
+
parse_named_type
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
if current_token.type == :bang
|
|
180
|
+
advance
|
|
181
|
+
type = NonNullType.new(type)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
type
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def parse_named_type
|
|
188
|
+
name = expect(:name).value
|
|
189
|
+
NamedType.new(name)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def parse_selection_set
|
|
193
|
+
expect(:lbrace)
|
|
194
|
+
selections = []
|
|
195
|
+
|
|
196
|
+
while current_token.type != :rbrace
|
|
197
|
+
selections << parse_selection
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
expect(:rbrace)
|
|
201
|
+
SelectionSet.new(selections)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def parse_selection
|
|
205
|
+
if current_token.type == :spread
|
|
206
|
+
advance
|
|
207
|
+
if current_token.type == :name && current_token.value != 'on'
|
|
208
|
+
# Fragment spread
|
|
209
|
+
name = expect(:name).value
|
|
210
|
+
directives = parse_directives
|
|
211
|
+
FragmentSpread.new(name, directives)
|
|
212
|
+
else
|
|
213
|
+
# Inline fragment
|
|
214
|
+
type_condition = nil
|
|
215
|
+
if current_token.type == :name && current_token.value == 'on'
|
|
216
|
+
advance
|
|
217
|
+
type_condition = parse_named_type
|
|
218
|
+
end
|
|
219
|
+
directives = parse_directives
|
|
220
|
+
selection_set = parse_selection_set
|
|
221
|
+
InlineFragment.new(type_condition, directives, selection_set)
|
|
222
|
+
end
|
|
223
|
+
else
|
|
224
|
+
parse_field
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def parse_field
|
|
229
|
+
# Alias or field name
|
|
230
|
+
name_or_alias = expect(:name).value
|
|
231
|
+
|
|
232
|
+
field_name = name_or_alias
|
|
233
|
+
field_alias = nil
|
|
234
|
+
|
|
235
|
+
if current_token.type == :colon
|
|
236
|
+
advance
|
|
237
|
+
field_alias = name_or_alias
|
|
238
|
+
field_name = expect(:name).value
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Arguments
|
|
242
|
+
arguments = current_token.type == :lparen ? parse_arguments : []
|
|
243
|
+
|
|
244
|
+
# Directives
|
|
245
|
+
directives = parse_directives
|
|
246
|
+
|
|
247
|
+
# Selection set (optional)
|
|
248
|
+
selection_set = current_token.type == :lbrace ? parse_selection_set : nil
|
|
249
|
+
|
|
250
|
+
Field.new(field_alias, field_name, arguments, directives, selection_set)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def parse_arguments
|
|
254
|
+
expect(:lparen)
|
|
255
|
+
arguments = []
|
|
256
|
+
|
|
257
|
+
while current_token.type != :rparen
|
|
258
|
+
arguments << parse_argument
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
expect(:rparen)
|
|
262
|
+
arguments
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def parse_argument
|
|
266
|
+
name = expect(:name).value
|
|
267
|
+
expect(:colon)
|
|
268
|
+
value = parse_value
|
|
269
|
+
Argument.new(name, value)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def parse_directives
|
|
273
|
+
directives = []
|
|
274
|
+
|
|
275
|
+
while current_token.type == :at
|
|
276
|
+
advance
|
|
277
|
+
name = expect(:name).value
|
|
278
|
+
arguments = current_token.type == :lparen ? parse_arguments : []
|
|
279
|
+
directives << Directive.new(name, arguments)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
directives
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def parse_value
|
|
286
|
+
case current_token.type
|
|
287
|
+
when :dollar
|
|
288
|
+
advance
|
|
289
|
+
name = expect(:name).value
|
|
290
|
+
Variable.new(name)
|
|
291
|
+
else
|
|
292
|
+
parse_value_literal
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def parse_value_literal
|
|
297
|
+
token = current_token
|
|
298
|
+
|
|
299
|
+
case token.type
|
|
300
|
+
when :int
|
|
301
|
+
advance
|
|
302
|
+
IntValue.new(token.value.to_i)
|
|
303
|
+
when :float
|
|
304
|
+
advance
|
|
305
|
+
FloatValue.new(token.value.to_f)
|
|
306
|
+
when :string
|
|
307
|
+
advance
|
|
308
|
+
StringValue.new(token.value)
|
|
309
|
+
when :boolean
|
|
310
|
+
advance
|
|
311
|
+
BooleanValue.new(token.value == 'true')
|
|
312
|
+
when :null
|
|
313
|
+
advance
|
|
314
|
+
NullValue.new(nil)
|
|
315
|
+
when :name
|
|
316
|
+
advance
|
|
317
|
+
EnumValue.new(token.value)
|
|
318
|
+
when :lbracket
|
|
319
|
+
parse_list_value
|
|
320
|
+
when :lbrace
|
|
321
|
+
parse_object_value
|
|
322
|
+
else
|
|
323
|
+
raise ParseError, "Unexpected token #{token.type} at #{token.line}:#{token.column}"
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def parse_list_value
|
|
328
|
+
expect(:lbracket)
|
|
329
|
+
values = []
|
|
330
|
+
|
|
331
|
+
while current_token.type != :rbracket
|
|
332
|
+
values << parse_value_literal
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
expect(:rbracket)
|
|
336
|
+
ListValue.new(values)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def parse_object_value
|
|
340
|
+
expect(:lbrace)
|
|
341
|
+
fields = []
|
|
342
|
+
|
|
343
|
+
while current_token.type != :rbrace
|
|
344
|
+
name = expect(:name).value
|
|
345
|
+
expect(:colon)
|
|
346
|
+
value = parse_value_literal
|
|
347
|
+
fields << ObjectField.new(name, value)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
expect(:rbrace)
|
|
351
|
+
ObjectValue.new(fields)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|