@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.
Files changed (110) hide show
  1. package/README.md +163 -20
  2. package/dist/Expression.d.ts +16 -0
  3. package/dist/Expression.js +28 -4
  4. package/dist/Expression.js.map +1 -1
  5. package/dist/Jexl.d.ts +20 -8
  6. package/dist/Jexl.js +34 -15
  7. package/dist/Jexl.js.map +1 -1
  8. package/dist/Lexer.d.ts +4 -3
  9. package/dist/Lexer.js +29 -21
  10. package/dist/Lexer.js.map +1 -1
  11. package/dist/analyze.d.ts +84 -0
  12. package/dist/analyze.js +259 -0
  13. package/dist/analyze.js.map +1 -0
  14. package/dist/check.d.ts +109 -0
  15. package/dist/check.js +1267 -0
  16. package/dist/check.js.map +1 -0
  17. package/dist/collections.d.ts +3 -0
  18. package/dist/collections.js +140 -0
  19. package/dist/collections.js.map +1 -0
  20. package/dist/conditions.d.ts +62 -0
  21. package/dist/conditions.js +207 -0
  22. package/dist/conditions.js.map +1 -0
  23. package/dist/errors.d.ts +5 -0
  24. package/dist/errors.js +18 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/evaluator/compile.d.ts +10 -7
  27. package/dist/evaluator/compile.js +295 -72
  28. package/dist/evaluator/compile.js.map +1 -1
  29. package/dist/grammar.d.ts +19 -9
  30. package/dist/grammar.js +66 -40
  31. package/dist/grammar.js.map +1 -1
  32. package/dist/index.d.ts +9 -0
  33. package/dist/index.js +16 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/operators.d.ts +34 -0
  36. package/dist/operators.js +131 -0
  37. package/dist/operators.js.map +1 -0
  38. package/dist/parser/Parser.d.ts +48 -91
  39. package/dist/parser/Parser.js +347 -221
  40. package/dist/parser/Parser.js.map +1 -1
  41. package/dist/types.d.ts +11 -5
  42. package/esm/Expression.d.ts +16 -0
  43. package/esm/Expression.js +28 -4
  44. package/esm/Expression.js.map +1 -1
  45. package/esm/Jexl.d.ts +20 -8
  46. package/esm/Jexl.js +34 -15
  47. package/esm/Jexl.js.map +1 -1
  48. package/esm/Lexer.d.ts +4 -3
  49. package/esm/Lexer.js +29 -21
  50. package/esm/Lexer.js.map +1 -1
  51. package/esm/analyze.d.ts +84 -0
  52. package/esm/analyze.js +256 -0
  53. package/esm/analyze.js.map +1 -0
  54. package/esm/check.d.ts +109 -0
  55. package/esm/check.js +1260 -0
  56. package/esm/check.js.map +1 -0
  57. package/esm/collections.d.ts +3 -0
  58. package/esm/collections.js +137 -0
  59. package/esm/collections.js.map +1 -0
  60. package/esm/conditions.d.ts +62 -0
  61. package/esm/conditions.js +200 -0
  62. package/esm/conditions.js.map +1 -0
  63. package/esm/errors.d.ts +5 -0
  64. package/esm/errors.js +14 -0
  65. package/esm/errors.js.map +1 -0
  66. package/esm/evaluator/compile.d.ts +10 -7
  67. package/esm/evaluator/compile.js +295 -72
  68. package/esm/evaluator/compile.js.map +1 -1
  69. package/esm/grammar.d.ts +19 -9
  70. package/esm/grammar.js +66 -39
  71. package/esm/grammar.js.map +1 -1
  72. package/esm/index.d.ts +9 -0
  73. package/esm/index.js +4 -0
  74. package/esm/index.js.map +1 -1
  75. package/esm/operators.d.ts +34 -0
  76. package/esm/operators.js +123 -0
  77. package/esm/operators.js.map +1 -0
  78. package/esm/parser/Parser.d.ts +48 -91
  79. package/esm/parser/Parser.js +349 -192
  80. package/esm/parser/Parser.js.map +1 -1
  81. package/esm/types.d.ts +11 -5
  82. package/package.json +1 -1
  83. package/src/Expression.ts +34 -4
  84. package/src/Jexl.ts +52 -16
  85. package/src/Lexer.ts +32 -21
  86. package/src/analyze.ts +378 -0
  87. package/src/check.ts +1682 -0
  88. package/src/collections.ts +148 -0
  89. package/src/conditions.ts +262 -0
  90. package/src/errors.ts +15 -0
  91. package/src/evaluator/compile.ts +356 -72
  92. package/src/grammar.ts +123 -42
  93. package/src/index.ts +15 -0
  94. package/src/operators.ts +151 -0
  95. package/src/parser/Parser.ts +389 -199
  96. package/src/types.ts +13 -3
  97. package/dist/parser/handlers.d.ts +0 -115
  98. package/dist/parser/handlers.js +0 -352
  99. package/dist/parser/handlers.js.map +0 -1
  100. package/dist/parser/states.d.ts +0 -47
  101. package/dist/parser/states.js +0 -176
  102. package/dist/parser/states.js.map +0 -1
  103. package/esm/parser/handlers.d.ts +0 -115
  104. package/esm/parser/handlers.js +0 -328
  105. package/esm/parser/handlers.js.map +0 -1
  106. package/esm/parser/states.d.ts +0 -47
  107. package/esm/parser/states.js +0 -140
  108. package/esm/parser/states.js.map +0 -1
  109. package/src/parser/handlers.ts +0 -372
  110. package/src/parser/states.ts +0 -159
