@shapeshift-labs/frontier-lang-assembly 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ShapeShift Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @shapeshift-labs/frontier-lang-assembly
2
+
3
+ Runtime-neutral assembly and low-level semantic merge evidence for Frontier Lang.
4
+
5
+ This package scans assembly-like source into source-bound records for labels, directives, instructions, comments, register hints, flag hints, memory references, and branch/call targets. It is designed for merge review and projection evidence. It does not assemble source, emulate hardware, verify object code, or claim binary/timing equivalence.
6
+
7
+ ```js
8
+ import { createAssemblySemanticMergeEvidence } from '@shapeshift-labs/frontier-lang-assembly';
9
+
10
+ const evidence = createAssemblySemanticMergeEvidence(`
11
+ Reset:
12
+ sep #$20
13
+ lda $2100
14
+ jsl LongCall
15
+ rtl
16
+ `, { dialect: 'snes' });
17
+
18
+ console.log(evidence.summary.instructions);
19
+ console.log(evidence.proofGaps.map((gap) => gap.code));
20
+ ```
21
+
22
+ ## What It Captures
23
+
24
+ - dialect profiles for generic assembly, x86, x86-64, arm64, riscv, LLVM IR, eBPF, 6502, 65816, SNES assembly, Z80, SM83, and m68k
25
+ - labels and branch/call targets as source-bound symbolic records
26
+ - directives such as `.include`, `.macro`, `.bank`, and conditional assembly as fail-closed proof gaps
27
+ - rough register, flag, memory-reference, branch, call, and return hints
28
+ - SNES/65816-specific gaps for processor mode, banked memory, memory-map, I/O-register, and cycle-timing proof
29
+
30
+ ## Boundary
31
+
32
+ `frontier-lang-assembly` is a conservative evidence scanner. Target-specific assembler validation, object/binary decoding, debug symbols, emulator traces, cycle-accurate runtime proof, ABI proof, and final semantic merge admission belong above this package.
33
+
34
+ High-level Frontier programs can lower toward assembly-like targets, but the reverse path is evidence-limited unless debug/source maps and semantic receipts are attached. This package gives Frontier a place to record what low-level source says about execution constraints without pretending those constraints are enough to recover all source-level meaning.
@@ -0,0 +1,8 @@
1
+ import { createAssemblySemanticMergeEvidence } from '../dist/index.js';
2
+
3
+ const source = Array.from({ length: 1000 }, (_, index) => `L${index}:\n lda #$${(index % 255).toString(16)}\n sta $2100`).join('\n');
4
+ const start = performance.now();
5
+ const evidence = createAssemblySemanticMergeEvidence(source, { dialect: 'snes' });
6
+ const elapsedMs = performance.now() - start;
7
+
8
+ console.log(JSON.stringify({ instructions: evidence.summary.instructions, proofGaps: evidence.summary.proofGaps, elapsedMs: Number(elapsedMs.toFixed(3)) }));
@@ -0,0 +1,52 @@
1
+ const RegisterSets = Object.freeze({
2
+ generic: ['r0', 'r1', 'r2', 'r3', 'sp', 'fp', 'pc'],
3
+ x86: ['eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'esp', 'ebp', 'eip', 'ax', 'bx', 'cx', 'dx', 'al', 'ah', 'bl', 'bh', 'cl', 'ch', 'dl', 'dh'],
4
+ x64: ['rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi', 'rsp', 'rbp', 'rip', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', 'eax', 'ebx', 'ecx', 'edx'],
5
+ arm64: ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10', 'x11', 'x12', 'x13', 'x14', 'x15', 'x16', 'x17', 'x18', 'x19', 'x20', 'x21', 'x22', 'x23', 'x24', 'x25', 'x26', 'x27', 'x28', 'x29', 'x30', 'sp', 'pc'],
6
+ riscv: ['x0', 'ra', 'sp', 'gp', 'tp', 't0', 't1', 't2', 's0', 'fp', 's1', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', 't3', 't4', 't5', 't6', 'pc'],
7
+ m6502: ['a', 'x', 'y', 'sp', 'pc', 'p'],
8
+ m65816: ['a', 'x', 'y', 's', 'd', 'db', 'pb', 'pc', 'p'],
9
+ z80: ['a', 'f', 'b', 'c', 'd', 'e', 'h', 'l', 'af', 'bc', 'de', 'hl', 'ix', 'iy', 'sp', 'pc'],
10
+ m68k: ['d0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'sp', 'pc', 'sr']
11
+ });
12
+
13
+ const FlowSets = Object.freeze({
14
+ generic: { branch: ['jmp', 'j', 'bra', 'br'], call: ['call', 'jsr', 'jal', 'bl', 'bsr'], ret: ['ret', 'rts', 'rtl', 'bx', 'jr'] },
15
+ x86: { branch: ['jmp', 'je', 'jne', 'jg', 'jge', 'jl', 'jle', 'ja', 'jb', 'jo', 'jno', 'js', 'jns'], call: ['call'], ret: ['ret', 'iret'] },
16
+ arm64: { branch: ['b', 'beq', 'bne', 'bgt', 'bge', 'blt', 'ble', 'cbz', 'cbnz', 'tbz', 'tbnz'], call: ['bl', 'blr'], ret: ['ret'] },
17
+ riscv: { branch: ['j', 'jal', 'beq', 'bne', 'blt', 'bge', 'bltu', 'bgeu'], call: ['call', 'jal', 'jalr'], ret: ['ret'] },
18
+ retro: { branch: ['bra', 'jmp', 'beq', 'bne', 'bpl', 'bmi', 'bcc', 'bcs', 'bvc', 'bvs', 'jp', 'jr', 'dbra'], call: ['jsr', 'jsl', 'call', 'bsr'], ret: ['rts', 'rtl', 'ret', 'rti', 'reti'] }
19
+ });
20
+
21
+ export const AssemblyDialectProfiles = Object.freeze([
22
+ profile('assembly', 'generic', ['asm', 's'], RegisterSets.generic, FlowSets.generic),
23
+ profile('x86', 'x86', [], RegisterSets.x86, FlowSets.x86),
24
+ profile('x86-64', 'x86', ['x64', 'x86_64', 'amd64'], RegisterSets.x64, FlowSets.x86),
25
+ profile('arm64', 'arm', ['aarch64'], RegisterSets.arm64, FlowSets.arm64),
26
+ profile('riscv', 'riscv', ['risc-v'], RegisterSets.riscv, FlowSets.riscv),
27
+ profile('llvm-ir', 'ir', ['ll', 'llvm'], ['ssa', 'ptr'], FlowSets.generic),
28
+ profile('ebpf', 'vm', ['bpf'], ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'fp', 'pc'], FlowSets.generic),
29
+ profile('asm-6502', 'retro', ['6502'], RegisterSets.m6502, FlowSets.retro),
30
+ profile('asm-65816', 'retro', ['65816'], RegisterSets.m65816, FlowSets.retro),
31
+ profile('snes-asm', 'retro', ['snes', 'sfc'], RegisterSets.m65816, FlowSets.retro, { hardware: 'snes' }),
32
+ profile('z80', 'retro', [], RegisterSets.z80, FlowSets.retro),
33
+ profile('sm83', 'retro', ['gbz80', 'gameboy'], RegisterSets.z80, FlowSets.retro),
34
+ profile('m68k', 'retro', ['68k'], RegisterSets.m68k, FlowSets.retro)
35
+ ]);
36
+
37
+ export function normalizeAssemblyDialect(input = 'assembly') {
38
+ const wanted = String(input || 'assembly').toLowerCase();
39
+ for (const dialect of AssemblyDialectProfiles) {
40
+ if (dialect.id === wanted || dialect.aliases.includes(wanted)) return dialect.id;
41
+ }
42
+ return 'assembly';
43
+ }
44
+
45
+ export function findAssemblyDialect(input) {
46
+ const id = normalizeAssemblyDialect(input);
47
+ return AssemblyDialectProfiles.find((item) => item.id === id) ?? AssemblyDialectProfiles[0];
48
+ }
49
+
50
+ function profile(id, family, aliases, registers, flow, extra = {}) {
51
+ return Object.freeze({ id, family, aliases, registers, flow, ...extra });
52
+ }
@@ -0,0 +1,140 @@
1
+ export interface SourceSpan {
2
+ readonly startOffset?: number;
3
+ readonly endOffset?: number;
4
+ readonly startLine?: number;
5
+ readonly startColumn?: number;
6
+ readonly endLine?: number;
7
+ readonly endColumn?: number;
8
+ }
9
+
10
+ export interface AssemblyProofGap {
11
+ readonly code: string;
12
+ readonly status: 'not-claimed';
13
+ readonly summary: string;
14
+ readonly failClosed: true;
15
+ readonly semanticEquivalenceClaim: false;
16
+ readonly binaryEquivalenceClaim: false;
17
+ readonly timingEquivalenceClaim: false;
18
+ readonly sourceSpan?: SourceSpan;
19
+ }
20
+
21
+ export interface AssemblyDialectProfile {
22
+ readonly id: string;
23
+ readonly family: string;
24
+ readonly aliases: readonly string[];
25
+ readonly registers: readonly string[];
26
+ readonly hardware?: string;
27
+ readonly flow: {
28
+ readonly branch: readonly string[];
29
+ readonly call: readonly string[];
30
+ readonly ret: readonly string[];
31
+ };
32
+ }
33
+
34
+ export interface AssemblyLabelRecord {
35
+ readonly kind: 'label';
36
+ readonly name: string;
37
+ readonly line: number;
38
+ readonly sourceSpan?: SourceSpan;
39
+ }
40
+
41
+ export interface AssemblyDirectiveRecord {
42
+ readonly kind: 'directive';
43
+ readonly name: string;
44
+ readonly args: readonly string[];
45
+ readonly line: number;
46
+ readonly sourceSpan?: SourceSpan;
47
+ readonly proofGaps: readonly AssemblyProofGap[];
48
+ }
49
+
50
+ export interface AssemblyInstructionRecord {
51
+ readonly kind: 'instruction';
52
+ readonly mnemonic: string;
53
+ readonly operands: readonly string[];
54
+ readonly line: number;
55
+ readonly sourceSpan?: SourceSpan;
56
+ readonly registersRead?: readonly string[];
57
+ readonly registersWritten?: readonly string[];
58
+ readonly flagsAffected?: readonly string[];
59
+ readonly memoryReferences?: readonly string[];
60
+ readonly controlFlow?: 'branch' | 'call' | 'return';
61
+ readonly branchTarget?: string;
62
+ readonly externalTarget?: boolean;
63
+ readonly proofGaps?: readonly AssemblyProofGap[];
64
+ }
65
+
66
+ export interface AssemblyCommentRecord {
67
+ readonly kind: 'comment';
68
+ readonly line: number;
69
+ readonly text: string;
70
+ readonly sourceSpan?: SourceSpan;
71
+ }
72
+
73
+ export type AssemblyRecord = AssemblyLabelRecord | AssemblyDirectiveRecord | AssemblyInstructionRecord;
74
+
75
+ export interface AssemblyScanSummary {
76
+ readonly labels: number;
77
+ readonly directives: number;
78
+ readonly instructions: number;
79
+ readonly comments: number;
80
+ readonly branches: number;
81
+ readonly calls: number;
82
+ readonly returns: number;
83
+ readonly memoryReferences: number;
84
+ readonly proofGaps: number;
85
+ }
86
+
87
+ export interface AssemblySemanticScan {
88
+ readonly kind: 'frontier.lang.assemblySemanticScan';
89
+ readonly version: 1;
90
+ readonly sourcePath?: string;
91
+ readonly sourceHash: string;
92
+ readonly scanHash: string;
93
+ readonly dialect: AssemblyDialectProfile;
94
+ readonly records: readonly AssemblyRecord[];
95
+ readonly labels: readonly AssemblyLabelRecord[];
96
+ readonly directives: readonly AssemblyDirectiveRecord[];
97
+ readonly instructions: readonly AssemblyInstructionRecord[];
98
+ readonly comments: readonly AssemblyCommentRecord[];
99
+ readonly proofGaps: readonly AssemblyProofGap[];
100
+ readonly parser: { readonly status: 'ok' | 'needs-review'; readonly errors: readonly string[] };
101
+ readonly summary: AssemblyScanSummary;
102
+ }
103
+
104
+ export interface AssemblySemanticMergeEvidence {
105
+ readonly kind: 'frontier.lang.assemblySemanticMergeEvidence';
106
+ readonly version: 1;
107
+ readonly status: 'ready' | 'needs-review';
108
+ readonly sourcePath?: string;
109
+ readonly dialect: AssemblyDialectProfile;
110
+ readonly sourceHash: string;
111
+ readonly scanHash: string;
112
+ readonly records: readonly AssemblyRecord[];
113
+ readonly proofGaps: readonly AssemblyProofGap[];
114
+ readonly summary: AssemblyScanSummary;
115
+ readonly autoMergeClaim: false;
116
+ readonly semanticEquivalenceClaim: false;
117
+ readonly binaryEquivalenceClaim: false;
118
+ readonly timingEquivalenceClaim: false;
119
+ }
120
+
121
+ export interface AssemblySymbolQueryResult {
122
+ readonly labels: readonly AssemblyLabelRecord[];
123
+ readonly branchTargets: readonly {
124
+ readonly mnemonic: string;
125
+ readonly branchTarget?: string;
126
+ readonly sourceSpan?: SourceSpan;
127
+ }[];
128
+ readonly externalTargets: readonly {
129
+ readonly mnemonic: string;
130
+ readonly externalTarget?: boolean;
131
+ readonly sourceSpan?: SourceSpan;
132
+ }[];
133
+ }
134
+
135
+ export const AssemblyDialectProfiles: readonly AssemblyDialectProfile[];
136
+ export function normalizeAssemblyDialect(input?: string): string;
137
+ export function scanAssemblySource(sourceText: string, options?: { readonly sourcePath?: string; readonly sourceHash?: string; readonly dialect?: string }): AssemblySemanticScan;
138
+ export function createAssemblySemanticMergeEvidence(sourceText: string, options?: { readonly sourcePath?: string; readonly sourceHash?: string; readonly dialect?: string }): AssemblySemanticMergeEvidence;
139
+ export function summarizeAssemblyScan(scan: Pick<AssemblySemanticScan, 'labels' | 'directives' | 'instructions' | 'comments' | 'proofGaps'>): AssemblyScanSummary;
140
+ export function queryAssemblySymbols(scan: Pick<AssemblySemanticScan, 'labels' | 'instructions'>): AssemblySymbolQueryResult;
package/dist/index.js ADDED
@@ -0,0 +1,289 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { findAssemblyDialect } from './dialects.js';
3
+ export { AssemblyDialectProfiles, normalizeAssemblyDialect } from './dialects.js';
4
+
5
+ export function scanAssemblySource(sourceText, options = {}) {
6
+ const dialect = findAssemblyDialect(options.dialect);
7
+ const sourceHash = options.sourceHash ?? hashSemanticValue({ kind: 'frontier.lang.assembly.source.v1', sourceText });
8
+ const lineStarts = computeLineStarts(sourceText);
9
+ const records = [];
10
+ const labels = [];
11
+ const directives = [];
12
+ const instructions = [];
13
+ const comments = [];
14
+ const proofGaps = [];
15
+ const lines = sourceText.split(/\n/);
16
+ let offset = 0;
17
+
18
+ for (let index = 0; index < lines.length; index += 1) {
19
+ const rawLine = lines[index];
20
+ const lineNumber = index + 1;
21
+ const split = splitComment(rawLine);
22
+ if (split.comment) comments.push({ kind: 'comment', line: lineNumber, text: split.comment.text, sourceSpan: sourceSpan(offset + split.comment.start, offset + rawLine.length, lineStarts) });
23
+ const code = split.code.trim();
24
+ const codeStart = rawLine.indexOf(split.code.trimStart());
25
+ if (code) parseCodeLine({ code, codeStart: Math.max(codeStart, 0), offset, lineStarts, lineNumber, dialect, records, labels, directives, instructions, proofGaps });
26
+ offset += rawLine.length + 1;
27
+ }
28
+
29
+ const scan = scanEnvelope({ sourcePath: options.sourcePath, sourceHash, dialect, records, labels, directives, instructions, comments, proofGaps });
30
+ return scan;
31
+ }
32
+
33
+ export function createAssemblySemanticMergeEvidence(sourceText, options = {}) {
34
+ const scan = scanAssemblySource(sourceText, options);
35
+ return {
36
+ kind: 'frontier.lang.assemblySemanticMergeEvidence',
37
+ version: 1,
38
+ status: scan.proofGaps.length ? 'needs-review' : 'ready',
39
+ sourcePath: options.sourcePath,
40
+ dialect: scan.dialect,
41
+ sourceHash: scan.sourceHash,
42
+ scanHash: scan.scanHash,
43
+ records: scan.records,
44
+ proofGaps: scan.proofGaps,
45
+ summary: scan.summary,
46
+ autoMergeClaim: false,
47
+ semanticEquivalenceClaim: false,
48
+ binaryEquivalenceClaim: false,
49
+ timingEquivalenceClaim: false
50
+ };
51
+ }
52
+
53
+ export function summarizeAssemblyScan(scan) {
54
+ return {
55
+ labels: scan.labels?.length ?? 0,
56
+ directives: scan.directives?.length ?? 0,
57
+ instructions: scan.instructions?.length ?? 0,
58
+ comments: scan.comments?.length ?? 0,
59
+ branches: (scan.instructions ?? []).filter((item) => item.controlFlow === 'branch').length,
60
+ calls: (scan.instructions ?? []).filter((item) => item.controlFlow === 'call').length,
61
+ returns: (scan.instructions ?? []).filter((item) => item.controlFlow === 'return').length,
62
+ memoryReferences: (scan.instructions ?? []).reduce((sum, item) => sum + (item.memoryReferences?.length ?? 0), 0),
63
+ proofGaps: scan.proofGaps?.length ?? 0
64
+ };
65
+ }
66
+
67
+ export function queryAssemblySymbols(scan) {
68
+ return {
69
+ labels: scan.labels ?? [],
70
+ branchTargets: (scan.instructions ?? []).filter((item) => item.branchTarget).map((item) => ({ mnemonic: item.mnemonic, branchTarget: item.branchTarget, sourceSpan: item.sourceSpan })),
71
+ externalTargets: (scan.instructions ?? []).filter((item) => item.externalTarget).map((item) => ({ mnemonic: item.mnemonic, externalTarget: item.externalTarget, sourceSpan: item.sourceSpan }))
72
+ };
73
+ }
74
+
75
+ function parseCodeLine(ctx) {
76
+ let rest = ctx.code;
77
+ const labelMatch = /^([.$@A-Za-z_][\w.$@]*):/.exec(rest);
78
+ if (labelMatch) {
79
+ const span = sourceSpan(ctx.offset + ctx.codeStart, ctx.offset + ctx.codeStart + labelMatch[0].length, ctx.lineStarts);
80
+ const label = { kind: 'label', name: labelMatch[1], line: ctx.lineNumber, sourceSpan: span };
81
+ ctx.labels.push(label);
82
+ ctx.records.push(label);
83
+ rest = rest.slice(labelMatch[0].length).trim();
84
+ ctx.codeStart += ctx.code.indexOf(rest);
85
+ if (!rest) return;
86
+ }
87
+ if (/^[.%!][\w.$@]+/.test(rest)) return parseDirective(ctx, rest);
88
+ parseInstruction(ctx, rest);
89
+ }
90
+
91
+ function parseDirective(ctx, text) {
92
+ const [nameToken, ...parts] = text.split(/\s+/);
93
+ const name = nameToken.toLowerCase();
94
+ const args = splitOperands(parts.join(' '));
95
+ const record = {
96
+ kind: 'directive',
97
+ name,
98
+ args,
99
+ line: ctx.lineNumber,
100
+ sourceSpan: spanForText(ctx, text),
101
+ proofGaps: directiveGaps(name, ctx.dialect, spanForText(ctx, text))
102
+ };
103
+ ctx.directives.push(record);
104
+ ctx.records.push(record);
105
+ ctx.proofGaps.push(...record.proofGaps);
106
+ }
107
+
108
+ function parseInstruction(ctx, text) {
109
+ const match = /^([A-Za-z_.][\w.$@]*)(?:\s+(.*))?$/.exec(text);
110
+ if (!match) {
111
+ ctx.proofGaps.push(proofGap('assembly-parser-recovery', `Unable to classify assembly line ${ctx.lineNumber}.`, spanForText(ctx, text)));
112
+ return;
113
+ }
114
+ const mnemonic = match[1].toLowerCase();
115
+ const operands = splitOperands(match[2] ?? '');
116
+ const effects = instructionEffects(mnemonic, operands, ctx.dialect);
117
+ const gaps = instructionGaps(mnemonic, operands, ctx.dialect, spanForText(ctx, text));
118
+ const record = compactRecord({
119
+ kind: 'instruction',
120
+ mnemonic,
121
+ operands,
122
+ line: ctx.lineNumber,
123
+ sourceSpan: spanForText(ctx, text),
124
+ registersRead: effects.registersRead,
125
+ registersWritten: effects.registersWritten,
126
+ flagsAffected: effects.flagsAffected,
127
+ memoryReferences: effects.memoryReferences,
128
+ controlFlow: effects.controlFlow,
129
+ branchTarget: effects.branchTarget,
130
+ externalTarget: effects.externalTarget,
131
+ proofGaps: gaps.length ? gaps : undefined
132
+ });
133
+ ctx.instructions.push(record);
134
+ ctx.records.push(record);
135
+ ctx.proofGaps.push(...gaps);
136
+ }
137
+
138
+ function instructionEffects(mnemonic, operands, dialect) {
139
+ const registerHits = unique(operands.flatMap((operand) => registersIn(operand, dialect)));
140
+ const firstOperandRegisters = registersIn(operands[0] ?? '', dialect);
141
+ const writes = firstOperandRegisters.length && writesFirstOperand(mnemonic, dialect) ? firstOperandRegisters : implicitWrites(mnemonic, dialect);
142
+ const reads = unique(registerHits.filter((name) => !writes.includes(name)));
143
+ const flow = controlFlow(mnemonic, operands, dialect);
144
+ return {
145
+ registersRead: reads,
146
+ registersWritten: unique(writes),
147
+ flagsAffected: flagsAffected(mnemonic, dialect),
148
+ memoryReferences: operands.filter((operand) => isMemoryReference(operand, dialect)),
149
+ ...flow
150
+ };
151
+ }
152
+
153
+ function instructionGaps(mnemonic, operands, dialect, span) {
154
+ const gaps = [];
155
+ if (dialect.family === 'retro') gaps.push(proofGap('assembly-cycle-timing-boundary', `${dialect.id} timing requires assembler and emulator trace evidence.`, span));
156
+ if ((dialect.id === 'asm-65816' || dialect.id === 'snes-asm') && ['rep', 'sep', 'xce'].includes(mnemonic)) gaps.push(proofGap('assembly-processor-mode-boundary', '65816 width and emulation mode changes require mode-state evidence.', span));
157
+ if ((dialect.id === 'asm-65816' || dialect.id === 'snes-asm') && /(^|\W)(jsl|rtl|phb|plb|pea|per)(\W|$)/i.test([mnemonic, ...operands].join(' '))) gaps.push(proofGap('assembly-banked-memory-boundary', '65816 banked memory behavior requires bank-state evidence.', span));
158
+ if (dialect.hardware === 'snes' && operands.some((operand) => /\$21[0-9a-f]{2}|\$42[0-9a-f]{2}/i.test(operand))) gaps.push(proofGap('assembly-snes-io-boundary', 'SNES PPU/APU/CPU I/O registers require hardware trace evidence.', span));
159
+ return gaps;
160
+ }
161
+
162
+ function directiveGaps(name, dialect, span) {
163
+ const gaps = [];
164
+ if (/%?macro|\.macro|\.endmacro|\.rept|\.irp/.test(name)) gaps.push(proofGap('assembly-macro-expansion-boundary', 'Macro expansion requires assembler-specific evidence.', span));
165
+ if (/\.include|\.incbin|include|incbin/.test(name)) gaps.push(proofGap('assembly-include-boundary', 'Included assembly or binary content must be hashed and bound as evidence.', span));
166
+ if (/\.if|\.ifdef|\.ifndef|\.else|\.endif|%if|%ifdef|%endif/.test(name)) gaps.push(proofGap('assembly-conditional-assembly-boundary', 'Conditional assembly requires configured symbol evidence.', span));
167
+ if (dialect.family === 'retro' && /\.org|\.bank|\.segment|\.section/.test(name)) gaps.push(proofGap('assembly-memory-map-boundary', 'Retro memory placement requires linker/map evidence.', span));
168
+ return gaps;
169
+ }
170
+
171
+ function controlFlow(mnemonic, operands, dialect) {
172
+ const flow = dialect.flow ?? FlowSets.generic;
173
+ if (flow.ret.includes(mnemonic)) return { controlFlow: 'return' };
174
+ if (flow.call.includes(mnemonic)) return { controlFlow: 'call', branchTarget: cleanTarget(operands.at(-1)), externalTarget: isExternalTarget(operands.at(-1)) };
175
+ if (flow.branch.includes(mnemonic)) return { controlFlow: 'branch', branchTarget: cleanTarget(operands.at(-1)), externalTarget: isExternalTarget(operands.at(-1)) };
176
+ return {};
177
+ }
178
+
179
+ function writesFirstOperand(mnemonic, dialect) {
180
+ if (dialect.family === 'arm' || dialect.family === 'riscv') return !['cmp', 'tst', 'b', 'bl', 'ret'].includes(mnemonic);
181
+ if (dialect.family === 'retro') return ['sta', 'stx', 'sty', 'stz', 'lda', 'ldx', 'ldy', 'adc', 'sbc', 'and', 'ora', 'eor', 'inc', 'dec', 'asl', 'lsr', 'rol', 'ror'].includes(mnemonic);
182
+ return !['cmp', 'test', 'jmp', 'call', 'ret', 'push'].includes(mnemonic);
183
+ }
184
+
185
+ function implicitWrites(mnemonic, dialect) {
186
+ if (dialect.family === 'retro' && ['lda', 'adc', 'sbc', 'and', 'ora', 'eor', 'asl', 'lsr', 'rol', 'ror'].includes(mnemonic)) return ['a'];
187
+ if (dialect.family === 'retro' && mnemonic === 'ldx') return ['x'];
188
+ if (dialect.family === 'retro' && mnemonic === 'ldy') return ['y'];
189
+ return [];
190
+ }
191
+
192
+ function flagsAffected(mnemonic, dialect) {
193
+ const flaggy = ['add', 'adc', 'sub', 'sbc', 'cmp', 'cpx', 'cpy', 'test', 'and', 'ora', 'eor', 'asl', 'lsr', 'rol', 'ror', 'inc', 'dec', 'rep', 'sep'];
194
+ if (!flaggy.some((name) => mnemonic.startsWith(name))) return [];
195
+ if (dialect.family === 'retro') return ['n', 'v', 'z', 'c'];
196
+ if (dialect.family === 'x86') return ['zf', 'cf', 'sf', 'of'];
197
+ if (dialect.family === 'arm') return ['n', 'z', 'c', 'v'];
198
+ return ['condition'];
199
+ }
200
+
201
+ function registersIn(operand, dialect) {
202
+ const lower = String(operand).toLowerCase();
203
+ return dialect.registers.filter((reg) => new RegExp(`(^|[^\\w])${escapeRegExp(reg)}([^\\w]|$)`).test(lower));
204
+ }
205
+
206
+ function isMemoryReference(operand, dialect) {
207
+ const text = String(operand).trim();
208
+ if (/\[[^\]]+\]|\([^)]+\)/.test(text)) return true;
209
+ if (dialect.family === 'retro' && /\$[0-9a-f]{2,6}|<[^>]+>|>[^>]+/i.test(text)) return true;
210
+ return false;
211
+ }
212
+
213
+ function splitComment(line) {
214
+ const slash = line.indexOf('//');
215
+ const semi = line.indexOf(';');
216
+ const hash = /^\s*#/.test(line) ? line.search(/#/) : -1;
217
+ const starts = [slash, semi, hash].filter((index) => index >= 0);
218
+ if (!starts.length) return { code: line };
219
+ const start = Math.min(...starts);
220
+ return { code: line.slice(0, start), comment: { start, text: line.slice(start) } };
221
+ }
222
+
223
+ function splitOperands(text) {
224
+ if (!text.trim()) return [];
225
+ return text.split(',').map((item) => item.trim()).filter(Boolean);
226
+ }
227
+
228
+ function scanEnvelope(scan) {
229
+ const summary = summarizeAssemblyScan(scan);
230
+ return {
231
+ kind: 'frontier.lang.assemblySemanticScan',
232
+ version: 1,
233
+ parser: { status: scan.proofGaps.length ? 'needs-review' : 'ok', errors: [] },
234
+ scanHash: hashSemanticValue({ kind: 'frontier.lang.assembly.scan.v1', dialect: scan.dialect.id, records: scan.records.map(hashableRecord), proofGaps: scan.proofGaps.map((gap) => gap.code) }),
235
+ summary,
236
+ ...scan
237
+ };
238
+ }
239
+
240
+ function proofGap(code, summary, span) {
241
+ return compactRecord({ code, status: 'not-claimed', summary, failClosed: true, semanticEquivalenceClaim: false, binaryEquivalenceClaim: false, timingEquivalenceClaim: false, sourceSpan: span });
242
+ }
243
+
244
+ function sourceSpan(start, end, lineStarts) {
245
+ const from = positionAt(start, lineStarts);
246
+ const to = positionAt(end, lineStarts);
247
+ return { startOffset: start, endOffset: end, startLine: from.line, startColumn: from.column, endLine: to.line, endColumn: to.column };
248
+ }
249
+
250
+ function positionAt(offset, lineStarts) {
251
+ let line = 0;
252
+ while (line + 1 < lineStarts.length && lineStarts[line + 1] <= offset) line += 1;
253
+ return { line: line + 1, column: offset - lineStarts[line] + 1 };
254
+ }
255
+
256
+ function computeLineStarts(text) {
257
+ const starts = [0];
258
+ for (let index = 0; index < text.length; index += 1) if (text[index] === '\n') starts.push(index + 1);
259
+ return starts;
260
+ }
261
+
262
+ function spanForText(ctx, text) {
263
+ return sourceSpan(ctx.offset + ctx.codeStart, ctx.offset + ctx.codeStart + text.length, ctx.lineStarts);
264
+ }
265
+
266
+ function hashableRecord(record) {
267
+ return compactRecord({ kind: record.kind, name: record.name, mnemonic: record.mnemonic, operands: record.operands, line: record.line, proofGaps: record.proofGaps?.map((gap) => gap.code) });
268
+ }
269
+
270
+ function cleanTarget(value) {
271
+ return String(value ?? '').replace(/^[#<$>]+|[,)\]]+$/g, '') || undefined;
272
+ }
273
+
274
+ function isExternalTarget(value) {
275
+ const target = cleanTarget(value);
276
+ return Boolean(target && !/^[.$@A-Za-z_][\w.$@]*$/.test(target));
277
+ }
278
+
279
+ function unique(items) {
280
+ return [...new Set(items.filter(Boolean))];
281
+ }
282
+
283
+ function compactRecord(record) {
284
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length)));
285
+ }
286
+
287
+ function escapeRegExp(value) {
288
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
289
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@shapeshift-labs/frontier-lang-assembly",
3
+ "version": "0.1.0",
4
+ "description": "Runtime-neutral assembly and low-level semantic merge evidence for Frontier Lang.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE",
19
+ "bench"
20
+ ],
21
+ "scripts": {
22
+ "build": "node scripts/build.mjs",
23
+ "test": "npm run build && node test/smoke.mjs",
24
+ "typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p test/tsconfig.json",
25
+ "fuzz": "npm run build && node fuzz/smoke.mjs",
26
+ "bench": "npm run build && node bench/smoke.mjs",
27
+ "prepare": "npm run build",
28
+ "prepack": "npm run lint && npm test && npm run typecheck",
29
+ "pack:dry": "npm pack --dry-run",
30
+ "lint": "node scripts/lint.mjs"
31
+ },
32
+ "keywords": [
33
+ "frontier",
34
+ "semantic-merge",
35
+ "assembly",
36
+ "snes",
37
+ "low-level"
38
+ ],
39
+ "author": "ShapeShift Labs",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+ssh://git@github.com/siliconjungle/-shapeshift-labs-frontier-lang-assembly.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/siliconjungle/-shapeshift-labs-frontier-lang-assembly/issues"
47
+ },
48
+ "homepage": "https://github.com/siliconjungle/-shapeshift-labs-frontier-lang-assembly#readme",
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "dependencies": {
53
+ "@shapeshift-labs/frontier-lang-kernel": "0.3.12"
54
+ },
55
+ "devDependencies": {
56
+ "typescript": "^5.9.3"
57
+ }
58
+ }