@jbrowse/jexl 4.0.0 → 5.0.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 +163 -20
- package/dist/Expression.d.ts +16 -0
- package/dist/Expression.js +28 -4
- package/dist/Expression.js.map +1 -1
- package/dist/Jexl.d.ts +20 -8
- package/dist/Jexl.js +34 -15
- package/dist/Jexl.js.map +1 -1
- package/dist/Lexer.d.ts +4 -3
- package/dist/Lexer.js +29 -21
- package/dist/Lexer.js.map +1 -1
- package/dist/analyze.d.ts +84 -0
- package/dist/analyze.js +259 -0
- package/dist/analyze.js.map +1 -0
- package/dist/check.d.ts +109 -0
- package/dist/check.js +1267 -0
- package/dist/check.js.map +1 -0
- package/dist/collections.d.ts +3 -0
- package/dist/collections.js +140 -0
- package/dist/collections.js.map +1 -0
- package/dist/conditions.d.ts +62 -0
- package/dist/conditions.js +207 -0
- package/dist/conditions.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/evaluator/compile.d.ts +10 -7
- package/dist/evaluator/compile.js +295 -72
- package/dist/evaluator/compile.js.map +1 -1
- package/dist/grammar.d.ts +19 -9
- package/dist/grammar.js +66 -40
- package/dist/grammar.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/dist/operators.d.ts +34 -0
- package/dist/operators.js +131 -0
- package/dist/operators.js.map +1 -0
- package/dist/parser/Parser.d.ts +48 -91
- package/dist/parser/Parser.js +347 -221
- package/dist/parser/Parser.js.map +1 -1
- package/dist/types.d.ts +11 -5
- package/esm/Expression.d.ts +16 -0
- package/esm/Expression.js +28 -4
- package/esm/Expression.js.map +1 -1
- package/esm/Jexl.d.ts +20 -8
- package/esm/Jexl.js +34 -15
- package/esm/Jexl.js.map +1 -1
- package/esm/Lexer.d.ts +4 -3
- package/esm/Lexer.js +29 -21
- package/esm/Lexer.js.map +1 -1
- package/esm/analyze.d.ts +84 -0
- package/esm/analyze.js +256 -0
- package/esm/analyze.js.map +1 -0
- package/esm/check.d.ts +109 -0
- package/esm/check.js +1260 -0
- package/esm/check.js.map +1 -0
- package/esm/collections.d.ts +3 -0
- package/esm/collections.js +137 -0
- package/esm/collections.js.map +1 -0
- package/esm/conditions.d.ts +62 -0
- package/esm/conditions.js +200 -0
- package/esm/conditions.js.map +1 -0
- package/esm/errors.d.ts +5 -0
- package/esm/errors.js +14 -0
- package/esm/errors.js.map +1 -0
- package/esm/evaluator/compile.d.ts +10 -7
- package/esm/evaluator/compile.js +295 -72
- package/esm/evaluator/compile.js.map +1 -1
- package/esm/grammar.d.ts +19 -9
- package/esm/grammar.js +66 -39
- package/esm/grammar.js.map +1 -1
- package/esm/index.d.ts +9 -0
- package/esm/index.js +4 -0
- package/esm/index.js.map +1 -1
- package/esm/operators.d.ts +34 -0
- package/esm/operators.js +123 -0
- package/esm/operators.js.map +1 -0
- package/esm/parser/Parser.d.ts +48 -91
- package/esm/parser/Parser.js +349 -192
- package/esm/parser/Parser.js.map +1 -1
- package/esm/types.d.ts +11 -5
- package/package.json +1 -1
- package/src/Expression.ts +34 -4
- package/src/Jexl.ts +52 -16
- package/src/Lexer.ts +32 -21
- package/src/analyze.ts +378 -0
- package/src/check.ts +1682 -0
- package/src/collections.ts +148 -0
- package/src/conditions.ts +262 -0
- package/src/errors.ts +15 -0
- package/src/evaluator/compile.ts +356 -72
- package/src/grammar.ts +123 -42
- package/src/index.ts +15 -0
- package/src/operators.ts +151 -0
- package/src/parser/Parser.ts +389 -199
- package/src/types.ts +13 -3
- package/dist/parser/handlers.d.ts +0 -115
- package/dist/parser/handlers.js +0 -352
- package/dist/parser/handlers.js.map +0 -1
- package/dist/parser/states.d.ts +0 -47
- package/dist/parser/states.js +0 -176
- package/dist/parser/states.js.map +0 -1
- package/esm/parser/handlers.d.ts +0 -115
- package/esm/parser/handlers.js +0 -328
- package/esm/parser/handlers.js.map +0 -1
- package/esm/parser/states.d.ts +0 -47
- package/esm/parser/states.js +0 -140
- package/esm/parser/states.js.map +0 -1
- package/src/parser/handlers.ts +0 -372
- package/src/parser/states.ts +0 -159
package/esm/parser/states.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Jexl
|
|
3
|
-
* Copyright 2020 Tom Shawver
|
|
4
|
-
*/
|
|
5
|
-
import * as h from "./handlers.js";
|
|
6
|
-
/**
|
|
7
|
-
* The tokens that may follow a finished operand: an operator to combine it
|
|
8
|
-
* with, a dot or subscript to keep drilling into it, a question mark opening a
|
|
9
|
-
* ternary, or a semicolon ending the statement. Shared by every state that
|
|
10
|
-
* sits on a complete value, which is also why each of them is completable.
|
|
11
|
-
*/
|
|
12
|
-
const afterOperand = {
|
|
13
|
-
binaryOp: { toState: 'expectOperand' },
|
|
14
|
-
dot: { toState: 'traverse' },
|
|
15
|
-
openBracket: { toState: 'filter' },
|
|
16
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
17
|
-
semicolon: { handler: h.semicolon }
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* A mapping of all states in the finite state machine to a set of instructions
|
|
21
|
-
* for handling or transitioning into other states. Each state can be handled
|
|
22
|
-
* in one of two schemes: a tokenType map, or a subHandler.
|
|
23
|
-
*
|
|
24
|
-
* Standard expression elements are handled through the tokenType object. This
|
|
25
|
-
* is an object map of all legal token types to encounter in this state (and
|
|
26
|
-
* any unexpected token types will generate a thrown error) to an options
|
|
27
|
-
* object that defines how they're handled. The available options are:
|
|
28
|
-
*
|
|
29
|
-
* {string} toState: The name of the state to which to transition
|
|
30
|
-
* immediately after handling this token
|
|
31
|
-
* {string} handler: The handler function to call when this token type is
|
|
32
|
-
* encountered in this state. If omitted, the default handler
|
|
33
|
-
* matching the token's "type" property will be called. If the handler
|
|
34
|
-
* function does not exist, no call will be made and no error will be
|
|
35
|
-
* generated. This is useful for tokens whose sole purpose is to
|
|
36
|
-
* transition to other states.
|
|
37
|
-
*
|
|
38
|
-
* States that consume a subexpression should define a subHandler, the
|
|
39
|
-
* function to be called with an expression tree argument when the
|
|
40
|
-
* subexpression is complete. Completeness is determined through the
|
|
41
|
-
* endStates object, which maps tokens on which an expression should end to the
|
|
42
|
-
* state to which to transition once the subHandler function has been called.
|
|
43
|
-
*
|
|
44
|
-
* Additionally, any state in which it is legal to mark the AST as completed
|
|
45
|
-
* should have a 'completable' property set to boolean true. Attempting to
|
|
46
|
-
* call {@link Parser#complete} in any state without this property will result
|
|
47
|
-
* in a thrown Error.
|
|
48
|
-
*
|
|
49
|
-
* @type {{}}
|
|
50
|
-
*/
|
|
51
|
-
export const states = {
|
|
52
|
-
expectOperand: {
|
|
53
|
-
tokenTypes: {
|
|
54
|
-
literal: { toState: 'expectBinOp' },
|
|
55
|
-
templateString: { toState: 'expectBinOp' },
|
|
56
|
-
identifier: { toState: 'identifier' },
|
|
57
|
-
unaryOp: {},
|
|
58
|
-
openParen: { toState: 'subExpression' },
|
|
59
|
-
openCurl: { toState: 'expectObjKey', handler: h.objStart },
|
|
60
|
-
dot: { toState: 'traverse' },
|
|
61
|
-
openBracket: { toState: 'arrayVal', handler: h.arrayStart }
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
expectBinOp: {
|
|
65
|
-
tokenTypes: afterOperand,
|
|
66
|
-
completable: true
|
|
67
|
-
},
|
|
68
|
-
expectObjKey: {
|
|
69
|
-
tokenTypes: {
|
|
70
|
-
literal: { toState: 'expectKeyValSep', handler: h.objKey },
|
|
71
|
-
identifier: { toState: 'expectKeyValSep', handler: h.objKey },
|
|
72
|
-
closeCurl: { toState: 'expectBinOp' }
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
expectKeyValSep: {
|
|
76
|
-
tokenTypes: {
|
|
77
|
-
colon: { toState: 'objVal' }
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
identifier: {
|
|
81
|
-
tokenTypes: {
|
|
82
|
-
...afterOperand,
|
|
83
|
-
// a name is the only operand that can be followed by an argument list,
|
|
84
|
-
// since a function is called by name rather than evaluated as a value
|
|
85
|
-
openParen: { toState: 'argVal', handler: h.functionCall }
|
|
86
|
-
},
|
|
87
|
-
completable: true
|
|
88
|
-
},
|
|
89
|
-
traverse: {
|
|
90
|
-
tokenTypes: {
|
|
91
|
-
identifier: { toState: 'identifier' }
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
filter: {
|
|
95
|
-
subHandler: h.filter,
|
|
96
|
-
endStates: {
|
|
97
|
-
closeBracket: 'identifier'
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
subExpression: {
|
|
101
|
-
subHandler: h.subExpression,
|
|
102
|
-
endStates: {
|
|
103
|
-
closeParen: 'expectBinOp'
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
argVal: {
|
|
107
|
-
subHandler: h.argVal,
|
|
108
|
-
endStates: {
|
|
109
|
-
comma: 'argVal',
|
|
110
|
-
// a call's result is an ordinary operand: it can be operated on or
|
|
111
|
-
// drilled into, but not called again, since only a name can be called
|
|
112
|
-
closeParen: 'expectBinOp'
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
objVal: {
|
|
116
|
-
subHandler: h.objVal,
|
|
117
|
-
endStates: {
|
|
118
|
-
comma: 'expectObjKey',
|
|
119
|
-
closeCurl: 'expectBinOp'
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
arrayVal: {
|
|
123
|
-
subHandler: h.arrayVal,
|
|
124
|
-
endStates: {
|
|
125
|
-
comma: 'arrayVal',
|
|
126
|
-
closeBracket: 'expectBinOp'
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
ternaryMid: {
|
|
130
|
-
subHandler: h.ternaryMid,
|
|
131
|
-
endStates: {
|
|
132
|
-
colon: 'ternaryEnd'
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
ternaryEnd: {
|
|
136
|
-
subHandler: h.ternaryEnd,
|
|
137
|
-
completable: true
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
//# sourceMappingURL=states.js.map
|
package/esm/parser/states.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"states.js","sourceRoot":"","sources":["../../src/parser/states.ts"],"names":[],"mappings":"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"}
|
package/src/parser/handlers.ts
DELETED
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Jexl
|
|
3
|
-
* Copyright 2020 Tom Shawver
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { precedenceOf } from '../grammar.ts'
|
|
7
|
-
// circular with Parser.ts, which is fine: the binding is only read from inside
|
|
8
|
-
// a handler, long after both modules have finished evaluating
|
|
9
|
-
import Parser from './Parser.ts'
|
|
10
|
-
|
|
11
|
-
import type {
|
|
12
|
-
ArrayLiteral,
|
|
13
|
-
AssignmentExpression,
|
|
14
|
-
AstNode,
|
|
15
|
-
BinaryExpression,
|
|
16
|
-
ConditionalExpression,
|
|
17
|
-
FilterExpression,
|
|
18
|
-
FunctionCall,
|
|
19
|
-
Identifier,
|
|
20
|
-
Literal,
|
|
21
|
-
ObjectLiteral,
|
|
22
|
-
TemplateLiteral,
|
|
23
|
-
Token,
|
|
24
|
-
UnaryExpression
|
|
25
|
-
} from '../types.ts'
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Handles a subexpression that's used to define a transform argument's value.
|
|
29
|
-
* @param {{type: <string>}} ast The subexpression tree
|
|
30
|
-
*/
|
|
31
|
-
export function argVal(this: Parser, ast: AstNode | null) {
|
|
32
|
-
if (ast) {
|
|
33
|
-
;(this._cursor as FunctionCall).args.push(ast)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Handles new array literals by adding them as a new node in the AST,
|
|
39
|
-
* initialized with an empty array.
|
|
40
|
-
*/
|
|
41
|
-
export function arrayStart(this: Parser) {
|
|
42
|
-
const node: ArrayLiteral = {
|
|
43
|
-
type: 'ArrayLiteral',
|
|
44
|
-
value: []
|
|
45
|
-
}
|
|
46
|
-
this._placeAtCursor(node)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Handles a subexpression representing an element of an array literal.
|
|
51
|
-
* @param {{type: <string>}} ast The subexpression tree
|
|
52
|
-
*/
|
|
53
|
-
export function arrayVal(this: Parser, ast: AstNode | null) {
|
|
54
|
-
if (ast) {
|
|
55
|
-
;(this._cursor as ArrayLiteral).value.push(ast)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Handles tokens of type 'binaryOp', indicating an operation that has two
|
|
61
|
-
* inputs: a left side and a right side.
|
|
62
|
-
* @param {{type: <string>}} token A token object
|
|
63
|
-
*/
|
|
64
|
-
export function binaryOp(this: Parser, token: Token) {
|
|
65
|
-
if (token.value === '=') {
|
|
66
|
-
// `from` marks a member access such as `a.b`; only a bare name can be
|
|
67
|
-
// assigned to, since the compiled closure writes straight into the context
|
|
68
|
-
if (
|
|
69
|
-
this._cursor?.type !== 'Identifier' ||
|
|
70
|
-
(this._cursor as Identifier).from
|
|
71
|
-
) {
|
|
72
|
-
throw new Error('Left side of assignment must be a variable name')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const node: AssignmentExpression = {
|
|
76
|
-
type: 'AssignmentExpression',
|
|
77
|
-
operator: '=',
|
|
78
|
-
left: this._cursor as Identifier
|
|
79
|
-
}
|
|
80
|
-
this._cursor = this._cursor._parent
|
|
81
|
-
this._placeAtCursor(node)
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const tokenValue = token.value as string
|
|
86
|
-
const precedence = precedenceOf(this._grammar.elements[tokenValue])
|
|
87
|
-
let parent = this._cursor?._parent
|
|
88
|
-
while (parent) {
|
|
89
|
-
const parentExpr = parent as BinaryExpression
|
|
90
|
-
if (!parentExpr.operator) {
|
|
91
|
-
break
|
|
92
|
-
}
|
|
93
|
-
// a prefix operator binds tighter than any binary one, so `-x ^ 2` groups
|
|
94
|
-
// as `(-x) ^ 2` — the same way `-2 ^ 2` already does, since the Lexer folds
|
|
95
|
-
// that sign into the literal. Looking `-` up in the grammar finds its
|
|
96
|
-
// binary precedence instead, which is lower than `^`'s and gave `-(x ^ 2)`
|
|
97
|
-
const parentPrecedence =
|
|
98
|
-
parent.type === 'UnaryExpression'
|
|
99
|
-
? Infinity
|
|
100
|
-
: precedenceOf(this._grammar.elements[parentExpr.operator])
|
|
101
|
-
if (parentPrecedence < precedence) {
|
|
102
|
-
break
|
|
103
|
-
}
|
|
104
|
-
this._cursor = parent
|
|
105
|
-
parent = parent._parent
|
|
106
|
-
}
|
|
107
|
-
const node: BinaryExpression = {
|
|
108
|
-
type: 'BinaryExpression',
|
|
109
|
-
operator: tokenValue,
|
|
110
|
-
left: this._cursor!
|
|
111
|
-
}
|
|
112
|
-
this._setParent(this._cursor!, node)
|
|
113
|
-
this._cursor = parent
|
|
114
|
-
this._placeAtCursor(node)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Handles successive nodes in an identifier chain. More specifically, it
|
|
119
|
-
* sets values that determine how the following identifier gets placed in the
|
|
120
|
-
* AST.
|
|
121
|
-
*/
|
|
122
|
-
export function dot(this: Parser) {
|
|
123
|
-
const cursor = this._cursor
|
|
124
|
-
// a dot continues an identifier chain. With nothing to continue — no cursor,
|
|
125
|
-
// or an operator still waiting on its right-hand side — it would instead open
|
|
126
|
-
// a relative path, the leading dot of `list[.price > 5]`. This fork removed
|
|
127
|
-
// array filtering expressions, which are the only construct that gives a
|
|
128
|
-
// relative path a root to resolve against, so it is rejected here rather than
|
|
129
|
-
// parsed into a tree that can only fail once it is evaluated.
|
|
130
|
-
if (
|
|
131
|
-
!cursor ||
|
|
132
|
-
cursor.type === 'UnaryExpression' ||
|
|
133
|
-
(cursor.type === 'BinaryExpression' && !cursor.right)
|
|
134
|
-
) {
|
|
135
|
-
throw new Error(`Relative paths are not supported: ${this._exprStr}`)
|
|
136
|
-
}
|
|
137
|
-
this._nextIdentEncapsulate = true
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Handles a subexpression used for filtering an array returned by an
|
|
142
|
-
* identifier chain.
|
|
143
|
-
* @param {{type: <string>}} ast The subexpression tree
|
|
144
|
-
*/
|
|
145
|
-
export function filter(this: Parser, ast: AstNode | null) {
|
|
146
|
-
const node: FilterExpression = {
|
|
147
|
-
type: 'FilterExpression',
|
|
148
|
-
expr: ast!,
|
|
149
|
-
subject: this._cursor!
|
|
150
|
-
}
|
|
151
|
-
this._placeBeforeCursor(node)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Handles identifier tokens when used to indicate the name of a function to
|
|
156
|
-
* be called.
|
|
157
|
-
*
|
|
158
|
-
* A call written against a value, `a.b(x)`, becomes `b(a, x)`: jexl functions
|
|
159
|
-
* live in one global pool and have no receiver, so the value the name was read
|
|
160
|
-
* from is passed as the first argument instead. Every function in the pool is
|
|
161
|
-
* already written that way — `get(feature, key)`, `split(str, sep)` — which is
|
|
162
|
-
* what makes the two spellings equivalent. Previously the receiver was simply
|
|
163
|
-
* dropped, so `refName.split(' ')` quietly called `split(' ')`.
|
|
164
|
-
*/
|
|
165
|
-
export function functionCall(this: Parser) {
|
|
166
|
-
const cursor = this._cursor
|
|
167
|
-
if (cursor?.type !== 'Identifier') {
|
|
168
|
-
// reached by `a[expr]()`, whose subject is a filter rather than a name;
|
|
169
|
-
// there is nothing to look up in the function pool
|
|
170
|
-
throw new Error(`Functions must be called by name: ${this._exprStr}`)
|
|
171
|
-
}
|
|
172
|
-
const { from, value } = cursor as Identifier
|
|
173
|
-
const node: FunctionCall = {
|
|
174
|
-
type: 'FunctionCall',
|
|
175
|
-
name: value,
|
|
176
|
-
args: from ? [from] : []
|
|
177
|
-
}
|
|
178
|
-
this._placeBeforeCursor(node)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Handles identifier tokens by adding them as a new node in the AST.
|
|
183
|
-
* @param {{type: <string>}} token A token object
|
|
184
|
-
*/
|
|
185
|
-
export function identifier(this: Parser, token: Token) {
|
|
186
|
-
const node: Identifier = {
|
|
187
|
-
type: 'Identifier',
|
|
188
|
-
value: token.value as string
|
|
189
|
-
}
|
|
190
|
-
if (this._nextIdentEncapsulate) {
|
|
191
|
-
node.from = this._cursor!
|
|
192
|
-
this._placeBeforeCursor(node)
|
|
193
|
-
this._nextIdentEncapsulate = false
|
|
194
|
-
} else {
|
|
195
|
-
this._placeAtCursor(node)
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Handles literal values, such as strings, booleans, and numerics, by adding
|
|
201
|
-
* them as a new node in the AST.
|
|
202
|
-
* @param {{type: <string>}} token A token object
|
|
203
|
-
*/
|
|
204
|
-
export function literal(this: Parser, token: Token) {
|
|
205
|
-
const node: Literal = {
|
|
206
|
-
type: 'Literal',
|
|
207
|
-
value: token.value as string | number | boolean
|
|
208
|
-
}
|
|
209
|
-
this._placeAtCursor(node)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Handles template string tokens by parsing their interpolation expressions
|
|
214
|
-
* and creating a TemplateLiteral AST node.
|
|
215
|
-
* @param {{type: <string>}} token A token object
|
|
216
|
-
*/
|
|
217
|
-
export function templateString(this: Parser, token: Token) {
|
|
218
|
-
const parts: TemplateLiteral['parts'] = []
|
|
219
|
-
const tokenParts = Array.isArray(token.value) ? token.value : []
|
|
220
|
-
|
|
221
|
-
for (const part of tokenParts) {
|
|
222
|
-
if (part.type === 'static') {
|
|
223
|
-
parts.push({
|
|
224
|
-
type: 'static',
|
|
225
|
-
value: this._lexer._unescapeTemplateString(part.value)
|
|
226
|
-
})
|
|
227
|
-
} else {
|
|
228
|
-
const subTokens = this._lexer.tokenize(part.value)
|
|
229
|
-
const subParser = new Parser(this._grammar, this._lexer)
|
|
230
|
-
subParser.addTokens(subTokens)
|
|
231
|
-
const subAst = subParser.complete()
|
|
232
|
-
|
|
233
|
-
if (!subAst) {
|
|
234
|
-
throw new Error('Empty interpolation in template string')
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
parts.push({
|
|
238
|
-
type: 'expression',
|
|
239
|
-
value: subAst
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const node: TemplateLiteral = {
|
|
245
|
-
type: 'TemplateLiteral',
|
|
246
|
-
parts
|
|
247
|
-
}
|
|
248
|
-
this._placeAtCursor(node)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Queues a new object literal key to be written once a value is collected.
|
|
253
|
-
* @param {{type: <string>}} token A token object
|
|
254
|
-
*/
|
|
255
|
-
export function objKey(this: Parser, token: Token) {
|
|
256
|
-
this._curObjKey = token.value as string
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Handles new object literals by adding them as a new node in the AST,
|
|
261
|
-
* initialized with an empty object.
|
|
262
|
-
*/
|
|
263
|
-
export function objStart(this: Parser) {
|
|
264
|
-
const node: ObjectLiteral = {
|
|
265
|
-
type: 'ObjectLiteral',
|
|
266
|
-
value: {}
|
|
267
|
-
}
|
|
268
|
-
this._placeAtCursor(node)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Handles an object value by adding its AST to the queued key on the object
|
|
273
|
-
* literal node currently at the cursor.
|
|
274
|
-
* @param {{type: <string>}} ast The subexpression tree
|
|
275
|
-
*/
|
|
276
|
-
export function objVal(this: Parser, ast: AstNode | null) {
|
|
277
|
-
// defined rather than assigned so that the key "__proto__" becomes an
|
|
278
|
-
// ordinary entry of the node's key map instead of re-pointing its prototype,
|
|
279
|
-
// which dropped the entry before it could be compiled
|
|
280
|
-
Object.defineProperty(
|
|
281
|
-
(this._cursor as ObjectLiteral).value,
|
|
282
|
-
this._curObjKey!,
|
|
283
|
-
{
|
|
284
|
-
value: ast!,
|
|
285
|
-
writable: true,
|
|
286
|
-
enumerable: true,
|
|
287
|
-
configurable: true
|
|
288
|
-
}
|
|
289
|
-
)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Handles traditional subexpressions, delineated with the groupStart and
|
|
294
|
-
* groupEnd elements.
|
|
295
|
-
* @param {{type: <string>}} ast The subexpression tree
|
|
296
|
-
*/
|
|
297
|
-
export function subExpression(this: Parser, ast: AstNode | null) {
|
|
298
|
-
this._placeAtCursor(ast!)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Handles a completed alternate subexpression of a ternary operator.
|
|
303
|
-
* @param {{type: <string>}} ast The subexpression tree
|
|
304
|
-
*/
|
|
305
|
-
export function ternaryEnd(this: Parser, ast: AstNode | null) {
|
|
306
|
-
;(this._cursor as ConditionalExpression).alternate = ast ?? undefined
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Handles a completed consequent subexpression of a ternary operator.
|
|
311
|
-
* @param {{type: <string>}} ast The subexpression tree
|
|
312
|
-
*/
|
|
313
|
-
export function ternaryMid(this: Parser, ast: AstNode | null) {
|
|
314
|
-
;(this._cursor as ConditionalExpression).consequent = ast ?? undefined
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Handles the start of a new ternary expression by encapsulating the tree so
|
|
319
|
-
* far in a ConditionalExpression node, and using it as the test element.
|
|
320
|
-
*
|
|
321
|
-
* Every operator binds tighter than the conditional and so belongs in the test
|
|
322
|
-
* — except `=`, which binds looser. `x = a ? b : c` therefore assigns the
|
|
323
|
-
* conditional rather than testing the assignment, which had stored `a` in `x`.
|
|
324
|
-
*/
|
|
325
|
-
export function ternaryStart(this: Parser) {
|
|
326
|
-
let assignment: AstNode | undefined
|
|
327
|
-
let test = this._tree!
|
|
328
|
-
while (test.type === 'AssignmentExpression' && test.right) {
|
|
329
|
-
assignment = test
|
|
330
|
-
test = test.right
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const node: ConditionalExpression = {
|
|
334
|
-
type: 'ConditionalExpression',
|
|
335
|
-
test
|
|
336
|
-
}
|
|
337
|
-
if (assignment) {
|
|
338
|
-
assignment.right = node
|
|
339
|
-
this._setParent(node, assignment)
|
|
340
|
-
} else {
|
|
341
|
-
this._tree = node
|
|
342
|
-
}
|
|
343
|
-
this._cursor = node
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Handles token of type 'unaryOp', indicating that the operation has only
|
|
348
|
-
* one input: a right side.
|
|
349
|
-
* @param {{type: <string>}} token A token object
|
|
350
|
-
*/
|
|
351
|
-
export function unaryOp(this: Parser, token: Token) {
|
|
352
|
-
const node: UnaryExpression = {
|
|
353
|
-
type: 'UnaryExpression',
|
|
354
|
-
operator: token.value as string
|
|
355
|
-
}
|
|
356
|
-
this._placeAtCursor(node)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Handles semicolon separator between expressions
|
|
361
|
-
*/
|
|
362
|
-
export function semicolon(this: Parser) {
|
|
363
|
-
if (!this._sequenceExpressions) {
|
|
364
|
-
this._sequenceExpressions = [this._tree!]
|
|
365
|
-
} else {
|
|
366
|
-
this._sequenceExpressions.push(this._tree!)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
this._tree = null
|
|
370
|
-
this._cursor = null
|
|
371
|
-
this._state = 'expectOperand'
|
|
372
|
-
}
|
package/src/parser/states.ts
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Jexl
|
|
3
|
-
* Copyright 2020 Tom Shawver
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as h from './handlers.ts'
|
|
7
|
-
|
|
8
|
-
import type { AstNode, Token } from '../types.ts'
|
|
9
|
-
import type Parser from './Parser.ts'
|
|
10
|
-
|
|
11
|
-
interface TokenTypeOptions {
|
|
12
|
-
toState?: string
|
|
13
|
-
handler?: (this: Parser, token: Token) => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface StateDefinition {
|
|
17
|
-
tokenTypes?: Record<string, TokenTypeOptions>
|
|
18
|
-
subHandler?: (this: Parser, ast: AstNode | null) => void
|
|
19
|
-
endStates?: Record<string, string>
|
|
20
|
-
completable?: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type States = Record<string, StateDefinition>
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The tokens that may follow a finished operand: an operator to combine it
|
|
27
|
-
* with, a dot or subscript to keep drilling into it, a question mark opening a
|
|
28
|
-
* ternary, or a semicolon ending the statement. Shared by every state that
|
|
29
|
-
* sits on a complete value, which is also why each of them is completable.
|
|
30
|
-
*/
|
|
31
|
-
const afterOperand: Record<string, TokenTypeOptions> = {
|
|
32
|
-
binaryOp: { toState: 'expectOperand' },
|
|
33
|
-
dot: { toState: 'traverse' },
|
|
34
|
-
openBracket: { toState: 'filter' },
|
|
35
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
36
|
-
semicolon: { handler: h.semicolon }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* A mapping of all states in the finite state machine to a set of instructions
|
|
41
|
-
* for handling or transitioning into other states. Each state can be handled
|
|
42
|
-
* in one of two schemes: a tokenType map, or a subHandler.
|
|
43
|
-
*
|
|
44
|
-
* Standard expression elements are handled through the tokenType object. This
|
|
45
|
-
* is an object map of all legal token types to encounter in this state (and
|
|
46
|
-
* any unexpected token types will generate a thrown error) to an options
|
|
47
|
-
* object that defines how they're handled. The available options are:
|
|
48
|
-
*
|
|
49
|
-
* {string} toState: The name of the state to which to transition
|
|
50
|
-
* immediately after handling this token
|
|
51
|
-
* {string} handler: The handler function to call when this token type is
|
|
52
|
-
* encountered in this state. If omitted, the default handler
|
|
53
|
-
* matching the token's "type" property will be called. If the handler
|
|
54
|
-
* function does not exist, no call will be made and no error will be
|
|
55
|
-
* generated. This is useful for tokens whose sole purpose is to
|
|
56
|
-
* transition to other states.
|
|
57
|
-
*
|
|
58
|
-
* States that consume a subexpression should define a subHandler, the
|
|
59
|
-
* function to be called with an expression tree argument when the
|
|
60
|
-
* subexpression is complete. Completeness is determined through the
|
|
61
|
-
* endStates object, which maps tokens on which an expression should end to the
|
|
62
|
-
* state to which to transition once the subHandler function has been called.
|
|
63
|
-
*
|
|
64
|
-
* Additionally, any state in which it is legal to mark the AST as completed
|
|
65
|
-
* should have a 'completable' property set to boolean true. Attempting to
|
|
66
|
-
* call {@link Parser#complete} in any state without this property will result
|
|
67
|
-
* in a thrown Error.
|
|
68
|
-
*
|
|
69
|
-
* @type {{}}
|
|
70
|
-
*/
|
|
71
|
-
export const states: States = {
|
|
72
|
-
expectOperand: {
|
|
73
|
-
tokenTypes: {
|
|
74
|
-
literal: { toState: 'expectBinOp' },
|
|
75
|
-
templateString: { toState: 'expectBinOp' },
|
|
76
|
-
identifier: { toState: 'identifier' },
|
|
77
|
-
unaryOp: {},
|
|
78
|
-
openParen: { toState: 'subExpression' },
|
|
79
|
-
openCurl: { toState: 'expectObjKey', handler: h.objStart },
|
|
80
|
-
dot: { toState: 'traverse' },
|
|
81
|
-
openBracket: { toState: 'arrayVal', handler: h.arrayStart }
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
expectBinOp: {
|
|
85
|
-
tokenTypes: afterOperand,
|
|
86
|
-
completable: true
|
|
87
|
-
},
|
|
88
|
-
expectObjKey: {
|
|
89
|
-
tokenTypes: {
|
|
90
|
-
literal: { toState: 'expectKeyValSep', handler: h.objKey },
|
|
91
|
-
identifier: { toState: 'expectKeyValSep', handler: h.objKey },
|
|
92
|
-
closeCurl: { toState: 'expectBinOp' }
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
expectKeyValSep: {
|
|
96
|
-
tokenTypes: {
|
|
97
|
-
colon: { toState: 'objVal' }
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
identifier: {
|
|
101
|
-
tokenTypes: {
|
|
102
|
-
...afterOperand,
|
|
103
|
-
// a name is the only operand that can be followed by an argument list,
|
|
104
|
-
// since a function is called by name rather than evaluated as a value
|
|
105
|
-
openParen: { toState: 'argVal', handler: h.functionCall }
|
|
106
|
-
},
|
|
107
|
-
completable: true
|
|
108
|
-
},
|
|
109
|
-
traverse: {
|
|
110
|
-
tokenTypes: {
|
|
111
|
-
identifier: { toState: 'identifier' }
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
filter: {
|
|
115
|
-
subHandler: h.filter,
|
|
116
|
-
endStates: {
|
|
117
|
-
closeBracket: 'identifier'
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
subExpression: {
|
|
121
|
-
subHandler: h.subExpression,
|
|
122
|
-
endStates: {
|
|
123
|
-
closeParen: 'expectBinOp'
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
argVal: {
|
|
127
|
-
subHandler: h.argVal,
|
|
128
|
-
endStates: {
|
|
129
|
-
comma: 'argVal',
|
|
130
|
-
// a call's result is an ordinary operand: it can be operated on or
|
|
131
|
-
// drilled into, but not called again, since only a name can be called
|
|
132
|
-
closeParen: 'expectBinOp'
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
objVal: {
|
|
136
|
-
subHandler: h.objVal,
|
|
137
|
-
endStates: {
|
|
138
|
-
comma: 'expectObjKey',
|
|
139
|
-
closeCurl: 'expectBinOp'
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
arrayVal: {
|
|
143
|
-
subHandler: h.arrayVal,
|
|
144
|
-
endStates: {
|
|
145
|
-
comma: 'arrayVal',
|
|
146
|
-
closeBracket: 'expectBinOp'
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
ternaryMid: {
|
|
150
|
-
subHandler: h.ternaryMid,
|
|
151
|
-
endStates: {
|
|
152
|
-
colon: 'ternaryEnd'
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
ternaryEnd: {
|
|
156
|
-
subHandler: h.ternaryEnd,
|
|
157
|
-
completable: true
|
|
158
|
-
}
|
|
159
|
-
}
|