@barefootjs/mojolicious 0.16.0 → 0.17.1
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/dist/adapter/analysis/component-tree.d.ts +30 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +96 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/array-method.d.ts +85 -0
- package/dist/adapter/expr/array-method.d.ts.map +1 -0
- package/dist/adapter/expr/emitters.d.ts +78 -0
- package/dist/adapter/expr/emitters.d.ts.map +1 -0
- package/dist/adapter/expr/operand.d.ts +34 -0
- package/dist/adapter/expr/operand.d.ts.map +1 -0
- package/dist/adapter/index.js +1482 -1339
- package/dist/adapter/lib/constants.d.ts +27 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +33 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/perl-naming.d.ts +29 -0
- package/dist/adapter/lib/perl-naming.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +28 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +34 -0
- package/dist/adapter/memo/seed.d.ts.map +1 -0
- package/dist/adapter/mojo-adapter.d.ts +46 -104
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/adapter/props/prop-classes.d.ts +48 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +64 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal.d.ts +26 -0
- package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
- package/dist/build.js +1482 -1339
- package/dist/index.js +1482 -1339
- package/dist/test-render.d.ts.map +1 -1
- package/lib/BarefootJS/Backend/Mojo.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS.pm +1 -1
- package/package.json +3 -3
- package/src/__tests__/mojo-adapter.test.ts +286 -69
- package/src/__tests__/query-href.test.ts +94 -0
- package/src/adapter/analysis/component-tree.ts +128 -0
- package/src/adapter/emit-context.ts +107 -0
- package/src/adapter/expr/array-method.ts +429 -0
- package/src/adapter/expr/emitters.ts +639 -0
- package/src/adapter/expr/operand.ts +55 -0
- package/src/adapter/lib/constants.ts +39 -0
- package/src/adapter/lib/ir-scope.ts +57 -0
- package/src/adapter/lib/perl-naming.ts +36 -0
- package/src/adapter/lib/types.ts +31 -0
- package/src/adapter/memo/seed.ts +73 -0
- package/src/adapter/mojo-adapter.ts +248 -1485
- package/src/adapter/props/prop-classes.ts +87 -0
- package/src/adapter/spread/spread-codegen.ts +181 -0
- package/src/adapter/value/parsed-literal.ts +34 -0
- package/src/test-render.ts +2 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array / string method lowering for the Mojolicious EP template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
|
|
5
|
+
* track D). Pure free functions shared by both the filter-context emitter
|
|
6
|
+
* and the top-level emitter — they take an `emit` callback for receiver /
|
|
7
|
+
* argument recursion and read no adapter instance state.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
serializeParsedExpr,
|
|
12
|
+
freeVarsInBody,
|
|
13
|
+
} from '@barefootjs/jsx'
|
|
14
|
+
import type {
|
|
15
|
+
ParsedExpr,
|
|
16
|
+
ArrayMethod,
|
|
17
|
+
SortComparator,
|
|
18
|
+
FlatDepth,
|
|
19
|
+
} from '@barefootjs/jsx'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Lower an `arr.<method>(...)` / `str.<method>(...)` value-builtin call to
|
|
23
|
+
* its Perl form. The IR lifts these into the dedicated `array-method` kind at
|
|
24
|
+
* parse time (see the `arrayMethod` emitter arms), so this is the single
|
|
25
|
+
* place every adapter-supported array/string method is mapped. An unhandled
|
|
26
|
+
* `ArrayMethod` variant throws rather than emitting a silent no-op — the
|
|
27
|
+
* drift defence we already apply to `ParsedExpr.kind` extended to its
|
|
28
|
+
* sub-discriminator.
|
|
29
|
+
*/
|
|
30
|
+
export function renderArrayMethod(
|
|
31
|
+
method: ArrayMethod,
|
|
32
|
+
object: ParsedExpr,
|
|
33
|
+
args: ParsedExpr[],
|
|
34
|
+
emit: (e: ParsedExpr) => string,
|
|
35
|
+
): string {
|
|
36
|
+
switch (method) {
|
|
37
|
+
case 'join': {
|
|
38
|
+
// arr.join(sep) → join(sep, @{arr}). The default `${obj}->{join}`
|
|
39
|
+
// hash-lookup fallback would emit invalid Perl, which is why the
|
|
40
|
+
// IR carves out a dedicated method node instead of routing
|
|
41
|
+
// through the generic call dispatcher. `.join()` defaults the
|
|
42
|
+
// separator to `,` (JS) and ignores any extra argument.
|
|
43
|
+
const obj = emit(object)
|
|
44
|
+
const sep = args.length >= 1 ? emit(args[0]) : `','`
|
|
45
|
+
return `join(${sep}, @{${obj}})`
|
|
46
|
+
}
|
|
47
|
+
case 'includes': {
|
|
48
|
+
// Both `arr.includes(x)` and `str.includes(sub)` route here —
|
|
49
|
+
// the parser can't disambiguate the receiver type. The Mojo
|
|
50
|
+
// runtime's `bf->includes($recv, $elem)` inspects `ref($recv)`
|
|
51
|
+
// and dispatches: ARRAY ref scans the list with `eq`, scalar
|
|
52
|
+
// falls back to `index(..., ...) != -1`. Helper lives in
|
|
53
|
+
// packages/adapter-perl/lib/BarefootJS.pm.
|
|
54
|
+
//
|
|
55
|
+
// The `bf->` (no `$`) form matches every other helper emit —
|
|
56
|
+
// in real Mojolicious `bf` is a controller helper; the
|
|
57
|
+
// standalone test-render in test-render.ts rewrites the bare
|
|
58
|
+
// `bf->` to `$bf->` so both render paths stay consistent.
|
|
59
|
+
const obj = emit(object)
|
|
60
|
+
const needle = emit(args[0])
|
|
61
|
+
return `bf->includes(${obj}, ${needle})`
|
|
62
|
+
}
|
|
63
|
+
case 'indexOf':
|
|
64
|
+
case 'lastIndexOf': {
|
|
65
|
+
// Array `.indexOf(x)` / `.lastIndexOf(x)` value-equality
|
|
66
|
+
// search. The Perl helpers (`bf->index_of`, `bf->last_index_of`)
|
|
67
|
+
// walk the array forward / backward and compare with `eq`
|
|
68
|
+
// (with defined/undef parity). The existing `.find` lowering
|
|
69
|
+
// uses Perl `grep` for struct-field find — disjoint surface,
|
|
70
|
+
// disjoint helpers.
|
|
71
|
+
const fn = method === 'indexOf' ? 'index_of' : 'last_index_of'
|
|
72
|
+
const obj = emit(object)
|
|
73
|
+
const needle = emit(args[0])
|
|
74
|
+
return `bf->${fn}(${obj}, ${needle})`
|
|
75
|
+
}
|
|
76
|
+
case 'at': {
|
|
77
|
+
// `.at(i)` with negative-index support — `.at(-1)` is the
|
|
78
|
+
// last element. The Mojo helper wraps the same `length + i`
|
|
79
|
+
// arithmetic the Go `bf_at` does so the lowering stays
|
|
80
|
+
// symmetric across adapters. `.at()` with no argument is `.at(0)`
|
|
81
|
+
// (the first element); extra arguments are ignored.
|
|
82
|
+
const obj = emit(object)
|
|
83
|
+
const idx = args.length >= 1 ? emit(args[0]) : '0'
|
|
84
|
+
return `bf->at(${obj}, ${idx})`
|
|
85
|
+
}
|
|
86
|
+
case 'concat': {
|
|
87
|
+
// `.concat(other)` merges two arrays. Returns a new ARRAY
|
|
88
|
+
// ref so the result composes with `.join(...)` / other
|
|
89
|
+
// array-shape methods downstream (the canonical Tier A
|
|
90
|
+
// conformance fixture chains `.concat(...).join(' ')`).
|
|
91
|
+
// `.concat()` with no argument is a shallow copy — indistinguishable
|
|
92
|
+
// from the receiver in an SSR snapshot, so it lowers to the receiver.
|
|
93
|
+
if (args.length === 0) {
|
|
94
|
+
return emit(object)
|
|
95
|
+
}
|
|
96
|
+
const a = emit(object)
|
|
97
|
+
const b = emit(args[0])
|
|
98
|
+
return `bf->concat(${a}, ${b})`
|
|
99
|
+
}
|
|
100
|
+
case 'slice': {
|
|
101
|
+
// `.slice()` / `.slice(start)` / `.slice(start, end)`. The Mojo
|
|
102
|
+
// helper mirrors the Go arithmetic (negative-index normalisation,
|
|
103
|
+
// out-of-bounds clamping, empty result on start >= end). A
|
|
104
|
+
// missing `start` defaults to 0 (full copy); an absent `end`
|
|
105
|
+
// lowers as `undef`, which the helper treats as "to length". JS
|
|
106
|
+
// ignores a third+ argument. Returns a new ARRAY ref so the
|
|
107
|
+
// result composes with `.join(...)` downstream.
|
|
108
|
+
const recv = emit(object)
|
|
109
|
+
const start = args.length >= 1 ? emit(args[0]) : '0'
|
|
110
|
+
const end = args.length >= 2 ? emit(args[1]) : 'undef'
|
|
111
|
+
return `bf->slice(${recv}, ${start}, ${end})`
|
|
112
|
+
}
|
|
113
|
+
case 'reverse':
|
|
114
|
+
case 'toReversed': {
|
|
115
|
+
// Both shapes share a lowering — see the parser arm + Go
|
|
116
|
+
// emit for the SSR-mutation-rationale. Returns a new ARRAY
|
|
117
|
+
// ref so the result composes with `.join(...)` downstream.
|
|
118
|
+
const recv = emit(object)
|
|
119
|
+
return `bf->reverse(${recv})`
|
|
120
|
+
}
|
|
121
|
+
case 'toLowerCase': {
|
|
122
|
+
// Perl's native `lc` is the obvious lowering — no helper
|
|
123
|
+
// method needed. The receiver flows through `emit` so any
|
|
124
|
+
// upstream coercion (`$value`, `$bf->string(...)`, etc.)
|
|
125
|
+
// composes naturally.
|
|
126
|
+
const recv = emit(object)
|
|
127
|
+
return `lc(${recv})`
|
|
128
|
+
}
|
|
129
|
+
case 'toUpperCase': {
|
|
130
|
+
// Perl's native `uc` — mirrors `toLowerCase` exactly.
|
|
131
|
+
const recv = emit(object)
|
|
132
|
+
return `uc(${recv})`
|
|
133
|
+
}
|
|
134
|
+
case 'trim': {
|
|
135
|
+
// No Perl native `trim`; route through the `bf->trim`
|
|
136
|
+
// helper so the regex stays in one place (and so an undef
|
|
137
|
+
// receiver doesn't trigger a warning about applying `s///`
|
|
138
|
+
// to undef).
|
|
139
|
+
const recv = emit(object)
|
|
140
|
+
return `bf->trim(${recv})`
|
|
141
|
+
}
|
|
142
|
+
case 'toFixed': {
|
|
143
|
+
// `.toFixed(digits?)` — Number → fixed-decimal string. `bf->to_fixed`
|
|
144
|
+
// mirrors JS rounding + zero-padding (default 0 digits). #1897.
|
|
145
|
+
const recv = emit(object)
|
|
146
|
+
const digits = args.length >= 1 ? emit(args[0]) : '0'
|
|
147
|
+
return `bf->to_fixed(${recv}, ${digits})`
|
|
148
|
+
}
|
|
149
|
+
case 'split': {
|
|
150
|
+
// `.split()` / `.split(sep)` / `.split(sep, limit)` — string →
|
|
151
|
+
// ARRAY ref via `bf->split`. With no separator the helper returns
|
|
152
|
+
// the whole string as a single element; otherwise it quotemetas
|
|
153
|
+
// the separator (literal match, not regex) and keeps trailing
|
|
154
|
+
// empties (`-1`), staying byte-equal with Go's `bf_split`. The
|
|
155
|
+
// optional `limit` caps the pieces; JS ignores a third+ argument.
|
|
156
|
+
// See #1448 Tier B.
|
|
157
|
+
const recv = emit(object)
|
|
158
|
+
if (args.length === 0) {
|
|
159
|
+
return `bf->split(${recv})`
|
|
160
|
+
}
|
|
161
|
+
const sep = emit(args[0])
|
|
162
|
+
if (args.length === 1) {
|
|
163
|
+
return `bf->split(${recv}, ${sep})`
|
|
164
|
+
}
|
|
165
|
+
const limit = emit(args[1])
|
|
166
|
+
return `bf->split(${recv}, ${sep}, ${limit})`
|
|
167
|
+
}
|
|
168
|
+
case 'startsWith':
|
|
169
|
+
case 'endsWith': {
|
|
170
|
+
// `.startsWith(prefix, position?)` / `.endsWith(suffix,
|
|
171
|
+
// endPosition?)` — string → boolean. The Perl helpers
|
|
172
|
+
// (`bf->starts_with` / `bf->ends_with`) do a `substr`-anchored
|
|
173
|
+
// comparison so the search string is matched literally (no regex
|
|
174
|
+
// metachar surprises) and undef receivers stay quiet. The optional
|
|
175
|
+
// second argument re-anchors the test; JS ignores a third+
|
|
176
|
+
// argument. See #1448 Tier B.
|
|
177
|
+
const fn = method === 'startsWith' ? 'starts_with' : 'ends_with'
|
|
178
|
+
const recv = emit(object)
|
|
179
|
+
const arg = emit(args[0])
|
|
180
|
+
if (args.length >= 2) {
|
|
181
|
+
return `bf->${fn}(${recv}, ${arg}, ${emit(args[1])})`
|
|
182
|
+
}
|
|
183
|
+
return `bf->${fn}(${recv}, ${arg})`
|
|
184
|
+
}
|
|
185
|
+
case 'replace': {
|
|
186
|
+
// `.replace(old, new)` — string-pattern form, first occurrence.
|
|
187
|
+
// The `bf->replace` helper splices via index/substr (not `s///`)
|
|
188
|
+
// so both the pattern and the replacement are literal — no Perl
|
|
189
|
+
// regex metacharacters and no `$1` / `$&` interpolation in the
|
|
190
|
+
// replacement, keeping it byte-equal with Go's `bf_replace`. The
|
|
191
|
+
// regex-pattern form is refused upstream at the parser. See
|
|
192
|
+
// #1448 Tier B.
|
|
193
|
+
const recv = emit(object)
|
|
194
|
+
const oldS = emit(args[0])
|
|
195
|
+
const newS = emit(args[1])
|
|
196
|
+
return `bf->replace(${recv}, ${oldS}, ${newS})`
|
|
197
|
+
}
|
|
198
|
+
case 'repeat': {
|
|
199
|
+
// `.repeat(n)` — string repeated `n` times. The `bf->repeat`
|
|
200
|
+
// helper wraps Perl's `x` operator with the same negative-count
|
|
201
|
+
// → "" clamp and integer truncation Go's `bf_repeat` applies, so
|
|
202
|
+
// the two adapters stay byte-equal. Full JS arity: the no-argument
|
|
203
|
+
// form is `repeat(0)` → ""; a second+ argument is ignored.
|
|
204
|
+
// See #1448 Tier B.
|
|
205
|
+
const recv = emit(object)
|
|
206
|
+
const count = args.length === 0 ? '0' : emit(args[0])
|
|
207
|
+
return `bf->repeat(${recv}, ${count})`
|
|
208
|
+
}
|
|
209
|
+
case 'padStart':
|
|
210
|
+
case 'padEnd': {
|
|
211
|
+
// `.padStart(target, pad?)` / `.padEnd(target, pad?)`. The
|
|
212
|
+
// `bf->pad_*` helpers default the pad to a single space when the
|
|
213
|
+
// arg is omitted and measure length in characters, matching Go's
|
|
214
|
+
// rune-based `bf_pad_*`. Full JS arity: the no-argument form is
|
|
215
|
+
// `padStart(0)` → the receiver unchanged; a third+ argument is
|
|
216
|
+
// ignored. See #1448 Tier B.
|
|
217
|
+
const fn = method === 'padStart' ? 'pad_start' : 'pad_end'
|
|
218
|
+
const recv = emit(object)
|
|
219
|
+
if (args.length === 0) {
|
|
220
|
+
return `bf->${fn}(${recv}, 0)`
|
|
221
|
+
}
|
|
222
|
+
const target = emit(args[0])
|
|
223
|
+
if (args.length === 1) {
|
|
224
|
+
return `bf->${fn}(${recv}, ${target})`
|
|
225
|
+
}
|
|
226
|
+
const pad = emit(args[1])
|
|
227
|
+
return `bf->${fn}(${recv}, ${target}, ${pad})`
|
|
228
|
+
}
|
|
229
|
+
default: {
|
|
230
|
+
// TS-level exhaustiveness guard. If this throws at runtime, the
|
|
231
|
+
// IR was constructed against a newer `ArrayMethod` variant that
|
|
232
|
+
// this adapter hasn't been updated for — loud failure is better
|
|
233
|
+
// than emitting a silent empty string downstream.
|
|
234
|
+
const _exhaustive: never = method
|
|
235
|
+
throw new Error(
|
|
236
|
+
`renderArrayMethod: unhandled ArrayMethod '${(_exhaustive as string)}'`,
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Escape a string for embedding in a Perl single-quoted literal. */
|
|
243
|
+
function escapePerlSingleQuote(s: string): string {
|
|
244
|
+
// Double every backslash and escape apostrophes: Perl single-quote
|
|
245
|
+
// un-escaping then restores the original bytes (a JSON `\\` must reach
|
|
246
|
+
// `JSON::PP->decode` as `\\`, so it travels as `\\\\` in the literal).
|
|
247
|
+
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Build the `base_env` hashref argument for an evaluator call: the
|
|
252
|
+
* comparator / reducer body's free variables (body idents minus the
|
|
253
|
+
* callback params), each materialised to its SSR value via `emit`. An
|
|
254
|
+
* empty capture set yields `{}` — the runtime's seed-once env.
|
|
255
|
+
*/
|
|
256
|
+
function emitEvalEnvArg(
|
|
257
|
+
body: ParsedExpr,
|
|
258
|
+
params: string[],
|
|
259
|
+
emit: (e: ParsedExpr) => string,
|
|
260
|
+
): string {
|
|
261
|
+
const free = freeVarsInBody(body, new Set(params))
|
|
262
|
+
if (free.length === 0) return '{}'
|
|
263
|
+
const pairs = free.map(
|
|
264
|
+
n => `'${escapePerlSingleQuote(n)}' => ${emit({ kind: 'identifier', name: n })}`,
|
|
265
|
+
)
|
|
266
|
+
return `{ ${pairs.join(', ')} }`
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Emit a `.sort(cmp)` / `.toSorted(cmp)` via the runtime evaluator (#2018):
|
|
271
|
+
* the comparator body travels as serialized-ParsedExpr JSON, evaluated per
|
|
272
|
+
* comparison against `{paramA, paramB, …captured}`. Returns null when the
|
|
273
|
+
* body can't be evaluated (e.g. a `localeCompare` comparator —
|
|
274
|
+
* `serializeParsedExpr` refuses it), so the caller falls back to the
|
|
275
|
+
* structured `bf->sort`. A `||`-chained multi-key comparator needs no
|
|
276
|
+
* special handling — JS `0 || next` is exactly the tie-break semantics.
|
|
277
|
+
*/
|
|
278
|
+
export function renderSortEval(
|
|
279
|
+
recv: string,
|
|
280
|
+
body: ParsedExpr,
|
|
281
|
+
params: string[],
|
|
282
|
+
emit: (e: ParsedExpr) => string,
|
|
283
|
+
): string | null {
|
|
284
|
+
if (params.length < 2) return null
|
|
285
|
+
const [paramA, paramB] = params
|
|
286
|
+
const json = serializeParsedExpr(body)
|
|
287
|
+
if (json === null) return null
|
|
288
|
+
const env = emitEvalEnvArg(body, [paramA, paramB], emit)
|
|
289
|
+
return `bf->sort_eval(${recv}, '${escapePerlSingleQuote(json)}', '${paramA}', '${paramB}', ${env})`
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Emit a `.reduce(fn, init)` / `.reduceRight(fn, init)` via the runtime
|
|
294
|
+
* evaluator (#2018): the reducer body travels as serialized-ParsedExpr JSON,
|
|
295
|
+
* folded over the receiver from `init` in `direction` order. Returns null when
|
|
296
|
+
* the body can't be evaluated (→ caller falls back to `bf->reduce`). A numeric
|
|
297
|
+
* seed passes through as a bare Perl number; a concat seed as a single-quoted
|
|
298
|
+
* string.
|
|
299
|
+
*/
|
|
300
|
+
export function renderReduceEval(
|
|
301
|
+
recv: string,
|
|
302
|
+
body: ParsedExpr,
|
|
303
|
+
params: string[],
|
|
304
|
+
init: ParsedExpr,
|
|
305
|
+
direction: 'left' | 'right',
|
|
306
|
+
emit: (e: ParsedExpr) => string,
|
|
307
|
+
): string | null {
|
|
308
|
+
if (params.length < 2) return null
|
|
309
|
+
const [paramAcc, paramItem] = params
|
|
310
|
+
const json = serializeParsedExpr(body)
|
|
311
|
+
if (json === null) return null
|
|
312
|
+
// Only literal seeds round-trip to a Perl scalar here: a string literal as a
|
|
313
|
+
// single-quoted Perl string, a number literal as a bare numeric. Anything
|
|
314
|
+
// else (an expression seed, an omitted seed) can't be materialised → null,
|
|
315
|
+
// and the caller records BF101.
|
|
316
|
+
let initPerl: string
|
|
317
|
+
if (init.kind === 'literal' && init.literalType === 'string') {
|
|
318
|
+
initPerl = `'${escapePerlSingleQuote(String(init.value))}'`
|
|
319
|
+
} else if (init.kind === 'literal' && init.literalType === 'number') {
|
|
320
|
+
initPerl = String(init.value)
|
|
321
|
+
} else {
|
|
322
|
+
return null
|
|
323
|
+
}
|
|
324
|
+
const env = emitEvalEnvArg(body, [paramAcc, paramItem], emit)
|
|
325
|
+
return `bf->reduce_eval(${recv}, '${escapePerlSingleQuote(json)}', '${paramAcc}', '${paramItem}', ${initPerl}, '${direction}', ${env})`
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Emit a higher-order predicate call via the runtime evaluator (#2018, P2):
|
|
330
|
+
* `bf->filter_eval` / `bf->every_eval` / `bf->some_eval` / `bf->find_eval` /
|
|
331
|
+
* `bf->find_index_eval`, carrying the serialized predicate body + captured env
|
|
332
|
+
* hashref. Generalizes the inline `grep` / `bf->find` lowering to the same
|
|
333
|
+
* JS-faithful evaluator the Go adapter uses (cross-adapter isomorphism).
|
|
334
|
+
* Returns null when the predicate is outside the evaluator surface (e.g. a
|
|
335
|
+
* method-call predicate — `serializeParsedExpr` refuses it), so the caller
|
|
336
|
+
* falls back to the `grep` / `bf->find` form. `forward` (find / findIndex
|
|
337
|
+
* family only) selects the search direction — `false` = findLast /
|
|
338
|
+
* findLastIndex.
|
|
339
|
+
*/
|
|
340
|
+
export function renderPredicateEval(
|
|
341
|
+
funcName: string,
|
|
342
|
+
recv: string,
|
|
343
|
+
predicate: ParsedExpr,
|
|
344
|
+
param: string,
|
|
345
|
+
emit: (e: ParsedExpr) => string,
|
|
346
|
+
forward?: boolean,
|
|
347
|
+
): string | null {
|
|
348
|
+
const json = serializeParsedExpr(predicate)
|
|
349
|
+
if (json === null) return null
|
|
350
|
+
const env = emitEvalEnvArg(predicate, [param], emit)
|
|
351
|
+
const fwd = forward === undefined ? '' : `, ${forward ? 1 : 0}`
|
|
352
|
+
return `bf->${funcName}(${recv}, '${escapePerlSingleQuote(json)}', '${param}'${fwd}, ${env})`
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Emit a `.flatMap(proj)` via the runtime evaluator (#2018, P3/P5): the
|
|
357
|
+
* projection `body` serializes to JSON and `bf->flat_map_eval` projects +
|
|
358
|
+
* flattens one level. The sole flatMap lowering now (the structured
|
|
359
|
+
* `bf->flat_map` / `bf->flat_map_tuple` fallback was retired with the
|
|
360
|
+
* `ParsedExpr` collapse); returns null when the projection is outside the
|
|
361
|
+
* evaluator surface, and the caller records BF101.
|
|
362
|
+
*/
|
|
363
|
+
export function renderFlatMapEval(
|
|
364
|
+
recv: string,
|
|
365
|
+
body: ParsedExpr,
|
|
366
|
+
param: string,
|
|
367
|
+
emit: (e: ParsedExpr) => string,
|
|
368
|
+
): string | null {
|
|
369
|
+
const json = serializeParsedExpr(body)
|
|
370
|
+
if (json === null) return null
|
|
371
|
+
const env = emitEvalEnvArg(body, [param], emit)
|
|
372
|
+
return `bf->flat_map_eval(${recv}, '${escapePerlSingleQuote(json)}', '${param}', ${env})`
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Emit a value-producing `.map(cb)` via the runtime evaluator (#2073): the
|
|
377
|
+
* projection `body` serializes to JSON and `bf->map_eval` projects each
|
|
378
|
+
* element, one result per element (no flatten — the JS `.map` contract).
|
|
379
|
+
* Composes through the array-method chain (`.map(cb).join(' ')`). Returns
|
|
380
|
+
* null when the projection is outside the evaluator surface, and the caller
|
|
381
|
+
* records BF101. The JSX-returning `.map` is an IRLoop upstream and never
|
|
382
|
+
* reaches this emit.
|
|
383
|
+
*/
|
|
384
|
+
export function renderMapEval(
|
|
385
|
+
recv: string,
|
|
386
|
+
body: ParsedExpr,
|
|
387
|
+
param: string,
|
|
388
|
+
emit: (e: ParsedExpr) => string,
|
|
389
|
+
): string | null {
|
|
390
|
+
const json = serializeParsedExpr(body)
|
|
391
|
+
if (json === null) return null
|
|
392
|
+
const env = emitEvalEnvArg(body, [param], emit)
|
|
393
|
+
return `bf->map_eval(${recv}, '${escapePerlSingleQuote(json)}', '${param}', ${env})`
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Shared Mojo emit for `.sort(cmp)` / `.toSorted(cmp)` (#1448 Tier B).
|
|
398
|
+
* Used by both the filter-context emitter and the top-level emitter,
|
|
399
|
+
* plus the loop-hoist path in `renderLoop` — same emit shape across
|
|
400
|
+
* all three so a regression in any one path surfaces consistently.
|
|
401
|
+
*
|
|
402
|
+
* The Perl helper accepts a hash-ref opts bag whose `keys` entry is
|
|
403
|
+
* an ordered list of per-key hashes (room for a future `nulls` knob
|
|
404
|
+
* without arity churn), and returns a fresh ARRAY ref so downstream
|
|
405
|
+
* composition (`@{bf->sort(...)}` in `join(...)`, etc.) stays
|
|
406
|
+
* straightforward.
|
|
407
|
+
*/
|
|
408
|
+
export function renderSortMethod(recv: string, c: SortComparator): string {
|
|
409
|
+
// One hash per comparison key, in priority order, under `keys`. A
|
|
410
|
+
// simple comparator yields a one-element list; a `||`-chained
|
|
411
|
+
// multi-key comparator yields one per operand. `bf->sort` walks them
|
|
412
|
+
// in order, falling through to the next on a tie.
|
|
413
|
+
const keyHashes = c.keys.map((k) => {
|
|
414
|
+
const keyEntry =
|
|
415
|
+
k.key.kind === 'self'
|
|
416
|
+
? `key_kind => 'self'`
|
|
417
|
+
: `key_kind => 'field', key => '${k.key.field}'`
|
|
418
|
+
return `{ ${keyEntry}, compare_type => '${k.type}', direction => '${k.direction}' }`
|
|
419
|
+
})
|
|
420
|
+
return `bf->sort(${recv}, { keys => [${keyHashes.join(', ')}] })`
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// `.flat(depth?)` → `bf->flat($recv, $depth)`. The `Infinity` form lowers
|
|
424
|
+
// to the `-1` sentinel (flatten fully); a finite depth flattens that many
|
|
425
|
+
// levels (`0` = shallow copy). See `sub flat` in BarefootJS.pm. (#1448)
|
|
426
|
+
export function renderFlatMethod(recv: string, depth: FlatDepth): string {
|
|
427
|
+
const d = depth === 'infinity' ? -1 : depth
|
|
428
|
+
return `bf->flat(${recv}, ${d})`
|
|
429
|
+
}
|