Skip to content

Commit 5bdff8b

Browse files
committedJun 1, 2021
Added more informative errors
1 parent 1149259 commit 5bdff8b

File tree

2 files changed

+73
-27
lines changed

2 files changed

+73
-27
lines changed
 

‎Sources/JMESPath/Parser.swift

+37-27
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,14 @@ class Parser {
6868
exitLoop: while true {
6969
let pair = try parseKeyValuePair()
7070
pairs[pair.key] = pair.value
71-
switch self.advance() {
71+
let token = self.advance()
72+
switch token {
7273
case .rightBrace:
7374
break exitLoop
7475
case .comma:
7576
break
7677
default:
77-
throw JMESPathError.compileTime("Expected '}' or ','")
78+
throw JMESPathError.compileTime("Expected '}' or ',', not a '\(token)'")
7879
}
7980
}
8081
return .multiHash(elements: pairs)
@@ -92,21 +93,23 @@ class Parser {
9293

9394
case .leftParenthesis:
9495
let result = try expression(rbp: 0)
95-
switch self.advance() {
96+
let token = self.advance()
97+
switch token {
9698
case .rightParenthesis:
9799
return result
98100
default:
99-
throw JMESPathError.compileTime("Expected ')' to close '('")
101+
throw JMESPathError.compileTime("Expected ')' to close '(', not a '\(token)'")
100102
}
101103

102104
default:
103-
throw JMESPathError.compileTime("Unexpected token")
105+
throw JMESPathError.compileTime("Unexpected token '\(token)'")
104106
}
105107
}
106108

107109
/// left denotation, tail handler function
108110
func led(left: Ast) throws -> Ast {
109-
switch self.advance() {
111+
let token = self.advance()
112+
switch token {
110113
case .dot:
111114
if self.peek() == .star {
112115
self.advance()
@@ -118,13 +121,14 @@ class Parser {
118121

119122
case .leftBracket:
120123
var isNumber: Bool
121-
switch self.peek() {
124+
let token = self.peek()
125+
switch token {
122126
case .number, .colon:
123127
isNumber = true
124128
case .star:
125129
isNumber = false
126130
default:
127-
throw JMESPathError.compileTime("Expected number, ':' or '*'")
131+
throw JMESPathError.compileTime("Expected number, ':' or '*', not a '\(token)'")
128132
}
129133
if isNumber {
130134
return .subExpr(lhs: left, rhs: try self.parseIndex())
@@ -150,7 +154,7 @@ class Parser {
150154
case .field(let name):
151155
return .function(name: name, args: try self.parseList(closing: .rightParenthesis))
152156
default:
153-
throw JMESPathError.compileTime("Invalid function name")
157+
throw JMESPathError.compileTime("Invalid function name '\(left)'")
154158
}
155159

156160
case .flatten:
@@ -173,34 +177,37 @@ class Parser {
173177
return try self.parseComparator(Comparator.greaterThanOrEqual, lhs: left)
174178

175179
default:
176-
throw JMESPathError.compileTime("Unexpected token")
180+
throw JMESPathError.compileTime("Unexpected token '\(token)'")
177181
}
178182
}
179183

180184
/// key : value
181185
func parseKeyValuePair() throws -> (key: String, value: Ast) {
182-
switch self.advance() {
186+
let token = self.advance()
187+
switch token {
183188
case .identifier(let value), .quotedIdentifier(let value):
184-
if self.peek() == .colon {
189+
let token2 = self.peek()
190+
if token2 == .colon {
185191
self.advance()
186192
return (key: value, value: try self.expression(rbp: 0))
187193
} else {
188-
throw JMESPathError.compileTime("Expected a ':' to follow key")
194+
throw JMESPathError.compileTime("Expected a ':' to follow key, not a '\(token2)'")
189195
}
190196
default:
191-
throw JMESPathError.compileTime("Expected field to start key value pair")
197+
throw JMESPathError.compileTime("Expected field to start key value pair, not a '\(token)'")
192198
}
193199
}
194200

195201
/// [?...]
196202
func parseFilter(lhs: Ast) throws -> Ast {
197203
let conditionLHS = try self.expression(rbp: 0)
198-
switch self.advance() {
204+
let token = self.advance()
205+
switch token {
199206
case .rightBracket:
200207
let conditionRHS = try projectionRHS(lbp: Token.filter.lbp)
201208
return .projection(lhs: lhs, rhs: .condition(predicate: conditionLHS, then: conditionRHS))
202209
default:
203-
throw JMESPathError.compileTime("Expected a ']' to end filter")
210+
throw JMESPathError.compileTime("Expected a ']' to end filter, not '\(token)'")
204211
}
205212
}
206213

@@ -221,13 +228,14 @@ class Parser {
221228

222229
func parseDot(lbp: Int) throws -> Ast {
223230
let isMultiList: Bool
224-
switch self.peek() {
231+
let token = self.peek()
232+
switch token {
225233
case .leftBracket:
226234
isMultiList = true
227235
case .identifier, .quotedIdentifier, .star, .leftBrace, .ampersand:
228236
isMultiList = false
229237
default:
230-
throw JMESPathError.compileTime("Expected identifier, '*', '{', '[', '&', or '[?'")
238+
throw JMESPathError.compileTime("Expected identifier, '*', '{', '[', '&', or '[?', not '\(token)'")
231239
}
232240
if isMultiList {
233241
self.advance()
@@ -249,7 +257,7 @@ class Parser {
249257
case (_, 0..<projectionStop):
250258
return .identity
251259
default:
252-
throw JMESPathError.compileTime("Expected '.', '[', or '[?'")
260+
throw JMESPathError.compileTime("Expected '.', '[', or '[?', not '\(token)'")
253261
}
254262
if isDot {
255263
self.advance()
@@ -260,12 +268,13 @@ class Parser {
260268
}
261269

262270
func parseWildcardIndex(lhs: Ast) throws -> Ast {
263-
switch self.advance() {
271+
let token = self.advance()
272+
switch token {
264273
case .rightBracket:
265274
let rhs = try projectionRHS(lbp: Token.star.lbp)
266275
return .projection(lhs: lhs, rhs: rhs)
267276
default:
268-
throw JMESPathError.compileTime("Expected ']' after wildcard index")
277+
throw JMESPathError.compileTime("Expected ']' after wildcard index, not '\(token)'")
269278
}
270279
}
271280

@@ -287,7 +296,7 @@ class Parser {
287296
if self.peek() == .comma {
288297
self.advance()
289298
if self.peek() == closing {
290-
throw JMESPathError.compileTime("Invalid token after ','")
299+
throw JMESPathError.compileTime("Invalid token '\(self.peek())' after ','")
291300
}
292301
}
293302
}
@@ -300,14 +309,15 @@ class Parser {
300309
var parts: [Int?] = [nil, nil, nil]
301310
var index = 0
302311
exitLoop: while true {
303-
switch self.advance() {
312+
let token = self.advance()
313+
switch token {
304314
case .number(let value):
305315
parts[index] = value
306316
switch self.peek() {
307317
case .colon, .rightBracket:
308318
break
309319
default:
310-
throw JMESPathError.compileTime("Expected ':' or ']' after index")
320+
throw JMESPathError.compileTime("Expected ':' or ']' after index, not '\(self.peek())'")
311321
}
312322

313323
case .rightBracket:
@@ -322,19 +332,19 @@ class Parser {
322332
case .number, .colon, .rightBracket:
323333
break
324334
default:
325-
throw JMESPathError.compileTime("Expected number, ':' or ']'")
335+
throw JMESPathError.compileTime("Expected number, ':' or ']', not '\(self.peek())'")
326336
}
327337

328338
default:
329-
throw JMESPathError.compileTime("Expected number, ':' or ']'")
339+
throw JMESPathError.compileTime("Expected number, ':' or ']', not '\(token)'")
330340
}
331341
}
332342

333343
if index == 0 {
334344
if let part = parts[0] {
335345
return .index(index: part)
336346
} else {
337-
throw JMESPathError.compileTime("Expected number")
347+
throw JMESPathError.compileTime("Expected a number")
338348
}
339349
} else {
340350
let step = parts[2] ?? 1

‎Sources/JMESPath/Token.swift

+36
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,39 @@ extension Token {
5757
}
5858
}
5959
}
60+
61+
extension Token: CustomStringConvertible {
62+
var description: String {
63+
switch self {
64+
case .identifier(let string): return string
65+
case .quotedIdentifier(let string): return string
66+
case .number(let number): return "\(number)"
67+
case .literal(_): return "`...`"
68+
case .dot: return "."
69+
case .star: return "*"
70+
case .flatten: return "[]"
71+
case .and: return "&&"
72+
case .or: return "||"
73+
case .pipe: return "|"
74+
case .filter: return "[?"
75+
case .leftBracket: return "["
76+
case .rightBracket: return "]"
77+
case .comma: return ","
78+
case .colon: return ":"
79+
case .not: return "!"
80+
case .notEqual: return "!="
81+
case .equals: return "=="
82+
case .greaterThan: return ">"
83+
case .greaterThanOrEqual: return ">="
84+
case .lessThan: return "<"
85+
case .lessThanOrEqual: return "<="
86+
case .at: return "@"
87+
case .ampersand: return "&"
88+
case .leftParenthesis: return "("
89+
case .rightParenthesis: return ")"
90+
case .leftBrace: return "{"
91+
case .rightBrace: return "}"
92+
case .eof: return "EOF"
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)
Please sign in to comment.