@jhlagado/azm 0.2.8 → 0.2.10
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/README.md +75 -15
- package/dist/src/api-compile.js +27 -0
- package/dist/src/assembly/assemble-program.js +5 -0
- package/dist/src/assembly/import-visibility.d.ts +3 -0
- package/dist/src/assembly/import-visibility.js +204 -0
- package/dist/src/node/source-host.js +40 -13
- package/dist/src/outputs/write-asm80.js +4 -0
- package/dist/src/register-contracts/programModel-routines.js +33 -17
- package/dist/src/register-contracts/report.js +15 -3
- package/dist/src/register-contracts/smartCommentParsing.d.ts +1 -0
- package/dist/src/register-contracts/smartCommentParsing.js +42 -7
- package/dist/src/register-contracts/smartComments.d.ts +2 -2
- package/dist/src/register-contracts/smartComments.js +3 -4
- package/dist/src/source/logical-lines.d.ts +3 -0
- package/dist/src/source/source-span.d.ts +2 -0
- package/dist/src/syntax/parse-directive-statement.d.ts +1 -6
- package/dist/src/syntax/parse-directive-statement.js +3 -1
- package/dist/src/syntax/parse-layout-declarations.js +11 -2
- package/dist/src/syntax/parse-line.js +18 -2
- package/dist/src/tooling/api.js +1 -1
- package/docs/codebase/01-orientation-and-repository-layout.md +192 -0
- package/docs/codebase/02-source-loading-and-parsing.md +263 -0
- package/docs/codebase/03-assembly-and-z80-emission.md +251 -0
- package/docs/codebase/04-ops-and-register-contracts.md +237 -0
- package/docs/codebase/05-interfaces-and-output-artifacts.md +253 -0
- package/docs/codebase/06-verification-and-maintenance.md +202 -0
- package/docs/codebase/appendices/a-directory-file-reference.md +253 -0
- package/docs/codebase/appendices/b-compile-flow-reference.md +103 -0
- package/docs/codebase/appendices/c-public-surface-reference.md +106 -0
- package/docs/codebase/appendices/index.md +16 -0
- package/docs/codebase/index.md +46 -0
- package/package.json +2 -3
- package/docs/reference/cli.md +0 -158
- package/docs/reference/tooling-api.md +0 -320
|
@@ -2,6 +2,7 @@ function list(units) {
|
|
|
2
2
|
return units.length === 0 ? '-' : units.join(',');
|
|
3
3
|
}
|
|
4
4
|
const FLAG_UNITS = new Set(['carry', 'zero', 'sign', 'parity', 'halfCarry']);
|
|
5
|
+
const FLAG_UNIT_LIST = ['carry', 'zero', 'sign', 'parity', 'halfCarry'];
|
|
5
6
|
const CONTRACT_CARRIER_PAIRS = [
|
|
6
7
|
{ label: 'BC', hi: 'B', lo: 'C' },
|
|
7
8
|
{ label: 'DE', hi: 'D', lo: 'E' },
|
|
@@ -32,6 +33,14 @@ export function contractCarrierList(units) {
|
|
|
32
33
|
}
|
|
33
34
|
return parts.length === 0 ? '-' : parts.join(',');
|
|
34
35
|
}
|
|
36
|
+
function sourceContractCarrierList(units) {
|
|
37
|
+
const unique = [...new Set(units)];
|
|
38
|
+
const hasAllFlags = FLAG_UNIT_LIST.every((unit) => unique.includes(unit));
|
|
39
|
+
const compacted = hasAllFlags
|
|
40
|
+
? unique.filter((unit) => !FLAG_UNITS.has(unit)).concat('F')
|
|
41
|
+
: unique;
|
|
42
|
+
return contractCarrierList(compacted);
|
|
43
|
+
}
|
|
35
44
|
function relationOutputUnits(relations) {
|
|
36
45
|
return relations.flatMap((rel) => rel.out);
|
|
37
46
|
}
|
|
@@ -59,9 +68,9 @@ function sourceContractEntries(summary) {
|
|
|
59
68
|
const outputUnits = relationOutputUnits(summary.valueRelations);
|
|
60
69
|
if (outputUnits.length > 0)
|
|
61
70
|
out.push({ keyword: 'out', carriers: contractCarrierList(outputUnits) });
|
|
62
|
-
const clobbers = summary.mayWrite.filter((unit) => !relationOut.has(unit)
|
|
71
|
+
const clobbers = summary.mayWrite.filter((unit) => !relationOut.has(unit));
|
|
63
72
|
if (clobbers.length > 0)
|
|
64
|
-
out.push({ keyword: 'clobbers', carriers:
|
|
73
|
+
out.push({ keyword: 'clobbers', carriers: sourceContractCarrierList(clobbers) });
|
|
65
74
|
return out;
|
|
66
75
|
}
|
|
67
76
|
function stackStatus(summary) {
|
|
@@ -152,5 +161,8 @@ export function renderRegisterContractsInterface(summaries) {
|
|
|
152
161
|
return `${lines.join('\n')}\n`;
|
|
153
162
|
}
|
|
154
163
|
export function renderRegisterContractsSourceBlock(summary) {
|
|
155
|
-
|
|
164
|
+
const entries = sourceContractEntries(summary);
|
|
165
|
+
if (entries.length === 0)
|
|
166
|
+
return [];
|
|
167
|
+
return [`;! ${entries.map((entry) => `${entry.keyword} ${entry.carriers}`).join('; ')}`];
|
|
156
168
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { SmartComment } from './types.js';
|
|
2
2
|
export declare function parseSmartCommentLine(line: string): SmartComment | undefined;
|
|
3
|
+
export declare function parseSmartCommentLines(line: string): SmartComment[];
|
|
3
4
|
export declare function isCompactSourceCommentLine(line: string): boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { expandCarrierList } from './carriers.js';
|
|
2
2
|
const COMPACT_SOURCE_TAG_RE = /^;?\s*!\s*(in|out|clobbers|preserves)(?:\s+(.+))?$/i;
|
|
3
|
+
const COMPACT_SOURCE_CLAUSE_RE = /^(in|out|clobbers|preserves)(?:\s+(.+))?$/i;
|
|
3
4
|
const COMPACT_SOURCE_LINE_RE = /^\s*;\s*!\s*(?:in|out|maybe-out|clobbers|preserves)(?:\s|$)/i;
|
|
4
5
|
const CARRIER_RE = /^\{([^}]+)\}(?:\s+(.+))?$/;
|
|
5
6
|
const CONTRACT_COMMENT_KINDS = new Set(['in', 'out', 'clobbers', 'preserves']);
|
|
@@ -35,17 +36,51 @@ function parseCarrierPayload(rest) {
|
|
|
35
36
|
return { carriers, ...(name ? { name } : {}) };
|
|
36
37
|
}
|
|
37
38
|
export function parseSmartCommentLine(line) {
|
|
39
|
+
return parseSmartCommentLines(line)[0];
|
|
40
|
+
}
|
|
41
|
+
export function parseSmartCommentLines(line) {
|
|
38
42
|
const trimmed = line.trim();
|
|
39
43
|
const expectOut = parseExpectOutComment(trimmed);
|
|
40
44
|
if (expectOut !== undefined)
|
|
41
|
-
return expectOut;
|
|
45
|
+
return [expectOut];
|
|
46
|
+
const semicolonSeparated = parseSemicolonSeparatedSourceComments(trimmed);
|
|
47
|
+
if (semicolonSeparated.length > 0)
|
|
48
|
+
return semicolonSeparated;
|
|
42
49
|
const match = COMPACT_SOURCE_TAG_RE.exec(trimmed);
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
if (match) {
|
|
51
|
+
const tag = match[1].toLowerCase();
|
|
52
|
+
if (!CONTRACT_COMMENT_KINDS.has(tag))
|
|
53
|
+
return [];
|
|
54
|
+
const comment = parseCarrierComment(tag, match[2]?.trim());
|
|
55
|
+
return comment === undefined ? [] : [comment];
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
function parseSemicolonSeparatedSourceComments(trimmed) {
|
|
60
|
+
const sourcePrefix = /^;?\s*!\s*/.exec(trimmed);
|
|
61
|
+
if (sourcePrefix === null)
|
|
62
|
+
return [];
|
|
63
|
+
const content = trimmed.slice(sourcePrefix[0].length);
|
|
64
|
+
const parts = content
|
|
65
|
+
.split(';')
|
|
66
|
+
.map((part) => part.trim())
|
|
67
|
+
.filter(Boolean);
|
|
68
|
+
if (parts.length <= 1)
|
|
69
|
+
return [];
|
|
70
|
+
const comments = [];
|
|
71
|
+
for (const part of parts) {
|
|
72
|
+
const match = COMPACT_SOURCE_CLAUSE_RE.exec(part);
|
|
73
|
+
if (!match)
|
|
74
|
+
return [];
|
|
75
|
+
const tag = match[1].toLowerCase();
|
|
76
|
+
if (!CONTRACT_COMMENT_KINDS.has(tag))
|
|
77
|
+
return [];
|
|
78
|
+
const comment = parseCarrierComment(tag, match[2]?.trim());
|
|
79
|
+
if (comment === undefined)
|
|
80
|
+
return [];
|
|
81
|
+
comments.push(comment);
|
|
82
|
+
}
|
|
83
|
+
return comments;
|
|
49
84
|
}
|
|
50
85
|
function parseExpectOutComment(trimmed) {
|
|
51
86
|
const expectOut = /^;?\s*expects\s+out\s+(.+)$/i.exec(trimmed);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LocatedSmartComment, RegisterContractsRoutine, RoutineContract } from './types.js';
|
|
2
|
-
import { parseSmartCommentLine } from './smartCommentParsing.js';
|
|
3
|
-
export { parseSmartCommentLine };
|
|
2
|
+
import { parseSmartCommentLine, parseSmartCommentLines } from './smartCommentParsing.js';
|
|
3
|
+
export { parseSmartCommentLine, parseSmartCommentLines };
|
|
4
4
|
export declare function parseSmartComments(sourceLineComments: ReadonlyMap<string, ReadonlyMap<number, string>>): LocatedSmartComment[];
|
|
5
5
|
export declare function buildRoutineContracts(comments: LocatedSmartComment[], routines?: RegisterContractsRoutine[], sourceTexts?: ReadonlyMap<string, string>): Map<string, RoutineContract>;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { collectPrecedingCommentBlock } from './smartCommentBlocks.js';
|
|
2
|
-
import { parseSmartCommentLine } from './smartCommentParsing.js';
|
|
3
|
-
export { parseSmartCommentLine };
|
|
2
|
+
import { parseSmartCommentLine, parseSmartCommentLines } from './smartCommentParsing.js';
|
|
3
|
+
export { parseSmartCommentLine, parseSmartCommentLines };
|
|
4
4
|
export function parseSmartComments(sourceLineComments) {
|
|
5
5
|
const out = [];
|
|
6
6
|
for (const [file, comments] of sourceLineComments) {
|
|
7
7
|
for (const [line, text] of comments) {
|
|
8
|
-
const parsed
|
|
9
|
-
if (parsed) {
|
|
8
|
+
for (const parsed of parseSmartCommentLines(`;${text}`)) {
|
|
10
9
|
out.push({ file, line, comment: parsed });
|
|
11
10
|
}
|
|
12
11
|
}
|
|
@@ -3,5 +3,8 @@ export interface LogicalLine {
|
|
|
3
3
|
readonly sourceName: string;
|
|
4
4
|
readonly line: number;
|
|
5
5
|
readonly text: string;
|
|
6
|
+
readonly sourceUnit?: string;
|
|
7
|
+
readonly sourceRelation?: SourceRelation;
|
|
6
8
|
}
|
|
9
|
+
export type SourceRelation = 'entry' | 'include' | 'import';
|
|
7
10
|
export declare function scanLogicalLines(source: SourceFile): LogicalLine[];
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import type { LogicalLine } from '../source/logical-lines.js';
|
|
2
|
+
import type { SourceSpan } from '../source/source-span.js';
|
|
2
3
|
import type { ParseLineResult } from './parse-line.js';
|
|
3
|
-
type SourceSpan = {
|
|
4
|
-
readonly sourceName: string;
|
|
5
|
-
readonly line: number;
|
|
6
|
-
readonly column: number;
|
|
7
|
-
};
|
|
8
4
|
export declare function parseDirectiveStatement(line: LogicalLine, text: string, span: SourceSpan): ParseLineResult | undefined;
|
|
9
5
|
export declare function parseColonDeclaration(line: LogicalLine, name: string, statementText: string, span: {
|
|
10
6
|
readonly sourceName: string;
|
|
11
7
|
readonly line: number;
|
|
12
8
|
readonly column: number;
|
|
13
9
|
}): ParseLineResult | undefined;
|
|
14
|
-
export {};
|
|
@@ -144,7 +144,9 @@ function parseDsDirective(line, valueText, span) {
|
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
function validateDsValueList(line, parts) {
|
|
147
|
-
return parts.length < 1 || parts.length > 2
|
|
147
|
+
return parts.length < 1 || parts.length > 2
|
|
148
|
+
? parseError(line, `invalid .ds value list`)
|
|
149
|
+
: undefined;
|
|
148
150
|
}
|
|
149
151
|
function parseDsSize(line, sizeText) {
|
|
150
152
|
const size = parseTypeSizeExpression(sizeText) ?? parseExpression(sizeText);
|
|
@@ -38,7 +38,7 @@ function parseTypeAlias(line, text) {
|
|
|
38
38
|
kind: 'type-alias',
|
|
39
39
|
name: nameLeftTypeAlias[1] ?? '',
|
|
40
40
|
typeExpr,
|
|
41
|
-
span:
|
|
41
|
+
span: spanForLine(line),
|
|
42
42
|
},
|
|
43
43
|
diagnostics: [],
|
|
44
44
|
};
|
|
@@ -99,10 +99,19 @@ function parseLayoutBlock(lines, index, header) {
|
|
|
99
99
|
name: header.name,
|
|
100
100
|
layoutKind,
|
|
101
101
|
fields,
|
|
102
|
-
span:
|
|
102
|
+
span: spanForLine(line),
|
|
103
103
|
},
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
+
function spanForLine(line) {
|
|
107
|
+
return {
|
|
108
|
+
sourceName: line.sourceName,
|
|
109
|
+
line: line.line,
|
|
110
|
+
column: firstColumn(line.text),
|
|
111
|
+
...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
|
|
112
|
+
...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
106
115
|
function skipToLayoutEnd(lines, index, directive) {
|
|
107
116
|
const endDirective = directive === 'union' ? '.endunion' : '.endtype';
|
|
108
117
|
for (let next = index + 1; next < lines.length; next += 1) {
|
|
@@ -7,7 +7,7 @@ export function parseLogicalLine(line, options = {}) {
|
|
|
7
7
|
if (text.length === 0) {
|
|
8
8
|
return commentOnlyLine(line);
|
|
9
9
|
}
|
|
10
|
-
const span =
|
|
10
|
+
const span = spanForLine(line);
|
|
11
11
|
const labelWithStatement = /^(@?[A-Za-z_.$?][A-Za-z0-9_.$?]*):\s*(.+)$/.exec(text);
|
|
12
12
|
if (labelWithStatement) {
|
|
13
13
|
const rawLabel = labelWithStatement[1] ?? '';
|
|
@@ -20,7 +20,10 @@ export function parseLogicalLine(line, options = {}) {
|
|
|
20
20
|
}
|
|
21
21
|
const parsedStatement = parseCanonicalStatement(line, statementText, span);
|
|
22
22
|
return withLineComment(line, {
|
|
23
|
-
items: [
|
|
23
|
+
items: [
|
|
24
|
+
{ kind: 'label', name: labelName, ...(isEntry ? { isEntry: true } : {}), span },
|
|
25
|
+
...parsedStatement.items,
|
|
26
|
+
],
|
|
24
27
|
diagnostics: parsedStatement.diagnostics,
|
|
25
28
|
});
|
|
26
29
|
}
|
|
@@ -56,6 +59,8 @@ function commentOnlyLine(line) {
|
|
|
56
59
|
sourceName: line.sourceName,
|
|
57
60
|
line: line.line,
|
|
58
61
|
column: firstColumn(line.text),
|
|
62
|
+
...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
|
|
63
|
+
...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
|
|
59
64
|
},
|
|
60
65
|
},
|
|
61
66
|
],
|
|
@@ -78,6 +83,8 @@ function withLineComment(line, result) {
|
|
|
78
83
|
sourceName: line.sourceName,
|
|
79
84
|
line: line.line,
|
|
80
85
|
column: firstColumn(line.text),
|
|
86
|
+
...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
|
|
87
|
+
...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
|
|
81
88
|
},
|
|
82
89
|
},
|
|
83
90
|
],
|
|
@@ -107,6 +114,15 @@ function parseCanonicalStatement(line, text, span) {
|
|
|
107
114
|
}
|
|
108
115
|
return { items: [], diagnostics: [parseError(line, `unsupported source line: ${text}`)] };
|
|
109
116
|
}
|
|
117
|
+
function spanForLine(line) {
|
|
118
|
+
return {
|
|
119
|
+
sourceName: line.sourceName,
|
|
120
|
+
line: line.line,
|
|
121
|
+
column: firstColumn(line.text),
|
|
122
|
+
...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
|
|
123
|
+
...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
110
126
|
function normalizeEntryLabelName(raw) {
|
|
111
127
|
return raw.startsWith('@') ? raw.slice(1) : raw;
|
|
112
128
|
}
|
package/dist/src/tooling/api.js
CHANGED
|
@@ -34,7 +34,7 @@ export function analyzeProgramNext(loadedProgram, options = {}) {
|
|
|
34
34
|
mode: options.caseStyle ?? 'off',
|
|
35
35
|
});
|
|
36
36
|
return {
|
|
37
|
-
diagnostics: caseStyleDiagnostics,
|
|
37
|
+
diagnostics: [...assembly.diagnostics, ...caseStyleDiagnostics],
|
|
38
38
|
env: { symbols: assembly.symbols },
|
|
39
39
|
};
|
|
40
40
|
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: 'Chapter 1 - Orientation and Repository Layout'
|
|
4
|
+
parent: 'AZM Engineering Manual'
|
|
5
|
+
nav_order: 1
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
[Manual](index.md) | [Source Loading and Parsing ->](02-source-loading-and-parsing.md)
|
|
9
|
+
|
|
10
|
+
# Chapter 1 - Orientation and Repository Layout
|
|
11
|
+
|
|
12
|
+
AZM is a Z80 assembler and tooling package. It turns `.asm` and `.z80` source
|
|
13
|
+
files into bytes, Intel HEX, flat binary output, Debug80 maps, lowered ASM80
|
|
14
|
+
source and register contract metadata. The same implementation serves the command
|
|
15
|
+
line, package consumers, Debug80 integration and the test suite.
|
|
16
|
+
|
|
17
|
+
The codebase follows the same path as an assembly run. A source file is loaded,
|
|
18
|
+
`.include` lines are expanded, source is split into logical lines, logical lines
|
|
19
|
+
become typed source items, visible `op` invocations expand into ordinary
|
|
20
|
+
instructions, assembler-time facts are collected, instructions and data emit
|
|
21
|
+
bytes, symbolic fixups are resolved and output writers serialize the result.
|
|
22
|
+
|
|
23
|
+
AZM's extensions are assembler-time features. Layout types, enums, type
|
|
24
|
+
aliases, AZMDoc comments and register contracts help the assembler
|
|
25
|
+
calculate addresses, check contracts and produce metadata. Runtime behaviour
|
|
26
|
+
still comes from the Z80 instructions and bytes that AZM emits.
|
|
27
|
+
|
|
28
|
+
## The Compiler Path
|
|
29
|
+
|
|
30
|
+
A small source file shows the main pipeline:
|
|
31
|
+
|
|
32
|
+
```asm
|
|
33
|
+
.org $0100
|
|
34
|
+
|
|
35
|
+
LIMIT .equ 8
|
|
36
|
+
SpriteArray .typealias Sprite[16]
|
|
37
|
+
|
|
38
|
+
Sprite .type
|
|
39
|
+
x .field byte
|
|
40
|
+
y .field byte
|
|
41
|
+
tile .field byte
|
|
42
|
+
flags .field byte
|
|
43
|
+
.endtype
|
|
44
|
+
|
|
45
|
+
@Start:
|
|
46
|
+
ld b,LIMIT
|
|
47
|
+
Loop:
|
|
48
|
+
djnz Loop
|
|
49
|
+
|
|
50
|
+
Sprites:
|
|
51
|
+
.ds SpriteArray
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The loader reads the entry file and expands includes. The logical-line scanner
|
|
55
|
+
records each line with source provenance. The parser emits source items for
|
|
56
|
+
`.org`, `.equ`, `.typealias`, the `Sprite` layout, labels, instructions and
|
|
57
|
+
`.ds`. Address planning assigns `$0100` to `@Start`, assigns the following
|
|
58
|
+
addresses to `Loop` and `Sprites`, records `LIMIT = 8` and records the size of
|
|
59
|
+
`SpriteArray`. The encoder turns `ld b,LIMIT` and `djnz Loop` into fragments.
|
|
60
|
+
Fixup emission resolves `LIMIT` and the relative branch displacement. The
|
|
61
|
+
output writers produce the selected artifacts.
|
|
62
|
+
|
|
63
|
+
The CLI and package consumers use this same path. AZM has one compiler pipeline
|
|
64
|
+
with several entry points.
|
|
65
|
+
|
|
66
|
+
## Main Layers
|
|
67
|
+
|
|
68
|
+
The implementation has six main layers:
|
|
69
|
+
|
|
70
|
+
1. **Public entry points** in `src/index.ts`, `src/api-compile.ts`,
|
|
71
|
+
`src/api-artifacts.ts`, `src/api-register-contracts.ts`, `src/api-tooling.ts`
|
|
72
|
+
and `src/cli.ts`.
|
|
73
|
+
2. **Loading and parsing** in `src/node/`, `src/source/`, `src/syntax/` and
|
|
74
|
+
`src/core/compile.ts`.
|
|
75
|
+
3. **Assembler-time analysis** in `src/assembly/` and `src/semantics/`.
|
|
76
|
+
4. **Z80 parsing and encoding** in `src/z80/`.
|
|
77
|
+
5. **Language services** in `src/expansion/`, `src/register-contracts/` and
|
|
78
|
+
`src/tooling/`.
|
|
79
|
+
6. **Artifact writers** in `src/outputs/`.
|
|
80
|
+
|
|
81
|
+
Each layer passes structured data to the next. Diagnostics are accumulated as
|
|
82
|
+
data objects and formatted at the CLI edge. Editor tooling, tests and package
|
|
83
|
+
consumers share the same diagnostic model.
|
|
84
|
+
|
|
85
|
+
## Runtime Boundary
|
|
86
|
+
|
|
87
|
+
AZM computes everything it can at assembly time. `sizeof(Sprite)`,
|
|
88
|
+
`offset(Sprite, flags)` and `<SpriteArray>Sprites[3].tile` fold to numbers while
|
|
89
|
+
the assembler runs. The generated Z80 program receives those numbers in
|
|
90
|
+
instructions and data. At runtime the CPU executes normal Z80 operations:
|
|
91
|
+
loads, stores, branches, calls, returns and port I/O.
|
|
92
|
+
|
|
93
|
+
This boundary explains where major features live. Layout code belongs to the
|
|
94
|
+
assembler because it calculates byte offsets. Register contracts belong to the
|
|
95
|
+
assembler because it analyses visible calls and register effects. Output writers
|
|
96
|
+
belong at the edge because they serialize already-assembled facts.
|
|
97
|
+
|
|
98
|
+
## Repository Shape
|
|
99
|
+
|
|
100
|
+
The AZM repository has a compact top-level structure:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
AZM/
|
|
104
|
+
src/ TypeScript implementation
|
|
105
|
+
test/ unit, integration, CLI, differential and acceptance tests
|
|
106
|
+
docs/ active contributor references, specs and design notes
|
|
107
|
+
examples/ small runnable source examples
|
|
108
|
+
scripts/ CI, guardrail and developer utility scripts
|
|
109
|
+
dist/ generated package output
|
|
110
|
+
package.json package exports, CLI bin, scripts and dependencies
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The repository is a Node package. The source is TypeScript ESM. The published
|
|
114
|
+
package exposes the CLI binary `azm` and stable imports for compile and tooling
|
|
115
|
+
consumers.
|
|
116
|
+
|
|
117
|
+
## Source Directories
|
|
118
|
+
|
|
119
|
+
`src/` is organised by compiler responsibility:
|
|
120
|
+
|
|
121
|
+
| Directory | Responsibility |
|
|
122
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
+
| `assembly/` | Address planning, placement, byte emission and fixups. |
|
|
124
|
+
| `cli/` | Argument parsing, usage text, artifact path calculation and disk artifact writing. |
|
|
125
|
+
| `core/` | In-memory compile helpers, conditional assembly and source-item parsing orchestration. |
|
|
126
|
+
| `diagnostics/` | Diagnostic text formatting. |
|
|
127
|
+
| `expansion/` | Visible `op` collection, operand modelling, overload selection and expansion. |
|
|
128
|
+
| `model/` | Shared data types used across layers. |
|
|
129
|
+
| `node/` | File-backed source loading and include expansion. |
|
|
130
|
+
| `outputs/` | BIN, HEX, D8 map, lowered ASM80 and artifact helper writers. |
|
|
131
|
+
| `register-contracts/` | Register contract routine modelling, instruction shape helpers, liveness, summaries, reports, interfaces and fixes. |
|
|
132
|
+
| `semantics/` | Expression evaluation, constant operators, byte functions and layout evaluation. |
|
|
133
|
+
| `source/` | Source files, spans, logical line scanning, comment scanning and comment stripping. |
|
|
134
|
+
| `syntax/` | Line parsing, directive parsing, expression tokenizing, token expression parsing, layout parsing and directive aliases. |
|
|
135
|
+
| `tooling/` | Editor/tooling APIs and source-style checks. |
|
|
136
|
+
| `z80/` | Z80 instruction model, operand splitting, parser families, encoder families and register effects. |
|
|
137
|
+
|
|
138
|
+
The root files expose public entry points. The subdirectories hold the compiler
|
|
139
|
+
pipeline. A change usually belongs to the directory that owns the data it
|
|
140
|
+
changes.
|
|
141
|
+
|
|
142
|
+
## Tests, Docs and Scripts
|
|
143
|
+
|
|
144
|
+
The test tree mirrors the implementation boundaries. Unit tests target narrow
|
|
145
|
+
modules. Integration tests cover cross-stage compiler behaviour. CLI tests
|
|
146
|
+
verify argument and artifact contracts. ASM80 and differential tests protect
|
|
147
|
+
compatibility and byte parity. Type tests protect the public TypeScript surface.
|
|
148
|
+
|
|
149
|
+
The repo-local docs are the active working set for implementation detail:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
docs/
|
|
153
|
+
codebase/
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`docs/codebase/` is the maintained engineering manual for the source tree,
|
|
157
|
+
compile flow, user-facing CLI, package-facing APIs, AZMDoc/register contract
|
|
158
|
+
metadata and verification lanes. Avoid reintroducing parallel reference,
|
|
159
|
+
planning or design trees unless there is a concrete active need.
|
|
160
|
+
|
|
161
|
+
`scripts/` contains verification and maintenance utilities. The package scripts
|
|
162
|
+
in `package.json` are the normal entry points. Invoke script files directly
|
|
163
|
+
while debugging the script itself.
|
|
164
|
+
|
|
165
|
+
## Package Exports
|
|
166
|
+
|
|
167
|
+
`package.json` exposes these public paths:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
@jhlagado/azm
|
|
171
|
+
@jhlagado/azm/compile
|
|
172
|
+
@jhlagado/azm/tooling
|
|
173
|
+
@jhlagado/azm/cli
|
|
174
|
+
@jhlagado/azm/package.json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Public consumers import from those paths. Internal files under `src/` and
|
|
178
|
+
compiled files under `dist/src/` are implementation details.
|
|
179
|
+
|
|
180
|
+
## Reading the Codebase
|
|
181
|
+
|
|
182
|
+
Start with the public entry point that matches your task. For a CLI bug, begin
|
|
183
|
+
in `src/cli/run.ts` and follow the option into `api-compile.ts`. For source
|
|
184
|
+
syntax, begin in `parseNextSourceItems()` and `parse-line.ts`. For an encoding
|
|
185
|
+
bug, begin in `parse-instruction.ts`, `instruction.ts` and `encode.ts`. For a
|
|
186
|
+
D8 map issue, begin in `program-emission.ts`, `outputs/types.ts` and
|
|
187
|
+
`write-d8.ts`.
|
|
188
|
+
|
|
189
|
+
The compiler is small enough that one feature can be followed from front to
|
|
190
|
+
back. `.typealias`, for example, appears in the parser, address planner,
|
|
191
|
+
expression evaluator, tests and manual examples. A feature is complete when
|
|
192
|
+
each boundary that observes it has the right structured fact.
|