parseman 0.56.0 → 0.57.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/dist/analysis/diagnostics.js +379 -72
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +379 -72
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/node.d.ts +1 -1
- package/dist/compiler/build-arity.d.ts +1 -1
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/cst/trivia-kinds.d.ts +9 -1
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/index.js +446 -75
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +1 -0
- package/dist/language-service/index.js.map +1 -1
- package/dist/parse-context.d.ts.map +1 -1
- package/dist/plugin/entry-supercompiler.d.ts +1 -1
- package/dist/plugin/entry-supercompiler.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +27 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +930 -157
- package/dist/plugin/index.js.map +3 -3
- package/dist/plugin/ir-eval.d.ts +2 -0
- package/dist/plugin/ir-eval.d.ts.map +1 -1
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/run/index.js +1 -0
- package/dist/run/index.js.map +1 -1
- package/dist/table/assemble.d.ts +9 -0
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/child-slots.d.ts +4 -6
- package/dist/table/child-slots.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts +14 -2
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +17 -2
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts +5 -1
- package/dist/table/emit.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/exec-baseline.d.ts.map +1 -1
- package/dist/table/exec.d.ts.map +1 -1
- package/dist/table/index.js +442 -70
- package/dist/table/index.js.map +3 -3
- package/dist/table/ops.d.ts +9 -6
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +13 -0
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/select-hot.d.ts +6 -0
- package/dist/table/select-hot.d.ts.map +1 -0
- package/dist/table/trivia-gap.d.ts +31 -0
- package/dist/table/trivia-gap.d.ts.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +4 -1
- package/src/combinators/node.ts +6 -13
- package/src/combinators/trivia-skip.ts +44 -1
- package/src/compiler/build-arity.ts +3 -3
- package/src/cst/trivia-kinds.ts +12 -1
- package/src/parse-context.ts +1 -0
- package/src/plugin/entry-supercompiler.ts +8 -9
- package/src/plugin/index.ts +138 -13
- package/src/plugin/ir-eval.ts +7 -1
- package/src/regex/classes.ts +3 -0
- package/src/table/assemble.ts +122 -24
- package/src/table/child-slots.ts +4 -6
- package/src/table/compile-rule-map.ts +55 -15
- package/src/table/emit-assembly.ts +139 -26
- package/src/table/emit.ts +8 -7
- package/src/table/encode.ts +69 -21
- package/src/table/exec-baseline.ts +1 -3
- package/src/table/exec.ts +7 -6
- package/src/table/ops.ts +9 -6
- package/src/table/program.ts +24 -0
- package/src/table/select-hot.ts +70 -0
- package/src/table/trivia-gap.ts +109 -0
- package/src/types.ts +18 -0
- package/src/version.ts +1 -1
|
@@ -4,20 +4,17 @@ import type { HostMode } from '../cst/host-mode.ts'
|
|
|
4
4
|
import { collectGrammarReflection, GRAMMAR_REFLECTION, type GrammarReflection } from '../cst/reflection.ts'
|
|
5
5
|
import { encodeTableProgram, type TableSettings } from './encode.ts'
|
|
6
6
|
import { defaultAssemblyCfgs, emitTableExpression } from './emit.ts'
|
|
7
|
+
import { emitAssemblySource, Unemittable } from './emit-assembly.ts'
|
|
8
|
+
import { selectHotSites } from './select-hot.ts'
|
|
9
|
+
|
|
10
|
+
/** The most the entry supercompiler may grow an assembly (`entry-supercompiler.ts`, its 5% source cap). */
|
|
11
|
+
const SUPERCOMPILE_GROWTH = 1.05
|
|
7
12
|
import { tableRules } from './assemble.ts'
|
|
8
|
-
import { closureArtifact } from './program.ts'
|
|
13
|
+
import { assemblyExtraIps, closureArtifact, resolveTable } from './program.ts'
|
|
9
14
|
import type { TableProgram, TableRule } from './program.ts'
|
|
10
15
|
import { buildGrammarPlan, type GrammarCoverageDefinition } from '../compiler/grammar-coverage-ids.ts'
|
|
11
16
|
import { runDuplicationDiagnosticRules, type DuplicationOption } from './duplication-hook.ts'
|
|
12
17
|
|
|
13
|
-
/**
|
|
14
|
-
* Static assembly has a fixed source cost that tiny grammars cannot amortize.
|
|
15
|
-
* The canonical size probe's composeLeaf is 62 words and grows by ~12 kB with
|
|
16
|
-
* a factory; the shipping Jess leaves are 6,365/10,892 words. Keep the policy
|
|
17
|
-
* between those measured populations so small artifacts retain table density.
|
|
18
|
-
*/
|
|
19
|
-
const DEFAULT_PRECOMPILE_MIN_WORDS = 1_024
|
|
20
|
-
|
|
21
18
|
/**
|
|
22
19
|
* `compileRuleMap()` FOR THE TABLE LOWERING — the counterpart that did not exist.
|
|
23
20
|
*
|
|
@@ -116,8 +113,16 @@ export type CompiledRuleMapTable = {
|
|
|
116
113
|
/** Re-emit the same table call with construction-time artifact metadata. */
|
|
117
114
|
replacementWithMetadata(
|
|
118
115
|
metadataSource: string,
|
|
119
|
-
options?: { readonly
|
|
116
|
+
options?: { readonly select?: ReadonlySet<number> },
|
|
120
117
|
): string
|
|
118
|
+
/**
|
|
119
|
+
* The sites a SELECTIVE default assembly emits within `budget` bytes — its
|
|
120
|
+
* fixed prelude plus the site bodies `select-hot.ts` ranks highest — and the
|
|
121
|
+
* bytes that spends. Empty for a CST or line-tracking table (only the default
|
|
122
|
+
* AST/no-lines option set is pre-compiled), for a budget smaller than the
|
|
123
|
+
* prelude, and for a table the emitter refuses, which names the construct.
|
|
124
|
+
*/
|
|
125
|
+
selectAssembly(budget: number, profile?: ArrayLike<number>): { readonly sites: ReadonlySet<number>; readonly bytes: number; readonly refusal?: string }
|
|
121
126
|
hostMode: HostMode
|
|
122
127
|
hostBranchElided: boolean
|
|
123
128
|
reflection: GrammarReflection
|
|
@@ -234,7 +239,7 @@ function withAmbient(
|
|
|
234
239
|
export function compileRuleMapRunnable(
|
|
235
240
|
ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
|
|
236
241
|
opts: TableRuleMapOptions = {},
|
|
237
|
-
): Omit<CompiledRuleMapTable, 'replacement' | 'replacementWithMetadata'> | null {
|
|
242
|
+
): Omit<CompiledRuleMapTable, 'replacement' | 'replacementWithMetadata' | 'selectAssembly'> | null {
|
|
238
243
|
const encoded = encodeForRun(ruleMap, opts)
|
|
239
244
|
if (encoded === null) return null
|
|
240
245
|
const { prog, hostMode, plan } = encoded
|
|
@@ -331,23 +336,58 @@ export function compileRuleMap(
|
|
|
331
336
|
|
|
332
337
|
const replacementWithMetadata = (
|
|
333
338
|
metadataSource?: string,
|
|
334
|
-
options: { readonly
|
|
339
|
+
options: { readonly select?: ReadonlySet<number> } = {},
|
|
335
340
|
): string => emitTableExpression(artifact, {
|
|
336
341
|
entry: null,
|
|
337
342
|
runtimeRef: opts.runtimeRef ?? 'tableRules',
|
|
338
343
|
fnSources: sources as string[],
|
|
339
|
-
...(options.
|
|
340
|
-
|
|
341
|
-
? { assemblies: defaultAssemblyCfgs(artifact).slice(0, 1) }
|
|
344
|
+
...(options.select !== undefined && options.select.size > 0
|
|
345
|
+
? { assemblies: [{ ...defaultAssemblyCfgs(artifact)[0]!, select: options.select }] }
|
|
342
346
|
: {}),
|
|
343
347
|
...(metadataSource === undefined ? {} : { metadataSource }),
|
|
344
348
|
})
|
|
345
349
|
const replacement = replacementWithMetadata()
|
|
350
|
+
const selectAssembly = (budget: number, profile?: ArrayLike<number>): { sites: ReadonlySet<number>; bytes: number; refusal?: string } => {
|
|
351
|
+
const none = { sites: new Set<number>(), bytes: 0 }
|
|
352
|
+
if (hostMode !== 'ast' || prog.lines === 1) return none
|
|
353
|
+
const table = resolveTable(artifact)
|
|
354
|
+
const cfg = defaultAssemblyCfgs(artifact)[0]!
|
|
355
|
+
const roots = assemblyExtraIps(artifact)
|
|
356
|
+
let siteBytes, fixed
|
|
357
|
+
try {
|
|
358
|
+
siteBytes = emitAssemblySource(table, artifact, cfg, roots, true).siteBytes
|
|
359
|
+
// What any selective assembly costs before its first site body: the shared
|
|
360
|
+
// prelude and the factory's tail. A budget that cannot cover it buys nothing.
|
|
361
|
+
fixed = emitAssemblySource(table, artifact, cfg, roots, true, new Set()).source.length
|
|
362
|
+
} catch (e) {
|
|
363
|
+
if (e instanceof Unemittable) return { ...none, refusal: e.construct }
|
|
364
|
+
throw e
|
|
365
|
+
}
|
|
366
|
+
// The estimate (prelude plus site bodies) leaves out what selected sites add
|
|
367
|
+
// around their bodies — shared helpers and the `_px` wrappers of the closure
|
|
368
|
+
// sites they call — and the supercompiler may grow the source by up to 5%. So
|
|
369
|
+
// the selection is judged by the source it actually emits, scaled for that 5%,
|
|
370
|
+
// and narrowed until that fits.
|
|
371
|
+
const ceiling = budget / SUPERCOMPILE_GROWTH
|
|
372
|
+
let target = ceiling
|
|
373
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
374
|
+
const sites = selectHotSites(artifact.code, Object.values(artifact.rules), siteBytes, target - fixed, profile)
|
|
375
|
+
if (sites.size === 0) return none
|
|
376
|
+
const bytes = emitAssemblySource(table, artifact, cfg, roots, true, sites).source.length
|
|
377
|
+
if (bytes <= ceiling) return { sites, bytes: Math.ceil(bytes * SUPERCOMPILE_GROWTH) }
|
|
378
|
+
// Scale by the overshoot, and by at least 1%: near the ceiling a site is
|
|
379
|
+
// the unit, and the ratio alone can stall a few bytes over it until the
|
|
380
|
+
// attempts run out and nothing ships.
|
|
381
|
+
target *= Math.min(ceiling / bytes, 0.99)
|
|
382
|
+
}
|
|
383
|
+
return none
|
|
384
|
+
}
|
|
346
385
|
|
|
347
386
|
return {
|
|
348
387
|
keys: ruleMap.map(([key]) => key),
|
|
349
388
|
replacement,
|
|
350
389
|
replacementWithMetadata,
|
|
390
|
+
selectAssembly,
|
|
351
391
|
hostMode,
|
|
352
392
|
// WHAT THE FLAG MEANS is "a DIRECT BUILDER's positioned-CST branch was
|
|
353
393
|
// dropped", which is what the driver's `'ast' artifact + CST host` check keys
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
import type { ParseContext } from '../types.ts'
|
|
48
48
|
import { regexCanMatchEmpty } from '../regex/first-set.ts'
|
|
49
49
|
import {
|
|
50
|
-
OP_ADJ, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
50
|
+
OP_ADJ, OP_ARMGATE, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
51
51
|
OP_LABEL, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
|
|
52
52
|
OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX,
|
|
53
|
-
OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
53
|
+
OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_WITHCTX, OP_XFORM,
|
|
54
54
|
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
55
55
|
} from './ops.ts'
|
|
56
56
|
import {
|
|
@@ -79,6 +79,14 @@ export type EmittedAssembly = {
|
|
|
79
79
|
readonly end: () => number
|
|
80
80
|
readonly begin: (ctx: ParseContext) => void
|
|
81
81
|
readonly finish: () => void
|
|
82
|
+
/**
|
|
83
|
+
* Present only on a SELECTIVE assembly: the unselected sites its bodies call,
|
|
84
|
+
* the hook that binds them to closure pieces (with a setter for the closure
|
|
85
|
+
* engine's installed trivia scanner), and that same setter for this scope's.
|
|
86
|
+
*/
|
|
87
|
+
readonly ext?: readonly number[]
|
|
88
|
+
readonly bind?: (setClosureScan: (s: unknown) => unknown, pieces: readonly EmittedPiece[]) => void
|
|
89
|
+
readonly scan?: (s: unknown) => unknown
|
|
82
90
|
}
|
|
83
91
|
/** The `new Function` result. Its parameters are `EMITTED_PARAMS`, in order. */
|
|
84
92
|
export type EmittedFactory = (...args: readonly unknown[]) => EmittedAssembly
|
|
@@ -127,6 +135,9 @@ export const EMITTED_PARAMS = [
|
|
|
127
135
|
// Appended for trivia-attributing fields (`field(…, { leadingTrivia })`);
|
|
128
136
|
// old factories ignore them.
|
|
129
137
|
'LEAD', 'triviaFieldCapture',
|
|
138
|
+
// Appended for the deferred-commit trivia skip (`trivia-gap.ts`); old
|
|
139
|
+
// factories ignore it.
|
|
140
|
+
'skipDeferredTrivia',
|
|
130
141
|
] as const
|
|
131
142
|
|
|
132
143
|
/**
|
|
@@ -162,7 +173,7 @@ function _skipTrivia(input,cur,ctx){
|
|
|
162
173
|
const s=_pfScan
|
|
163
174
|
if(s!==null&&ctx._triviaLog===undefined&&!(ctx.captureTrivia===true&&(ctx._cstBuf!==undefined||ctx._cstTriviaLog!==undefined)))return s(input,cur)
|
|
164
175
|
if(s!==null)return skipTriviaScanned(s,input,cur,ctx)
|
|
165
|
-
if(needsDeferredTriviaCommit(ctx))return
|
|
176
|
+
if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
|
|
166
177
|
return advanceTrivia(input,cur,ctx)
|
|
167
178
|
}
|
|
168
179
|
function _pushLeaf(ctx,value,s,e){pushCstLeaf(ctx,{_tag:'leaf',value,span:{start:s,end:e}})}
|
|
@@ -479,6 +490,8 @@ export type EmitResult = {
|
|
|
479
490
|
readonly armExpected: readonly (readonly string[])[][]
|
|
480
491
|
/** The same three pools as indices, for a build-time emitter. */
|
|
481
492
|
readonly plan: PoolPlan
|
|
493
|
+
/** Source length of each emitted site body, by site — what selection prices. */
|
|
494
|
+
readonly siteBytes: ReadonlyMap<number, number>
|
|
482
495
|
}
|
|
483
496
|
|
|
484
497
|
/** The candidate mask for one `CLS` row — the ONE definition, shared with the emitter. */
|
|
@@ -526,6 +539,11 @@ export function rebuildPools(
|
|
|
526
539
|
* ordinary factory literal in a macro artifact. It permits macro-only code
|
|
527
540
|
* shaping without perturbing the runtime `compile()` emitter, whose generated
|
|
528
541
|
* parser must remain byte-identical when the optimization cannot affect it.
|
|
542
|
+
*
|
|
543
|
+
* `select`, when given, makes this a SELECTIVE (hybrid) assembly: only sites
|
|
544
|
+
* whose alias-resolved target is in the set get a body. Every other child a
|
|
545
|
+
* body calls is an EXTERNAL — `_px<ip>`, a wrapper bound after construction to
|
|
546
|
+
* the closure engine's piece for that site (`assemble.ts`).
|
|
529
547
|
*/
|
|
530
548
|
export function emitAssemblySource(
|
|
531
549
|
t: ResolvedTable,
|
|
@@ -541,6 +559,7 @@ export function emitAssemblySource(
|
|
|
541
559
|
},
|
|
542
560
|
extraIps: readonly number[] = [],
|
|
543
561
|
staticBuild = false,
|
|
562
|
+
select?: ReadonlySet<number>,
|
|
544
563
|
): EmitResult {
|
|
545
564
|
const { code, k, fx, disp, dsp, triviaLabelled } = t
|
|
546
565
|
const swapLegal = !cfg.trackLines
|
|
@@ -716,6 +735,9 @@ export function emitAssemblySource(
|
|
|
716
735
|
|
|
717
736
|
const bodies: string[] = []
|
|
718
737
|
const byIp = new Map<number, string>()
|
|
738
|
+
/** Unselected sites a selective assembly calls into — see `select`. */
|
|
739
|
+
const externals: number[] = []
|
|
740
|
+
const siteBytes = new Map<number, number>()
|
|
719
741
|
const reached = new Set<number>()
|
|
720
742
|
const prelude: string[] = []
|
|
721
743
|
const skipDefs: string[] = []
|
|
@@ -900,6 +922,28 @@ return 1
|
|
|
900
922
|
* site property — nothing in `src` writes it except a caller-supplied
|
|
901
923
|
* `ParseContext` (`types.ts:524`) — so it stays the one runtime read.
|
|
902
924
|
*/
|
|
925
|
+
/** The test an `OP_GATE` row at `gip` fails on, with its class hoisted. */
|
|
926
|
+
function gateMissTest(gip: number): string {
|
|
927
|
+
const resolved = t.cc[code[gip + 1]!]!
|
|
928
|
+
const ci = classes.push([resolved]) - 1
|
|
929
|
+
classPlan.push([code[gip + 1]!])
|
|
930
|
+
const cls = hoist('cl', `CLS[${ci}][0]`)
|
|
931
|
+
return isAsciiOnlyClass(resolved)
|
|
932
|
+
? `${cls}.ascii[input.charCodeAt(pos)]!==1`
|
|
933
|
+
: `!classHas(${cls},lead(input,pos))`
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* An `optional`/`not`/`peek` child's first-character gate (`encode.ts`
|
|
938
|
+
* `lookaheadGate`), tested BEFORE the wrapper's mark: a miss is the wrapper's
|
|
939
|
+
* decided outcome, `miss`. Nothing where the child has no gate, or where a
|
|
940
|
+
* probe or tolerant parse forwards through it (`aliasOf`).
|
|
941
|
+
*/
|
|
942
|
+
function lookaheadMiss(child: number, miss: string): string {
|
|
943
|
+
if (code[child] !== OP_GATE || cfg.tolerant || cfg.probe) return ''
|
|
944
|
+
return `if(${gateMissTest(child)}){${miss}}\n`
|
|
945
|
+
}
|
|
946
|
+
|
|
903
947
|
function skipFor(l: SiteLabel): string {
|
|
904
948
|
if (l.tri < 0) return '_skipTrivia'
|
|
905
949
|
const ki = l.tri
|
|
@@ -919,9 +963,9 @@ return 1
|
|
|
919
963
|
// through `scanTrivia`. `needsDeferredTriviaCommit` is implied by an open
|
|
920
964
|
// buffer, so an in-node site skips the call that asks.
|
|
921
965
|
skipDefs.push(l.buf
|
|
922
|
-
? `function ${nm}(input,cur,ctx){return
|
|
966
|
+
? `function ${nm}(input,cur,ctx){return skipDeferredTrivia(input,cur,ctx)}`
|
|
923
967
|
: `function ${nm}(input,cur,ctx){
|
|
924
|
-
if(needsDeferredTriviaCommit(ctx))return
|
|
968
|
+
if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
|
|
925
969
|
return advanceTrivia(input,cur,ctx)}`)
|
|
926
970
|
return nm
|
|
927
971
|
}
|
|
@@ -1002,12 +1046,20 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
1002
1046
|
const target = resolveAlias(ip)
|
|
1003
1047
|
const done = byIp.get(target)
|
|
1004
1048
|
if (done !== undefined) return done
|
|
1049
|
+
if (select !== undefined && !select.has(target)) {
|
|
1050
|
+
const ext = `_px${target}`
|
|
1051
|
+
byIp.set(target, ext)
|
|
1052
|
+
externals.push(target)
|
|
1053
|
+
return ext
|
|
1054
|
+
}
|
|
1005
1055
|
const fname = `_pf${target}`
|
|
1006
1056
|
// Reserved BEFORE lowering, so a back-edge into a site still in flight binds
|
|
1007
1057
|
// to the hoisted declaration rather than to a forwarding stub. This is the
|
|
1008
1058
|
// whole of `assemble.ts`'s `inFlight` map and its one shared closure.
|
|
1009
1059
|
byIp.set(target, fname)
|
|
1010
|
-
|
|
1060
|
+
const body = lower(target, fname)
|
|
1061
|
+
siteBytes.set(target, body.length)
|
|
1062
|
+
bodies.push(body)
|
|
1011
1063
|
return fname
|
|
1012
1064
|
}
|
|
1013
1065
|
|
|
@@ -1266,19 +1318,40 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
1266
1318
|
|
|
1267
1319
|
case OP_GATE: {
|
|
1268
1320
|
const child = link(code[ip + 2]!)
|
|
1269
|
-
const resolved = t.cc[code[ip + 1]!]!
|
|
1270
|
-
const ci = classes.push([resolved]) - 1
|
|
1271
|
-
classPlan.push([code[ip + 1]!])
|
|
1272
|
-
const cls = hoist('cl', `CLS[${ci}][0]`)
|
|
1273
1321
|
const xf = fxRef(code[ip + 3]!)
|
|
1274
1322
|
return `${head}
|
|
1275
|
-
if(${
|
|
1276
|
-
|
|
1277
|
-
|
|
1323
|
+
if(${gateMissTest(ip)}){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
1324
|
+
return ${child}(input,pos,ctx)
|
|
1325
|
+
}`
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
case OP_ARMGATE: {
|
|
1329
|
+
// `choice({ gate, combinator })`, as `assemble.ts` lowers it. A blocked
|
|
1330
|
+
// arm is SKIPPED, not failed (choice.ts `continue`): it clears the cut,
|
|
1331
|
+
// sets no failure position for the ordered path to rank, and leaves the
|
|
1332
|
+
// arm's own expected set for the dispatch path.
|
|
1333
|
+
const pred = fnRef(code[ip + 1]!)
|
|
1334
|
+
const child = link(code[ip + 2]!)
|
|
1335
|
+
const xf = fxRef(code[ip + 3]!)
|
|
1336
|
+
return `${head}
|
|
1337
|
+
if(!${pred}(ctx.state)){ctx._fc=false;ctx._fe=-1;ctx._fx=${xf};return FAIL}
|
|
1278
1338
|
return ${child}(input,pos,ctx)
|
|
1279
1339
|
}`
|
|
1280
1340
|
}
|
|
1281
1341
|
|
|
1342
|
+
case OP_WITHCTX: {
|
|
1343
|
+
// `withCtx(extra | derive, p)`, as `assemble.ts` lowers it: a negative
|
|
1344
|
+
// operand is a derive callback over the outer state, else a constant.
|
|
1345
|
+
// `finally` restores the outer state on every exit, a throw included.
|
|
1346
|
+
const ki = code[ip + 1]!
|
|
1347
|
+
const child = link(code[ip + 2]!)
|
|
1348
|
+
return `${head}
|
|
1349
|
+
const saved=ctx.state
|
|
1350
|
+
ctx.state=${ki < 0 ? `${fnRef(~ki)}(saved)` : kRef(ki)}
|
|
1351
|
+
try{return ${child}(input,pos,ctx)}finally{ctx.state=saved}
|
|
1352
|
+
}`
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1282
1355
|
case OP_XFORM: {
|
|
1283
1356
|
const fn = fnRef(code[ip + 1]!)
|
|
1284
1357
|
const child = link(code[ip + 2]!)
|
|
@@ -1589,7 +1662,7 @@ return FAIL
|
|
|
1589
1662
|
const xf = fxRef(code[ip + 2]!)
|
|
1590
1663
|
const p = tmp()
|
|
1591
1664
|
return `${head}
|
|
1592
|
-
${emitMark(p, L.buf, L.raw, sinks)}
|
|
1665
|
+
${lookaheadMiss(code[ip + 1]!, 'EC.e=pos;return null')}${emitMark(p, L.buf, L.raw, sinks)}
|
|
1593
1666
|
const v=${child}(input,pos,ctx)
|
|
1594
1667
|
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1595
1668
|
if(v===FAIL){EC.e=pos;return null}
|
|
@@ -1604,7 +1677,7 @@ return FAIL
|
|
|
1604
1677
|
const xf = fxRef(code[ip + 2]!)
|
|
1605
1678
|
const p = tmp()
|
|
1606
1679
|
return `${head}
|
|
1607
|
-
${emitMark(p, L.buf, L.raw, sinks)}
|
|
1680
|
+
${lookaheadMiss(code[ip + 1]!, `ctx._fe=pos;ctx._fx=${xf};return FAIL`)}${emitMark(p, L.buf, L.raw, sinks)}
|
|
1608
1681
|
const v=${child}(input,pos,ctx)
|
|
1609
1682
|
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1610
1683
|
if(v===FAIL){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
@@ -1618,7 +1691,7 @@ return null
|
|
|
1618
1691
|
const clean = !REC && failureRollbackClean(prog, ip)
|
|
1619
1692
|
const p = tmp()
|
|
1620
1693
|
return `${head}
|
|
1621
|
-
${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
1694
|
+
${lookaheadMiss(code[ip + 1]!, 'EC.e=pos;return null')}${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
1622
1695
|
ctx._fc=false
|
|
1623
1696
|
const v=${child}(input,pos,ctx)
|
|
1624
1697
|
if(v===FAIL){
|
|
@@ -1735,7 +1808,7 @@ return v
|
|
|
1735
1808
|
// array index feeding ONE call site, which is the megamorphic site
|
|
1736
1809
|
// this unit exists to remove.
|
|
1737
1810
|
const armSwitch = arms.map((a, i) => `case ${i}:v=${a}(input,pos,ctx);break`).join('\n')
|
|
1738
|
-
const directAscii =
|
|
1811
|
+
const directAscii = table.hi.length === 0
|
|
1739
1812
|
return `${head}
|
|
1740
1813
|
const c=${directAscii ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
|
|
1741
1814
|
let arm=-1
|
|
@@ -1766,7 +1839,7 @@ return FAIL
|
|
|
1766
1839
|
const expected = Array.from({ length: n }, (_, i) => fxRef(code[base + n + i]!))
|
|
1767
1840
|
const gates = Array.from({ length: n }, (_, i) => table.armCls[i] ?? null)
|
|
1768
1841
|
const maskable = n <= 32
|
|
1769
|
-
const directAsciiMask =
|
|
1842
|
+
const directAsciiMask = maskable && gates.every(isAsciiOnlyClass)
|
|
1770
1843
|
const gateRefs = directAsciiMask
|
|
1771
1844
|
? []
|
|
1772
1845
|
: Array.from({ length: n }, (_, i) => hoist('g', `DISP[${di}].armCls[${i}]`))
|
|
@@ -2001,6 +2074,9 @@ ${rollbackFor(i)}
|
|
|
2001
2074
|
// A zero compatible-arm mask is stronger than a speculative miss: no
|
|
2002
2075
|
// arm can run, so the choice's encoded flat expected set is already
|
|
2003
2076
|
// the exact result. Do not walk the arm ladder merely to rebuild it.
|
|
2077
|
+
// The same holds when the mask admitted arms but none was entered
|
|
2078
|
+
// (`prev` still 0, so `acc` is unset): the catch-up would rebuild
|
|
2079
|
+
// `choiceFx`, the arms' flatMap, set by set.
|
|
2004
2080
|
return `${head}
|
|
2005
2081
|
const c=${directAsciiMask ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
|
|
2006
2082
|
${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
|
|
@@ -2011,7 +2087,7 @@ const bits=${maskName}[${directAsciiMask ? 'c' : 'c<0?128:c'}]
|
|
|
2011
2087
|
if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
|
|
2012
2088
|
${startFailureExact ? '' : 'let prev=0'}
|
|
2013
2089
|
${maskArms}
|
|
2014
|
-
${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${n},prev,acc)`}
|
|
2090
|
+
${startFailureExact ? '' : `if(best===pos&&prev!==0)acc=${catchName}(${n},prev,acc)`}
|
|
2015
2091
|
ctx._fe=pos;ctx._fx=${startFailureExact ? `best===pos?${choiceFx}:acc??${choiceFx}` : `acc??${choiceFx}`}
|
|
2016
2092
|
return FAIL
|
|
2017
2093
|
}
|
|
@@ -2123,7 +2199,7 @@ return [key,v]
|
|
|
2123
2199
|
const ci = classes.push([resolved]) - 1
|
|
2124
2200
|
classPlan.push([itemClassIndex])
|
|
2125
2201
|
itemCls = hoist('ri', `CLS[${ci}][0]`)
|
|
2126
|
-
itemAscii =
|
|
2202
|
+
itemAscii = isAsciiOnlyClass(resolved)
|
|
2127
2203
|
}
|
|
2128
2204
|
}
|
|
2129
2205
|
const itemMiss = (at: string): string => itemAscii
|
|
@@ -2437,7 +2513,7 @@ return nd
|
|
|
2437
2513
|
const keepChildren = !structural || cfg.hostReadsChildren !== false || collapse || unwrap
|
|
2438
2514
|
const omitsRaw = !hostCst && build !== undefined && proj < 0 && (flags & 2) !== 0
|
|
2439
2515
|
const ty = q(type)
|
|
2440
|
-
const stArg =
|
|
2516
|
+
const stArg = 'ctx.state'
|
|
2441
2517
|
const hostCall = `_pfHost(${ty},hostKids,fieldMap,span,rawKids,tlog,${stArg},${tags})`
|
|
2442
2518
|
let value: string
|
|
2443
2519
|
if (proj >= 0) {
|
|
@@ -2512,7 +2588,7 @@ ctx.captureTrivia=sCap
|
|
|
2512
2588
|
if(v===FAIL)return FAIL
|
|
2513
2589
|
const end=EC.e
|
|
2514
2590
|
const span=${tracked ? 'spanLines(ctx,pos,end)' : '{start:pos,end}'}
|
|
2515
|
-
const st=${readsState ? '
|
|
2591
|
+
const st=${readsState ? 'ctx.state' : 'undefined'}
|
|
2516
2592
|
let nd
|
|
2517
2593
|
${unwrap ? 'if(kids.length===1)nd=unwrapChild(kids[0])\nelse ' : ''}${collapse ? 'if(kids.length===1)nd=kids[0]\nelse ' : ''}${collapsible ? `if(_pfHost!==undefined&&_pfHost._parsemanCstCollapse!==undefined&&kids.length===1&&rawKids.length===1&&_pfHost._parsemanCstCollapse(${ty},kids[0],kids,rawKids))nd=kids[0]
|
|
2518
2594
|
else ` : ''}{${value}}
|
|
@@ -2535,8 +2611,12 @@ return nd
|
|
|
2535
2611
|
}
|
|
2536
2612
|
}
|
|
2537
2613
|
|
|
2614
|
+
// A selective assembly serves only its selected sites; the closure engine
|
|
2615
|
+
// serves every other rule entry and scan root (`assemble.ts`).
|
|
2616
|
+
const served = (ip: number): boolean => select === undefined || select.has(resolveAlias(ip))
|
|
2538
2617
|
const ruleEntries: string[] = []
|
|
2539
2618
|
for (const [rname, entryIp] of Object.entries(prog.rules)) {
|
|
2619
|
+
if (!served(entryIp)) continue
|
|
2540
2620
|
// `_r_<Name>` — the composition surface, in `codegen.ts`'s own spelling and
|
|
2541
2621
|
// deliberately NOT namespaced, so a sibling calls it by name.
|
|
2542
2622
|
const target = link(entryIp)
|
|
@@ -2547,7 +2627,40 @@ return nd
|
|
|
2547
2627
|
// Sites the SCAN pool and the scan-skip sets reference. They are linked
|
|
2548
2628
|
// through `subtreeComb` outside the emitted scope, so they need names.
|
|
2549
2629
|
const extra: string[] = []
|
|
2550
|
-
|
|
2630
|
+
const exported = new Set<number>()
|
|
2631
|
+
for (const ip of extraIps) {
|
|
2632
|
+
if (!served(ip)) continue
|
|
2633
|
+
exported.add(ip)
|
|
2634
|
+
extra.push(`${ip}:${link(ip)}`)
|
|
2635
|
+
}
|
|
2636
|
+
// Selected sites a CLOSURE piece may call: every selected child of an
|
|
2637
|
+
// unselected reachable site. Linking one may lower it for the first time,
|
|
2638
|
+
// when only the closure side reaches it.
|
|
2639
|
+
if (select !== undefined) {
|
|
2640
|
+
const kids: number[] = []
|
|
2641
|
+
for (const ip of reachable) {
|
|
2642
|
+
if (select.has(resolveAlias(ip))) continue
|
|
2643
|
+
kids.length = 0
|
|
2644
|
+
childSlots(code, ip, kids)
|
|
2645
|
+
for (const kid of kids) {
|
|
2646
|
+
const target = resolveAlias(kid)
|
|
2647
|
+
if (!select.has(target) || exported.has(target)) continue
|
|
2648
|
+
exported.add(target)
|
|
2649
|
+
extra.push(`${target}:${link(target)}`)
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
const hybrid = select === undefined ? '' : `,
|
|
2654
|
+
ext:[${externals.join(',')}],
|
|
2655
|
+
bind:function(xs,ps){_xScan=xs
|
|
2656
|
+
${externals.map((ip, i) => `_xp${ip}=ps[${i}]`).join('\n')}},
|
|
2657
|
+
scan:function(s){const o=_pfScan;_pfScan=s;return o}`
|
|
2658
|
+
// One wrapper per external: the closure engine reads its OWN installed trivia
|
|
2659
|
+
// scanner, so the emitted one is handed across for the call and restored.
|
|
2660
|
+
const externalDefs = select === undefined ? '' : `let _xScan
|
|
2661
|
+
${externals.map(ip => `let _xp${ip}
|
|
2662
|
+
function _px${ip}(input,pos,ctx){const o=_xScan(_pfScan);const v=_xp${ip}(input,pos,ctx);_xScan(o);return v}`).join('\n')}
|
|
2663
|
+
`
|
|
2551
2664
|
|
|
2552
2665
|
// The per-label trivia scans sit AFTER the hoisted pool they close over: they
|
|
2553
2666
|
// are `function` declarations, so a body may call one that is textually below
|
|
@@ -2558,7 +2671,7 @@ ${prelude.join('\n')}
|
|
|
2558
2671
|
${skipDefs.join('\n')}
|
|
2559
2672
|
${choiceDefs.join('\n')}
|
|
2560
2673
|
${bodies.join('\n')}
|
|
2561
|
-
function _begin(ctx){
|
|
2674
|
+
${externalDefs}function _begin(ctx){
|
|
2562
2675
|
const host=ctx.build
|
|
2563
2676
|
if(_pfDepth>0)_pfFrames.push([_pfScan,_pfHost,EC.e])
|
|
2564
2677
|
_pfDepth++
|
|
@@ -2585,11 +2698,11 @@ pieces:{${ruleEntries.join(',')}},
|
|
|
2585
2698
|
byIp:{${extra.join(',')}},
|
|
2586
2699
|
end:function(){return EC.e},
|
|
2587
2700
|
begin:_begin,
|
|
2588
|
-
finish:_finish
|
|
2701
|
+
finish:_finish${hybrid}
|
|
2589
2702
|
}`
|
|
2590
2703
|
|
|
2591
2704
|
return {
|
|
2592
|
-
source, reached, masks, classes, armExpected,
|
|
2705
|
+
source, reached, masks, classes, armExpected, siteBytes,
|
|
2593
2706
|
plan: { classes: classPlan, armExpected: armExpectedPlan, masks: maskPlan },
|
|
2594
2707
|
}
|
|
2595
2708
|
}
|
package/src/table/emit.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assemblyExtraIps } from './program.ts'
|
|
2
2
|
import type { FoldedProgram, TableProgram } from './program.ts'
|
|
3
3
|
import { resolveTable } from './program.ts'
|
|
4
4
|
import { EMITTED_PARAMS, Unemittable, emitAssemblySource } from './emit-assembly.ts'
|
|
@@ -170,7 +170,7 @@ export function defaultAssemblyCfgs(prog: TableProgram): RunCfg[] {
|
|
|
170
170
|
* not lower; that option set simply gets no entry, and `assemble.ts` runs the
|
|
171
171
|
* closure engine for it and RECORDS why on `Assembly.emitRefusal`.
|
|
172
172
|
*/
|
|
173
|
-
function emitAssemblies(prog: TableProgram, cfgs: readonly
|
|
173
|
+
function emitAssemblies(prog: TableProgram, cfgs: readonly AssemblyCfg[]): string[] {
|
|
174
174
|
/**
|
|
175
175
|
* `a:[]` IS NOT `a` ABSENT, and the difference is the whole property.
|
|
176
176
|
*
|
|
@@ -201,9 +201,7 @@ function emitAssemblies(prog: TableProgram, cfgs: readonly RunCfg[]): string[] {
|
|
|
201
201
|
const t = resolveTable(prog)
|
|
202
202
|
// The scan pool and the scan-skip sets are linked from subtrees, so their
|
|
203
203
|
// sites need emitted names too — same list `assemble.ts` builds.
|
|
204
|
-
const extraIps
|
|
205
|
-
for (const s of prog.scans ?? []) for (const r of scanRefs(s)) extraIps.push(r[0])
|
|
206
|
-
for (const set of prog.scanSkip ?? []) for (const r of set) extraIps.push(r[0])
|
|
204
|
+
const extraIps = assemblyExtraIps(prog)
|
|
207
205
|
|
|
208
206
|
const out: string[] = []
|
|
209
207
|
const seen = new Set<number>()
|
|
@@ -213,7 +211,7 @@ function emitAssemblies(prog: TableProgram, cfgs: readonly RunCfg[]): string[] {
|
|
|
213
211
|
seen.add(key)
|
|
214
212
|
let em
|
|
215
213
|
try {
|
|
216
|
-
em = emitAssemblySource(t, prog, cfg, extraIps, true)
|
|
214
|
+
em = emitAssemblySource(t, prog, cfg, extraIps, true, cfg.select)
|
|
217
215
|
} catch (e) {
|
|
218
216
|
if (e instanceof Unemittable) continue
|
|
219
217
|
throw e
|
|
@@ -233,6 +231,9 @@ function emitAssemblies(prog: TableProgram, cfgs: readonly RunCfg[]): string[] {
|
|
|
233
231
|
return [`a:[${out.join(',')}],`]
|
|
234
232
|
}
|
|
235
233
|
|
|
234
|
+
/** One pre-compiled option set; `select` makes it SELECTIVE (see `emitAssemblySource`). */
|
|
235
|
+
export type AssemblyCfg = RunCfg & { readonly select?: ReadonlySet<number> }
|
|
236
|
+
|
|
236
237
|
export type EmitOptions = {
|
|
237
238
|
/** Name of the exported binding. */
|
|
238
239
|
readonly name?: string
|
|
@@ -244,7 +245,7 @@ export type EmitOptions = {
|
|
|
244
245
|
* Supplying factories here is a low-level serialization experiment, not a
|
|
245
246
|
* second normal compilation path; it is not used by the macro plugin.
|
|
246
247
|
*/
|
|
247
|
-
readonly assemblies?: readonly
|
|
248
|
+
readonly assemblies?: readonly AssemblyCfg[]
|
|
248
249
|
/**
|
|
249
250
|
* Sources for the author callbacks, in `prog.fns` order. A build has these
|
|
250
251
|
* from the module it is lowering; pass `undefined` to emit a placeholder and
|