@rhinostone/swig-core 2.0.0-alpha.3
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/lib/backend.js +718 -0
- package/lib/cache.js +75 -0
- package/lib/dateformatter.js +201 -0
- package/lib/engine.js +414 -0
- package/lib/filters.js +43 -0
- package/lib/index.js +21 -0
- package/lib/ir.js +873 -0
- package/lib/loaders/filesystem.js +59 -0
- package/lib/loaders/index.js +53 -0
- package/lib/loaders/memory.js +63 -0
- package/lib/security.js +25 -0
- package/lib/tokenparser.js +920 -0
- package/lib/tokentypes.js +78 -0
- package/lib/utils.js +184 -0
- package/package.json +26 -0
package/lib/ir.js
ADDED
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swig IR — intermediate representation for the shared backend.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: typedef stubs only. No runtime code here — Phase 1 keeps the
|
|
5
|
+
* native frontend emitting JS source directly. Phase 2 ports the native
|
|
6
|
+
* frontend to emit IR, at which point `@rhinostone/swig-core/lib/backend.js`
|
|
7
|
+
* walks an IRTemplate and produces the compiled `new Function(...)` body.
|
|
8
|
+
*
|
|
9
|
+
* Every frontend (native Swig, Twig, Jinja2, Django) must lower its
|
|
10
|
+
* parse tree into these shapes. Constructs that cannot lower cleanly
|
|
11
|
+
* must throw at parse time — no silent partial behavior.
|
|
12
|
+
*
|
|
13
|
+
* See .claude/architecture/multi-flavor-ir.md for the full design doc,
|
|
14
|
+
* trade-offs considered, and open questions.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Source-location metadata carried by every IR node so backend errors
|
|
19
|
+
* surface the original line/column/filename unchanged.
|
|
20
|
+
*
|
|
21
|
+
* @typedef {Object} IRLoc
|
|
22
|
+
* @property {number} line
|
|
23
|
+
* @property {number} [column]
|
|
24
|
+
* @property {string} [filename]
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/* ------------------------------------------------------------------ *
|
|
28
|
+
* Statement nodes — body-level.
|
|
29
|
+
* ------------------------------------------------------------------ */
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Root of every compiled template.
|
|
33
|
+
*
|
|
34
|
+
* @typedef {Object} IRTemplate
|
|
35
|
+
* @property {'Template'} type
|
|
36
|
+
* @property {IRStatement[]} body
|
|
37
|
+
* @property {string} [parent] Resolved path of the parent template (from `extends`).
|
|
38
|
+
* @property {Object<string, IRBlock>} [blocks] Block-name → Block IR subtree.
|
|
39
|
+
* @property {IRLoc} [loc]
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Literal text chunk. Value is JSON-escaped at backend emit time.
|
|
44
|
+
*
|
|
45
|
+
* @typedef {Object} IRText
|
|
46
|
+
* @property {'Text'} type
|
|
47
|
+
* @property {string} value
|
|
48
|
+
* @property {IRLoc} [loc]
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Output an expression with optional filter chain.
|
|
53
|
+
* `safe: true` bypasses autoescape.
|
|
54
|
+
*
|
|
55
|
+
* `expr` is typed `IRExpr | IRLegacyJS` for Phase 2 — see Session 14b
|
|
56
|
+
* Commit 9. The IR shape can't represent legacy Swig's per-operand
|
|
57
|
+
* filter precedence (filter binds to the last operand across binary
|
|
58
|
+
* ops, e.g. `{{ a + b|upper }}` → `a + _filters["upper"](b)`); for
|
|
59
|
+
* those outputs the frontend falls back to the legacy JS-string
|
|
60
|
+
* emission and wraps it in {@link IRLegacyJS}. The narrow back to
|
|
61
|
+
* strict `IRExpr` is gated on lifting filter chains to expression-level
|
|
62
|
+
* ({@link IRExpr}-position filters) — a follow-up commit.
|
|
63
|
+
*
|
|
64
|
+
* @typedef {Object} IROutput
|
|
65
|
+
* @property {'Output'} type
|
|
66
|
+
* @property {IRExpr|IRLegacyJS} expr
|
|
67
|
+
* @property {IRFilterCall[]} [filters]
|
|
68
|
+
* @property {boolean} [safe]
|
|
69
|
+
* @property {IRLoc} [loc]
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Filter invocation inside an Output or Filter region.
|
|
74
|
+
* Distinct from statement-level {@link IRFilter} (region pipe).
|
|
75
|
+
*
|
|
76
|
+
* @typedef {Object} IRFilterCall
|
|
77
|
+
* @property {string} name
|
|
78
|
+
* @property {IRExpr[]} [args]
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* if / elif / else chain. Each branch's `test` is `null` for the
|
|
83
|
+
* trailing else.
|
|
84
|
+
*
|
|
85
|
+
* @typedef {Object} IRIf
|
|
86
|
+
* @property {'If'} type
|
|
87
|
+
* @property {IRIfBranch[]} branches
|
|
88
|
+
* @property {IRLoc} [loc]
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* `test` is `null` for the trailing else branch. For regular `if` /
|
|
93
|
+
* `elseif` branches it is an {@link IRExpr} when the test expression
|
|
94
|
+
* lowers cleanly, or an {@link IRLegacyJS} escape-hatch when the test
|
|
95
|
+
* contains a top-level filter chain mixed with a binary op (e.g.
|
|
96
|
+
* `{% if a === b|upper %}`) — per-operand filter precedence cannot be
|
|
97
|
+
* represented in a flat IR, same widening as {@link IROutput.expr} in
|
|
98
|
+
* Session 14b Commit 9. The factory is opaque — it accepts any value
|
|
99
|
+
* and stores it — but consumers (backends) dispatch on the shape.
|
|
100
|
+
*
|
|
101
|
+
* @typedef {Object} IRIfBranch
|
|
102
|
+
* @property {IRExpr|IRLegacyJS|null} test
|
|
103
|
+
* @property {IRStatement[]} body
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* For-loop. `emptyBody` supports Twig/Django `{% for … %}{% else %}`.
|
|
108
|
+
*
|
|
109
|
+
* @typedef {Object} IRFor
|
|
110
|
+
* @property {'For'} type
|
|
111
|
+
* @property {string} [key] Loop key var (second binding).
|
|
112
|
+
* @property {string} value Loop value var (first binding).
|
|
113
|
+
* @property {IRExpr} iterable
|
|
114
|
+
* @property {IRStatement[]} body
|
|
115
|
+
* @property {IRStatement[]} [emptyBody]
|
|
116
|
+
* @property {IRLoc} [loc]
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Named override point for template inheritance.
|
|
121
|
+
*
|
|
122
|
+
* @typedef {Object} IRBlock
|
|
123
|
+
* @property {'Block'} type
|
|
124
|
+
* @property {string} name
|
|
125
|
+
* @property {IRStatement[]} body
|
|
126
|
+
* @property {IRLoc} [loc]
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* `ignoreMissing` maps to swig's `ignore missing` modifier (silently swallow
|
|
131
|
+
* a compile-time load error from the included file). `resolveFrom` carries
|
|
132
|
+
* the including template's filename so the loader can resolve relative
|
|
133
|
+
* paths from the right anchor; empty string means "no anchor" (consumers
|
|
134
|
+
* must treat it as a JS-safe double-quoted-string fragment).
|
|
135
|
+
*
|
|
136
|
+
* @typedef {Object} IRInclude
|
|
137
|
+
* @property {'Include'} type
|
|
138
|
+
* @property {IRExpr} path Usually a string literal, but any expression is allowed.
|
|
139
|
+
* @property {IRExpr} [context] Explicit locals for the included template.
|
|
140
|
+
* @property {boolean} [isolated] Maps to Twig's `only`.
|
|
141
|
+
* @property {boolean} [ignoreMissing] Swallow loader errors when the file is missing.
|
|
142
|
+
* @property {string} [resolveFrom] Including template's filename (backslash-escaped) for loader-relative resolution.
|
|
143
|
+
* @property {IRLoc} [loc]
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @typedef {Object} IRImport
|
|
148
|
+
* @property {'Import'} type
|
|
149
|
+
* @property {IRExpr} path
|
|
150
|
+
* @property {string} alias Namespace name. MUST pass the dangerousProps guard.
|
|
151
|
+
* @property {IRLoc} [loc]
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Macro definition. Params are bare identifier names.
|
|
156
|
+
*
|
|
157
|
+
* @typedef {Object} IRMacro
|
|
158
|
+
* @property {'Macro'} type
|
|
159
|
+
* @property {string} name MUST pass the dangerousProps guard.
|
|
160
|
+
* @property {IRMacroParam[]} params
|
|
161
|
+
* @property {IRStatement[]} body
|
|
162
|
+
* @property {IRLoc} [loc]
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @typedef {Object} IRMacroParam
|
|
167
|
+
* @property {string} name
|
|
168
|
+
* @property {IRExpr} [default]
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Statement-level function / macro invocation (no output capture).
|
|
173
|
+
*
|
|
174
|
+
* @typedef {Object} IRCall
|
|
175
|
+
* @property {'Call'} type
|
|
176
|
+
* @property {IRExpr} callee
|
|
177
|
+
* @property {IRExpr[]} args
|
|
178
|
+
* @property {IRLoc} [loc]
|
|
179
|
+
*/
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Phase 2 Session 14b Commit 10: pure-dot LHS shapes (`foo`, `foo.bar`,
|
|
183
|
+
* `foo.bar.baz`) are now structured {@link IRVarRef} nodes. The
|
|
184
|
+
* bracket-touched path (`foo[bar]`, `foo["bar"]`, mixed dot+bracket)
|
|
185
|
+
* stays on the transitional `string` form — bracket-lvalue semantics
|
|
186
|
+
* (notably the runtime-variable-key case `foo[bar]`) is a cross-flavor
|
|
187
|
+
* design call deferred to a dedicated session. Backends MUST tolerate
|
|
188
|
+
* both shapes for now.
|
|
189
|
+
*
|
|
190
|
+
* `op` carries the assignment operator (`=`, `+=`, `-=`, `*=`, `/=`) so
|
|
191
|
+
* the backend can emit `<target> <op> <value>;` without re-parsing.
|
|
192
|
+
*
|
|
193
|
+
* @typedef {Object} IRSet
|
|
194
|
+
* @property {'Set'} type
|
|
195
|
+
* @property {IRVarRef|string} target MUST pass the dangerousProps guard at every path segment.
|
|
196
|
+
* @property {string} op Assignment operator.
|
|
197
|
+
* @property {IRExpr} value
|
|
198
|
+
* @property {IRLoc} [loc]
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Verbatim text. Never autoescaped, never re-parsed by the frontend.
|
|
203
|
+
*
|
|
204
|
+
* @typedef {Object} IRRaw
|
|
205
|
+
* @property {'Raw'} type
|
|
206
|
+
* @property {string} value
|
|
207
|
+
* @property {IRLoc} [loc]
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Emit the parent block's compiled content (super() / block.super).
|
|
212
|
+
*
|
|
213
|
+
* During Phase 2 (#T15), IRParent optionally carries a `body` slot so
|
|
214
|
+
* the native frontend's parent tag can resolve the parent-chain lookup
|
|
215
|
+
* at compile time (emitting the matched block's body) without the
|
|
216
|
+
* backend having to re-walk the parents array. Target shape is a bare
|
|
217
|
+
* marker — the backend resolves parent() by itself — reached once the
|
|
218
|
+
* IR owns the extends/blocks graph directly (post-Phase 2).
|
|
219
|
+
*
|
|
220
|
+
* @typedef {Object} IRParent
|
|
221
|
+
* @property {'Parent'} type
|
|
222
|
+
* @property {IRStatement[]} [body] Transitional: pre-resolved parent-block content.
|
|
223
|
+
* @property {IRLoc} [loc]
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Push/pop an autoescape strategy for a body region.
|
|
228
|
+
*
|
|
229
|
+
* @typedef {Object} IRAutoescape
|
|
230
|
+
* @property {'Autoescape'} type
|
|
231
|
+
* @property {true|false|'html'|'js'} strategy
|
|
232
|
+
* @property {IRStatement[]} body
|
|
233
|
+
* @property {IRLoc} [loc]
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Region-level filter pipe (Swig's `{% filter %}`, Twig's `{% apply %}`).
|
|
238
|
+
*
|
|
239
|
+
* @typedef {Object} IRFilter
|
|
240
|
+
* @property {'Filter'} type
|
|
241
|
+
* @property {string} name
|
|
242
|
+
* @property {IRExpr[]} [args]
|
|
243
|
+
* @property {IRStatement[]} body
|
|
244
|
+
* @property {IRLoc} [loc]
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Scoped-context region (Twig's `{% with %}`, Jinja2's `{% with %}`).
|
|
249
|
+
*
|
|
250
|
+
* `context` is an optional {@link IRExpr} evaluated once at entry. When
|
|
251
|
+
* `isolated` is true, the body sees only `context` (or an empty object
|
|
252
|
+
* when `context` is absent). When `isolated` is false, the body sees
|
|
253
|
+
* `_utils.extend({}, _ctx, context)` — or a shallow copy of `_ctx` when
|
|
254
|
+
* `context` is absent. The backend emits the region as an IIFE that
|
|
255
|
+
* shadows `_ctx` for the body's lexical scope.
|
|
256
|
+
*
|
|
257
|
+
* @typedef {Object} IRWith
|
|
258
|
+
* @property {'With'} type
|
|
259
|
+
* @property {IRExpr} [context]
|
|
260
|
+
* @property {boolean} [isolated]
|
|
261
|
+
* @property {IRStatement[]} body
|
|
262
|
+
* @property {IRLoc} [loc]
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Legacy JS-string escape hatch for constructs whose codegen still lives
|
|
267
|
+
* outside the IR emitters — userland `setTag`-registered tag `compile`
|
|
268
|
+
* functions, and built-in tags not yet migrated to real IR nodes. The
|
|
269
|
+
* backend concatenates `js` verbatim into the compiled template body.
|
|
270
|
+
*
|
|
271
|
+
* Transitional per the Phase 2 layering decision (hybrid / option iii);
|
|
272
|
+
* see .claude/architecture/multi-flavor-ir.md.
|
|
273
|
+
*
|
|
274
|
+
* @typedef {Object} IRLegacyJS
|
|
275
|
+
* @property {'LegacyJS'} type
|
|
276
|
+
* @property {string} js
|
|
277
|
+
* @property {IRLoc} [loc]
|
|
278
|
+
*/
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Any body-level IR node.
|
|
282
|
+
*
|
|
283
|
+
* @typedef {(
|
|
284
|
+
* IRText | IROutput | IRIf | IRFor | IRBlock | IRInclude | IRImport |
|
|
285
|
+
* IRMacro | IRCall | IRSet | IRRaw | IRParent | IRAutoescape | IRFilter |
|
|
286
|
+
* IRWith | IRLegacyJS
|
|
287
|
+
* )} IRStatement
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
/* ------------------------------------------------------------------ *
|
|
291
|
+
* Expression nodes.
|
|
292
|
+
* ------------------------------------------------------------------ */
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @typedef {Object} IRLiteral
|
|
296
|
+
* @property {'Literal'} type
|
|
297
|
+
* @property {'string'|'number'|'bool'|'null'|'undefined'} kind
|
|
298
|
+
* @property {string|number|boolean|null|undefined} value
|
|
299
|
+
* @property {IRLoc} [loc]
|
|
300
|
+
*/
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Dot-path variable reference: `user.profile.name` → `{ path: ['user', 'profile', 'name'] }`.
|
|
304
|
+
* Every path segment MUST pass the dangerousProps guard at backend emit time.
|
|
305
|
+
*
|
|
306
|
+
* @typedef {Object} IRVarRef
|
|
307
|
+
* @property {'VarRef'} type
|
|
308
|
+
* @property {string[]} path
|
|
309
|
+
* @property {IRLoc} [loc]
|
|
310
|
+
*/
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Dynamic (bracket) property access: `obj[key]`. `key` is any expression.
|
|
314
|
+
* When `key` is an {@link IRLiteral} of kind `'string'`, the backend
|
|
315
|
+
* applies the dangerousProps guard.
|
|
316
|
+
*
|
|
317
|
+
* @typedef {Object} IRAccess
|
|
318
|
+
* @property {'Access'} type
|
|
319
|
+
* @property {IRExpr} object
|
|
320
|
+
* @property {IRExpr} key
|
|
321
|
+
* @property {IRLoc} [loc]
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Binary operation. Frontends normalize aliases into canonical ops:
|
|
326
|
+
* `gt` → `>`, `and` → `&&`, `is` → `===` (with sentinel-RHS lowering
|
|
327
|
+
* for `is defined`, `is divisibleby(3)`, etc.).
|
|
328
|
+
*
|
|
329
|
+
* @typedef {Object} IRBinaryOp
|
|
330
|
+
* @property {'BinaryOp'} type
|
|
331
|
+
* @property {string} op
|
|
332
|
+
* @property {IRExpr} left
|
|
333
|
+
* @property {IRExpr} right
|
|
334
|
+
* @property {IRLoc} [loc]
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @typedef {Object} IRUnaryOp
|
|
339
|
+
* @property {'UnaryOp'} type
|
|
340
|
+
* @property {'!'|'-'|'+'} op
|
|
341
|
+
* @property {IRExpr} operand
|
|
342
|
+
* @property {IRLoc} [loc]
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Ternary.
|
|
347
|
+
*
|
|
348
|
+
* @typedef {Object} IRConditional
|
|
349
|
+
* @property {'Conditional'} type
|
|
350
|
+
* @property {IRExpr} test
|
|
351
|
+
* @property {IRExpr} then
|
|
352
|
+
* @property {IRExpr} else
|
|
353
|
+
* @property {IRLoc} [loc]
|
|
354
|
+
*/
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @typedef {Object} IRArrayLiteral
|
|
358
|
+
* @property {'ArrayLiteral'} type
|
|
359
|
+
* @property {IRExpr[]} elements
|
|
360
|
+
* @property {IRLoc} [loc]
|
|
361
|
+
*/
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* @typedef {Object} IRObjectLiteral
|
|
365
|
+
* @property {'ObjectLiteral'} type
|
|
366
|
+
* @property {IRObjectProperty[]} properties
|
|
367
|
+
* @property {IRLoc} [loc]
|
|
368
|
+
*/
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* @typedef {Object} IRObjectProperty
|
|
372
|
+
* @property {IRExpr} key Usually an IRLiteral of kind 'string', but any expression is allowed.
|
|
373
|
+
* @property {IRExpr} value
|
|
374
|
+
*/
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Function / method invocation at expression position.
|
|
378
|
+
*
|
|
379
|
+
* @typedef {Object} IRFnCall
|
|
380
|
+
* @property {'FnCall'} type
|
|
381
|
+
* @property {IRExpr} callee
|
|
382
|
+
* @property {IRExpr[]} args
|
|
383
|
+
* @property {IRLoc} [loc]
|
|
384
|
+
*/
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Filter invocation at expression position — distinct from the
|
|
388
|
+
* type-less {@link IRFilterCall} helper that lives inside
|
|
389
|
+
* `IROutput.filters`. This shape carries its own `input` expression so a
|
|
390
|
+
* filter can appear anywhere an expression can — inside a binary op
|
|
391
|
+
* (`a + b|upper`), inside a function call (`foo(bar|upper)`), inside a
|
|
392
|
+
* bracket access (`foo[bar|upper]`), chained (`a|upper|reverse`).
|
|
393
|
+
*
|
|
394
|
+
* The IRFilterCall helper stays the carrier for the top-level filter
|
|
395
|
+
* pipe on an Output, because that pipe feeds the accumulated output
|
|
396
|
+
* expression positionally — it doesn't fit a `{ input, ... }` shape.
|
|
397
|
+
*
|
|
398
|
+
* @typedef {Object} IRFilterCallExpr
|
|
399
|
+
* @property {'FilterCall'} type
|
|
400
|
+
* @property {string} name
|
|
401
|
+
* @property {IRExpr} input
|
|
402
|
+
* @property {IRExpr[]} [args]
|
|
403
|
+
* @property {IRLoc} [loc]
|
|
404
|
+
*/
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Any expression-position IR node.
|
|
408
|
+
*
|
|
409
|
+
* @typedef {(
|
|
410
|
+
* IRLiteral | IRVarRef | IRAccess | IRBinaryOp | IRUnaryOp |
|
|
411
|
+
* IRConditional | IRArrayLiteral | IRObjectLiteral | IRFnCall |
|
|
412
|
+
* IRFilterCallExpr
|
|
413
|
+
* )} IRExpr
|
|
414
|
+
*/
|
|
415
|
+
|
|
416
|
+
/* ------------------------------------------------------------------ *
|
|
417
|
+
* Runtime node factories — Phase 2 scaffold (Session 7, 2026-04-14).
|
|
418
|
+
*
|
|
419
|
+
* Each factory returns a plain JSON-serialisable object matching one
|
|
420
|
+
* of the typedefs above. `loc` is always optional; when omitted it is
|
|
421
|
+
* not set on the returned node (consumers can distinguish via
|
|
422
|
+
* `'loc' in node`). All other parameters are required unless documented
|
|
423
|
+
* otherwise on the corresponding typedef.
|
|
424
|
+
*
|
|
425
|
+
* No consumers yet — this commit introduces the schema surface only.
|
|
426
|
+
* Subsequent sessions will migrate the native frontend's token-tree
|
|
427
|
+
* production over to these shapes. See the Phase 2 layering notes in
|
|
428
|
+
* .claude/architecture/multi-flavor-ir.md.
|
|
429
|
+
* ------------------------------------------------------------------ */
|
|
430
|
+
|
|
431
|
+
/*!
|
|
432
|
+
* Attach `loc` to the node if provided, skipping the assignment otherwise
|
|
433
|
+
* so consumers can tell "no source location available" from
|
|
434
|
+
* "source location is the default IRLoc".
|
|
435
|
+
* @private
|
|
436
|
+
*/
|
|
437
|
+
function withLoc(node, loc) {
|
|
438
|
+
if (loc !== undefined) {
|
|
439
|
+
node.loc = loc;
|
|
440
|
+
}
|
|
441
|
+
return node;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* -- Statement factories ------------------------------------------- */
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Build a {@link IRTemplate} root node.
|
|
448
|
+
* @param {IRStatement[]} body
|
|
449
|
+
* @param {string} [parent]
|
|
450
|
+
* @param {Object<string, IRBlock>} [blocks]
|
|
451
|
+
* @param {IRLoc} [loc]
|
|
452
|
+
* @return {IRTemplate}
|
|
453
|
+
*/
|
|
454
|
+
exports.template = function (body, parent, blocks, loc) {
|
|
455
|
+
var node = { type: 'Template', body: body };
|
|
456
|
+
if (parent !== undefined) { node.parent = parent; }
|
|
457
|
+
if (blocks !== undefined) { node.blocks = blocks; }
|
|
458
|
+
return withLoc(node, loc);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Build an {@link IRText} literal-text node.
|
|
463
|
+
* @param {string} value
|
|
464
|
+
* @param {IRLoc} [loc]
|
|
465
|
+
* @return {IRText}
|
|
466
|
+
*/
|
|
467
|
+
exports.text = function (value, loc) {
|
|
468
|
+
return withLoc({ type: 'Text', value: value }, loc);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Build an {@link IROutput} node.
|
|
473
|
+
* @param {IRExpr} expr
|
|
474
|
+
* @param {IRFilterCall[]} [filters]
|
|
475
|
+
* @param {boolean} [safe]
|
|
476
|
+
* @param {IRLoc} [loc]
|
|
477
|
+
* @return {IROutput}
|
|
478
|
+
*/
|
|
479
|
+
exports.output = function (expr, filters, safe, loc) {
|
|
480
|
+
var node = { type: 'Output', expr: expr };
|
|
481
|
+
if (filters !== undefined) { node.filters = filters; }
|
|
482
|
+
if (safe !== undefined) { node.safe = safe; }
|
|
483
|
+
return withLoc(node, loc);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Build an {@link IRFilterCall}. Used inside `Output.filters` and as
|
|
488
|
+
* the tag-level filter invocation carried by {@link IRFilter}.
|
|
489
|
+
* Note: not a statement — helper shape.
|
|
490
|
+
* @param {string} name
|
|
491
|
+
* @param {IRExpr[]} [args]
|
|
492
|
+
* @return {IRFilterCall}
|
|
493
|
+
*/
|
|
494
|
+
exports.filterCall = function (name, args) {
|
|
495
|
+
var node = { name: name };
|
|
496
|
+
if (args !== undefined) { node.args = args; }
|
|
497
|
+
return node;
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Build an {@link IRIf} node from a sequence of branches.
|
|
502
|
+
* @param {IRIfBranch[]} branches
|
|
503
|
+
* @param {IRLoc} [loc]
|
|
504
|
+
* @return {IRIf}
|
|
505
|
+
*/
|
|
506
|
+
exports.ifStmt = function (branches, loc) {
|
|
507
|
+
return withLoc({ type: 'If', branches: branches }, loc);
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Build an {@link IRIfBranch}. `test` is null for the trailing else.
|
|
512
|
+
*
|
|
513
|
+
* The factory stores `test` opaquely and does not inspect it; consumers
|
|
514
|
+
* expect a real {@link IRExpr} node or `null`.
|
|
515
|
+
*
|
|
516
|
+
* @param {IRExpr|null} test
|
|
517
|
+
* @param {IRStatement[]} body
|
|
518
|
+
* @return {IRIfBranch}
|
|
519
|
+
*/
|
|
520
|
+
exports.ifBranch = function (test, body) {
|
|
521
|
+
return { test: test, body: body };
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Build an {@link IRFor} node.
|
|
526
|
+
*
|
|
527
|
+
* `iterable` is typed `IRExpr | string` for Phase 2 — see the IRFor typedef
|
|
528
|
+
* for the transitional shape. The factory stores `iterable` opaquely and
|
|
529
|
+
* does not inspect it.
|
|
530
|
+
*
|
|
531
|
+
* @param {string} value Loop value identifier (first binding).
|
|
532
|
+
* @param {IRExpr|string} iterable
|
|
533
|
+
* @param {IRStatement[]} body
|
|
534
|
+
* @param {string} [key] Loop key identifier (second binding).
|
|
535
|
+
* @param {IRStatement[]} [emptyBody]
|
|
536
|
+
* @param {IRLoc} [loc]
|
|
537
|
+
* @return {IRFor}
|
|
538
|
+
*/
|
|
539
|
+
exports.forStmt = function (value, iterable, body, key, emptyBody, loc) {
|
|
540
|
+
var node = { type: 'For', value: value, iterable: iterable, body: body };
|
|
541
|
+
if (key !== undefined) { node.key = key; }
|
|
542
|
+
if (emptyBody !== undefined) { node.emptyBody = emptyBody; }
|
|
543
|
+
return withLoc(node, loc);
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Build an {@link IRBlock} override point.
|
|
548
|
+
* @param {string} name
|
|
549
|
+
* @param {IRStatement[]} body
|
|
550
|
+
* @param {IRLoc} [loc]
|
|
551
|
+
* @return {IRBlock}
|
|
552
|
+
*/
|
|
553
|
+
exports.block = function (name, body, loc) {
|
|
554
|
+
return withLoc({ type: 'Block', name: name, body: body }, loc);
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Build an {@link IRInclude} node.
|
|
559
|
+
*
|
|
560
|
+
* `path` and `context` are {@link IRExpr} nodes (Session 14b Commit 7).
|
|
561
|
+
* The factory stores both opaquely and does not inspect them — backward-
|
|
562
|
+
* compat string fallback is handled at backend emit time for userland
|
|
563
|
+
* setTag tags that may still hand in raw JS-source fragments.
|
|
564
|
+
* `ignoreMissing` and `resolveFrom` are swig-native modifiers carried
|
|
565
|
+
* through so the backend can build the `_swig.compileFile(...)` emission.
|
|
566
|
+
*
|
|
567
|
+
* @param {IRExpr} path
|
|
568
|
+
* @param {IRExpr} [context]
|
|
569
|
+
* @param {boolean} [isolated]
|
|
570
|
+
* @param {boolean} [ignoreMissing]
|
|
571
|
+
* @param {string} [resolveFrom]
|
|
572
|
+
* @param {IRLoc} [loc]
|
|
573
|
+
* @return {IRInclude}
|
|
574
|
+
*/
|
|
575
|
+
exports.include = function (path, context, isolated, ignoreMissing, resolveFrom, loc) {
|
|
576
|
+
var node = { type: 'Include', path: path };
|
|
577
|
+
if (context !== undefined) { node.context = context; }
|
|
578
|
+
if (isolated !== undefined) { node.isolated = isolated; }
|
|
579
|
+
if (ignoreMissing !== undefined) { node.ignoreMissing = ignoreMissing; }
|
|
580
|
+
if (resolveFrom !== undefined) { node.resolveFrom = resolveFrom; }
|
|
581
|
+
return withLoc(node, loc);
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Build an {@link IRImport} node. `alias` MUST pass the dangerousProps
|
|
586
|
+
* guard at backend emit time.
|
|
587
|
+
* @param {IRExpr} path
|
|
588
|
+
* @param {string} alias
|
|
589
|
+
* @param {IRLoc} [loc]
|
|
590
|
+
* @return {IRImport}
|
|
591
|
+
*/
|
|
592
|
+
exports.importStmt = function (path, alias, loc) {
|
|
593
|
+
return withLoc({ type: 'Import', path: path, alias: alias }, loc);
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Build an {@link IRMacro} definition. `name` MUST pass the
|
|
598
|
+
* dangerousProps guard at backend emit time.
|
|
599
|
+
* @param {string} name
|
|
600
|
+
* @param {IRMacroParam[]} params
|
|
601
|
+
* @param {IRStatement[]} body
|
|
602
|
+
* @param {IRLoc} [loc]
|
|
603
|
+
* @return {IRMacro}
|
|
604
|
+
*/
|
|
605
|
+
exports.macro = function (name, params, body, loc) {
|
|
606
|
+
return withLoc({ type: 'Macro', name: name, params: params, body: body }, loc);
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Build an {@link IRMacroParam}.
|
|
611
|
+
* @param {string} name
|
|
612
|
+
* @param {IRExpr} [defaultValue]
|
|
613
|
+
* @return {IRMacroParam}
|
|
614
|
+
*/
|
|
615
|
+
exports.macroParam = function (name, defaultValue) {
|
|
616
|
+
var node = { name: name };
|
|
617
|
+
if (defaultValue !== undefined) { node['default'] = defaultValue; }
|
|
618
|
+
return node;
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Build an {@link IRCall} statement-level invocation.
|
|
623
|
+
* @param {IRExpr} callee
|
|
624
|
+
* @param {IRExpr[]} args
|
|
625
|
+
* @param {IRLoc} [loc]
|
|
626
|
+
* @return {IRCall}
|
|
627
|
+
*/
|
|
628
|
+
exports.call = function (callee, args, loc) {
|
|
629
|
+
return withLoc({ type: 'Call', callee: callee, args: args }, loc);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Build an {@link IRSet} node. `target` MUST pass the dangerousProps
|
|
634
|
+
* guard at every path segment at backend emit time.
|
|
635
|
+
*
|
|
636
|
+
* `target` is typed `IRVarRef | string` for Phase 2 — pure-dot LHS
|
|
637
|
+
* shapes are structured {@link IRVarRef} (Session 14b Commit 10);
|
|
638
|
+
* bracket-touched LHS stays a string fragment until the cross-flavor
|
|
639
|
+
* bracket-lvalue contract lands. The factory stores `target` and
|
|
640
|
+
* `value` opaquely and does not inspect them. `op` is the JS assignment
|
|
641
|
+
* operator (`=`, `+=`, etc.).
|
|
642
|
+
*
|
|
643
|
+
* @param {IRVarRef|string} target
|
|
644
|
+
* @param {string} op
|
|
645
|
+
* @param {IRExpr} value
|
|
646
|
+
* @param {IRLoc} [loc]
|
|
647
|
+
* @return {IRSet}
|
|
648
|
+
*/
|
|
649
|
+
exports.set = function (target, op, value, loc) {
|
|
650
|
+
return withLoc({ type: 'Set', target: target, op: op, value: value }, loc);
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Build an {@link IRRaw} verbatim-text node.
|
|
655
|
+
* @param {string} value
|
|
656
|
+
* @param {IRLoc} [loc]
|
|
657
|
+
* @return {IRRaw}
|
|
658
|
+
*/
|
|
659
|
+
exports.raw = function (value, loc) {
|
|
660
|
+
return withLoc({ type: 'Raw', value: value }, loc);
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Build an {@link IRParent} super()-equivalent node.
|
|
665
|
+
*
|
|
666
|
+
* Phase 2: optional `body` slot carries the pre-resolved parent-block
|
|
667
|
+
* content as IR statements. Swig's parent tag walks the parents chain
|
|
668
|
+
* at compile time and drops the matched block's body here; backends
|
|
669
|
+
* emit the body as-is. A bare Parent (no body) is valid and means the
|
|
670
|
+
* backend will resolve the lookup itself — reached post-Phase 2.
|
|
671
|
+
*
|
|
672
|
+
* @param {IRStatement[]} [body]
|
|
673
|
+
* @param {IRLoc} [loc]
|
|
674
|
+
* @return {IRParent}
|
|
675
|
+
*/
|
|
676
|
+
exports.parent = function (body, loc) {
|
|
677
|
+
var node = { type: 'Parent' };
|
|
678
|
+
if (body !== undefined) { node.body = body; }
|
|
679
|
+
return withLoc(node, loc);
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Build an {@link IRAutoescape} region.
|
|
684
|
+
* @param {true|false|'html'|'js'} strategy
|
|
685
|
+
* @param {IRStatement[]} body
|
|
686
|
+
* @param {IRLoc} [loc]
|
|
687
|
+
* @return {IRAutoescape}
|
|
688
|
+
*/
|
|
689
|
+
exports.autoescape = function (strategy, body, loc) {
|
|
690
|
+
return withLoc({ type: 'Autoescape', strategy: strategy, body: body }, loc);
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Build an {@link IRFilter} region-level filter pipe.
|
|
695
|
+
*
|
|
696
|
+
* The factory stores `args` opaquely and does not inspect its elements.
|
|
697
|
+
*
|
|
698
|
+
* @param {string} name
|
|
699
|
+
* @param {IRStatement[]} body
|
|
700
|
+
* @param {IRExpr[]} [args]
|
|
701
|
+
* @param {IRLoc} [loc]
|
|
702
|
+
* @return {IRFilter}
|
|
703
|
+
*/
|
|
704
|
+
exports.filter = function (name, body, args, loc) {
|
|
705
|
+
var node = { type: 'Filter', name: name, body: body };
|
|
706
|
+
if (args !== undefined) { node.args = args; }
|
|
707
|
+
return withLoc(node, loc);
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Build an {@link IRWith} scoped-context region.
|
|
712
|
+
*
|
|
713
|
+
* @param {IRExpr} [context] Optional context expression.
|
|
714
|
+
* @param {boolean} [isolated] `true` drops `_ctx` from the body.
|
|
715
|
+
* @param {IRStatement[]} body
|
|
716
|
+
* @param {IRLoc} [loc]
|
|
717
|
+
* @return {IRWith}
|
|
718
|
+
*/
|
|
719
|
+
exports.withStmt = function (context, isolated, body, loc) {
|
|
720
|
+
var node = { type: 'With', body: body };
|
|
721
|
+
if (context !== undefined) { node.context = context; }
|
|
722
|
+
if (isolated !== undefined) { node.isolated = isolated; }
|
|
723
|
+
return withLoc(node, loc);
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Build an {@link IRLegacyJS} escape-hatch node. Wraps a raw JS-source
|
|
728
|
+
* fragment so the backend walker can splice it into the compiled body
|
|
729
|
+
* without a dedicated emitter. The first consumer is `backend.compile`,
|
|
730
|
+
* which wraps every current parse-tree token (string, VarToken, TagToken)
|
|
731
|
+
* as an `IRLegacyJS` before emission; further built-in tags migrate to
|
|
732
|
+
* real IR shapes in subsequent sessions.
|
|
733
|
+
* @param {string} js
|
|
734
|
+
* @param {IRLoc} [loc]
|
|
735
|
+
* @return {IRLegacyJS}
|
|
736
|
+
*/
|
|
737
|
+
exports.legacyJS = function (js, loc) {
|
|
738
|
+
return withLoc({ type: 'LegacyJS', js: js }, loc);
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
/* -- Expression factories ------------------------------------------ */
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Build an {@link IRLiteral}.
|
|
745
|
+
* @param {'string'|'number'|'bool'|'null'|'undefined'} kind
|
|
746
|
+
* @param {string|number|boolean|null|undefined} value
|
|
747
|
+
* @param {IRLoc} [loc]
|
|
748
|
+
* @return {IRLiteral}
|
|
749
|
+
*/
|
|
750
|
+
exports.literal = function (kind, value, loc) {
|
|
751
|
+
return withLoc({ type: 'Literal', kind: kind, value: value }, loc);
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Build an {@link IRVarRef} dot-path variable reference. Every path
|
|
756
|
+
* segment MUST pass the dangerousProps guard at backend emit time.
|
|
757
|
+
* @param {string[]} path
|
|
758
|
+
* @param {IRLoc} [loc]
|
|
759
|
+
* @return {IRVarRef}
|
|
760
|
+
*/
|
|
761
|
+
exports.varRef = function (path, loc) {
|
|
762
|
+
return withLoc({ type: 'VarRef', path: path }, loc);
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Build an {@link IRAccess} dynamic-bracket property access.
|
|
767
|
+
* @param {IRExpr} object
|
|
768
|
+
* @param {IRExpr} key
|
|
769
|
+
* @param {IRLoc} [loc]
|
|
770
|
+
* @return {IRAccess}
|
|
771
|
+
*/
|
|
772
|
+
exports.access = function (object, key, loc) {
|
|
773
|
+
return withLoc({ type: 'Access', object: object, key: key }, loc);
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Build an {@link IRBinaryOp}.
|
|
778
|
+
* @param {string} op
|
|
779
|
+
* @param {IRExpr} left
|
|
780
|
+
* @param {IRExpr} right
|
|
781
|
+
* @param {IRLoc} [loc]
|
|
782
|
+
* @return {IRBinaryOp}
|
|
783
|
+
*/
|
|
784
|
+
exports.binaryOp = function (op, left, right, loc) {
|
|
785
|
+
return withLoc({ type: 'BinaryOp', op: op, left: left, right: right }, loc);
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Build an {@link IRUnaryOp}.
|
|
790
|
+
* @param {'!'|'-'|'+'} op
|
|
791
|
+
* @param {IRExpr} operand
|
|
792
|
+
* @param {IRLoc} [loc]
|
|
793
|
+
* @return {IRUnaryOp}
|
|
794
|
+
*/
|
|
795
|
+
exports.unaryOp = function (op, operand, loc) {
|
|
796
|
+
return withLoc({ type: 'UnaryOp', op: op, operand: operand }, loc);
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Build an {@link IRConditional} ternary. The parameter is named
|
|
801
|
+
* `els` to avoid shadowing the reserved-word `else`; the produced
|
|
802
|
+
* object uses `else` as a string key per the typedef.
|
|
803
|
+
* @param {IRExpr} test
|
|
804
|
+
* @param {IRExpr} then
|
|
805
|
+
* @param {IRExpr} els
|
|
806
|
+
* @param {IRLoc} [loc]
|
|
807
|
+
* @return {IRConditional}
|
|
808
|
+
*/
|
|
809
|
+
exports.conditional = function (test, then, els, loc) {
|
|
810
|
+
var node = { type: 'Conditional', test: test, then: then };
|
|
811
|
+
node['else'] = els;
|
|
812
|
+
return withLoc(node, loc);
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Build an {@link IRArrayLiteral}.
|
|
817
|
+
* @param {IRExpr[]} elements
|
|
818
|
+
* @param {IRLoc} [loc]
|
|
819
|
+
* @return {IRArrayLiteral}
|
|
820
|
+
*/
|
|
821
|
+
exports.arrayLiteral = function (elements, loc) {
|
|
822
|
+
return withLoc({ type: 'ArrayLiteral', elements: elements }, loc);
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Build an {@link IRObjectLiteral}.
|
|
827
|
+
* @param {IRObjectProperty[]} properties
|
|
828
|
+
* @param {IRLoc} [loc]
|
|
829
|
+
* @return {IRObjectLiteral}
|
|
830
|
+
*/
|
|
831
|
+
exports.objectLiteral = function (properties, loc) {
|
|
832
|
+
return withLoc({ type: 'ObjectLiteral', properties: properties }, loc);
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Build an {@link IRObjectProperty}.
|
|
837
|
+
* @param {IRExpr} key
|
|
838
|
+
* @param {IRExpr} value
|
|
839
|
+
* @return {IRObjectProperty}
|
|
840
|
+
*/
|
|
841
|
+
exports.objectProperty = function (key, value) {
|
|
842
|
+
return { key: key, value: value };
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Build an {@link IRFnCall} expression-position invocation.
|
|
847
|
+
* @param {IRExpr} callee
|
|
848
|
+
* @param {IRExpr[]} args
|
|
849
|
+
* @param {IRLoc} [loc]
|
|
850
|
+
* @return {IRFnCall}
|
|
851
|
+
*/
|
|
852
|
+
exports.fnCall = function (callee, args, loc) {
|
|
853
|
+
return withLoc({ type: 'FnCall', callee: callee, args: args }, loc);
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Build an {@link IRFilterCallExpr} expression-position filter
|
|
858
|
+
* invocation. Distinct from {@link exports.filterCall} — this is a
|
|
859
|
+
* first-class IRExpr that wraps its own input, so filters can appear
|
|
860
|
+
* mid-expression (`a + b|upper`, `foo[bar|upper]`) and chain
|
|
861
|
+
* (`a|upper|reverse`).
|
|
862
|
+
*
|
|
863
|
+
* @param {string} name
|
|
864
|
+
* @param {IRExpr} input
|
|
865
|
+
* @param {IRExpr[]} [args]
|
|
866
|
+
* @param {IRLoc} [loc]
|
|
867
|
+
* @return {IRFilterCallExpr}
|
|
868
|
+
*/
|
|
869
|
+
exports.filterCallExpr = function (name, input, args, loc) {
|
|
870
|
+
var node = { type: 'FilterCall', name: name, input: input };
|
|
871
|
+
if (args !== undefined) { node.args = args; }
|
|
872
|
+
return withLoc(node, loc);
|
|
873
|
+
};
|