@barefootjs/jinja 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 +66 -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 +84 -0
- package/dist/adapter/boolean-result.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +106 -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/expr/operand.d.ts +25 -0
- package/dist/adapter/expr/operand.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 +189097 -0
- package/dist/adapter/jinja-adapter.d.ts +394 -0
- package/dist/adapter/jinja-adapter.d.ts.map +1 -0
- package/dist/adapter/lib/constants.d.ts +21 -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/jinja-naming.d.ts +77 -0
- package/dist/adapter/lib/jinja-naming.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +27 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +81 -0
- package/dist/adapter/memo/seed.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 +61 -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 +28 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +189117 -0
- package/dist/conformance-pins.d.ts +12 -0
- package/dist/conformance-pins.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +189118 -0
- package/package.json +66 -0
- package/python/VERSION +1 -0
- package/python/barefootjs/__init__.py +57 -0
- package/python/barefootjs/backend_jinja.py +125 -0
- package/python/barefootjs/evaluator.py +787 -0
- package/python/barefootjs/runtime.py +1334 -0
- package/python/barefootjs/search_params.py +89 -0
- package/python/pyproject.toml +32 -0
- package/python/tests/__init__.py +0 -0
- package/python/tests/test_eval_vectors.py +88 -0
- package/python/tests/test_evaluator.py +406 -0
- package/python/tests/test_helper_vectors.py +288 -0
- package/python/tests/test_omit.py +62 -0
- package/python/tests/test_props_attr.py +54 -0
- package/python/tests/test_query.py +41 -0
- package/python/tests/test_render.py +102 -0
- package/python/tests/test_render_child.py +96 -0
- package/python/tests/test_search_params.py +50 -0
- package/python/tests/test_spread_attrs.py +86 -0
- package/python/tests/test_template_primitives.py +347 -0
- package/python/tests/vector-divergences.json +42 -0
- package/src/__tests__/jinja-adapter-unit.test.ts +390 -0
- package/src/__tests__/jinja-adapter.test.ts +53 -0
- package/src/__tests__/jinja-counter.test.ts +62 -0
- package/src/__tests__/jinja-query-href.test.ts +99 -0
- package/src/__tests__/jinja-spread-attrs.test.ts +225 -0
- package/src/adapter/analysis/component-tree.ts +119 -0
- package/src/adapter/boolean-result.ts +176 -0
- package/src/adapter/emit-context.ts +118 -0
- package/src/adapter/expr/array-method.ts +346 -0
- package/src/adapter/expr/emitters.ts +608 -0
- package/src/adapter/expr/operand.ts +35 -0
- package/src/adapter/index.ts +6 -0
- package/src/adapter/jinja-adapter.ts +1747 -0
- package/src/adapter/lib/constants.ts +33 -0
- package/src/adapter/lib/ir-scope.ts +95 -0
- package/src/adapter/lib/jinja-naming.ts +114 -0
- package/src/adapter/lib/types.ts +30 -0
- package/src/adapter/memo/seed.ts +132 -0
- package/src/adapter/props/prop-classes.ts +65 -0
- package/src/adapter/spread/spread-codegen.ts +166 -0
- package/src/adapter/value/parsed-literal.ts +76 -0
- package/src/build.ts +37 -0
- package/src/conformance-pins.ts +101 -0
- package/src/index.ts +9 -0
- package/src/test-render.ts +714 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array / string method lowering for the Jinja2 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 { escapeJinjaSingleQuoted, jinjaHashKey } from '../lib/jinja-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 => `${jinjaHashKey(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}, '${escapeJinjaSingleQuoted(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
|
+
? `'${escapeJinjaSingleQuoted(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}, '${escapeJinjaSingleQuoted(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}, '${escapeJinjaSingleQuoted(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}, '${escapeJinjaSingleQuoted(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}, '${escapeJinjaSingleQuoted(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 helper
|
|
330
|
+
// (`bf.flat_dynamic`), not `bf.flat`. `bf.flat`'s literal-depth path
|
|
331
|
+
// bakes `-1` in as a compile-time SENTINEL meaning "the source
|
|
332
|
+
// literally said `Infinity`". A genuinely dynamic depth expression that
|
|
333
|
+
// happens to evaluate to `-1` at render time means the OPPOSITE in real
|
|
334
|
+
// JS (`[1,[2]].flat(-1)` never recurses — same as `.flat(0)`). Since
|
|
335
|
+
// both paths would otherwise hand the same literal-looking argument to
|
|
336
|
+
// one shared function, that function can't tell which case it's in —
|
|
337
|
+
// so `bf.flat_dynamic` coerces the raw expression value via JS
|
|
338
|
+
// `ToIntegerOrInfinity` (truncate toward zero; negative → 0; NaN/non-
|
|
339
|
+
// numeric → 0; +Infinity or a huge finite value → flatten fully) before
|
|
340
|
+
// delegating to the same recursive flatten as `bf.flat` (mirrors the
|
|
341
|
+
// Go adapter's `bf_flat_dynamic` / `coerceFlatDepth`).
|
|
342
|
+
return `bf.flat_dynamic(${recv}, ${emit(depth.expr)})`
|
|
343
|
+
}
|
|
344
|
+
const d = depth === 'infinity' ? -1 : depth
|
|
345
|
+
return `bf.flat(${recv}, ${d})`
|
|
346
|
+
}
|