squirreling 0.4.8 → 0.6.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.
- package/README.md +5 -1
- package/package.json +3 -3
- package/src/backend/dataSource.js +15 -14
- package/src/execute/aggregates.js +10 -4
- package/src/execute/execute.js +58 -40
- package/src/execute/expression.js +212 -54
- package/src/execute/having.js +7 -2
- package/src/execute/join.js +35 -34
- package/src/execute/math.js +340 -0
- package/src/execute/utils.js +2 -2
- package/src/executionErrors.js +63 -0
- package/src/index.js +1 -0
- package/src/parse/comparison.js +41 -8
- package/src/parse/expression.js +53 -13
- package/src/parse/state.js +13 -3
- package/src/parse/tokenize.js +34 -21
- package/src/parseErrors.js +118 -0
- package/src/types.d.ts +55 -16
- package/src/validation.js +14 -1
- package/src/validationErrors.js +138 -0
- package/src/errors.js +0 -230
package/src/parse/comparison.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { syntaxError } from '../
|
|
1
|
+
import { syntaxError } from '../parseErrors.js'
|
|
2
2
|
import { isBinaryOp } from '../validation.js'
|
|
3
3
|
import { parseAdditive, parseExpression, parseSubquery } from './expression.js'
|
|
4
|
-
import { consume, current, expect, match, peekToken } from './state.js'
|
|
4
|
+
import { consume, current, expect, lastPosition, match, peekToken } from './state.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @import { ExprNode, ParserState } from '../types.js'
|
|
@@ -26,6 +26,8 @@ export function parseComparison(state) {
|
|
|
26
26
|
type: 'unary',
|
|
27
27
|
op: 'IS NOT NULL',
|
|
28
28
|
argument: left,
|
|
29
|
+
positionStart: left.positionStart,
|
|
30
|
+
positionEnd: lastPosition(state),
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
expect(state, 'keyword', 'NULL')
|
|
@@ -33,6 +35,8 @@ export function parseComparison(state) {
|
|
|
33
35
|
type: 'unary',
|
|
34
36
|
op: 'IS NULL',
|
|
35
37
|
argument: left,
|
|
38
|
+
positionStart: left.positionStart,
|
|
39
|
+
positionEnd: lastPosition(state),
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -40,6 +44,7 @@ export function parseComparison(state) {
|
|
|
40
44
|
if (tok.type === 'keyword' && tok.value === 'NOT') {
|
|
41
45
|
const nextTok = peekToken(state, 1)
|
|
42
46
|
if (nextTok.type === 'keyword' && nextTok.value === 'LIKE') {
|
|
47
|
+
const notPositionStart = tok.positionStart
|
|
43
48
|
consume(state) // NOT
|
|
44
49
|
consume(state) // LIKE
|
|
45
50
|
const right = parseAdditive(state)
|
|
@@ -51,7 +56,11 @@ export function parseComparison(state) {
|
|
|
51
56
|
op: 'LIKE',
|
|
52
57
|
left,
|
|
53
58
|
right,
|
|
59
|
+
positionStart: left.positionStart,
|
|
60
|
+
positionEnd: right.positionEnd,
|
|
54
61
|
},
|
|
62
|
+
positionStart: notPositionStart,
|
|
63
|
+
positionEnd: right.positionEnd,
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
}
|
|
@@ -64,6 +73,8 @@ export function parseComparison(state) {
|
|
|
64
73
|
op: 'LIKE',
|
|
65
74
|
left,
|
|
66
75
|
right,
|
|
76
|
+
positionStart: left.positionStart,
|
|
77
|
+
positionEnd: right.positionEnd,
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
|
|
@@ -71,6 +82,7 @@ export function parseComparison(state) {
|
|
|
71
82
|
if (tok.type === 'keyword' && tok.value === 'NOT') {
|
|
72
83
|
const nextTok = peekToken(state, 1)
|
|
73
84
|
if (nextTok.type === 'keyword' && nextTok.value === 'BETWEEN') {
|
|
85
|
+
const notPositionStart = tok.positionStart
|
|
74
86
|
consume(state) // NOT
|
|
75
87
|
consume(state) // BETWEEN
|
|
76
88
|
const lower = parseAdditive(state)
|
|
@@ -80,8 +92,10 @@ export function parseComparison(state) {
|
|
|
80
92
|
return {
|
|
81
93
|
type: 'binary',
|
|
82
94
|
op: 'OR',
|
|
83
|
-
left: { type: 'binary', op: '<', left, right: lower },
|
|
84
|
-
right: { type: 'binary', op: '>', left, right: upper },
|
|
95
|
+
left: { type: 'binary', op: '<', left, right: lower, positionStart: left.positionStart, positionEnd: lower.positionEnd },
|
|
96
|
+
right: { type: 'binary', op: '>', left, right: upper, positionStart: left.positionStart, positionEnd: upper.positionEnd },
|
|
97
|
+
positionStart: notPositionStart,
|
|
98
|
+
positionEnd: upper.positionEnd,
|
|
85
99
|
}
|
|
86
100
|
}
|
|
87
101
|
}
|
|
@@ -95,8 +109,10 @@ export function parseComparison(state) {
|
|
|
95
109
|
return {
|
|
96
110
|
type: 'binary',
|
|
97
111
|
op: 'AND',
|
|
98
|
-
left: { type: 'binary', op: '>=', left, right: lower },
|
|
99
|
-
right: { type: 'binary', op: '<=', left, right: upper },
|
|
112
|
+
left: { type: 'binary', op: '>=', left, right: lower, positionStart: left.positionStart, positionEnd: lower.positionEnd },
|
|
113
|
+
right: { type: 'binary', op: '<=', left, right: upper, positionStart: left.positionStart, positionEnd: upper.positionEnd },
|
|
114
|
+
positionStart: left.positionStart,
|
|
115
|
+
positionEnd: upper.positionEnd,
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
|
|
@@ -104,6 +120,7 @@ export function parseComparison(state) {
|
|
|
104
120
|
if (tok.type === 'keyword' && tok.value === 'NOT') {
|
|
105
121
|
const nextTok = peekToken(state, 1)
|
|
106
122
|
if (nextTok.type === 'keyword' && nextTok.value === 'IN') {
|
|
123
|
+
const notPositionStart = tok.positionStart
|
|
107
124
|
consume(state) // NOT
|
|
108
125
|
consume(state) // IN
|
|
109
126
|
|
|
@@ -111,12 +128,13 @@ export function parseComparison(state) {
|
|
|
111
128
|
// parseSubquery expects to consume the opening paren itself
|
|
112
129
|
const parenTok = current(state)
|
|
113
130
|
if (parenTok.type !== 'paren' || parenTok.value !== '(') {
|
|
114
|
-
throw syntaxError({ expected: '(', received: `"${parenTok.value}"`,
|
|
131
|
+
throw syntaxError({ expected: '(', received: `"${parenTok.value}"`, positionStart: parenTok.positionStart, positionEnd: parenTok.positionEnd, after: 'IN' })
|
|
115
132
|
}
|
|
116
133
|
const peekTok = peekToken(state, 1)
|
|
117
134
|
if (peekTok.type === 'keyword' && peekTok.value === 'SELECT') {
|
|
118
135
|
// Subquery - let parseSubquery handle the parens
|
|
119
136
|
const subquery = parseSubquery(state)
|
|
137
|
+
const positionEnd = lastPosition(state)
|
|
120
138
|
return {
|
|
121
139
|
type: 'unary',
|
|
122
140
|
op: 'NOT',
|
|
@@ -124,7 +142,11 @@ export function parseComparison(state) {
|
|
|
124
142
|
type: 'in',
|
|
125
143
|
expr: left,
|
|
126
144
|
subquery,
|
|
145
|
+
positionStart: left.positionStart,
|
|
146
|
+
positionEnd,
|
|
127
147
|
},
|
|
148
|
+
positionStart: notPositionStart,
|
|
149
|
+
positionEnd,
|
|
128
150
|
}
|
|
129
151
|
} else {
|
|
130
152
|
// Parse list of values - we handle the parens
|
|
@@ -136,6 +158,7 @@ export function parseComparison(state) {
|
|
|
136
158
|
if (!match(state, 'comma')) break
|
|
137
159
|
}
|
|
138
160
|
expect(state, 'paren', ')')
|
|
161
|
+
const positionEnd = lastPosition(state)
|
|
139
162
|
return {
|
|
140
163
|
type: 'unary',
|
|
141
164
|
op: 'NOT',
|
|
@@ -143,7 +166,11 @@ export function parseComparison(state) {
|
|
|
143
166
|
type: 'in valuelist',
|
|
144
167
|
expr: left,
|
|
145
168
|
values,
|
|
169
|
+
positionStart: left.positionStart,
|
|
170
|
+
positionEnd,
|
|
146
171
|
},
|
|
172
|
+
positionStart: notPositionStart,
|
|
173
|
+
positionEnd,
|
|
147
174
|
}
|
|
148
175
|
}
|
|
149
176
|
}
|
|
@@ -156,7 +183,7 @@ export function parseComparison(state) {
|
|
|
156
183
|
// parseSubquery expects to consume the opening paren itself
|
|
157
184
|
const parenTok = current(state)
|
|
158
185
|
if (parenTok.type !== 'paren' || parenTok.value !== '(') {
|
|
159
|
-
throw syntaxError({ expected: '(', received: `"${parenTok.value}"`,
|
|
186
|
+
throw syntaxError({ expected: '(', received: `"${parenTok.value}"`, positionStart: parenTok.positionStart, positionEnd: parenTok.positionEnd, after: 'IN' })
|
|
160
187
|
}
|
|
161
188
|
const peekTok = peekToken(state, 1)
|
|
162
189
|
if (peekTok.type === 'keyword' && peekTok.value === 'SELECT') {
|
|
@@ -166,6 +193,8 @@ export function parseComparison(state) {
|
|
|
166
193
|
type: 'in',
|
|
167
194
|
expr: left,
|
|
168
195
|
subquery,
|
|
196
|
+
positionStart: left.positionStart,
|
|
197
|
+
positionEnd: lastPosition(state),
|
|
169
198
|
}
|
|
170
199
|
} else {
|
|
171
200
|
// Parse list of values - we handle the parens
|
|
@@ -181,6 +210,8 @@ export function parseComparison(state) {
|
|
|
181
210
|
type: 'in valuelist',
|
|
182
211
|
expr: left,
|
|
183
212
|
values,
|
|
213
|
+
positionStart: left.positionStart,
|
|
214
|
+
positionEnd: lastPosition(state),
|
|
184
215
|
}
|
|
185
216
|
}
|
|
186
217
|
}
|
|
@@ -193,6 +224,8 @@ export function parseComparison(state) {
|
|
|
193
224
|
op: tok.value,
|
|
194
225
|
left,
|
|
195
226
|
right,
|
|
227
|
+
positionStart: left.positionStart,
|
|
228
|
+
positionEnd: right.positionEnd,
|
|
196
229
|
}
|
|
197
230
|
}
|
|
198
231
|
|
package/src/parse/expression.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
missingClauseError,
|
|
4
4
|
syntaxError,
|
|
5
5
|
unknownFunctionError,
|
|
6
|
-
} from '../
|
|
7
|
-
import { isAggregateFunc, isIntervalUnit, isStringFunc } from '../validation.js'
|
|
6
|
+
} from '../parseErrors.js'
|
|
7
|
+
import { isAggregateFunc, isIntervalUnit, isMathFunc, isStringFunc } from '../validation.js'
|
|
8
8
|
import { parseComparison } from './comparison.js'
|
|
9
9
|
import { parseSelectInternal } from './parse.js'
|
|
10
|
-
import { consume, current, expect, expectIdentifier, match, peekToken } from './state.js'
|
|
10
|
+
import { consume, current, expect, expectIdentifier, lastPosition, match, peekToken } from './state.js'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @import { ExprNode, IntervalNode, ParserState, SelectStatement, WhenClause } from '../types.js'
|
|
@@ -18,6 +18,7 @@ import { consume, current, expect, expectIdentifier, match, peekToken } from './
|
|
|
18
18
|
* @returns {IntervalNode}
|
|
19
19
|
*/
|
|
20
20
|
function parseInterval(state) {
|
|
21
|
+
const { positionStart } = current(state)
|
|
21
22
|
consume(state) // INTERVAL
|
|
22
23
|
|
|
23
24
|
// Handle optional negative sign
|
|
@@ -39,11 +40,11 @@ function parseInterval(state) {
|
|
|
39
40
|
consume(state)
|
|
40
41
|
const parsed = parseFloat(valueTok.value)
|
|
41
42
|
if (isNaN(parsed)) {
|
|
42
|
-
throw invalidLiteralError({ type: 'interval value', value: valueTok.value,
|
|
43
|
+
throw invalidLiteralError({ type: 'interval value', value: valueTok.value, positionStart: valueTok.positionStart, positionEnd: valueTok.positionEnd })
|
|
43
44
|
}
|
|
44
45
|
value = sign * parsed
|
|
45
46
|
} else {
|
|
46
|
-
throw syntaxError({ expected: 'interval value (number)', received: `"${valueTok.value}"`,
|
|
47
|
+
throw syntaxError({ expected: 'interval value (number)', received: `"${valueTok.value}"`, positionStart: valueTok.positionStart, positionEnd: valueTok.positionEnd })
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
// Get unit keyword
|
|
@@ -52,13 +53,14 @@ function parseInterval(state) {
|
|
|
52
53
|
throw invalidLiteralError({
|
|
53
54
|
type: 'interval unit',
|
|
54
55
|
value: unitTok.value,
|
|
55
|
-
|
|
56
|
+
positionStart: unitTok.positionStart,
|
|
57
|
+
positionEnd: unitTok.positionEnd,
|
|
56
58
|
validValues: 'DAY, MONTH, YEAR, HOUR, MINUTE, SECOND',
|
|
57
59
|
})
|
|
58
60
|
}
|
|
59
61
|
consume(state)
|
|
60
62
|
|
|
61
|
-
return { type: 'interval', value, unit: unitTok.value }
|
|
63
|
+
return { type: 'interval', value, unit: unitTok.value, positionStart, positionEnd: lastPosition(state) }
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/**
|
|
@@ -75,6 +77,7 @@ export function parseExpression(state) {
|
|
|
75
77
|
*/
|
|
76
78
|
export function parsePrimary(state) {
|
|
77
79
|
const tok = current(state)
|
|
80
|
+
const { positionStart } = tok
|
|
78
81
|
|
|
79
82
|
if (tok.type === 'paren' && tok.value === '(') {
|
|
80
83
|
// Peek ahead to see if this is a scalar subquery
|
|
@@ -85,6 +88,8 @@ export function parsePrimary(state) {
|
|
|
85
88
|
return {
|
|
86
89
|
type: 'subquery',
|
|
87
90
|
subquery,
|
|
91
|
+
positionStart,
|
|
92
|
+
positionEnd: lastPosition(state),
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
// Regular grouped expression
|
|
@@ -109,6 +114,8 @@ export function parsePrimary(state) {
|
|
|
109
114
|
type: 'cast',
|
|
110
115
|
expr,
|
|
111
116
|
toType: typeTok.value,
|
|
117
|
+
positionStart,
|
|
118
|
+
positionEnd: lastPosition(state),
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -117,8 +124,8 @@ export function parsePrimary(state) {
|
|
|
117
124
|
const funcName = tok.value
|
|
118
125
|
|
|
119
126
|
// validate function names
|
|
120
|
-
if (!isStringFunc(funcName) && !isAggregateFunc(funcName)) {
|
|
121
|
-
throw unknownFunctionError(funcName, tok.
|
|
127
|
+
if (!isStringFunc(funcName) && !isAggregateFunc(funcName) && !isMathFunc(funcName)) {
|
|
128
|
+
throw unknownFunctionError({ funcName, positionStart: tok.positionStart, positionEnd: tok.positionEnd })
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
consume(state) // function name
|
|
@@ -131,10 +138,13 @@ export function parsePrimary(state) {
|
|
|
131
138
|
while (true) {
|
|
132
139
|
// Handle COUNT(*) - treat * as a special identifier
|
|
133
140
|
if (current(state).type === 'operator' && current(state).value === '*') {
|
|
141
|
+
const starTok = current(state)
|
|
134
142
|
consume(state)
|
|
135
143
|
args.push({
|
|
136
144
|
type: 'identifier',
|
|
137
145
|
name: '*',
|
|
146
|
+
positionStart: starTok.positionStart,
|
|
147
|
+
positionEnd: lastPosition(state),
|
|
138
148
|
})
|
|
139
149
|
} else {
|
|
140
150
|
const arg = parseExpression(state)
|
|
@@ -150,6 +160,8 @@ export function parsePrimary(state) {
|
|
|
150
160
|
type: 'function',
|
|
151
161
|
name: funcName,
|
|
152
162
|
args,
|
|
163
|
+
positionStart,
|
|
164
|
+
positionEnd: lastPosition(state),
|
|
153
165
|
}
|
|
154
166
|
}
|
|
155
167
|
|
|
@@ -161,6 +173,8 @@ export function parsePrimary(state) {
|
|
|
161
173
|
type: 'function',
|
|
162
174
|
name: tok.value,
|
|
163
175
|
args: [],
|
|
176
|
+
positionStart,
|
|
177
|
+
positionEnd: lastPosition(state),
|
|
164
178
|
}
|
|
165
179
|
}
|
|
166
180
|
|
|
@@ -177,6 +191,8 @@ export function parsePrimary(state) {
|
|
|
177
191
|
return {
|
|
178
192
|
type: 'identifier',
|
|
179
193
|
name,
|
|
194
|
+
positionStart,
|
|
195
|
+
positionEnd: lastPosition(state),
|
|
180
196
|
}
|
|
181
197
|
}
|
|
182
198
|
|
|
@@ -185,6 +201,8 @@ export function parsePrimary(state) {
|
|
|
185
201
|
return {
|
|
186
202
|
type: 'literal',
|
|
187
203
|
value: tok.numericValue ?? null,
|
|
204
|
+
positionStart,
|
|
205
|
+
positionEnd: lastPosition(state),
|
|
188
206
|
}
|
|
189
207
|
}
|
|
190
208
|
|
|
@@ -193,21 +211,23 @@ export function parsePrimary(state) {
|
|
|
193
211
|
return {
|
|
194
212
|
type: 'literal',
|
|
195
213
|
value: tok.value,
|
|
214
|
+
positionStart,
|
|
215
|
+
positionEnd: lastPosition(state),
|
|
196
216
|
}
|
|
197
217
|
}
|
|
198
218
|
|
|
199
219
|
if (tok.type === 'keyword') {
|
|
200
220
|
if (tok.value === 'TRUE') {
|
|
201
221
|
consume(state)
|
|
202
|
-
return { type: 'literal', value: true }
|
|
222
|
+
return { type: 'literal', value: true, positionStart, positionEnd: lastPosition(state) }
|
|
203
223
|
}
|
|
204
224
|
if (tok.value === 'FALSE') {
|
|
205
225
|
consume(state)
|
|
206
|
-
return { type: 'literal', value: false }
|
|
226
|
+
return { type: 'literal', value: false, positionStart, positionEnd: lastPosition(state) }
|
|
207
227
|
}
|
|
208
228
|
if (tok.value === 'NULL') {
|
|
209
229
|
consume(state)
|
|
210
|
-
return { type: 'literal', value: null }
|
|
230
|
+
return { type: 'literal', value: null, positionStart, positionEnd: lastPosition(state) }
|
|
211
231
|
}
|
|
212
232
|
if (tok.value === 'EXISTS') {
|
|
213
233
|
consume(state) // EXISTS
|
|
@@ -215,6 +235,8 @@ export function parsePrimary(state) {
|
|
|
215
235
|
return {
|
|
216
236
|
type: 'exists',
|
|
217
237
|
subquery,
|
|
238
|
+
positionStart,
|
|
239
|
+
positionEnd: lastPosition(state),
|
|
218
240
|
}
|
|
219
241
|
}
|
|
220
242
|
if (tok.value === 'CASE') {
|
|
@@ -260,6 +282,8 @@ export function parsePrimary(state) {
|
|
|
260
282
|
caseExpr,
|
|
261
283
|
whenClauses,
|
|
262
284
|
elseResult,
|
|
285
|
+
positionStart,
|
|
286
|
+
positionEnd: lastPosition(state),
|
|
263
287
|
}
|
|
264
288
|
}
|
|
265
289
|
if (tok.value === 'INTERVAL') {
|
|
@@ -274,11 +298,13 @@ export function parsePrimary(state) {
|
|
|
274
298
|
type: 'unary',
|
|
275
299
|
op: '-',
|
|
276
300
|
argument,
|
|
301
|
+
positionStart,
|
|
302
|
+
positionEnd: argument.positionEnd,
|
|
277
303
|
}
|
|
278
304
|
}
|
|
279
305
|
|
|
280
306
|
const found = tok.type === 'eof' ? 'end of query' : `"${tok.originalValue ?? tok.value}"`
|
|
281
|
-
throw syntaxError({ expected: 'expression', received: found,
|
|
307
|
+
throw syntaxError({ expected: 'expression', received: found, positionStart: tok.positionStart, positionEnd: tok.positionEnd })
|
|
282
308
|
}
|
|
283
309
|
|
|
284
310
|
/**
|
|
@@ -294,6 +320,8 @@ function parseOr(state) {
|
|
|
294
320
|
op: 'OR',
|
|
295
321
|
left: node,
|
|
296
322
|
right,
|
|
323
|
+
positionStart: node.positionStart,
|
|
324
|
+
positionEnd: right.positionEnd,
|
|
297
325
|
}
|
|
298
326
|
}
|
|
299
327
|
return node
|
|
@@ -312,6 +340,8 @@ function parseAnd(state) {
|
|
|
312
340
|
op: 'AND',
|
|
313
341
|
left: node,
|
|
314
342
|
right,
|
|
343
|
+
positionStart: node.positionStart,
|
|
344
|
+
positionEnd: right.positionEnd,
|
|
315
345
|
}
|
|
316
346
|
}
|
|
317
347
|
return node
|
|
@@ -322,7 +352,9 @@ function parseAnd(state) {
|
|
|
322
352
|
* @returns {ExprNode}
|
|
323
353
|
*/
|
|
324
354
|
function parseNot(state) {
|
|
355
|
+
const tok = current(state)
|
|
325
356
|
if (match(state, 'keyword', 'NOT')) {
|
|
357
|
+
const { positionStart } = tok
|
|
326
358
|
// Check for NOT EXISTS
|
|
327
359
|
const nextTok = current(state)
|
|
328
360
|
if (nextTok.type === 'keyword' && nextTok.value === 'EXISTS') {
|
|
@@ -331,6 +363,8 @@ function parseNot(state) {
|
|
|
331
363
|
return {
|
|
332
364
|
type: 'not exists',
|
|
333
365
|
subquery,
|
|
366
|
+
positionStart,
|
|
367
|
+
positionEnd: lastPosition(state),
|
|
334
368
|
}
|
|
335
369
|
}
|
|
336
370
|
const argument = parseNot(state)
|
|
@@ -338,6 +372,8 @@ function parseNot(state) {
|
|
|
338
372
|
type: 'unary',
|
|
339
373
|
op: 'NOT',
|
|
340
374
|
argument,
|
|
375
|
+
positionStart,
|
|
376
|
+
positionEnd: argument.positionEnd,
|
|
341
377
|
}
|
|
342
378
|
}
|
|
343
379
|
return parseComparison(state)
|
|
@@ -359,6 +395,8 @@ export function parseAdditive(state) {
|
|
|
359
395
|
op: tok.value,
|
|
360
396
|
left: node,
|
|
361
397
|
right,
|
|
398
|
+
positionStart: node.positionStart,
|
|
399
|
+
positionEnd: right.positionEnd,
|
|
362
400
|
}
|
|
363
401
|
} else {
|
|
364
402
|
break
|
|
@@ -383,6 +421,8 @@ function parseMultiplicative(state) {
|
|
|
383
421
|
op: tok.value,
|
|
384
422
|
left: node,
|
|
385
423
|
right,
|
|
424
|
+
positionStart: node.positionStart,
|
|
425
|
+
positionEnd: right.positionEnd,
|
|
386
426
|
}
|
|
387
427
|
} else {
|
|
388
428
|
break
|
package/src/parse/state.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { syntaxError } from '../
|
|
1
|
+
import { syntaxError } from '../parseErrors.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @import { ParserState, Token, TokenType } from '../types.js'
|
|
@@ -31,12 +31,22 @@ export function peekToken(state, offset) {
|
|
|
31
31
|
*/
|
|
32
32
|
export function consume(state) {
|
|
33
33
|
const tok = current(state)
|
|
34
|
+
state.lastPos = tok.positionEnd
|
|
34
35
|
if (state.pos < state.tokens.length - 1) {
|
|
35
36
|
state.pos += 1
|
|
36
37
|
}
|
|
37
38
|
return tok
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Gets the position after the last consumed token.
|
|
43
|
+
* @param {ParserState} state
|
|
44
|
+
* @returns {number}
|
|
45
|
+
*/
|
|
46
|
+
export function lastPosition(state) {
|
|
47
|
+
return state.lastPos ?? 0
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
/**
|
|
41
51
|
* @param {ParserState} state
|
|
42
52
|
* @param {TokenType} type
|
|
@@ -83,12 +93,12 @@ export function expectIdentifier(state) {
|
|
|
83
93
|
* Helper function to create consistent parser error messages.
|
|
84
94
|
* @param {ParserState} state
|
|
85
95
|
* @param {string} expected - Description of what was expected
|
|
86
|
-
* @returns {
|
|
96
|
+
* @returns {import('../parseErrors.js').ParseError}
|
|
87
97
|
*/
|
|
88
98
|
export function parseError(state, expected) {
|
|
89
99
|
const tok = current(state)
|
|
90
100
|
const prevToken = state.tokens[state.pos - 1]
|
|
91
101
|
const after = prevToken ? prevToken.originalValue ?? prevToken.value : undefined
|
|
92
102
|
const received = tok.type === 'eof' ? 'end of query' : `"${tok.originalValue ?? tok.value}"`
|
|
93
|
-
return syntaxError({ expected, received,
|
|
103
|
+
return syntaxError({ expected, received, positionStart: tok.positionStart, positionEnd: tok.positionEnd, after })
|
|
94
104
|
}
|
package/src/parse/tokenize.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
invalidLiteralError,
|
|
3
3
|
unexpectedCharError,
|
|
4
4
|
unterminatedError,
|
|
5
|
-
} from '../
|
|
5
|
+
} from '../parseErrors.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @import { Token } from '../types.d.ts'
|
|
@@ -117,24 +117,26 @@ export function tokenize(sql) {
|
|
|
117
117
|
return {
|
|
118
118
|
type: 'number',
|
|
119
119
|
value: text,
|
|
120
|
-
|
|
120
|
+
positionStart: startPos,
|
|
121
|
+
positionEnd: i,
|
|
121
122
|
numericValue: BigInt(text.slice(0, -1)),
|
|
122
123
|
}
|
|
123
124
|
} catch {
|
|
124
|
-
throw invalidLiteralError({ type: 'bigint', value: text.slice(0, -1),
|
|
125
|
+
throw invalidLiteralError({ type: 'bigint', value: text.slice(0, -1), positionStart: startPos, positionEnd: i })
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
if (isAlpha(peek())) {
|
|
128
|
-
throw invalidLiteralError({ type: 'number', value: text + peek(),
|
|
129
|
+
throw invalidLiteralError({ type: 'number', value: text + peek(), positionStart: startPos, positionEnd: i + 1 })
|
|
129
130
|
}
|
|
130
131
|
const num = parseFloat(text)
|
|
131
132
|
if (isNaN(num)) {
|
|
132
|
-
throw invalidLiteralError({ type: 'number', value: text,
|
|
133
|
+
throw invalidLiteralError({ type: 'number', value: text, positionStart: startPos, positionEnd: i })
|
|
133
134
|
}
|
|
134
135
|
return {
|
|
135
136
|
type: 'number',
|
|
136
137
|
value: text,
|
|
137
|
-
|
|
138
|
+
positionStart: startPos,
|
|
139
|
+
positionEnd: i,
|
|
138
140
|
numericValue: num,
|
|
139
141
|
}
|
|
140
142
|
}
|
|
@@ -204,13 +206,15 @@ export function tokenize(sql) {
|
|
|
204
206
|
type: 'keyword',
|
|
205
207
|
value: upper,
|
|
206
208
|
originalValue: text,
|
|
207
|
-
|
|
209
|
+
positionStart: pos,
|
|
210
|
+
positionEnd: i,
|
|
208
211
|
})
|
|
209
212
|
} else {
|
|
210
213
|
tokens.push({
|
|
211
214
|
type: 'identifier',
|
|
212
215
|
value: text,
|
|
213
|
-
|
|
216
|
+
positionStart: pos,
|
|
217
|
+
positionEnd: i,
|
|
214
218
|
})
|
|
215
219
|
}
|
|
216
220
|
continue
|
|
@@ -222,7 +226,7 @@ export function tokenize(sql) {
|
|
|
222
226
|
let text = ''
|
|
223
227
|
while (i <= length) {
|
|
224
228
|
if (i === length) {
|
|
225
|
-
throw unterminatedError('string', pos)
|
|
229
|
+
throw unterminatedError('string', pos, length)
|
|
226
230
|
}
|
|
227
231
|
const c = nextChar()
|
|
228
232
|
if (c === quote) {
|
|
@@ -239,7 +243,8 @@ export function tokenize(sql) {
|
|
|
239
243
|
tokens.push({
|
|
240
244
|
type: 'string',
|
|
241
245
|
value: text,
|
|
242
|
-
|
|
246
|
+
positionStart: pos,
|
|
247
|
+
positionEnd: i,
|
|
243
248
|
})
|
|
244
249
|
continue
|
|
245
250
|
}
|
|
@@ -250,7 +255,7 @@ export function tokenize(sql) {
|
|
|
250
255
|
let text = ''
|
|
251
256
|
while (i <= length) {
|
|
252
257
|
if (i === length) {
|
|
253
|
-
throw unterminatedError('identifier', pos)
|
|
258
|
+
throw unterminatedError('identifier', pos, length)
|
|
254
259
|
}
|
|
255
260
|
const c = nextChar()
|
|
256
261
|
if (c === quote) {
|
|
@@ -267,7 +272,8 @@ export function tokenize(sql) {
|
|
|
267
272
|
tokens.push({
|
|
268
273
|
type: 'identifier',
|
|
269
274
|
value: text,
|
|
270
|
-
|
|
275
|
+
positionStart: pos,
|
|
276
|
+
positionEnd: i,
|
|
271
277
|
})
|
|
272
278
|
continue
|
|
273
279
|
}
|
|
@@ -283,7 +289,8 @@ export function tokenize(sql) {
|
|
|
283
289
|
tokens.push({
|
|
284
290
|
type: 'operator',
|
|
285
291
|
value: op,
|
|
286
|
-
|
|
292
|
+
positionStart: pos,
|
|
293
|
+
positionEnd: i,
|
|
287
294
|
})
|
|
288
295
|
continue
|
|
289
296
|
}
|
|
@@ -294,7 +301,8 @@ export function tokenize(sql) {
|
|
|
294
301
|
tokens.push({
|
|
295
302
|
type: 'operator',
|
|
296
303
|
value: ch,
|
|
297
|
-
|
|
304
|
+
positionStart: pos,
|
|
305
|
+
positionEnd: i,
|
|
298
306
|
})
|
|
299
307
|
continue
|
|
300
308
|
}
|
|
@@ -304,7 +312,8 @@ export function tokenize(sql) {
|
|
|
304
312
|
tokens.push({
|
|
305
313
|
type: 'comma',
|
|
306
314
|
value: ',',
|
|
307
|
-
|
|
315
|
+
positionStart: pos,
|
|
316
|
+
positionEnd: i,
|
|
308
317
|
})
|
|
309
318
|
continue
|
|
310
319
|
}
|
|
@@ -314,7 +323,8 @@ export function tokenize(sql) {
|
|
|
314
323
|
tokens.push({
|
|
315
324
|
type: 'dot',
|
|
316
325
|
value: '.',
|
|
317
|
-
|
|
326
|
+
positionStart: pos,
|
|
327
|
+
positionEnd: i,
|
|
318
328
|
})
|
|
319
329
|
continue
|
|
320
330
|
}
|
|
@@ -324,7 +334,8 @@ export function tokenize(sql) {
|
|
|
324
334
|
tokens.push({
|
|
325
335
|
type: 'paren',
|
|
326
336
|
value: ch,
|
|
327
|
-
|
|
337
|
+
positionStart: pos,
|
|
338
|
+
positionEnd: i,
|
|
328
339
|
})
|
|
329
340
|
continue
|
|
330
341
|
}
|
|
@@ -334,21 +345,23 @@ export function tokenize(sql) {
|
|
|
334
345
|
tokens.push({
|
|
335
346
|
type: 'semicolon',
|
|
336
347
|
value: ';',
|
|
337
|
-
|
|
348
|
+
positionStart: pos,
|
|
349
|
+
positionEnd: i,
|
|
338
350
|
})
|
|
339
351
|
continue
|
|
340
352
|
}
|
|
341
353
|
|
|
342
354
|
if (tokens.length === 0) {
|
|
343
|
-
throw unexpectedCharError(ch, pos, true)
|
|
355
|
+
throw unexpectedCharError({ char: ch, positionStart: pos, expectsSelect: true })
|
|
344
356
|
}
|
|
345
|
-
throw unexpectedCharError(ch, pos)
|
|
357
|
+
throw unexpectedCharError({ char: ch, positionStart: pos })
|
|
346
358
|
}
|
|
347
359
|
|
|
348
360
|
tokens.push({
|
|
349
361
|
type: 'eof',
|
|
350
362
|
value: '',
|
|
351
|
-
|
|
363
|
+
positionStart: length,
|
|
364
|
+
positionEnd: length,
|
|
352
365
|
})
|
|
353
366
|
|
|
354
367
|
return tokens
|