@henols/vice-mcp 0.2.3 → 0.2.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/anno-cli.ts +156 -158
- package/anno-confidence.ts +2 -2
- package/anno-derive.ts +6 -6
- package/anno-details.ts +4 -4
- package/anno-export-asm.ts +100 -101
- package/anno-graphics.ts +16 -16
- package/anno-hazard-report.ts +2 -2
- package/anno-import.ts +15 -15
- package/anno-index.ts +8 -8
- package/anno-join.ts +35 -35
- package/anno-memmap-render.ts +22 -21
- package/anno-provenance-ledger.ts +4 -4
- package/anno-regbits-gen.ts +13 -13
- package/anno-store-export.ts +11 -11
- package/anno-store.ts +139 -144
- package/anno-symbols.ts +7 -7
- package/anno-types.ts +55 -55
- package/package.json +1 -1
- package/resources/broker-control.mjs +85 -92
- package/resources/broker-epoch.mjs +6 -7
- package/resources/broker-kill.mjs +29 -30
- package/resources/broker-launch.mjs +352 -370
- package/resources/broker-state.mjs +9 -10
- package/resources/host-tool.mjs +636 -664
- package/resources/vice-broker.mjs +189 -191
- package/vice-broker-client.ts +98 -100
package/anno-export-asm.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// anno-export-asm.ts -- the ONE place annotation-store rows plus image bytes
|
|
2
|
-
// become ACME source text
|
|
2
|
+
// become ACME source text.
|
|
3
3
|
//
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// WHY THIS FILE EXISTS
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
|
-
// The previous export route was WITHDRAWN
|
|
7
|
+
// The previous export route was WITHDRAWN rather than left
|
|
8
8
|
// standing, because it made a reassembly claim nothing verified: it produced
|
|
9
9
|
// something that looked like ACME source and asserted, in effect, that
|
|
10
10
|
// assembling it would reproduce the program. No assembler ever ran. Withdrawing
|
|
11
11
|
// it was the right call and the withdrawal notices in both skill trees are the
|
|
12
12
|
// record that the capability was missing.
|
|
13
13
|
//
|
|
14
|
-
// This module is the rebuild, over
|
|
14
|
+
// This module is the rebuild, over this project's own annotation store, and it is
|
|
15
15
|
// allowed to exist only because the claim is now settled somewhere else: a real
|
|
16
16
|
// ACME 0.97 assembles this module's output and the resulting bytes are diffed
|
|
17
17
|
// against the IMAGE bytes. Nothing in this file verifies this file. Re-reading,
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
// against a real assembler across all 256 opcodes. Decoding here happens
|
|
37
37
|
// through `decode()` and nowhere else.
|
|
38
38
|
// - Never write a second `!byte` / `+2` / hex emitter. `disasm-renderer.ts`
|
|
39
|
-
// owns
|
|
39
|
+
// owns the `!byte` substitution rule and the operand-width invariant, both
|
|
40
40
|
// verified against real ACME. A second emitter would be a second answer to
|
|
41
41
|
// "how wide is this operand", and the two would drift silently.
|
|
42
42
|
// - Never restate the eleven auto-name prefixes here. `anno-types.ts:93-99`
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
// - Never compare a `dataType` string in this module beyond the FOUR
|
|
57
57
|
// places that already do, each of which says so in its own comment:
|
|
58
58
|
// `CODE_DATA_TYPE`'s decoder-or-dump branch, `WORD_PAIR_DATA_TYPES`'s
|
|
59
|
-
// `!word` eligibility check,
|
|
59
|
+
// `!word` eligibility check,
|
|
60
60
|
// `EXTERNAL_FILE_DATA_TYPE`'s `!binary`-versus-inline branch inside
|
|
61
|
-
// `emitDataLines()`, and
|
|
61
|
+
// `emitDataLines()`, and `isSplitAddressDataType()`'s
|
|
62
62
|
// paired-symbol-versus-raw-byte branch in the block loop. All four are
|
|
63
63
|
// questions about the emitted TEXT. `block-class.ts` is the one place in
|
|
64
64
|
// this tree allowed to INTERPRET that column -- what the data means -- and
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
// - Never interpolate a read file's own bytes into an error message. A path,
|
|
73
73
|
// an address and a length are facts ABOUT a file; its contents are not, and
|
|
74
74
|
// an error text that quotes them turns a refusal into a content-disclosure
|
|
75
|
-
// oracle
|
|
75
|
+
// oracle. Every throw below carries paths, addresses and counts and
|
|
76
76
|
// nothing read out of the image or the store.
|
|
77
77
|
// - Never sanitise a label name. `assertLegalAcmeIdentifier()`'s contract is
|
|
78
78
|
// REJECT: a space-to-underscore substitution silently merges two distinct
|
|
@@ -114,13 +114,12 @@ import { assertLegalAcmeIdentifier } from "./anno-acme-ident.ts";
|
|
|
114
114
|
//
|
|
115
115
|
// WHAT NOT TO DO: do not restate the eleven here, in any form -- not as an
|
|
116
116
|
// array, not as a second regex, not as a doc comment listing them.
|
|
117
|
-
// `anno-types.ts:93-99` forbids it by name
|
|
118
|
-
// mode: a five-prefix copy under-counts silently.
|
|
117
|
+
// `anno-types.ts:93-99` forbids it by name: a five-prefix copy under-counts silently.
|
|
119
118
|
import { AUTO_NAME_PREFIX_RE } from "./anno-coverage.ts";
|
|
120
|
-
// THE ONE OWNING DECODER
|
|
119
|
+
// THE ONE OWNING DECODER. This module decodes NOTHING
|
|
121
120
|
// itself -- `decomposeRegisterValue()` is the ONLY place a register value is
|
|
122
|
-
// split into named bit-fields, and `anno_disassemble` (
|
|
123
|
-
//
|
|
121
|
+
// split into named bit-fields, and `anno_disassemble` (the other renderer sharing
|
|
122
|
+
// this decoder) calls the SAME function. Never re-derive a per-field bit
|
|
124
123
|
// mask in this file -- a phase-45 verification gate greps this file's own
|
|
125
124
|
// text for that shape and must find none.
|
|
126
125
|
import { decomposeRegisterValue, hasRegBitsEntry, type RegisterDecomposition } from "./anno-enum-gen.ts";
|
|
@@ -128,7 +127,7 @@ import { decode } from "./disasm-decoder.ts";
|
|
|
128
127
|
import type { Instruction } from "./disasm-decoder.ts";
|
|
129
128
|
import { renderLine } from "./disasm-renderer.ts";
|
|
130
129
|
import { parsePrg, flatImageOrigin } from "./prg-image.ts";
|
|
131
|
-
//
|
|
130
|
+
// the ONE reader of `recovery/PROVENANCE.md`'s
|
|
132
131
|
// generated tier. This module never recomputes a verdict -- see
|
|
133
132
|
// `anno-provenance-ledger.ts`'s own header for why reading and recomputing
|
|
134
133
|
// are deliberately kept apart.
|
|
@@ -142,7 +141,7 @@ import { provenanceForRange, readProvenanceLedger, type ProvenanceLedger } from
|
|
|
142
141
|
const CODE_DATA_TYPE = "code";
|
|
143
142
|
|
|
144
143
|
/** The store's own spelling for a large binary blob exported AS ITS OWN
|
|
145
|
-
* FILE rather than inline (
|
|
144
|
+
* FILE rather than inline (`anno_set_data_type`'s
|
|
146
145
|
* schema calls this "large binary blob to export as-is") -- the eleventh of
|
|
147
146
|
* `DATA_TYPES`' twelve members, the one vocabulary this module imports
|
|
148
147
|
* rather than restates. The SAME discipline `CODE_DATA_TYPE` above already
|
|
@@ -167,7 +166,7 @@ export interface ExportBlock {
|
|
|
167
166
|
/** How many CONTENT lines the block emitted -- not its `* =` origin line and
|
|
168
167
|
* not the two `!if * != ...` assertions that bracket it. */
|
|
169
168
|
lineCount: number;
|
|
170
|
-
/**
|
|
169
|
+
/** exactly what `emitBlock()` returned for this block
|
|
171
170
|
* -- the `* =` origin line, both `!if * != ...` bracket assertions, and
|
|
172
171
|
* every content line between them, in emitted order. This is FOR a tree
|
|
173
172
|
* writer to partition an emission that is already proven, rather than a
|
|
@@ -180,7 +179,7 @@ export interface ExportBlock {
|
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
/**
|
|
183
|
-
*
|
|
182
|
+
* one `external_file`-typed block's own
|
|
184
183
|
* `.bin` sibling. `bytes` is the block's own slice of the IMAGE, carried
|
|
185
184
|
* verbatim -- never a copy that passed through any conversion, on the same
|
|
186
185
|
* terms `expectedBytes` is derived from the image and never from `source`.
|
|
@@ -226,8 +225,8 @@ export interface ExportAsmOptions {
|
|
|
226
225
|
/**
|
|
227
226
|
* The provenance ledger to annotate every emitted block from -- `c64-
|
|
228
227
|
* provenance-diff`'s generated `recovery/PROVENANCE.md`, read (never
|
|
229
|
-
* re-derived) through `readProvenanceLedger()`. OPTIONAL
|
|
230
|
-
*
|
|
228
|
+
* re-derived) through `readProvenanceLedger()`. OPTIONAL: every existing
|
|
229
|
+
* caller that omits it keeps exporting
|
|
231
230
|
* exactly as before, byte for byte and comment for comment. NOTHING
|
|
232
231
|
* CONFINES THIS PATH INSIDE THIS MODULE, on the same terms as `imagePath`
|
|
233
232
|
* above -- the CALLER owns its confinement. Supplying it and having the
|
|
@@ -240,14 +239,14 @@ export interface ExportAsmOptions {
|
|
|
240
239
|
export interface ExportAsmResult {
|
|
241
240
|
/** The ACME source text. */
|
|
242
241
|
source: string;
|
|
243
|
-
/**
|
|
242
|
+
/** the symbol-definition block VERBATIM -- every
|
|
244
243
|
* enum-variant definition line followed by every store label's own
|
|
245
244
|
* definition line, in exactly the order `source` carries them. This is FOR
|
|
246
245
|
* `exportAsmTree()` to write `symbols.a` from, never re-derived: `source`
|
|
247
246
|
* is still `["!cpu 6510", ...headerLines, ...blockLines].join("\n")` plus
|
|
248
247
|
* the trailing newline, byte for byte unchanged by this field's addition. */
|
|
249
248
|
headerLines: string[];
|
|
250
|
-
/**
|
|
249
|
+
/** every scope the store holds, read by `listScopes()`
|
|
251
250
|
* as a SEVENTH call inside this function's existing store handle -- there
|
|
252
251
|
* is still exactly one handle opened for the whole export. `exportAsmTree()`
|
|
253
252
|
* uses this to decide which scope (or none) each block belongs to; nothing
|
|
@@ -267,7 +266,7 @@ export interface ExportAsmResult {
|
|
|
267
266
|
* i.e. `sortedLabels.length`, one per `anno_label` row in range.
|
|
268
267
|
*
|
|
269
268
|
* THIS DOC USED TO SAY "how many symbol definitions the header carries",
|
|
270
|
-
* AND THAT WAS NOT WHAT IT COUNTED (
|
|
269
|
+
* AND THAT WAS NOT WHAT IT COUNTED (corrected 2026-08-31).
|
|
271
270
|
* The two readings diverge in BOTH directions: a mid-instruction label is
|
|
272
271
|
* defined inline and skipped by the header loop yet still counted here,
|
|
273
272
|
* and every `enumDefinitionLines` entry IS a header definition yet is not.
|
|
@@ -283,7 +282,7 @@ export interface ExportAsmResult {
|
|
|
283
282
|
/** How many definition lines the HEADER block actually carries -- enum
|
|
284
283
|
* variant definitions plus every store label NOT defined inline. Computed
|
|
285
284
|
* from `headerLines` itself, so it cannot drift from the emitted text the
|
|
286
|
-
* way a separately-maintained count did
|
|
285
|
+
* way a separately-maintained count did. */
|
|
287
286
|
headerDefinitionCount: number;
|
|
288
287
|
/** How many decoded instructions ACME's `!cpu 6510` cannot express, and
|
|
289
288
|
* which therefore went out as `!byte` directives with their mnemonic moved
|
|
@@ -293,12 +292,12 @@ export interface ExportAsmResult {
|
|
|
293
292
|
* block whose `dataType` is not `code`. A code block contributes nothing
|
|
294
293
|
* here, however many bytes it decoded. */
|
|
295
294
|
dataByteCount: number;
|
|
296
|
-
/**
|
|
295
|
+
/** every `external_file`-typed block's
|
|
297
296
|
* own sibling data file, ascending by `start`. `dataByteCount` above still
|
|
298
297
|
* counts these bytes -- they went out through the data path exactly as an
|
|
299
298
|
* inline `!byte`-typed range's bytes would have, only to a different
|
|
300
299
|
* destination file, and a count whose name stops matching what it counts
|
|
301
|
-
* is the
|
|
300
|
+
* is the same lesson this module already learned once. See
|
|
302
301
|
* `ExportBinary`'s own doc-comment for the obligation a caller that reads
|
|
303
302
|
* this field takes on. */
|
|
304
303
|
binaries: ExportBinary[];
|
|
@@ -313,8 +312,8 @@ export interface ExportAsmResult {
|
|
|
313
312
|
* header definition block, because such a label is defined inline and
|
|
314
313
|
* defining it twice is ACME's `Symbol already defined.`
|
|
315
314
|
*
|
|
316
|
-
* COUNTED PER EMITTED DEFINITION, NOT PER ADDRESS (
|
|
317
|
-
*
|
|
315
|
+
* COUNTED PER EMITTED DEFINITION, NOT PER ADDRESS (corrected 2026-08-31).
|
|
316
|
+
* It used to be `midInstructionLabelAddresses.size`,
|
|
318
317
|
* a set of ADDRESSES, while the inline loop emits one line per LABEL.
|
|
319
318
|
* `anno_label` is `unique` on `name` only and `setLabel()` refuses only a
|
|
320
319
|
* name already bound to a DIFFERENT address, so two names at one address is
|
|
@@ -334,7 +333,7 @@ export interface ExportAsmResult {
|
|
|
334
333
|
* two named figures, never one combined figure). */
|
|
335
334
|
enumSubstitutionCount: number;
|
|
336
335
|
/** How many of `enumSubstitutionCount`'s substitutions were rendered as an
|
|
337
|
-
* OR-ed multi-bit decomposition
|
|
336
|
+
* OR-ed multi-bit decomposition rather than a single whole-value
|
|
338
337
|
* variant name -- one register key `regbits` entry with two or more
|
|
339
338
|
* fields, decoded through `decomposeRegisterValue()`. A single-field
|
|
340
339
|
* register, or an enum usage whose name is not a register key at all,
|
|
@@ -342,10 +341,11 @@ export interface ExportAsmResult {
|
|
|
342
341
|
enumDecompositionCount: number;
|
|
343
342
|
/** How many RECORDS from `anno_excluded_range` the export emitted a marker
|
|
344
343
|
* for -- at least one of their addresses overlapping at least one emitted
|
|
345
|
-
* block.
|
|
344
|
+
* block. This is the marker for a user-requested exclusion, recorded rather
|
|
345
|
+
* than silently dropped.
|
|
346
346
|
*
|
|
347
347
|
* COUNTS RECORDS, NOT MARKER LINES AND NOT EXCLUDED BYTES -- the same
|
|
348
|
-
*
|
|
348
|
+
* lesson this file already learned once about `symbolCount`: a count
|
|
349
349
|
* whose name does not match what it counts gets printed to a user
|
|
350
350
|
* verbatim. One exclusion record spanning two emitted blocks emits TWO
|
|
351
351
|
* marker lines (one per block) and counts ONCE here. And no byte is ever
|
|
@@ -376,7 +376,7 @@ const WORDS_PER_DATA_LINE = 8;
|
|
|
376
376
|
const WORD_PAIR_DATA_TYPES: readonly string[] = Object.freeze(["word", "address"]);
|
|
377
377
|
|
|
378
378
|
/**
|
|
379
|
-
*
|
|
379
|
+
* true iff `dataType` is one of the TWO split
|
|
380
380
|
* layouts that denote ADDRESSES -- `lo_hi_address` / `hi_lo_address` -- as
|
|
381
381
|
* opposed to the two that denote WORDS (`lo_hi_word` / `hi_lo_word`), which
|
|
382
382
|
* stay on the raw-`!byte` fallback below unchanged. This is the branch that
|
|
@@ -425,7 +425,7 @@ interface DataLine {
|
|
|
425
425
|
* table is NOT invented here; that would be the same drift hazard wearing a
|
|
426
426
|
* local name.
|
|
427
427
|
*
|
|
428
|
-
* `external_file`
|
|
428
|
+
* `external_file` IS THE OPPOSITE CASE from `!text`
|
|
429
429
|
* above, and for the identical reason. `!text` is refused because ACME's
|
|
430
430
|
* conversion table is outside this exporter's control; `!binary` is used
|
|
431
431
|
* here because it applies NO table at all -- the bytes written to the
|
|
@@ -486,7 +486,7 @@ function emitDataLines(slice: Uint8Array, dataType: string, blockStart: number):
|
|
|
486
486
|
return out;
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
/**
|
|
489
|
+
/** one split-address block's own contribution
|
|
490
490
|
* to the export's shared in-tree-reference bookkeeping -- the SAME
|
|
491
491
|
* `unresolvedReferences`/`inTreeReferenceCount` totals the code path's in-tree
|
|
492
492
|
* symbol rule feeds inside `exportAsm()`'s block loop. Returned rather than
|
|
@@ -638,8 +638,7 @@ function hex4(value: number): string {
|
|
|
638
638
|
/**
|
|
639
639
|
* `$XXXX` FOR A BLOCK'S EXCLUSIVE END, WHICH IS `hex4()` -- MASKED, NOT PADDED.
|
|
640
640
|
*
|
|
641
|
-
* THIS FUNCTION USED TO DO THE OPPOSITE, AND IT WAS WRONG (
|
|
642
|
-
* corrected 2026-08-31). It padded without masking, so a range ending at
|
|
641
|
+
* THIS FUNCTION USED TO DO THE OPPOSITE, AND IT WAS WRONG (corrected 2026-08-31). It padded without masking, so a range ending at
|
|
643
642
|
* `$ffff` produced the end assertion `!if * != $10000`. Its doc justified that
|
|
644
643
|
* by asserting that `hex4()`'s mask "would render that as `$0000` -- an
|
|
645
644
|
* assertion no assembly can ever satisfy, firing on a correct export". That
|
|
@@ -761,7 +760,7 @@ function loadImage(imagePath: string): { origin: number; bytes: Uint8Array } {
|
|
|
761
760
|
* incorrect.
|
|
762
761
|
*
|
|
763
762
|
* THE HEX CASE IS LOWER, MATCHING EVERY OTHER EMITTER IN THIS DOCUMENT
|
|
764
|
-
* (
|
|
763
|
+
* (corrected 2026-08-31). This function used to emit
|
|
765
764
|
* uppercase (`start = $C000`) while `hex2()`, `hex4()` and `hexExtent()` all
|
|
766
765
|
* emit lowercase (`* = $0801`, `!byte $a9`), so one generated file carried two
|
|
767
766
|
* conventions. Both assemble identically -- ACME is case-insensitive for hex
|
|
@@ -781,10 +780,9 @@ function formatSymbolDefinition(name: string, address: number): string {
|
|
|
781
780
|
/**
|
|
782
781
|
* One mid-instruction label definition, in the golden witness's own compact
|
|
783
782
|
* spelling -- no spaces around the `=`, the offset in two hex digits:
|
|
784
|
-
* `f_0900 =*+$01
|
|
785
|
-
*
|
|
786
|
-
* the
|
|
787
|
-
* `30-RESEARCH.md`.
|
|
783
|
+
* `f_0900 =*+$01`. That witness carries SIX such labels; an earlier note
|
|
784
|
+
* claiming four was documentation drift, corrected after a recount against
|
|
785
|
+
* the witness disassembly itself.
|
|
788
786
|
*
|
|
789
787
|
* `offset` is `label.address - instr.address`, so it is 1 or 2 for every
|
|
790
788
|
* 6502/6510 instruction -- the value is rendered rather than bounded here
|
|
@@ -811,7 +809,7 @@ const AUTO_NAME_MARKER = " ; auto-generated name -- still in the annotation bac
|
|
|
811
809
|
|
|
812
810
|
/**
|
|
813
811
|
* The fixed trailing comment that marks a definition at an address carrying
|
|
814
|
-
* MORE THAN ONE store label
|
|
812
|
+
* MORE THAN ONE store label. ONE spelling, in one place, for
|
|
815
813
|
* the same reason `AUTO_NAME_MARKER` is: a second wording makes it ungreppable
|
|
816
814
|
* for the only reader it exists for.
|
|
817
815
|
*
|
|
@@ -823,7 +821,7 @@ const ALIAS_MARKER_PREFIX = " ; ALIAS: this address also carries ";
|
|
|
823
821
|
|
|
824
822
|
/**
|
|
825
823
|
* The fixed leading comment on every line carrying a provenance ledger row's
|
|
826
|
-
* Verdict and Confidence
|
|
824
|
+
* Verdict and Confidence. ONE spelling, in one
|
|
827
825
|
* place, for the same reason `AUTO_NAME_MARKER` and `ALIAS_MARKER_PREFIX`
|
|
828
826
|
* are: a second wording makes it ungreppable for the only reader it exists
|
|
829
827
|
* for.
|
|
@@ -832,10 +830,10 @@ const ALIAS_MARKER_PREFIX = " ; ALIAS: this address also carries ";
|
|
|
832
830
|
* `renderLedger()` header prose mentions `; PROVENANCE:` once, as an
|
|
833
831
|
* unwired, forward-looking remark about THIS PROJECT'S OWN documentation
|
|
834
832
|
* provenance conventions -- it names no writer, no reader and no format, and
|
|
835
|
-
*
|
|
836
|
-
*
|
|
833
|
+
* this project's own architecture document (the current one, not an early
|
|
834
|
+
* research-phase draft of the same name) never uses the word "provenance" at all. Reusing that
|
|
837
835
|
* string silently would attribute intent to it that it does not carry
|
|
838
|
-
* (
|
|
836
|
+
* (a pitfall caught before this constant shipped). `PROVENANCE_MARKER_PREFIX` is therefore a
|
|
839
837
|
* deliberately DIFFERENT, prefix-distinct spelling: `; PROVENANCE LEDGER:`,
|
|
840
838
|
* never `; PROVENANCE:`.
|
|
841
839
|
*/
|
|
@@ -852,7 +850,7 @@ const PROVENANCE_AMBIGUITY_MARKER_PREFIX = " ; PROVENANCE LEDGER AMBIGUITY: ";
|
|
|
852
850
|
|
|
853
851
|
/**
|
|
854
852
|
* The fixed leading comment on every line naming a user-requested exclusion
|
|
855
|
-
*
|
|
853
|
+
* ONE spelling, in one place, for the same
|
|
856
854
|
* reason `AUTO_NAME_MARKER`, `ALIAS_MARKER_PREFIX` and `PROVENANCE_MARKER_PREFIX`
|
|
857
855
|
* are: a second wording would make it ungreppable for the only reader it
|
|
858
856
|
* exists for, and criterion 2's own readback -- recovering what was excluded
|
|
@@ -860,8 +858,8 @@ const PROVENANCE_AMBIGUITY_MARKER_PREFIX = " ; PROVENANCE LEDGER AMBIGUITY: ";
|
|
|
860
858
|
* one spelling to anchor on.
|
|
861
859
|
*
|
|
862
860
|
* THIS IS HOW A USER'S REQUEST TO LEAVE A SPAN OUT APPEARS IN THE ARTEFACT,
|
|
863
|
-
* and it is deliberately a MARKER rather than an OMISSION:
|
|
864
|
-
*
|
|
861
|
+
* and it is deliberately a MARKER rather than an OMISSION: the export is
|
|
862
|
+
* REQUIRED to be lossless by default, and any exclusion be "emitted as a
|
|
865
863
|
* recorded excluded range rather than a hole". The failure this constant's
|
|
866
864
|
* existence prevents is the obvious wrong implementation -- skipping the
|
|
867
865
|
* block would satisfy the word "exclude" and lose the bytes, and the
|
|
@@ -886,7 +884,8 @@ const MAX_IMMEDIATE_VARIANT_VALUE = 0xff;
|
|
|
886
884
|
/**
|
|
887
885
|
* The SHAPE `registerKeyFor(address).slice(1)` always produces (uppercase,
|
|
888
886
|
* exactly four hex digits) -- the SAME string `planEnumsForPairing()` uses as
|
|
889
|
-
* a project enum's own `enumName` (`anno-enum-gen.ts
|
|
887
|
+
* a project enum's own `enumName` (`anno-enum-gen.ts`'s own naming rule).
|
|
888
|
+
* An enum usage
|
|
890
889
|
* whose `enumName` matches this shape is a CANDIDATE for
|
|
891
890
|
* `decomposeRegisterValue()`; one that does not (a hand-authored name like
|
|
892
891
|
* `viccolor`) is never a candidate at all -- this module holds no second
|
|
@@ -896,7 +895,7 @@ const MAX_IMMEDIATE_VARIANT_VALUE = 0xff;
|
|
|
896
895
|
* convention (`registerKeyFor()`) emits uppercase, and matching lowercase too
|
|
897
896
|
* would accept a shape nothing in this codebase produces.
|
|
898
897
|
*
|
|
899
|
-
* SHAPE ALONE IS NOT ENOUGH (
|
|
898
|
+
* SHAPE ALONE IS NOT ENOUGH (fixed 2026-09-11): a candidate
|
|
900
899
|
* is only ATTEMPTED once `hasRegBitsEntry()` also confirms `anno-regbits.json`
|
|
901
900
|
* has a table entry for it. `$D020`/`$D021` -- among the most commonly
|
|
902
901
|
* hand-annotated C64 registers -- are register-shaped and absent from the
|
|
@@ -910,7 +909,7 @@ const REGISTER_ENUM_NAME_RE = /^[0-9A-F]{4}$/;
|
|
|
910
909
|
* Replaces the `#$XX` immediate literal `renderLine()` produced with `#symbol`.
|
|
911
910
|
*
|
|
912
911
|
* WHY A TARGETED TEXT SUBSTITUTION RATHER THAN A `RenderOptions` WIDENING.
|
|
913
|
-
*
|
|
912
|
+
* This module's own invariant forbids `renderLine()` from substituting a symbol into an immediate
|
|
914
913
|
* operand at all, because of the `#<`/`#>` high/low-byte ambiguity, and that
|
|
915
914
|
* rule is verified against a real assembler in `disasm-roundtrip.test.ts`. It
|
|
916
915
|
* is not relaxed here. What an ENUM adds is a caller-supplied fact the renderer
|
|
@@ -925,7 +924,7 @@ const REGISTER_ENUM_NAME_RE = /^[0-9A-F]{4}$/;
|
|
|
925
924
|
* substitution happened.
|
|
926
925
|
*
|
|
927
926
|
* THE SEARCH IS CONFINED TO THE DIRECTIVE HALF OF THE LINE, AND THAT IS THE
|
|
928
|
-
* SECOND HALF OF
|
|
927
|
+
* SECOND HALF OF THIS MODULE'S OWN 2026-08-31 FIX. `renderLine()` emits a
|
|
929
928
|
* trailing `" ; "` comment for notes, and for an instruction whose
|
|
930
929
|
* `acmeExpressible` is false it emits the whole thing as a `!byte` directive
|
|
931
930
|
* with the mnemonic AND its `#$xx` operand moved INTO that comment
|
|
@@ -993,7 +992,7 @@ const [LINE_COMMENT, SIDE_COMMENT] = COMMENT_TYPES;
|
|
|
993
992
|
* The store validator's own message is deliberately DISCARDED and replaced.
|
|
994
993
|
* That message interpolates the offending text for one of its four cases, and
|
|
995
994
|
* an exporter error that quotes a file's contents back is a content-disclosure
|
|
996
|
-
* oracle
|
|
995
|
+
* oracle. What survives is the address and which rule fired -- facts
|
|
997
996
|
* ABOUT the comment, never the comment.
|
|
998
997
|
*/
|
|
999
998
|
export function assertExportableCommentText(text: string, address: number): string {
|
|
@@ -1012,7 +1011,7 @@ export function assertExportableCommentText(text: string, address: number): stri
|
|
|
1012
1011
|
|
|
1013
1012
|
/**
|
|
1014
1013
|
* Re-checks, at the EXPORT boundary, that a stored range's `dataType` is one
|
|
1015
|
-
* the store's own vocabulary defines
|
|
1014
|
+
* the store's own vocabulary defines.
|
|
1016
1015
|
*
|
|
1017
1016
|
* The sibling of `assertExportableCommentText()` below, on the same terms and
|
|
1018
1017
|
* for the same reason: `listRanges()` casts the column with no validator, so
|
|
@@ -1067,7 +1066,7 @@ interface CommentPlacement {
|
|
|
1067
1066
|
* the order they were written in rather than an order this module invented.
|
|
1068
1067
|
*
|
|
1069
1068
|
* A COMMENT ON A MULTI-ADDRESS LINE IS QUALIFIED WITH ITS OWN ADDRESS
|
|
1070
|
-
* (
|
|
1069
|
+
* (added 2026-08-31). The CODE path calls this with a span of
|
|
1071
1070
|
* exactly ONE address (`[instr.address, instr.address + 1)`), so a comment
|
|
1072
1071
|
* there is unambiguous and is emitted unchanged -- nothing about the existing
|
|
1073
1072
|
* output moves. The DATA path calls it with a span of up to
|
|
@@ -1086,7 +1085,7 @@ interface CommentPlacement {
|
|
|
1086
1085
|
* with an address it already sits next to is noise, and it would rewrite every
|
|
1087
1086
|
* existing expected line in the test suite for nothing.
|
|
1088
1087
|
*
|
|
1089
|
-
* `generatedSuffix`
|
|
1088
|
+
* `generatedSuffix` is this module's OWN mechanical text --
|
|
1090
1089
|
* today, only `decomposeRegisterValue()`'s decoded-field comment for an OR-ed
|
|
1091
1090
|
* multi-bit enum substitution -- never a second stored comment. It is NEVER
|
|
1092
1091
|
* DROPPED and NEVER REORDERED BEHIND authored text: when a stored SIDE
|
|
@@ -1140,7 +1139,7 @@ function withComments(text: string, start: number, endExclusive: number, ctx: Co
|
|
|
1140
1139
|
}
|
|
1141
1140
|
|
|
1142
1141
|
/**
|
|
1143
|
-
*
|
|
1142
|
+
* the address one decoded instruction's
|
|
1144
1143
|
* operand REFERENCES, for the in-tree symbol rule below -- the SAME address
|
|
1145
1144
|
* a substitution would substitute, extracted through `resolvedTarget` FIRST
|
|
1146
1145
|
* (`disasm-decoder.ts` rule 5 for every relative branch, rule 6 for absolute
|
|
@@ -1153,7 +1152,7 @@ function withComments(text: string, start: number, endExclusive: number, ctx: Co
|
|
|
1153
1152
|
* immediate is a BYTE VALUE, not an address: project enums (the
|
|
1154
1153
|
* `usageByAddress` branch above) are what give one a name, and treating it as
|
|
1155
1154
|
* an address is how `lda #$08` would start demanding a label be recorded at
|
|
1156
|
-
* `$0008`. `disasm-renderer.ts`'s own
|
|
1155
|
+
* `$0008`. `disasm-renderer.ts`'s own comment states the same exclusion
|
|
1157
1156
|
* for the identical reason, on the substitution side of this same boundary.
|
|
1158
1157
|
*/
|
|
1159
1158
|
function referencedAddress(instr: Instruction): number | undefined {
|
|
@@ -1164,7 +1163,7 @@ function referencedAddress(instr: Instruction): number | undefined {
|
|
|
1164
1163
|
}
|
|
1165
1164
|
|
|
1166
1165
|
/**
|
|
1167
|
-
*
|
|
1166
|
+
* true iff `address` falls inside
|
|
1168
1167
|
* `[block.start, block.endExclusive)` for SOME block this export emitted --
|
|
1169
1168
|
* the half-open interval every other boundary test in this module already
|
|
1170
1169
|
* uses. Tested against the `blocks` array `exportAsm()` already built at the
|
|
@@ -1222,12 +1221,12 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1222
1221
|
comments = listComments(handle);
|
|
1223
1222
|
projectEnums = listProjectEnums(handle);
|
|
1224
1223
|
enumUsage = listEnumUsage(handle);
|
|
1225
|
-
//
|
|
1224
|
+
// a sixth read in the SAME handle and the
|
|
1226
1225
|
// SAME `try`, mirroring the discipline the five siblings above already
|
|
1227
1226
|
// follow -- one handle for the whole export, closed once in the
|
|
1228
1227
|
// `finally` below. There is no second store opened for this.
|
|
1229
1228
|
excludedRanges = listExcludedRanges(handle);
|
|
1230
|
-
//
|
|
1229
|
+
// a SEVENTH read in the SAME handle and the SAME
|
|
1231
1230
|
// `try`, on the same terms as the sixth above -- still one handle for
|
|
1232
1231
|
// the whole export, closed once in the `finally` below. There is no
|
|
1233
1232
|
// second store opened for this either.
|
|
@@ -1251,8 +1250,8 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1251
1250
|
const blocks: ExportBlock[] = sortedRanges.map((row) => ({
|
|
1252
1251
|
start: row.start,
|
|
1253
1252
|
endExclusive: row.endInclusive + 1,
|
|
1254
|
-
// THE STORE'S `dataType` IS RE-CHECKED AT THIS BOUNDARY (
|
|
1255
|
-
//
|
|
1253
|
+
// THE STORE'S `dataType` IS RE-CHECKED AT THIS BOUNDARY (fixed 2026-08-31),
|
|
1254
|
+
// for exactly the reason `withComments()` re-checks
|
|
1256
1255
|
// `commentType` a few functions up: "Unreachable through the type, and
|
|
1257
1256
|
// reachable through a store file somebody edited. Refusing beats
|
|
1258
1257
|
// guessing." That reasoning applies here and had not been applied.
|
|
@@ -1293,7 +1292,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1293
1292
|
}
|
|
1294
1293
|
}
|
|
1295
1294
|
|
|
1296
|
-
//
|
|
1295
|
+
// ONE read for the whole export, mirroring the
|
|
1297
1296
|
// one-store-handle discipline directly above. `ledgerPath === undefined` is
|
|
1298
1297
|
// the ONLY question asked of the caller's intent here -- everything after
|
|
1299
1298
|
// this line either has a ledger to join against or does not, and no branch
|
|
@@ -1312,7 +1311,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1312
1311
|
for (const label of sortedLabels) {
|
|
1313
1312
|
assertLegalAcmeIdentifier(label.name, `exportAsm: label at ${hex4(label.address)}`);
|
|
1314
1313
|
// TWO NAMES AT ONE ADDRESS IS RECORDED IN THE EMITTED SOURCE, NOT RESOLVED
|
|
1315
|
-
// IN SILENCE (
|
|
1314
|
+
// IN SILENCE (fixed 2026-08-31).
|
|
1316
1315
|
//
|
|
1317
1316
|
// `labelIndex` is a `Map<number, string>` while `anno_label` is `unique`
|
|
1318
1317
|
// on NAME only -- `setLabel()` refuses only a name already bound to a
|
|
@@ -1352,7 +1351,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1352
1351
|
* only the first name at each, so an ALIASED label would be invisible to a
|
|
1353
1352
|
* collision check reading it. ACME has ONE symbol namespace, so an enum
|
|
1354
1353
|
* variant symbol colliding with any of these is `Symbol already defined.`
|
|
1355
|
-
|
|
1354
|
+
*/
|
|
1356
1355
|
const labelSymbolNames = new Set(sortedLabels.map((label) => label.name));
|
|
1357
1356
|
|
|
1358
1357
|
// Comments indexed by the address they annotate, each address's list left in
|
|
@@ -1379,7 +1378,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1379
1378
|
* They join the header block for the same reason label definitions do. */
|
|
1380
1379
|
const enumDefinitionLines: string[] = [];
|
|
1381
1380
|
/** Every emitted enum-derived symbol name (single-value or OR-ed term) to
|
|
1382
|
-
* the ONE value it was defined with. A Map, not a Set
|
|
1381
|
+
* the ONE value it was defined with. A Map, not a Set:
|
|
1383
1382
|
* a term name defined by one instruction with one value and referenced by a
|
|
1384
1383
|
* SECOND instruction with a DIFFERENT value is a genuine collision in
|
|
1385
1384
|
* ACME's one symbol namespace, and the value is what makes that collision
|
|
@@ -1389,13 +1388,13 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1389
1388
|
let unexpressibleCount = 0;
|
|
1390
1389
|
let dataByteCount = 0;
|
|
1391
1390
|
let enumDecompositionCount = 0;
|
|
1392
|
-
/**
|
|
1391
|
+
/** every `external_file`-typed block's
|
|
1393
1392
|
* own `.bin` sibling, populated at the same point `dataByteCount` above
|
|
1394
1393
|
* is incremented for that block -- see `ExportAsmResult.binaries`'s own
|
|
1395
1394
|
* doc-comment for what a caller reading this field is obliged to do. */
|
|
1396
1395
|
const binaries: ExportBinary[] = [];
|
|
1397
1396
|
|
|
1398
|
-
/**
|
|
1397
|
+
/** one in-tree reference this export could
|
|
1399
1398
|
* not resolve to a symbol -- collected across the WHOLE block loop and
|
|
1400
1399
|
* refused ONCE at the end, in the shape the unapplied-enum-usage and
|
|
1401
1400
|
* unplaced-comment refusals below already use. */
|
|
@@ -1423,8 +1422,8 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1423
1422
|
return `${line}${AUTO_NAME_MARKER}`;
|
|
1424
1423
|
};
|
|
1425
1424
|
|
|
1426
|
-
// THE ALIAS PICK IS MADE VISIBLE IN THE SOURCE (
|
|
1427
|
-
//
|
|
1425
|
+
// THE ALIAS PICK IS MADE VISIBLE IN THE SOURCE (second half of the same fix
|
|
1426
|
+
// above). Two store labels at one address are BOTH defined -- ACME accepts
|
|
1428
1427
|
// two symbols with one value and the bytes are unaffected -- but a
|
|
1429
1428
|
// REFERENCE to that address can render through only one of them. Which one
|
|
1430
1429
|
// was previously invisible. Marking both definitions with the same fixed
|
|
@@ -1446,7 +1445,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1446
1445
|
// or ACME refuses the whole source with `Symbol already defined.`
|
|
1447
1446
|
const midInstructionLabelAddresses = new Set<number>();
|
|
1448
1447
|
|
|
1449
|
-
// ONE PER EMITTED INLINE DEFINITION, not one per address
|
|
1448
|
+
// ONE PER EMITTED INLINE DEFINITION, not one per address.
|
|
1450
1449
|
// The set above answers the HEADER's question ("is this address defined
|
|
1451
1450
|
// inline already?"), which is per-address by nature. This counter answers
|
|
1452
1451
|
// the RESULT's question ("how many inline definitions does the source
|
|
@@ -1469,11 +1468,11 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1469
1468
|
const content: string[] = [];
|
|
1470
1469
|
|
|
1471
1470
|
if (block.dataType === CODE_DATA_TYPE) {
|
|
1472
|
-
//
|
|
1471
|
+
// That invariant is inherited UNCHANGED: `renderLine()` decides operand width and
|
|
1473
1472
|
// refuses to substitute a symbol into an immediate or zeropage-family
|
|
1474
1473
|
// operand. Do not widen `RenderOptions` and do not bypass `renderLine()`.
|
|
1475
1474
|
// `end` IS INCLUSIVE, SO IT IS HANDED AN INCLUSIVE VALUE (30-REVIEW
|
|
1476
|
-
//
|
|
1475
|
+
// corrected 2026-08-31). This used to pass `block.endExclusive`.
|
|
1477
1476
|
// `DecodeOptions.end` is compared with `if (end !== undefined && address
|
|
1478
1477
|
// > end) break` and documented as "an instruction starting past `end` is
|
|
1479
1478
|
// dropped ... an instruction starting AT OR BEFORE `end` is emitted in
|
|
@@ -1556,7 +1555,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1556
1555
|
|
|
1557
1556
|
let rendered = renderLine(instr, { showSymbols: true, symbolFor });
|
|
1558
1557
|
|
|
1559
|
-
//
|
|
1558
|
+
// THE IN-TREE SYMBOL RULE, beside
|
|
1560
1559
|
// the `renderLine()` call it has to agree with. `rendered` above
|
|
1561
1560
|
// already went through `symbolFor` -- the SAME `labelIndex` map read
|
|
1562
1561
|
// here -- so a reference this check calls unresolved is a reference
|
|
@@ -1578,7 +1577,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1578
1577
|
}
|
|
1579
1578
|
}
|
|
1580
1579
|
|
|
1581
|
-
// The mechanical decode text
|
|
1580
|
+
// The mechanical decode text, set only by the OR-ed
|
|
1582
1581
|
// decomposition branch below and merged into this instruction's
|
|
1583
1582
|
// trailing comment by `withComments()` after the enum-substitution
|
|
1584
1583
|
// block finishes.
|
|
@@ -1598,7 +1597,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1598
1597
|
}
|
|
1599
1598
|
|
|
1600
1599
|
// ROLE IS NOT ENOUGH: THE OPERAND MUST ALSO BE ASSEMBLER-VISIBLE
|
|
1601
|
-
// (
|
|
1600
|
+
// (fixed 2026-08-31). `decode()` assigns
|
|
1602
1601
|
// `role: "immediate"` from the ADDRESSING MODE alone, independently
|
|
1603
1602
|
// of `acmeExpressible`. Six opcodes in `disasm-opcodes.ts` are
|
|
1604
1603
|
// `mode: "immediate"` AND `acmeExpressible: false` -- $2b (`anc`),
|
|
@@ -1622,9 +1621,9 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1622
1621
|
// !byte $eb, $00 ; sbc #viccolor_BLACK [illegal opcode | ...]
|
|
1623
1622
|
// === enumSubstitutionCount: 1
|
|
1624
1623
|
//
|
|
1625
|
-
// This
|
|
1626
|
-
// REFUSED loudly and by name, never silently dropped while
|
|
1627
|
-
// export reports success
|
|
1624
|
+
// This inverts, exactly, the rule that an annotation the exporter cannot
|
|
1625
|
+
// express is REFUSED loudly and by name, never silently dropped while
|
|
1626
|
+
// the export reports success. It is refused now.
|
|
1628
1627
|
if (!instr.acmeExpressible) {
|
|
1629
1628
|
throw new Error(
|
|
1630
1629
|
`exportAsm: enum ${JSON.stringify(usage.enumName)} is bound to the immediate operand at ${hex4(usage.address)}, but that ` +
|
|
@@ -1647,8 +1646,8 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1647
1646
|
);
|
|
1648
1647
|
}
|
|
1649
1648
|
|
|
1650
|
-
//
|
|
1651
|
-
// BOTH (
|
|
1649
|
+
// THE ONE OWNING DECODER. Attempted ONLY when
|
|
1650
|
+
// BOTH (fixed 2026-09-11):
|
|
1652
1651
|
// 1. `usage.enumName` has the exact shape `registerKeyFor().slice(1)`
|
|
1653
1652
|
// produces -- see `REGISTER_ENUM_NAME_RE`'s own comment for why a
|
|
1654
1653
|
// name that does not have this shape (e.g. `viccolor`) is never
|
|
@@ -1679,7 +1678,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1679
1678
|
}
|
|
1680
1679
|
|
|
1681
1680
|
if (decomposition !== undefined && decomposition.multiField) {
|
|
1682
|
-
//
|
|
1681
|
+
// OR-ED NAMED CONSTANTS AND THE DECODED COMMENT -- BOTH,
|
|
1683
1682
|
// never either alone. A bare hex constant with a comment still
|
|
1684
1683
|
// "emits one hex constant"; bare constants with no comment are
|
|
1685
1684
|
// not readable.
|
|
@@ -1700,11 +1699,11 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1700
1699
|
);
|
|
1701
1700
|
}
|
|
1702
1701
|
// Same name, same value, already defined by an earlier
|
|
1703
|
-
// instruction -- no second definition line
|
|
1702
|
+
// instruction -- no second definition line, the same
|
|
1704
1703
|
// own "only what the source references" discipline, extended
|
|
1705
1704
|
// to terms).
|
|
1706
1705
|
} else {
|
|
1707
|
-
// THE SAME LABEL COLLISION CHECK
|
|
1706
|
+
// THE SAME LABEL COLLISION CHECK ADDED FOR A
|
|
1708
1707
|
// SINGLE ENUM SYMBOL, EXTENDED HERE -- not a second check.
|
|
1709
1708
|
if (labelSymbolNames.has(term.name)) {
|
|
1710
1709
|
throw new Error(
|
|
@@ -1726,9 +1725,9 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1726
1725
|
enumDecompositionCount++;
|
|
1727
1726
|
decompositionComment = decomposition.comment;
|
|
1728
1727
|
} else {
|
|
1729
|
-
// THE EXISTING SINGLE-SYMBOL PATH (
|
|
1728
|
+
// THE EXISTING SINGLE-SYMBOL PATH (unchanged, not
|
|
1730
1729
|
// replaced) -- a single-field register, an enum usage whose name
|
|
1731
|
-
// is not register-shaped at all, OR
|
|
1730
|
+
// is not register-shaped at all, OR a
|
|
1732
1731
|
// register-shaped name for a register `anno-regbits.json` simply
|
|
1733
1732
|
// has no entry for (e.g. `D020`) -- reached here with no
|
|
1734
1733
|
// substitution counted above, never a throw.
|
|
@@ -1771,7 +1770,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1771
1770
|
assertLegalAcmeIdentifier(symbol, `exportAsm: enum variant symbol for ${hex4(usage.address)}`);
|
|
1772
1771
|
|
|
1773
1772
|
// THE COLLISION THE COMMENT BELOW NAMES IS NOW CHECKED FOR
|
|
1774
|
-
// (
|
|
1773
|
+
// (fixed 2026-08-31). That comment identified the
|
|
1775
1774
|
// hazard exactly -- "every extra emitted symbol is one more chance to
|
|
1776
1775
|
// collide with a label name and turn a correct export into ACME's
|
|
1777
1776
|
// `Symbol already defined.`" -- and then did not look.
|
|
@@ -1822,7 +1821,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1822
1821
|
block.lineCount += emitted.length;
|
|
1823
1822
|
}
|
|
1824
1823
|
} else if (isSplitAddressDataType(block.dataType)) {
|
|
1825
|
-
//
|
|
1824
|
+
// the two split ADDRESS layouts get
|
|
1826
1825
|
// PAIRED symbol emission, never the raw-`!byte` fallback -- see
|
|
1827
1826
|
// `emitSplitAddressLines()`'s own doc-comment for the orientation and
|
|
1828
1827
|
// the one-symbol-per-entry rule. Read the SAME `blocks`/`labelIndex`
|
|
@@ -1851,7 +1850,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1851
1850
|
}
|
|
1852
1851
|
dataByteCount += slice.length;
|
|
1853
1852
|
|
|
1854
|
-
//
|
|
1853
|
+
// an `external_file` block's bytes
|
|
1855
1854
|
// also go out as their own sibling data file. `bytes` is `slice`
|
|
1856
1855
|
// itself -- the same bytes `dataByteCount` above just counted, carried
|
|
1857
1856
|
// verbatim and never re-read from anywhere else.
|
|
@@ -1860,7 +1859,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1860
1859
|
}
|
|
1861
1860
|
}
|
|
1862
1861
|
|
|
1863
|
-
//
|
|
1862
|
+
// when ledger mode is on, every block gets a
|
|
1864
1863
|
// provenance comment BEFORE it is bracketed -- never as a threshold, never
|
|
1865
1864
|
// gating which blocks reach `emitBlock()` below. The only conditionals
|
|
1866
1865
|
// here are "is ledger mode on", "did zero rows come back" (refuse) and
|
|
@@ -1903,7 +1902,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1903
1902
|
content.unshift(...provenanceLines);
|
|
1904
1903
|
}
|
|
1905
1904
|
|
|
1906
|
-
//
|
|
1905
|
+
// every recorded exclusion overlapping this
|
|
1907
1906
|
// block gets a marker naming ITS OWN extent (never the block's) and its
|
|
1908
1907
|
// checked reason -- NEVER a skipped block, a shortened slice, or a
|
|
1909
1908
|
// shrunk `expectedBytes`. `block.start`/`block.endExclusive` above are
|
|
@@ -1950,7 +1949,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1950
1949
|
|
|
1951
1950
|
// EVERY block goes through `emitBlock()`, code and data alike, so there is
|
|
1952
1951
|
// exactly one place that brackets a block and no route that emits an
|
|
1953
|
-
// unbracketed one. Captured onto `block.lines`
|
|
1952
|
+
// unbracketed one. Captured onto `block.lines` at
|
|
1954
1953
|
// the SAME point it is pushed onto `blockLines` -- one call, two
|
|
1955
1954
|
// destinations, never a second bracketing.
|
|
1956
1955
|
const emittedBlockLines = emitBlock(block.start, block.endExclusive, content);
|
|
@@ -1958,7 +1957,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
1958
1957
|
blockLines.push(...emittedBlockLines);
|
|
1959
1958
|
}
|
|
1960
1959
|
|
|
1961
|
-
//
|
|
1960
|
+
// an in-tree reference this export could
|
|
1962
1961
|
// not resolve to a symbol is REFUSED BY NAME, once, across the whole
|
|
1963
1962
|
// export -- never emitted as a hex literal that freezes the target's
|
|
1964
1963
|
// address into the source while the export reports success. In the same
|
|
@@ -2070,7 +2069,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
|
|
|
2070
2069
|
}
|
|
2071
2070
|
|
|
2072
2071
|
// ---------------------------------------------------------------------------
|
|
2073
|
-
//
|
|
2072
|
+
// the TREE writer (D47-A). `exportAsm()` above stays the
|
|
2074
2073
|
// proven EMITTER -- this is the primary shape a caller reaches for, built by
|
|
2075
2074
|
// PARTITIONING `exportAsm()`'s already-proven emission, never by emitting a
|
|
2076
2075
|
// second time through a second route. See `exportAsmTree()`'s own doc-comment
|
|
@@ -2102,7 +2101,7 @@ export function scopeFileName(start: number): string {
|
|
|
2102
2101
|
}
|
|
2103
2102
|
|
|
2104
2103
|
/**
|
|
2105
|
-
*
|
|
2104
|
+
* the `.bin` sibling file name for the
|
|
2106
2105
|
* `external_file`-typed block starting at `start` -- `data_XXXX.bin`, four
|
|
2107
2106
|
* LOWERCASE hex digits, no `$`, no store free text anywhere in it. Module-
|
|
2108
2107
|
* private: nothing outside this file needs the name computed independently
|
|
@@ -2127,13 +2126,13 @@ export interface ExportAsmTreeOptions extends ExportAsmOptions {
|
|
|
2127
2126
|
* absent comment beside a present one is itself a claim, and a silently-
|
|
2128
2127
|
* undocumented path field is what a prior review named as the mechanism of
|
|
2129
2128
|
* a real defect. The CALLER owns confining it (the CLI does, through
|
|
2130
|
-
* `storePathWithinWorkspace()
|
|
2129
|
+
* `storePathWithinWorkspace()`). Within the directory, every
|
|
2131
2130
|
* name this function writes is DERIVED (`scopeFileName()`, the three fixed
|
|
2132
2131
|
* constants above), so nothing the caller supplies can escape it a second
|
|
2133
2132
|
* time. */
|
|
2134
2133
|
outDir: string;
|
|
2135
2134
|
/**
|
|
2136
|
-
*
|
|
2135
|
+
* What the CALLER IS ASKING FOR, not a
|
|
2137
2136
|
* switch that widens what this function is willing to destroy. Set, it
|
|
2138
2137
|
* means "this directory already holds a tree I exported before -- replace
|
|
2139
2138
|
* it." It deliberately does NOT mean "remove whatever is in my way": a
|
|
@@ -2157,7 +2156,7 @@ export interface ExportAsmTreeResult extends ExportAsmResult {
|
|
|
2157
2156
|
}
|
|
2158
2157
|
|
|
2159
2158
|
/**
|
|
2160
|
-
*
|
|
2159
|
+
* decides which file ONE block belongs to, given the
|
|
2161
2160
|
* store's own scopes -- the ONE containment predicate, applied ONCE, so the
|
|
2162
2161
|
* tree writer's placement answer can never drift from a second copy of this
|
|
2163
2162
|
* question (see `blocks`'s own `.map()` above for the sibling boundary this
|
|
@@ -2272,7 +2271,7 @@ export function exportAsmTree(options: ExportAsmTreeOptions): ExportAsmTreeResul
|
|
|
2272
2271
|
// never a guess revised after the fact.
|
|
2273
2272
|
const populatedScopeStarts = [...scopeBlocks.keys()].sort((a, b) => a - b);
|
|
2274
2273
|
const hasUnscoped = unscopedBlocks.length > 0;
|
|
2275
|
-
//
|
|
2274
|
+
// every `.bin` sibling this call will write
|
|
2276
2275
|
// joins the SAME name set the directory contract below evaluates, so a
|
|
2277
2276
|
// re-export with `force: true` may replace a previously-exported `.bin`
|
|
2278
2277
|
// exactly as it may replace a previously-exported `.a` file -- and, without
|
|
@@ -2287,7 +2286,7 @@ export function exportAsmTree(options: ExportAsmTreeOptions): ExportAsmTreeResul
|
|
|
2287
2286
|
];
|
|
2288
2287
|
|
|
2289
2288
|
// ---------------------------------------------------------------------
|
|
2290
|
-
//
|
|
2289
|
+
// The output-directory contract. Two rules,
|
|
2291
2290
|
// both evaluated BEFORE the first write below -- a refusal that has
|
|
2292
2291
|
// already written half a tree has left an artefact a later assemble might
|
|
2293
2292
|
// succeed on (the same reason the placement pass above runs to completion
|
|
@@ -2367,7 +2366,7 @@ export function exportAsmTree(options: ExportAsmTreeOptions): ExportAsmTreeResul
|
|
|
2367
2366
|
sourceOrder.push(UNSCOPED_FILE_NAME);
|
|
2368
2367
|
}
|
|
2369
2368
|
|
|
2370
|
-
// .bin siblings (T-47-08/T-47-09/T-47-10
|
|
2369
|
+
// .bin siblings (T-47-08/T-47-09/T-47-10) -- one per
|
|
2371
2370
|
// `external_file`-typed block, written in the SAME pass as every `.a`
|
|
2372
2371
|
// file above and, like them, BEFORE root.a: data files precede the root
|
|
2373
2372
|
// for the identical interruption-safety reason the `.a` files already do
|