@@ -7,15 +7,16 @@ struct Token
77 property value : Char | Int32 | String | Nil
88 property start : Char | Int32 | String | Nil
99 property end : Char | Int32 | String | Nil
10+ property json_value : JSON ::Any ?
1011
1112 NULL_TOKEN = Token .new(" eof" , " " , nil , nil )
1213
13- def initialize (@type , @value , @start , @end )
14+ def initialize (@type , @value , @start , @end , @json_value = nil )
1415 end
1516end
1617
1718class Lexer
18- property current : Char ? # @current can be a Char or Nil, initialized later
19+ property current : Char ?
1920 property expression : String = " "
2021 property chars : Array (Char ) = [] of Char
2122 property position : Int32 = 0
@@ -57,49 +58,45 @@ class Lexer
5758 tokens = [] of Token
5859 while @current
5960 if SIMPLE_TOKENS .keys.includes?(@current )
60- token = token(@current , SIMPLE_TOKENS [@current ]).tap { next_char }
61+ tok = Token .new(SIMPLE_TOKENS [@current .not_nil!], @current , @position , @position + 1 )
62+ next_char
63+ tokens << tok
6164 elsif START_IDENTIFIER .includes?(@current )
62- token = identifier
65+ tokens << identifier
6366 elsif WHITESPACE .includes?(@current )
6467 next_char
65- token = nil
6668 elsif @current == '['
67- token = handle_bracket
68- elsif @current == " ' "
69- token = consume_raw_string_literal
69+ tokens << handle_bracket
70+ elsif @current == ' \' '
71+ tokens << consume_raw_string_literal
7072 elsif @current == '|'
71- token = match_or_else('|' , " or" , " pipe" )
73+ tokens << match_or_else('|' , " or" , " pipe" )
7274 elsif @current == '&'
73- token = match_or_else('&' , " and" , " expref" )
75+ tokens << match_or_else('&' , " and" , " expref" )
7476 elsif @current == '<'
75- token = match_or_else('=' , " lte" , " lt" )
77+ tokens << match_or_else('=' , " lte" , " lt" )
7678 elsif @current == '>'
77- token = match_or_else('=' , " gte" , " gt" )
79+ tokens << match_or_else('=' , " gte" , " gt" )
7880 elsif @current == '!'
79- token = match_or_else('=' , " ne" , " not" )
81+ tokens << match_or_else('=' , " ne" , " not" )
8082 elsif @current == '='
81- token = equal_sign
83+ tokens << equal_sign
8284 elsif @current == '`'
83- token = consume_literal
85+ tokens << consume_literal
8486 elsif VALID_NUMBER .includes?(@current )
85- token = number
87+ tokens << number
8688 elsif @current == '-'
87- token = negative_number
89+ tokens << negative_number
8890 elsif @current == '"'
89- token = consume_quoted_identifier
91+ tokens << consume_quoted_identifier
9092 else
9193 raise LexerError .new(@position , @current .to_s, " Unknown token #{ @current } " )
9294 end
93- tokens << Token .new(token[" type" ].to_s, token[" value" ], token[" start" ], token[" end" ]) if token
9495 end
9596 tokens << Token ::NULL_TOKEN unless tokens.empty? || tokens.last.type == " eof"
9697 tokens
9798 end
9899
99- private def token (value , type )
100- {" type" => type , " value" => value, " start" => @position , " end" => @position + 1 }
101- end
102-
103100 private def next_char : Char ?
104101 @position += 1
105102 if @position < @length
@@ -110,7 +107,7 @@ class Lexer
110107 @current
111108 end
112109
113- private def identifier : Hash ( String , String | Int32 )
110+ private def identifier : Token
114111 start = @position
115112 buffer = String .build do |str |
116113 str << @current .to_s
@@ -120,21 +117,21 @@ class Lexer
120117 next_char
121118 end
122119 end
123- { " type " => " unquoted_identifier" , " value " => buffer, " start" => start, " end " => @position }
120+ Token .new( " unquoted_identifier" , buffer, start, @position )
124121 end
125122
126- private def handle_bracket : Hash ( String , String | Int32 )
123+ private def handle_bracket : Token
127124 start = @position
128125 next_char
129126 case @current
130127 when ']'
131128 next_char
132- { " type " => " flatten" , " value " => " []" , " start" => start, " end " => @position }
129+ Token .new( " flatten" , " []" , start, @position )
133130 when '?'
134131 next_char
135- { " type " => " filter" , " value " => " [?" , " start" => start, " end " => @position }
132+ Token .new( " filter" , " [?" , start, @position )
136133 else
137- { " type " => " lbracket" , " value " => " [" , " start" => start, " end " => @position }
134+ Token .new( " lbracket" , " [" , start, @position )
138135 end
139136 end
140137
@@ -159,68 +156,51 @@ class Lexer
159156 buffer.to_s
160157 end
161158
162- private def consume_raw_string_literal : Hash ( String , String | Int32 )
159+ private def consume_raw_string_literal : Token
163160 start = @position
164161 buffer = consume_until('\' ' )
165- { " type " => " literal" , " value " => buffer, " start" => start, " end " => @position }
162+ Token .new( " literal" , buffer, start, @position , JSON :: Any .new(buffer))
166163 end
167164
168- private def consume_quoted_identifier : Hash ( String , String | Int32 )
165+ private def consume_quoted_identifier : Token
169166 start = @position
170167 buffer = consume_until('"' )
171168 begin
172169 parsed_value = JSON .parse(%Q[ "#{ buffer } "] )
173- # Emsure the value is a string or Int32
174170 value = case parsed_value
175171 when JSON ::Any
176172 parsed_value.as_i? || parsed_value.as_s
177173 else
178174 parsed_value.to_s
179175 end
180176 raise LexerError .new(@position , buffer, " Invalid JSON value type" ) unless value
181- { " type " => " quoted_identifier" , " value" => value, " start" => start, " end " => @position }
177+ Token .new( " quoted_identifier" , value, start, @position )
182178 rescue ex : JSON ::ParseException
183179 raise LexerError .new(@position , buffer, " Invalid JSON format: #{ ex.message } " )
184180 end
185181 end
186182
187- private def parse_value (parsed_value )
188- case parsed_value
189- when JSON ::Any
190- parsed_value.as_i? || parsed_value.as_s
191- else
192- parsed_value.to_s
193- end
194- end
195-
196- private def consume_literal : Hash (String , String | Int32 )
183+ private def consume_literal : Token
197184 start = @position
198185 lexeme = consume_until('`' )
199186 begin
200- parsed_value = JSON .parse(lexeme)
201- value = parse_value(parsed_value)
202- raise LexerError .new(@position , lexeme, " Invalid JSON value type" ) unless value
203- {" type" => " literal" , " value" => value, " start" => start, " end" => @position }
187+ parsed_json = JSON .parse(lexeme)
188+ Token .new(" literal" , lexeme, start, @position , parsed_json)
204189 rescue ex : JSON ::ParseException
205- # Invalid JSON values should be converted to quoted
206- # JSON strings during the JEP-12 deprecation period.
207- # call JSON.parse with the string wrapped in double quotes
208- parsed_value = JSON .parse(%Q[ "#{ lexeme } "] )
209- value = parse_value(parsed_value)
210- raise LexerError .new(@position , lexeme, " Invalid JSON format: #{ ex.message } " ) unless value
211- {" type" => " quoted_identifier" , " value" => value, " start" => start, " end" => @position }
190+ # JEP-12 deprecation: invalid JSON values are treated as quoted strings
191+ Token .new(" literal" , lexeme, start, @position , JSON ::Any .new(lexeme))
212192 end
213193 end
214194
215- private def match_or_else (expected : Char , match_type : String , else_type : String ) : Hash ( String , String | Int32 )
195+ private def match_or_else (expected : Char , match_type : String , else_type : String ) : Token
216196 start = @position
217197 value = @current .to_s
218198 nc = next_char
219199 if nc == expected
220200 next_char
221- { " type " => match_type, " value " => " #{ value } #{ expected } " , " start" => start, " end " => @position }
201+ Token .new( match_type, " #{ value } #{ expected } " , start, @position )
222202 else
223- { " type " => else_type, " value" => value, " start" => start, " end " => start + 1 }
203+ Token .new( else_type, value, start, start + 1 )
224204 end
225205 end
226206
@@ -234,36 +214,31 @@ class Lexer
234214 buffer
235215 end
236216
237- private def number : Hash ( String , String | Int32 )
217+ private def number : Token
238218 start = @position
239219 number_string = consume_number
240- { " type " => " number" , " value " => number_string.to_i, " start" => start, " end " => @position }
220+ Token .new( " number" , number_string.to_i, start, @position )
241221 end
242222
243- private def negative_number : Hash ( String , String | Int32 )
223+ private def negative_number : Token
244224 start = @position
245225 next_char
246226 if VALID_NUMBER .includes?(@current )
247227 number_string = " -" + consume_number
248- { " type " => " number" , " value " => number_string.to_i32, " start" => start, " end " => @position }
228+ Token .new( " number" , number_string.to_i32, start, @position )
249229 else
250230 raise LexerError .new(@position , @current .to_s, " Unknown token '-'" )
251231 end
252232 end
253233
254- private def equal_sign : Hash ( String , String | Int32 )
234+ private def equal_sign : Token
255235 start = @position
256236 if next_char == '='
257237 next_char
258- { " type " => " eq" , " value " => " == " , " start" => start, " end " => @position }
238+ Token .new( " eq" , " == " , start, @position )
259239 else
260- # If we're at the EOF, we never advanced the position
261240 position = @current .nil? ? @position : @position - 1
262241 raise LexerError .new(position, " =" , " Unknown token '='" )
263242 end
264243 end
265-
266- private def eof_token : Hash (String , String | Int32 )
267- {" type" => " eof" , " value" => " " , " start" => @length , " end" => @length }
268- end
269244end
0 commit comments