@@ -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
@@ -1 +0,0 @@
1
- {"version":3,"file":"states.js","sourceRoot":"","sources":["../../src/parser/states.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,CAAC,MAAM,eAAe,CAAA;AAmBlC;;;;;GAKG;AACH,MAAM,YAAY,GAAqC;IACrD,QAAQ,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE;IACtC,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE;IAC5B,WAAW,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;IAClC,QAAQ,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,YAAY,EAAE;IAC5D,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,SAAS,EAAE;CACpC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,MAAM,GAAW;IAC5B,aAAa,EAAE;QACb,UAAU,EAAE;YACV,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE;YACnC,cAAc,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE;YAC1C,UAAU,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE;YACrC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE;YACvC,QAAQ,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;YAC1D,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE;YAC5B,WAAW,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,UAAU,EAAE;SAC5D;KACF;IACD,WAAW,EAAE;QACX,UAAU,EAAE,YAAY;QACxB,WAAW,EAAE,IAAI;KAClB;IACD,YAAY,EAAE;QACZ,UAAU,EAAE;YACV,OAAO,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YAC1D,UAAU,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YAC7D,SAAS,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE;SACtC;KACF;IACD,eAAe,EAAE;QACf,UAAU,EAAE;YACV,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;SAC7B;KACF;IACD,UAAU,EAAE;QACV,UAAU,EAAE;YACV,GAAG,YAAY;YACf,uEAAuE;YACvE,sEAAsE;YACtE,SAAS,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,YAAY,EAAE;SAC1D;QACD,WAAW,EAAE,IAAI;KAClB;IACD,QAAQ,EAAE;QACR,UAAU,EAAE;YACV,UAAU,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE;SACtC;KACF;IACD,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,CAAC,MAAM;QACpB,SAAS,EAAE;YACT,YAAY,EAAE,YAAY;SAC3B;KACF;IACD,aAAa,EAAE;QACb,UAAU,EAAE,CAAC,CAAC,aAAa;QAC3B,SAAS,EAAE;YACT,UAAU,EAAE,aAAa;SAC1B;KACF;IACD,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,CAAC,MAAM;QACpB,SAAS,EAAE;YACT,KAAK,EAAE,QAAQ;YACf,mEAAmE;YACnE,sEAAsE;YACtE,UAAU,EAAE,aAAa;SAC1B;KACF;IACD,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,CAAC,MAAM;QACpB,SAAS,EAAE;YACT,KAAK,EAAE,cAAc;YACrB,SAAS,EAAE,aAAa;SACzB;KACF;IACD,QAAQ,EAAE;QACR,UAAU,EAAE,CAAC,CAAC,QAAQ;QACtB,SAAS,EAAE;YACT,KAAK,EAAE,UAAU;YACjB,YAAY,EAAE,aAAa;SAC5B;KACF;IACD,UAAU,EAAE;QACV,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,SAAS,EAAE;YACT,KAAK,EAAE,YAAY;SACpB;KACF;IACD,UAAU,EAAE;QACV,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,WAAW,EAAE,IAAI;KAClB;CACF,CAAA"}
@@ -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
- }
@@ -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
- }