Skip to content

Commit be7f8a8

Browse files
authored
Merge pull request #2 from qequ/feature/refactor
refactor
2 parents 7a2aebf + 95c353f commit be7f8a8

8 files changed

Lines changed: 714 additions & 647 deletions

File tree

‎shard.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: jmespath
2-
version: 0.1.0
2+
version: 0.2.0
33

44
description: |
55
A Crystal implementation of JMESPath - JSON Query Language

‎src/ast.cr‎

Lines changed: 0 additions & 96 deletions
This file was deleted.

‎src/jmespath.cr‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
require "json"
22
require "./lexer"
33
require "./exceptions"
4-
require "./ast"
4+
require "./nodes"
55
require "./parser"
6-
require "./visitor"
76
require "./parsed_result"
87

98
module JMESPath
10-
VERSION = "0.1.0"
9+
VERSION = "0.2.0"
1110

1211
# Main entry point for JMESPath expressions
1312
#

‎src/lexer.cr‎

Lines changed: 44 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -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
1516
end
1617

1718
class 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
269244
end

0 commit comments

Comments
 (0)