@barefootjs/rust 0.1.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 +194 -0
- package/dist/adapter/analysis/component-tree.d.ts +26 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/boolean-result.d.ts +85 -0
- package/dist/adapter/boolean-result.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +107 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/array-method.d.ts +75 -0
- package/dist/adapter/expr/array-method.d.ts.map +1 -0
- package/dist/adapter/expr/emitters.d.ts +143 -0
- package/dist/adapter/expr/emitters.d.ts.map +1 -0
- package/dist/adapter/index.d.ts +6 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +189091 -0
- package/dist/adapter/lib/constants.d.ts +25 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +50 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/minijinja-naming.d.ts +64 -0
- package/dist/adapter/lib/minijinja-naming.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +32 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +84 -0
- package/dist/adapter/memo/seed.d.ts.map +1 -0
- package/dist/adapter/minijinja-adapter.d.ts +421 -0
- package/dist/adapter/minijinja-adapter.d.ts.map +1 -0
- package/dist/adapter/props/prop-classes.d.ts +33 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +63 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal.d.ts +28 -0
- package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
- package/dist/build.d.ts +29 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +189111 -0
- package/dist/conformance-pins.d.ts +13 -0
- package/dist/conformance-pins.d.ts.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +189112 -0
- package/package.json +67 -0
- package/runtime/Cargo.lock +124 -0
- package/runtime/Cargo.toml +21 -0
- package/runtime/src/backend_minijinja.rs +176 -0
- package/runtime/src/bin/bf-render.rs +147 -0
- package/runtime/src/evaluator.rs +770 -0
- package/runtime/src/lib.rs +19 -0
- package/runtime/src/manifest.rs +258 -0
- package/runtime/src/num.rs +558 -0
- package/runtime/src/runtime.rs +1548 -0
- package/runtime/src/search_params.rs +173 -0
- package/runtime/tests/eval_vectors.rs +94 -0
- package/runtime/tests/evaluator.rs +407 -0
- package/runtime/tests/helper_vectors.rs +348 -0
- package/runtime/tests/manifest.rs +169 -0
- package/runtime/tests/omit.rs +79 -0
- package/runtime/tests/props_attr.rs +75 -0
- package/runtime/tests/query.rs +50 -0
- package/runtime/tests/render_child.rs +210 -0
- package/runtime/tests/search_params.rs +68 -0
- package/runtime/tests/spread_attrs.rs +94 -0
- package/runtime/tests/template_primitives.rs +376 -0
- package/runtime/tests/vector-divergences.json +33 -0
- package/src/__tests__/minijinja-adapter-unit.test.ts +392 -0
- package/src/__tests__/minijinja-adapter.test.ts +58 -0
- package/src/__tests__/minijinja-counter.test.ts +61 -0
- package/src/__tests__/minijinja-query-href.test.ts +101 -0
- package/src/__tests__/minijinja-spread-attrs.test.ts +227 -0
- package/src/adapter/analysis/component-tree.ts +119 -0
- package/src/adapter/boolean-result.ts +177 -0
- package/src/adapter/emit-context.ts +119 -0
- package/src/adapter/expr/array-method.ts +346 -0
- package/src/adapter/expr/emitters.ts +608 -0
- package/src/adapter/index.ts +6 -0
- package/src/adapter/lib/constants.ts +37 -0
- package/src/adapter/lib/ir-scope.ts +95 -0
- package/src/adapter/lib/minijinja-naming.ts +85 -0
- package/src/adapter/lib/types.ts +35 -0
- package/src/adapter/memo/seed.ts +135 -0
- package/src/adapter/minijinja-adapter.ts +1796 -0
- package/src/adapter/props/prop-classes.ts +65 -0
- package/src/adapter/spread/spread-codegen.ts +168 -0
- package/src/adapter/value/parsed-literal.ts +76 -0
- package/src/build.ts +38 -0
- package/src/conformance-pins.ts +101 -0
- package/src/index.ts +12 -0
- package/src/test-render.ts +680 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array / string method lowering for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-xslate/src/adapter/expr/array-method.ts`.
|
|
5
|
+
* Pure free functions shared by both the filter-context emitter and the
|
|
6
|
+
* top-level emitter — they take an `emit` callback for receiver / argument
|
|
7
|
+
* recursion and read no adapter instance state.
|
|
8
|
+
*
|
|
9
|
+
* The receiver/array helpers are the same runtime methods the Xslate adapter
|
|
10
|
+
* calls, invoked as `bf.NAME(...)` (bare, no `$` sigil) instead of
|
|
11
|
+
* `$bf.NAME(...)`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
serializeParsedExpr,
|
|
16
|
+
freeVarsInBody,
|
|
17
|
+
} from '@barefootjs/jsx'
|
|
18
|
+
import type {
|
|
19
|
+
ParsedExpr,
|
|
20
|
+
ArrayMethod,
|
|
21
|
+
SortComparator,
|
|
22
|
+
FlatDepth,
|
|
23
|
+
} from '@barefootjs/jsx'
|
|
24
|
+
import { escapeMinijinjaSingleQuoted, minijinjaHashKey } from '../lib/minijinja-naming.ts'
|
|
25
|
+
|
|
26
|
+
export function renderArrayMethod(
|
|
27
|
+
method: ArrayMethod,
|
|
28
|
+
object: ParsedExpr,
|
|
29
|
+
args: ParsedExpr[],
|
|
30
|
+
emit: (e: ParsedExpr) => string,
|
|
31
|
+
): string {
|
|
32
|
+
switch (method) {
|
|
33
|
+
case 'join': {
|
|
34
|
+
// Route through the runtime (`bf.join`) rather than a Jinja/Python
|
|
35
|
+
// builtin, so the JS-compat element handling (undef → empty, default
|
|
36
|
+
// separator) is applied consistently.
|
|
37
|
+
const obj = emit(object)
|
|
38
|
+
const sep = args.length >= 1 ? emit(args[0]) : `','`
|
|
39
|
+
return `bf.join(${obj}, ${sep})`
|
|
40
|
+
}
|
|
41
|
+
case 'includes': {
|
|
42
|
+
const obj = emit(object)
|
|
43
|
+
const needle = emit(args[0])
|
|
44
|
+
return `bf.includes(${obj}, ${needle})`
|
|
45
|
+
}
|
|
46
|
+
case 'indexOf':
|
|
47
|
+
case 'lastIndexOf': {
|
|
48
|
+
const fn = method === 'indexOf' ? 'index_of' : 'last_index_of'
|
|
49
|
+
const obj = emit(object)
|
|
50
|
+
const needle = emit(args[0])
|
|
51
|
+
return `bf.${fn}(${obj}, ${needle})`
|
|
52
|
+
}
|
|
53
|
+
case 'at': {
|
|
54
|
+
const obj = emit(object)
|
|
55
|
+
const idx = args.length >= 1 ? emit(args[0]) : '0'
|
|
56
|
+
return `bf.at(${obj}, ${idx})`
|
|
57
|
+
}
|
|
58
|
+
case 'concat': {
|
|
59
|
+
if (args.length === 0) {
|
|
60
|
+
return emit(object)
|
|
61
|
+
}
|
|
62
|
+
const a = emit(object)
|
|
63
|
+
const b = emit(args[0])
|
|
64
|
+
return `bf.concat(${a}, ${b})`
|
|
65
|
+
}
|
|
66
|
+
case 'slice': {
|
|
67
|
+
const recv = emit(object)
|
|
68
|
+
const start = args.length >= 1 ? emit(args[0]) : '0'
|
|
69
|
+
// Jinja's undefined-literal is `none`, not Kolon's `nil` — the runtime
|
|
70
|
+
// `slice` treats it as "to end".
|
|
71
|
+
const end = args.length >= 2 ? emit(args[1]) : 'none'
|
|
72
|
+
return `bf.slice(${recv}, ${start}, ${end})`
|
|
73
|
+
}
|
|
74
|
+
case 'reverse':
|
|
75
|
+
case 'toReversed': {
|
|
76
|
+
const recv = emit(object)
|
|
77
|
+
return `bf.reverse(${recv})`
|
|
78
|
+
}
|
|
79
|
+
case 'toLowerCase': {
|
|
80
|
+
// Route through the runtime (consistent with bf.includes / bf.slice /
|
|
81
|
+
// etc.) rather than a bare `.lower()` filter, so a non-string operand
|
|
82
|
+
// still gets JS-compatible coercion first.
|
|
83
|
+
const recv = emit(object)
|
|
84
|
+
return `bf.lc(${recv})`
|
|
85
|
+
}
|
|
86
|
+
case 'toUpperCase': {
|
|
87
|
+
const recv = emit(object)
|
|
88
|
+
return `bf.uc(${recv})`
|
|
89
|
+
}
|
|
90
|
+
case 'trim': {
|
|
91
|
+
const recv = emit(object)
|
|
92
|
+
return `bf.trim(${recv})`
|
|
93
|
+
}
|
|
94
|
+
case 'toFixed': {
|
|
95
|
+
// `.toFixed(digits?)` — `bf.to_fixed` mirrors JS rounding +
|
|
96
|
+
// zero-padding (default 0 digits). #1897.
|
|
97
|
+
const recv = emit(object)
|
|
98
|
+
const digits = args.length >= 1 ? emit(args[0]) : '0'
|
|
99
|
+
return `bf.to_fixed(${recv}, ${digits})`
|
|
100
|
+
}
|
|
101
|
+
case 'split': {
|
|
102
|
+
const recv = emit(object)
|
|
103
|
+
if (args.length === 0) {
|
|
104
|
+
return `bf.split(${recv})`
|
|
105
|
+
}
|
|
106
|
+
const sep = emit(args[0])
|
|
107
|
+
if (args.length === 1) {
|
|
108
|
+
return `bf.split(${recv}, ${sep})`
|
|
109
|
+
}
|
|
110
|
+
const limit = emit(args[1])
|
|
111
|
+
return `bf.split(${recv}, ${sep}, ${limit})`
|
|
112
|
+
}
|
|
113
|
+
case 'startsWith':
|
|
114
|
+
case 'endsWith': {
|
|
115
|
+
const fn = method === 'startsWith' ? 'starts_with' : 'ends_with'
|
|
116
|
+
const recv = emit(object)
|
|
117
|
+
const arg = emit(args[0])
|
|
118
|
+
if (args.length >= 2) {
|
|
119
|
+
return `bf.${fn}(${recv}, ${arg}, ${emit(args[1])})`
|
|
120
|
+
}
|
|
121
|
+
return `bf.${fn}(${recv}, ${arg})`
|
|
122
|
+
}
|
|
123
|
+
case 'replace': {
|
|
124
|
+
const recv = emit(object)
|
|
125
|
+
const oldS = emit(args[0])
|
|
126
|
+
const newS = emit(args[1])
|
|
127
|
+
return `bf.replace(${recv}, ${oldS}, ${newS})`
|
|
128
|
+
}
|
|
129
|
+
case 'repeat': {
|
|
130
|
+
const recv = emit(object)
|
|
131
|
+
const count = args.length === 0 ? '0' : emit(args[0])
|
|
132
|
+
return `bf.repeat(${recv}, ${count})`
|
|
133
|
+
}
|
|
134
|
+
case 'padStart':
|
|
135
|
+
case 'padEnd': {
|
|
136
|
+
const fn = method === 'padStart' ? 'pad_start' : 'pad_end'
|
|
137
|
+
const recv = emit(object)
|
|
138
|
+
if (args.length === 0) {
|
|
139
|
+
return `bf.${fn}(${recv}, 0)`
|
|
140
|
+
}
|
|
141
|
+
const target = emit(args[0])
|
|
142
|
+
if (args.length === 1) {
|
|
143
|
+
return `bf.${fn}(${recv}, ${target})`
|
|
144
|
+
}
|
|
145
|
+
const pad = emit(args[1])
|
|
146
|
+
return `bf.${fn}(${recv}, ${target}, ${pad})`
|
|
147
|
+
}
|
|
148
|
+
default: {
|
|
149
|
+
// TS-level exhaustiveness guard.
|
|
150
|
+
const _exhaustive: never = method
|
|
151
|
+
throw new Error(
|
|
152
|
+
`renderArrayMethod: unhandled ArrayMethod '${(_exhaustive as string)}'`,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build the `base_env` dict argument for an evaluator call: the
|
|
160
|
+
* comparator / reducer body's free variables (body idents minus the
|
|
161
|
+
* callback params), each materialised to its SSR value via `emit`. An
|
|
162
|
+
* empty capture set yields `{}` — the runtime's seed-once env.
|
|
163
|
+
*/
|
|
164
|
+
function emitEvalEnvArg(
|
|
165
|
+
body: ParsedExpr,
|
|
166
|
+
params: string[],
|
|
167
|
+
emit: (e: ParsedExpr) => string,
|
|
168
|
+
): string {
|
|
169
|
+
const free = freeVarsInBody(body, new Set(params))
|
|
170
|
+
if (free.length === 0) return '{}'
|
|
171
|
+
const pairs = free.map(
|
|
172
|
+
n => `${minijinjaHashKey(n)}: ${emit({ kind: 'identifier', name: n })}`,
|
|
173
|
+
)
|
|
174
|
+
return `{${pairs.join(', ')}}`
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Emit a `.sort(cmp)` / `.toSorted(cmp)` via the runtime evaluator (#2018):
|
|
179
|
+
* the comparator body travels as serialized-ParsedExpr JSON, evaluated per
|
|
180
|
+
* comparison against `{paramA, paramB, …captured}`. Returns null when the
|
|
181
|
+
* body is outside the evaluator surface (e.g. a `localeCompare` comparator —
|
|
182
|
+
* `serializeParsedExpr` refuses it), so the caller falls back to the
|
|
183
|
+
* structured `bf.sort`. `params` are the comparator arrow's two params
|
|
184
|
+
* (`[paramA, paramB]`).
|
|
185
|
+
*/
|
|
186
|
+
export function renderSortEval(
|
|
187
|
+
recv: string,
|
|
188
|
+
body: ParsedExpr,
|
|
189
|
+
params: string[],
|
|
190
|
+
emit: (e: ParsedExpr) => string,
|
|
191
|
+
): string | null {
|
|
192
|
+
const json = serializeParsedExpr(body)
|
|
193
|
+
if (json === null) return null
|
|
194
|
+
// A comparator needs both params; a wrong-arity arrow would emit an
|
|
195
|
+
// 'undefined' param name, so fail over to the structured fallback / BF101
|
|
196
|
+
// (mirrors the Go / Xslate guards).
|
|
197
|
+
if (params.length < 2) return null
|
|
198
|
+
const [paramA, paramB] = params
|
|
199
|
+
const env = emitEvalEnvArg(body, params, emit)
|
|
200
|
+
return `bf.sort_eval(${recv}, '${escapeMinijinjaSingleQuoted(json)}', '${paramA}', '${paramB}', ${env})`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Emit a `.reduce(fn, init)` / `.reduceRight(fn, init)` via the runtime
|
|
205
|
+
* evaluator (#2018): the reducer body travels as serialized-ParsedExpr JSON,
|
|
206
|
+
* folded over the receiver from `init` in `direction` order. `params` are the
|
|
207
|
+
* reducer arrow's params (`[paramAcc, paramItem]`); `init` is the initial-value
|
|
208
|
+
* `ParsedExpr` from the call's trailing argument. Returns null when the body is
|
|
209
|
+
* outside the evaluator surface, or when `init` is not a literal string/number
|
|
210
|
+
* (→ caller refuses with BF101). A numeric seed passes through as a bare
|
|
211
|
+
* Jinja number; a string seed as a single-quoted literal.
|
|
212
|
+
*/
|
|
213
|
+
export function renderReduceEval(
|
|
214
|
+
recv: string,
|
|
215
|
+
body: ParsedExpr,
|
|
216
|
+
params: string[],
|
|
217
|
+
init: ParsedExpr,
|
|
218
|
+
direction: 'left' | 'right',
|
|
219
|
+
emit: (e: ParsedExpr) => string,
|
|
220
|
+
): string | null {
|
|
221
|
+
const json = serializeParsedExpr(body)
|
|
222
|
+
if (json === null) return null
|
|
223
|
+
if (init.kind !== 'literal') return null
|
|
224
|
+
const initOut =
|
|
225
|
+
init.literalType === 'string'
|
|
226
|
+
? `'${escapeMinijinjaSingleQuoted(String(init.value))}'`
|
|
227
|
+
: init.literalType === 'number'
|
|
228
|
+
? String(init.value)
|
|
229
|
+
: null
|
|
230
|
+
if (initOut === null) return null
|
|
231
|
+
// A reducer needs both the accumulator and element param; refuse a
|
|
232
|
+
// wrong-arity arrow cleanly (→ BF101) rather than emitting an 'undefined'
|
|
233
|
+
// param name (mirrors the Go / Xslate guards).
|
|
234
|
+
if (params.length < 2) return null
|
|
235
|
+
const [paramAcc, paramItem] = params
|
|
236
|
+
const env = emitEvalEnvArg(body, params, emit)
|
|
237
|
+
return `bf.reduce_eval(${recv}, '${escapeMinijinjaSingleQuoted(json)}', '${paramAcc}', '${paramItem}', ${initOut}, '${direction}', ${env})`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Emit a higher-order predicate call via the runtime evaluator (#2018, P2):
|
|
242
|
+
* `bf.filter_eval` / `bf.every_eval` / `bf.some_eval` / `bf.find_eval` /
|
|
243
|
+
* `bf.find_index_eval`, carrying the serialized predicate body + captured env
|
|
244
|
+
* dict. Generalizes the lambda lowering to the same JS-faithful evaluator
|
|
245
|
+
* the Go/Xslate adapters use. Returns null when the predicate is outside the
|
|
246
|
+
* evaluator surface (e.g. a method-call predicate — `serializeParsedExpr`
|
|
247
|
+
* refuses it), so the caller falls back to the lambda form. `forward`
|
|
248
|
+
* (find / findIndex family only) selects the search direction — `false` =
|
|
249
|
+
* findLast / findLastIndex.
|
|
250
|
+
*/
|
|
251
|
+
export function renderPredicateEval(
|
|
252
|
+
funcName: string,
|
|
253
|
+
recv: string,
|
|
254
|
+
predicate: ParsedExpr,
|
|
255
|
+
param: string,
|
|
256
|
+
emit: (e: ParsedExpr) => string,
|
|
257
|
+
forward?: boolean,
|
|
258
|
+
): string | null {
|
|
259
|
+
const json = serializeParsedExpr(predicate)
|
|
260
|
+
if (json === null) return null
|
|
261
|
+
const env = emitEvalEnvArg(predicate, [param], emit)
|
|
262
|
+
const fwd = forward === undefined ? '' : `, ${forward ? 'true' : 'false'}`
|
|
263
|
+
return `bf.${funcName}(${recv}, '${escapeMinijinjaSingleQuoted(json)}', '${param}'${fwd}, ${env})`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Emit a `.flatMap(proj)` via the runtime evaluator (#2018, P3): the projection
|
|
268
|
+
* body serializes to JSON and `bf.flat_map_eval` projects + flattens one
|
|
269
|
+
* level. `param` is the projection arrow's single param. Returns null when the
|
|
270
|
+
* projection is outside the evaluator surface (→ caller refuses with BF101).
|
|
271
|
+
*/
|
|
272
|
+
export function renderFlatMapEval(
|
|
273
|
+
recv: string,
|
|
274
|
+
body: ParsedExpr,
|
|
275
|
+
param: string,
|
|
276
|
+
emit: (e: ParsedExpr) => string,
|
|
277
|
+
): string | null {
|
|
278
|
+
const json = serializeParsedExpr(body)
|
|
279
|
+
if (json === null) return null
|
|
280
|
+
const env = emitEvalEnvArg(body, [param], emit)
|
|
281
|
+
return `bf.flat_map_eval(${recv}, '${escapeMinijinjaSingleQuoted(json)}', '${param}', ${env})`
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Emit a value-producing `.map(cb)` via the runtime evaluator (#2073): the
|
|
286
|
+
* projection body serializes to JSON and `bf.map_eval` projects each element,
|
|
287
|
+
* one result per element (no flatten — the JS `.map` contract). Composes
|
|
288
|
+
* through the array-method chain (`.map(cb).join(' ')`). Returns null when
|
|
289
|
+
* the projection is outside the evaluator surface (→ caller refuses with
|
|
290
|
+
* BF101). The JSX-returning `.map` is an IRLoop upstream and never reaches
|
|
291
|
+
* this emit.
|
|
292
|
+
*/
|
|
293
|
+
export function renderMapEval(
|
|
294
|
+
recv: string,
|
|
295
|
+
body: ParsedExpr,
|
|
296
|
+
param: string,
|
|
297
|
+
emit: (e: ParsedExpr) => string,
|
|
298
|
+
): string | null {
|
|
299
|
+
const json = serializeParsedExpr(body)
|
|
300
|
+
if (json === null) return null
|
|
301
|
+
const env = emitEvalEnvArg(body, [param], emit)
|
|
302
|
+
return `bf.map_eval(${recv}, '${escapeMinijinjaSingleQuoted(json)}', '${param}', ${env})`
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Shared Jinja emit for `.sort(cmp)` / `.toSorted(cmp)`. Used by both the
|
|
307
|
+
* filter-context emitter and the top-level emitter, plus the loop-array
|
|
308
|
+
* wrap in `renderLoop`. The runtime `bf.sort` accepts an opts dict and
|
|
309
|
+
* returns a fresh list.
|
|
310
|
+
*/
|
|
311
|
+
export function renderSortMethod(recv: string, c: SortComparator): string {
|
|
312
|
+
const keyDicts = c.keys.map((k) => {
|
|
313
|
+
const keyEntry =
|
|
314
|
+
k.key.kind === 'self'
|
|
315
|
+
? `'key_kind': 'self'`
|
|
316
|
+
: `'key_kind': 'field', 'key': '${k.key.field}'`
|
|
317
|
+
return `{${keyEntry}, 'compare_type': '${k.type}', 'direction': '${k.direction}'}`
|
|
318
|
+
})
|
|
319
|
+
return `bf.sort(${recv}, {'keys': [${keyDicts.join(', ')}]})`
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// `.flat(depth?)` → `bf.flat(recv, depth)`.
|
|
323
|
+
export function renderFlatMethod(
|
|
324
|
+
recv: string,
|
|
325
|
+
depth: FlatDepth | { expr: ParsedExpr },
|
|
326
|
+
emit: (e: ParsedExpr) => string,
|
|
327
|
+
): string {
|
|
328
|
+
if (typeof depth === 'object') {
|
|
329
|
+
// Dynamic depth (#2094): routed to a DISTINCT runtime entry point,
|
|
330
|
+
// `bf.flat_dynamic`, rather than reusing `bf.flat`. `bf.flat`'s `depth`
|
|
331
|
+
// is a compile-time int baked directly into the template source, where
|
|
332
|
+
// `-1` is a SENTINEL meaning "the source literally wrote `Infinity`".
|
|
333
|
+
// A genuinely dynamic depth value that happens to evaluate to `-1` at
|
|
334
|
+
// render time means the OPPOSITE in real JS (`[1,[2]].flat(-1)` never
|
|
335
|
+
// recurses — same as `.flat(0)`). Since both call sites would otherwise
|
|
336
|
+
// hand the same literal-looking argument to one shared function, that
|
|
337
|
+
// function couldn't tell which case it's in — so it's two functions.
|
|
338
|
+
// `bf.flat_dynamic` coerces its depth argument at RENDER time via JS
|
|
339
|
+
// `ToIntegerOrInfinity` (truncate toward zero; negative → 0; NaN → 0;
|
|
340
|
+
// +Infinity / a huge finite value → flatten fully) and delegates to the
|
|
341
|
+
// same underlying flatten routine as `bf.flat`.
|
|
342
|
+
return `bf.flat_dynamic(${recv}, ${emit(depth.expr)})`
|
|
343
|
+
}
|
|
344
|
+
const d = depth === 'infinity' ? -1 : depth
|
|
345
|
+
return `bf.flat(${recv}, ${d})`
|
|
346
|
+
}
|