@tikoci/centrs 0.1.2 → 0.1.4
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/CHANGELOG.md +98 -0
- package/GLOSSARY.txt +284 -0
- package/LICENSE +21 -0
- package/README.md +55 -11
- package/SECURITY.md +3 -3
- package/commands/AGENTS.md +7 -1
- package/commands/api/README.md +46 -27
- package/commands/api/examples.md +124 -6
- package/commands/btest/README.md +21 -32
- package/commands/check/README.md +376 -53
- package/commands/check/examples.md +390 -0
- package/commands/devices/README.md +177 -37
- package/commands/devices/examples.md +148 -0
- package/commands/discover/README.md +5 -11
- package/commands/execute/README.md +73 -26
- package/commands/execute/examples.md +131 -9
- package/commands/explain/README.md +1716 -0
- package/commands/explain/examples.md +606 -0
- package/commands/retrieve/README.md +55 -71
- package/commands/retrieve/examples.md +56 -9
- package/commands/settings/README.md +286 -22
- package/commands/settings/examples.md +408 -0
- package/commands/terminal/README.md +6 -0
- package/commands/transfer/README.md +57 -19
- package/commands/transfer/examples.md +95 -0
- package/docs/CLI.md +353 -0
- package/docs/CONSTITUTION.md +102 -26
- package/docs/MATRIX.md +106 -173
- package/docs/errors/README.md +5 -1
- package/docs/errors/cdb/empty-selection.md +19 -0
- package/docs/errors/input/incomplete-gps.md +9 -0
- package/docs/errors/input/invalid-altitude.md +9 -0
- package/docs/errors/input/invalid-bbox.md +9 -0
- package/docs/errors/input/invalid-coordinate.md +9 -0
- package/docs/errors/input/invalid-radius.md +9 -0
- package/docs/errors/internal/settings-failed.md +9 -0
- package/docs/errors/quickchr/machine-not-found.md +22 -0
- package/docs/errors/quickchr/machine-stopped.md +20 -0
- package/docs/errors/quickchr/package-unavailable.md +27 -0
- package/docs/errors/quickchr/unsupported-via.md +35 -0
- package/docs/errors/quickchr/unsupported.md +35 -0
- package/docs/errors/routeros/version-unsupported.md +26 -0
- package/docs/errors/settings/consequential-value.md +26 -0
- package/docs/errors/settings/invalid-boolean.md +21 -3
- package/docs/errors/settings/invalid-format.md +18 -3
- package/docs/errors/settings/invalid-integer.md +18 -3
- package/docs/errors/settings/invalid-timeout.md +20 -3
- package/docs/errors/settings/invalid-via.md +22 -3
- package/docs/errors/settings/reserved-key.md +29 -0
- package/docs/errors/settings/skip-env-file-active.md +25 -0
- package/docs/errors/settings/unknown-key.md +25 -0
- package/docs/errors/transport/provider-trust.md +29 -0
- package/docs/errors/usage/fanout-not-supported.md +16 -0
- package/docs/errors/usage/stdin-ignored.md +28 -0
- package/docs/errors/validation/unknown-attribute.md +37 -4
- package/package.json +49 -14
- package/src/api-fanout.ts +469 -0
- package/src/api.ts +171 -56
- package/src/cli/api.ts +156 -13
- package/src/cli/btest.ts +38 -4
- package/src/cli/common.ts +11 -0
- package/src/cli/devices.ts +235 -6
- package/src/cli/discover.ts +3 -1
- package/src/cli/execute.ts +129 -9
- package/src/cli/explain.ts +503 -0
- package/src/cli/mcp.ts +8 -1
- package/src/cli/retrieve.ts +167 -90
- package/src/cli/selection.ts +260 -0
- package/src/cli/settings.ts +286 -0
- package/src/cli/terminal.ts +22 -7
- package/src/cli/transfer.ts +192 -51
- package/src/cli.ts +22 -2
- package/src/core/envelope.ts +5 -2
- package/src/core/error-catalog.ts +95 -0
- package/src/core/fanout.ts +143 -2
- package/src/core/preflight.ts +306 -0
- package/src/core/routeros-string.ts +24 -0
- package/src/devices.ts +212 -20
- package/src/discover.ts +7 -2
- package/src/errors.ts +1 -0
- package/src/execute-fanout.ts +453 -0
- package/src/execute.ts +216 -68
- package/src/explain/arg-tokens.ts +114 -0
- package/src/explain/args.ts +1403 -0
- package/src/explain/blocks.ts +132 -0
- package/src/explain/brace-slots.ts +143 -0
- package/src/explain/brace-tokens.ts +38 -0
- package/src/explain/catalog.ts +1345 -0
- package/src/explain/coordinates.ts +329 -0
- package/src/explain/defects.ts +217 -0
- package/src/explain/is-known-menu.ts +17 -0
- package/src/explain/menus.ts +664 -0
- package/src/explain/operator-tokens.ts +310 -0
- package/src/explain/operators.ts +458 -0
- package/src/explain/path-tokens.ts +150 -0
- package/src/explain/pathresolve.ts +1324 -0
- package/src/explain/quoted-string.ts +241 -0
- package/src/explain/scope-brace.ts +308 -0
- package/src/explain/segment.ts +757 -0
- package/src/explain/string-tokens.ts +67 -0
- package/src/explain/symbol-values.ts +520 -0
- package/src/explain/symbols.ts +1597 -0
- package/src/explain/token-ranges.ts +72 -0
- package/src/explain/transport.ts +497 -0
- package/src/explain/value-tokens.ts +80 -0
- package/src/explain/values.ts +357 -0
- package/src/explain/verbs.ts +64 -0
- package/src/explain/verbsplit.ts +813 -0
- package/src/explain/write.ts +815 -0
- package/src/explain.ts +2152 -0
- package/src/index.ts +307 -1
- package/src/mcp/config.ts +12 -2
- package/src/mcp/tools.ts +137 -2
- package/src/protocols/adapter.ts +38 -18
- package/src/protocols/index.ts +11 -5
- package/src/protocols/mac-telnet-console.ts +61 -17
- package/src/resolver/cdb.ts +10 -2
- package/src/resolver/comment-kv.ts +58 -4
- package/src/resolver/config-file.ts +136 -0
- package/src/resolver/facts.ts +78 -0
- package/src/resolver/geo.ts +453 -0
- package/src/resolver/index.ts +65 -0
- package/src/resolver/mac.ts +12 -3
- package/src/resolver/quickchr-provider.ts +597 -0
- package/src/resolver/selection.ts +391 -0
- package/src/resolver/service-endpoint.ts +155 -0
- package/src/resolver/settings.ts +54 -5
- package/src/resolver/target.ts +21 -3
- package/src/retrieve-fanout.ts +239 -133
- package/src/retrieve.ts +120 -25
- package/src/settings.ts +1078 -0
- package/src/terminal.ts +11 -1
- package/src/transfer-fanout.ts +544 -0
- package/src/transfer.ts +133 -40
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block / scope-brace classification for `explain` (centrs canonicalizer).
|
|
3
|
+
*
|
|
4
|
+
* Ratified by the phase-0 lab, question Q2 (#185) and promoted from the
|
|
5
|
+
* throwaway probe `.scratch/explain-lab-blocktree.ts`. Q2 asks: can an offline,
|
|
6
|
+
* schema-free brace tracker tell a `{…}` SCOPE (`do=`/`else=`/`on-error=`, which
|
|
7
|
+
* IL lowers to `name=;(evl …)`) from a `name={…}` LITERAL value (braces
|
|
8
|
+
* stripped, body kept as RAW text, never lowered)? Descending into a literal
|
|
9
|
+
* body as if it held statements is the prior-art bug (canonicalize-audit #8/H5).
|
|
10
|
+
*
|
|
11
|
+
* The source-visible scope set is a small CLOSED set harvested across the
|
|
12
|
+
* 913-script corpus, so the distinction is schema-free — it reads the argument
|
|
13
|
+
* name (and, for the one head-dependent case, the leading directive) off the
|
|
14
|
+
* statement itself.
|
|
15
|
+
*
|
|
16
|
+
* This module ships only the (single-pass, non-recursive) classification
|
|
17
|
+
* primitives the path resolver needs. The recursive block *tree* / topology
|
|
18
|
+
* surface stays in the lab until it can be promoted with a bounded, index-based
|
|
19
|
+
* traversal of its own.
|
|
20
|
+
*
|
|
21
|
+
* Every string skip here goes through `segment.ts`'s `scanQuotedString` rather
|
|
22
|
+
* than a local quote-to-quote loop, so this module and the segmenter cannot
|
|
23
|
+
* drift on where a string ends: a `}` inside the nested string of a `$[…]`
|
|
24
|
+
* substitution is not a brace, and reading it as one truncated the scope body
|
|
25
|
+
* (#199).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
DIRECTIVE_BODY,
|
|
30
|
+
HEAD_SCOPED_ARG_NAMES,
|
|
31
|
+
SCOPE_ARG_NAMES,
|
|
32
|
+
scopeNameFromMasked,
|
|
33
|
+
} from "./scope-brace.ts";
|
|
34
|
+
import { maskComments, scanQuotedString } from "./segment.ts";
|
|
35
|
+
|
|
36
|
+
export { DIRECTIVE_BODY, HEAD_SCOPED_ARG_NAMES, SCOPE_ARG_NAMES };
|
|
37
|
+
/** One depth-0 scope block found in a statement: its name and raw body text. */
|
|
38
|
+
export interface ScopeBlock {
|
|
39
|
+
name: string;
|
|
40
|
+
body: string;
|
|
41
|
+
/**
|
|
42
|
+
* Offset of `body`'s first character within the text `scopeBlocks` was given
|
|
43
|
+
* — i.e. one past the opening `{`.
|
|
44
|
+
*
|
|
45
|
+
* Without it a caller that re-segments `body` gets BODY-RELATIVE offsets and
|
|
46
|
+
* no way to rebase them, so a span or region found inside a `do={…}` cannot
|
|
47
|
+
* be reported in document space. `pathresolve` is that caller.
|
|
48
|
+
*/
|
|
49
|
+
start: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The scope name for the `{` at `open`, or null when the brace is a value.
|
|
54
|
+
* Two shapes qualify: `<name>={` with name in the closed set (or head-scoped
|
|
55
|
+
* under the right leading directive), and a brace body directly following a
|
|
56
|
+
* body-taking directive (`:do {`, `:retry {`, `:onerror Err {`).
|
|
57
|
+
*/
|
|
58
|
+
export function scopeNameAt(text: string, open: number): string | null {
|
|
59
|
+
return scopeNameFromMasked(maskComments(text), open);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** True when the `{` at `open` opens a scope rather than a literal value. */
|
|
63
|
+
export function isScopeBrace(text: string, open: number): boolean {
|
|
64
|
+
return scopeNameAt(text, open) !== null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The depth-0 scope `{…}` in a statement, with names and raw body text. A
|
|
69
|
+
* single left-to-right pass (no recursion): each scope's body is returned raw
|
|
70
|
+
* for the caller to segment/recurse under its own depth budget. Literal `{…}`
|
|
71
|
+
* values are skipped, not descended.
|
|
72
|
+
*/
|
|
73
|
+
export function scopeBlocks(text: string): ScopeBlock[] {
|
|
74
|
+
// Scan a comment-masked copy for structure so a `#`-comment `}`/`[` cannot
|
|
75
|
+
// truncate a body or shift depth; slice the ORIGINAL for the body text.
|
|
76
|
+
const masked = maskComments(text);
|
|
77
|
+
const blocks: ScopeBlock[] = [];
|
|
78
|
+
let depth = 0;
|
|
79
|
+
for (let i = 0; i < masked.length; i++) {
|
|
80
|
+
const c = masked[i];
|
|
81
|
+
if (c === '"') {
|
|
82
|
+
i = scanQuotedString(masked, i).end - 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (c === "[" || c === "(") depth++;
|
|
86
|
+
else if (c === "]" || c === ")") {
|
|
87
|
+
if (depth > 0) depth--;
|
|
88
|
+
} else if (c === "{") {
|
|
89
|
+
const end = matchBraceInMasked(masked, i);
|
|
90
|
+
if (depth === 0) {
|
|
91
|
+
const name = scopeNameFromMasked(masked, i);
|
|
92
|
+
if (name !== null)
|
|
93
|
+
blocks.push({
|
|
94
|
+
name,
|
|
95
|
+
body: text.slice(i + 1, end),
|
|
96
|
+
start: i + 1,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
i = end;
|
|
100
|
+
} else if (c === "}") {
|
|
101
|
+
if (depth > 0) depth--;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return blocks;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Bodies of the depth-0 scope `{…}` in a statement (for block recursion). */
|
|
108
|
+
export function scopeBodies(text: string): string[] {
|
|
109
|
+
return scopeBlocks(text).map((b) => b.body);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function matchBraceInMasked(masked: string, open: number): number {
|
|
113
|
+
let depth = 0;
|
|
114
|
+
for (let i = open; i < masked.length; i++) {
|
|
115
|
+
const c = masked[i];
|
|
116
|
+
if (c === '"') {
|
|
117
|
+
i = scanQuotedString(masked, i).end - 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (c === "{") depth++;
|
|
121
|
+
else if (c === "}") {
|
|
122
|
+
depth--;
|
|
123
|
+
if (depth === 0) return i;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return masked.length;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Index of the `}` matching the `{` at `open`, honoring strings and comments. */
|
|
130
|
+
export function matchBrace(text: string, open: number): number {
|
|
131
|
+
return matchBraceInMasked(maskComments(text), open);
|
|
132
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which root-directive value slots read a `{…}` as an ARRAY (#225, #257).
|
|
3
|
+
*
|
|
4
|
+
* `explain` used to gate brace arrays on the statement's resolved path: any
|
|
5
|
+
* `/` path allowed one, positionally or under `in=`. A device sweep showed that
|
|
6
|
+
* is wrong in both directions on CHR 7.23.3 — `:delay {1;2}`, `:beep {1;2}`,
|
|
7
|
+
* `:resolve {1;2}`, `:if condition={1;2}` and `:local name={1;2}` are syntax
|
|
8
|
+
* errors the path rule called arrays, while `:for from=`, `:execute script=`,
|
|
9
|
+
* `:local value=` and 50-odd more are accepted braces it refused.
|
|
10
|
+
*
|
|
11
|
+
* The real key is the SLOT, and a slot has four outcomes, not two:
|
|
12
|
+
*
|
|
13
|
+
* - **array** — the content is EVALUATED: `:foreach i in={(1,2)} do={}`
|
|
14
|
+
* lowers to `in=(, 1 2)`. Only these 72 belong here.
|
|
15
|
+
* - **code** — the braces are a program. `:onerror e in={1;2} do={}` lowers
|
|
16
|
+
* to `in=;(evl (<%% bad command name 1 …))`, so the SAME NAME is an array
|
|
17
|
+
* on `:foreach` and a block on `:onerror`.
|
|
18
|
+
* - **text** — the brace is accepted and its content taken VERBATIM as a
|
|
19
|
+
* script: `:execute script={(1,2)}` lowers to `script=(1,2)`, not
|
|
20
|
+
* `(, 1 2)`. This outcome is why `{1;2}` alone is not a sufficient probe —
|
|
21
|
+
* `:local z {1;2}` and `:execute script={1;2}` both lower to `…=1;2`, and
|
|
22
|
+
* reading only that put `:execute script=` in the array set, one step from
|
|
23
|
+
* hinting `array` on a script body.
|
|
24
|
+
* - **error** — `:parse` refuses the statement.
|
|
25
|
+
*
|
|
26
|
+
* A slot the sweep could not ask (30 of 222, mostly ones whose scalar control
|
|
27
|
+
* also fails) is absent from this table, which is the fail-closed direction:
|
|
28
|
+
* absent means the walk refuses to read the value, and a refusal costs nothing
|
|
29
|
+
* but coverage. Every brace array in the 948-script corpus lands on a slot
|
|
30
|
+
* listed here except one `:execute {…}` script body, which is `text`.
|
|
31
|
+
*
|
|
32
|
+
* A positional slot is keyed by its zero-based index among positionals, because
|
|
33
|
+
* position decides: `:local {1;2}` puts the literal in the NAME slot (a syntax
|
|
34
|
+
* error) while `:local z {1;2}` puts it in the VALUE slot.
|
|
35
|
+
*
|
|
36
|
+
* **The verb is matched VERBATIM, and that is the device's rule, not laziness.**
|
|
37
|
+
* `verbsplit.ts` keeps a run token's source casing, and RouterOS accepts only
|
|
38
|
+
* the lower-case spelling: `:LOCAL z {1;2}`, `:Local z {1;2}`, `:PUT {1;2}` and
|
|
39
|
+
* even `/IP/DNS/set servers=1.1.1.1` all draw `expected command name (line 1
|
|
40
|
+
* column 2)` on CHR 7.23.3, while an ARGUMENT's casing is free (`:local Z {1;2}`
|
|
41
|
+
* parses and binds `$Z`). Normalizing the verb here would open the gate on a
|
|
42
|
+
* statement the device refuses to parse — the exact fabrication this table
|
|
43
|
+
* exists to remove — so a differently-cased directive misses every entry and is
|
|
44
|
+
* refused, which is the same answer the device gives.
|
|
45
|
+
*
|
|
46
|
+
* The evidence is `test/fixtures/explain/values.json` →
|
|
47
|
+
* `interiorGrounding.braceSlots` (222 rows, CHR 7.23.3, `:parse` IL — NOT
|
|
48
|
+
* `highlight`, which accepts `{1;2,}` and `(1,)` that `:parse` rejects), and
|
|
49
|
+
* `explain-values.test.ts` asserts this table still equals the `array` rows of
|
|
50
|
+
* that block. Re-derive with `bun run explain:probe:brace-slots` (#186).
|
|
51
|
+
*/
|
|
52
|
+
const BRACE_ARRAY_SLOTS: ReadonlyMap<string, ReadonlySet<string>> = new Map(
|
|
53
|
+
(
|
|
54
|
+
[
|
|
55
|
+
[
|
|
56
|
+
"convert",
|
|
57
|
+
["#0", "from-scheme", "to-scheme", "transform-scheme", "value"],
|
|
58
|
+
],
|
|
59
|
+
["deserialize", ["#0", "value"]],
|
|
60
|
+
["error", ["#0", "message"]],
|
|
61
|
+
["find", ["#0", "#1", "from", "in", "key"]],
|
|
62
|
+
["for", ["from", "step", "to"]],
|
|
63
|
+
["foreach", ["in"]],
|
|
64
|
+
["global", ["#1", "value"]],
|
|
65
|
+
["len", ["#0", "value"]],
|
|
66
|
+
["local", ["#1", "value"]],
|
|
67
|
+
["parse", ["#0", "value"]],
|
|
68
|
+
["pick", ["#0", "#1", "begin", "counter", "end"]],
|
|
69
|
+
["put", ["#0", "message"]],
|
|
70
|
+
["range", ["#0", "#1", "from", "to"]],
|
|
71
|
+
["return", ["#0", "value"]],
|
|
72
|
+
["rndnum", ["#0", "#1", "from", "to"]],
|
|
73
|
+
["rndstr", ["#0", "#1", "from", "length"]],
|
|
74
|
+
["serialize", ["#0", "order", "value"]],
|
|
75
|
+
["set", ["#1", "value"]],
|
|
76
|
+
["toarray", ["#0", "value"]],
|
|
77
|
+
["tobool", ["#0", "value"]],
|
|
78
|
+
["toid", ["#0", "value"]],
|
|
79
|
+
["toip", ["#0", "value"]],
|
|
80
|
+
["toip6", ["#0", "value"]],
|
|
81
|
+
["tonsec", ["#0", "value"]],
|
|
82
|
+
["tonum", ["#0", "value"]],
|
|
83
|
+
["tostr", ["#0", "value"]],
|
|
84
|
+
["totime", ["#0", "value"]],
|
|
85
|
+
["typeof", ["#0", "value"]],
|
|
86
|
+
] as const
|
|
87
|
+
).map(([verb, slots]) => [verb, new Set<string>(slots)]),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
/** Slots that accept a brace as code or verbatim script text, not as an array. */
|
|
91
|
+
const BRACE_PROGRAM_SLOTS: ReadonlyMap<string, ReadonlySet<string>> = new Map(
|
|
92
|
+
(
|
|
93
|
+
[
|
|
94
|
+
["set", ["do"]],
|
|
95
|
+
["do", ["#0", "#1", "command", "on-error"]],
|
|
96
|
+
["execute", ["#0", "script"]],
|
|
97
|
+
["for", ["do", "on-error"]],
|
|
98
|
+
["foreach", ["do", "on-error"]],
|
|
99
|
+
["global", ["do"]],
|
|
100
|
+
["grep", ["#0", "script"]],
|
|
101
|
+
["if", ["do", "else"]],
|
|
102
|
+
["local", ["do"]],
|
|
103
|
+
["onerror", ["#1", "do", "in"]],
|
|
104
|
+
["retry", ["#0", "#1", "command", "on-error"]],
|
|
105
|
+
["time", ["#0", "#1", "command"]],
|
|
106
|
+
["while", ["do"]],
|
|
107
|
+
] as const
|
|
108
|
+
).map(([verb, slots]) => [verb, new Set<string>(slots)]),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/** The table as flat `verb slot` pairs, for the fixture-drift assertion. */
|
|
112
|
+
export function braceArraySlotPairs(): string[] {
|
|
113
|
+
const pairs: string[] = [];
|
|
114
|
+
for (const [verb, slots] of BRACE_ARRAY_SLOTS)
|
|
115
|
+
for (const slot of slots) pairs.push(`${verb} ${slot}`);
|
|
116
|
+
return pairs.sort();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The code/text table as flat pairs, for the same fixture-drift assertion. */
|
|
120
|
+
export function braceProgramSlotPairs(): string[] {
|
|
121
|
+
const pairs: string[] = [];
|
|
122
|
+
for (const [verb, slots] of BRACE_PROGRAM_SLOTS)
|
|
123
|
+
for (const slot of slots) pairs.push(`${verb} ${slot}`);
|
|
124
|
+
return pairs.sort();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether a `{…}` in this slot of this root directive is an array literal.
|
|
129
|
+
*
|
|
130
|
+
* `verb` is the resolved directive name without its colon; `slot` is the
|
|
131
|
+
* lowercase argument name, or `#<index>` for a positional.
|
|
132
|
+
*/
|
|
133
|
+
export function braceSlotTakesArray(verb: string, slot: string): boolean {
|
|
134
|
+
return BRACE_ARRAY_SLOTS.get(verb)?.has(slot) === true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Whether the device sweep proved this root-directive slot accepts `{...}`. */
|
|
138
|
+
export function braceSlotAcceptsBrace(verb: string, slot: string): boolean {
|
|
139
|
+
return (
|
|
140
|
+
braceSlotTakesArray(verb, slot) ||
|
|
141
|
+
BRACE_PROGRAM_SLOTS.get(verb)?.has(slot) === true
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B3 brace fill — claims brace delimiters on the residual.
|
|
3
|
+
*
|
|
4
|
+
* Value-anchored array literals are claimed as `value` leaves, but their outer
|
|
5
|
+
* delimiters remain unclassified (container excluded to avoid overlap). Scope
|
|
6
|
+
* braces (`do={...}` etc.) are never array values. Both families therefore
|
|
7
|
+
* appear on the residual after the `value` and `string` fills, so this fill
|
|
8
|
+
* claims every remaining `{` / `}` byte there as provisional `brace`. Whether
|
|
9
|
+
* scope vs array braces later deserve distinct classes is #264 B5 and does not
|
|
10
|
+
* move byte coverage.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ExplainToken } from "../explain.ts";
|
|
14
|
+
import { clipToResidual } from "./token-ranges.ts";
|
|
15
|
+
|
|
16
|
+
/** Brace delimiter spans on the residual. */
|
|
17
|
+
export function braceSpans(
|
|
18
|
+
analyzed: string,
|
|
19
|
+
residual: readonly { start: number; end: number }[],
|
|
20
|
+
): ExplainToken[] {
|
|
21
|
+
const len = analyzed.length;
|
|
22
|
+
if (len === 0 || residual.length === 0) return [];
|
|
23
|
+
const out: ExplainToken[] = [];
|
|
24
|
+
for (let i = 0; i < analyzed.length; i++) {
|
|
25
|
+
const c = analyzed[i] as string;
|
|
26
|
+
if (c !== "{" && c !== "}") continue;
|
|
27
|
+
for (const r of clipToResidual(i, i + 1, residual)) {
|
|
28
|
+
out.push({
|
|
29
|
+
start: r.start,
|
|
30
|
+
end: r.end,
|
|
31
|
+
class: "brace" as const,
|
|
32
|
+
ev: "e14",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
out.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
37
|
+
return out;
|
|
38
|
+
}
|