@jhlagado/azm 0.2.8 → 0.2.9
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 +68 -6
- 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/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
|
@@ -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.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: 'Chapter 2 - Source Loading and Parsing'
|
|
4
|
+
parent: 'AZM Engineering Manual'
|
|
5
|
+
nav_order: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
[<- Orientation and Repository Layout](01-orientation-and-repository-layout.md) | [Assembly and Z80 Emission ->](03-assembly-and-z80-emission.md)
|
|
9
|
+
|
|
10
|
+
# Chapter 2 - Source Loading and Parsing
|
|
11
|
+
|
|
12
|
+
Source loading and parsing turn entry files into typed source items. This
|
|
13
|
+
chapter follows the path from a filename to the structured data that assembly,
|
|
14
|
+
tooling and register contracts consume.
|
|
15
|
+
|
|
16
|
+
The loading boundary lives in `src/node/source-host.ts`. The parser is
|
|
17
|
+
orchestrated by `parseNextSourceItems()` in `src/core/compile.ts`, with
|
|
18
|
+
single-line parsing in `src/syntax/parse-line.ts`. Expression and declaration
|
|
19
|
+
parsing is split across tokenizer, token-expression, directive and layout
|
|
20
|
+
modules in `src/syntax/`.
|
|
21
|
+
|
|
22
|
+
## Entry Files and Source Text
|
|
23
|
+
|
|
24
|
+
The public tooling and compile APIs enter loading through `loadProgramNext()` in
|
|
25
|
+
`src/tooling/api.ts`. That function calls `expandSourceForTooling()` and then
|
|
26
|
+
passes the expanded logical lines to `parseNextSourceItems()`.
|
|
27
|
+
|
|
28
|
+
`expandSourceForTooling()` accepts:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
export interface LoadProgramNextOptions {
|
|
32
|
+
readonly entryFile: string;
|
|
33
|
+
readonly includeDirs?: readonly string[];
|
|
34
|
+
readonly directiveAliasFiles?: readonly string[];
|
|
35
|
+
readonly preloadedText?: string;
|
|
36
|
+
readonly signal?: AbortSignal;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The entry file is normalised and checked for a source extension. AZM source
|
|
41
|
+
entries use `.asm` or `.z80`. `preloadedText` lets editor integrations parse an
|
|
42
|
+
unsaved buffer for the entry file while included files still come from disk.
|
|
43
|
+
`signal` lets an editor cancel stale work when a newer buffer arrives.
|
|
44
|
+
|
|
45
|
+
The loader keeps the full text of every loaded source file in `sourceTexts`.
|
|
46
|
+
Later stages use parsed source items for compiler logic, but several features
|
|
47
|
+
need original text:
|
|
48
|
+
|
|
49
|
+
- register contract annotation rewrites exact source lines
|
|
50
|
+
- tooling reads source text for diagnostics and code actions
|
|
51
|
+
- D8 map generation needs file names and line provenance
|
|
52
|
+
- case-style linting inspects original token case
|
|
53
|
+
|
|
54
|
+
Logical lines drive parsing. Source texts support tools that need to point back
|
|
55
|
+
into the user's files.
|
|
56
|
+
|
|
57
|
+
## Source Loading Directives
|
|
58
|
+
|
|
59
|
+
The tooling loader recognises two source-loading directives before parsing:
|
|
60
|
+
`.include` and `.import`.
|
|
61
|
+
|
|
62
|
+
`.include` is textual inclusion. The loader reads the entry file, scans it into
|
|
63
|
+
logical lines and recursively expands include directives. Include paths resolve
|
|
64
|
+
relative to the including source file first, then through configured include
|
|
65
|
+
directories.
|
|
66
|
+
|
|
67
|
+
`.import` uses the same path resolution rule, but it starts a new source
|
|
68
|
+
ownership unit for tooling. Parsed items from the imported file still join the
|
|
69
|
+
same flattened logical line stream, though their spans record the imported file
|
|
70
|
+
as the owning unit. This lets tools distinguish entry-owned source, text pulled
|
|
71
|
+
in by `.include` and routines introduced by imported modules.
|
|
72
|
+
|
|
73
|
+
Repeated imports of the same resolved file are idempotent. The first import
|
|
74
|
+
loads and emits the module at the import point; later imports of that same
|
|
75
|
+
resolved file are skipped. Repeated includes remain textual and repeatable.
|
|
76
|
+
Recursive include or import stacks are diagnosed before parsing, with the
|
|
77
|
+
diagnostic naming the recursive source relation.
|
|
78
|
+
|
|
79
|
+
Labels in imported source keep their physical source locations. Imported
|
|
80
|
+
`@Name:` labels are public exports visible to outside source as `Name`. Plain
|
|
81
|
+
labels in an imported file are private to that import unit. Text included from
|
|
82
|
+
inside an imported file remains part of that imported unit, so its plain labels
|
|
83
|
+
are private unless they are also public `@` labels.
|
|
84
|
+
|
|
85
|
+
That rule keeps library files portable. A library can include a sibling file and
|
|
86
|
+
still assemble when the entry file is run from another directory. Include
|
|
87
|
+
directories then act as project-level search paths for shared headers, vendor
|
|
88
|
+
source and imported modules.
|
|
89
|
+
|
|
90
|
+
The loader returns:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
export interface ExpandedNextSource {
|
|
94
|
+
readonly entryFile: string;
|
|
95
|
+
readonly lines: readonly LogicalLine[];
|
|
96
|
+
readonly sourceTexts: ReadonlyMap<string, string>;
|
|
97
|
+
readonly sourceLineComments: ReadonlyMap<string, ReadonlyMap<number, string>>;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`lines` is the flattened source stream for parsing. `sourceTexts` keeps the
|
|
102
|
+
original file text. `sourceLineComments` keeps comments indexed by file and line
|
|
103
|
+
so register contract analysis can reconstruct AZMDoc contract blocks after routines have
|
|
104
|
+
been identified.
|
|
105
|
+
|
|
106
|
+
## Logical Lines and Comments
|
|
107
|
+
|
|
108
|
+
`src/source/logical-lines.ts` scans a `SourceFile` into `LogicalLine` objects. A
|
|
109
|
+
logical line records the source name, line number and original text. Tooling
|
|
110
|
+
loads can also attach `sourceUnit` and `sourceRelation`:
|
|
111
|
+
|
|
112
|
+
- `sourceUnit` is the owning file for the current tooling unit
|
|
113
|
+
- `sourceRelation` is `entry`, `include` or `import`
|
|
114
|
+
|
|
115
|
+
This thin structure gives every later diagnostic a stable location and enough
|
|
116
|
+
provenance for tooling features that need to reason about module ownership.
|
|
117
|
+
|
|
118
|
+
The source helpers are small and important:
|
|
119
|
+
|
|
120
|
+
| File | Role |
|
|
121
|
+
| ------------------------- | ------------------------------------------------------ |
|
|
122
|
+
| `source-file.ts` | Wraps source text with a source name. |
|
|
123
|
+
| `logical-lines.ts` | Splits text into line records. |
|
|
124
|
+
| `source-span.ts` | Defines the common span shape. |
|
|
125
|
+
| `line-comment-scanner.ts` | Finds line comments while respecting quoted text. |
|
|
126
|
+
| `strip-line-comment.ts` | Removes semicolon comments through the shared scanner. |
|
|
127
|
+
|
|
128
|
+
`strip-line-comment.ts` is used by source-loading directive recognition, layout parsing,
|
|
129
|
+
conditional assembly and single-line parsing. Shared comment handling prevents
|
|
130
|
+
each stage from inventing a slightly different rule for semicolons inside
|
|
131
|
+
strings and character literals.
|
|
132
|
+
|
|
133
|
+
## Directive Aliases
|
|
134
|
+
|
|
135
|
+
Directive aliases are loaded during `loadProgramNext()`:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const directiveAliasProfiles = await Promise.all(
|
|
139
|
+
(options.directiveAliasFiles ?? []).map((path) => readDirectiveAliasProfile(path)),
|
|
140
|
+
);
|
|
141
|
+
const directiveAliasPolicy = buildDirectiveAliasPolicy(directiveAliasProfiles);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`src/syntax/directive-aliases.ts` owns the alias policy. Built-in aliases and
|
|
145
|
+
project alias files are normalised before line parsing. The parser then
|
|
146
|
+
receives canonical directive forms and emits canonical source items.
|
|
147
|
+
|
|
148
|
+
Aliases are a syntax boundary. They affect directive recognition before parsing.
|
|
149
|
+
The assembler-time model receives canonical source items.
|
|
150
|
+
|
|
151
|
+
## Source Items
|
|
152
|
+
|
|
153
|
+
The parser is the first place where AZM source becomes compiler data. Before
|
|
154
|
+
this point, a line is text with a file name and line number. After this point, a
|
|
155
|
+
line is a label, instruction, directive, layout declaration or comment item.
|
|
156
|
+
|
|
157
|
+
`src/model/source-item.ts` defines the parser output. The model includes:
|
|
158
|
+
|
|
159
|
+
- labels
|
|
160
|
+
- `.org`, `.equ`, `.db`, `.dw`, `.ds`, `.align`, string directives and `.end`
|
|
161
|
+
- instructions
|
|
162
|
+
- record and union layout declarations
|
|
163
|
+
- type aliases
|
|
164
|
+
- enums
|
|
165
|
+
- op-expanded items
|
|
166
|
+
- comments
|
|
167
|
+
|
|
168
|
+
Each item carries a source span where appropriate. Tooling spans now preserve
|
|
169
|
+
optional `sourceUnit` and `sourceRelation` fields when the loader attached them.
|
|
170
|
+
Assembly uses item kind to decide size and emission. Register contract analysis
|
|
171
|
+
uses instruction, label and comment items to build routines. D8 map output uses
|
|
172
|
+
spans to connect emitted bytes back to files and lines.
|
|
173
|
+
|
|
174
|
+
## Top-Level Parse Order
|
|
175
|
+
|
|
176
|
+
`parseNextSourceItems()` handles structural forms before ordinary line parsing:
|
|
177
|
+
|
|
178
|
+
1. `applyConditionalAssembly()` in `src/core/conditional-assembly.ts` filters
|
|
179
|
+
the logical line stream.
|
|
180
|
+
2. `collectOps()` records top-level `op` definitions and marks their body lines.
|
|
181
|
+
3. Name-left `.typealias` declarations are parsed.
|
|
182
|
+
4. Record and union headers collect `.field` declarations until `.endtype` or
|
|
183
|
+
`.endunion`.
|
|
184
|
+
5. Visible op invocations expand into ordinary source items.
|
|
185
|
+
6. `parseLogicalLine()` handles single-line labels, directives, data and
|
|
186
|
+
instructions.
|
|
187
|
+
|
|
188
|
+
This order matters. Ops must be collected before invocation expansion. Layout
|
|
189
|
+
declarations must collect their body lines as one source item. Ordinary
|
|
190
|
+
instruction parsing should see the lines that remain after those structural
|
|
191
|
+
forms have been handled.
|
|
192
|
+
|
|
193
|
+
## Layout and Declaration Parsing
|
|
194
|
+
|
|
195
|
+
Name-left layout syntax is parsed in `parseNextSourceItems()` because a record
|
|
196
|
+
or union body spans multiple lines:
|
|
197
|
+
|
|
198
|
+
```asm
|
|
199
|
+
Sprite .type
|
|
200
|
+
x .field byte
|
|
201
|
+
y .field byte
|
|
202
|
+
tile .field byte
|
|
203
|
+
flags .field byte
|
|
204
|
+
.endtype
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Fields are parsed as `LayoutField` values. Each field has a name and a type
|
|
208
|
+
expression. The parser checks declaration shape. `address-planning.ts` later
|
|
209
|
+
checks duplicate field names, layout size and type references.
|
|
210
|
+
|
|
211
|
+
Type aliases are parsed as named bindings:
|
|
212
|
+
|
|
213
|
+
```asm
|
|
214
|
+
SpriteArray .typealias Sprite[16]
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The parser stores the alias target as a type expression. Assembly resolves the
|
|
218
|
+
target against scalar layout names, record names, union names and other type
|
|
219
|
+
aliases.
|
|
220
|
+
|
|
221
|
+
The parser also distinguishes address labels from declarations. An address
|
|
222
|
+
label uses a colon and becomes a label item. Name-left declarations become
|
|
223
|
+
equate, enum, type, union or type-alias items.
|
|
224
|
+
|
|
225
|
+
```asm
|
|
226
|
+
Start:
|
|
227
|
+
ret
|
|
228
|
+
|
|
229
|
+
COUNT .equ 8
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
A label contributes an address based on placement. An equate contributes an
|
|
233
|
+
assembler-time value based on expression evaluation.
|
|
234
|
+
|
|
235
|
+
## Expressions and Conditionals
|
|
236
|
+
|
|
237
|
+
`src/syntax/expression-tokenizer.ts` tokenizes expression text.
|
|
238
|
+
`parse-token-expression.ts` builds expression trees from tokens.
|
|
239
|
+
`parse-expression.ts` is the public syntax wrapper used by line parsing.
|
|
240
|
+
`parse-layout-expression.ts` parses layout type expressions used by `.ds`,
|
|
241
|
+
`.field`, `.typealias`, `sizeof(...)`, `offset(...)` and layout casts.
|
|
242
|
+
`parse-directive-statement.ts` parses directive statements that need more than
|
|
243
|
+
single-token recognition.
|
|
244
|
+
|
|
245
|
+
The parser produces expression trees from `src/model/expression.ts`.
|
|
246
|
+
`src/semantics/expression-evaluation.ts` evaluates those trees when the
|
|
247
|
+
assembler-time environment is available.
|
|
248
|
+
|
|
249
|
+
Conditional assembly is handled before final line parsing. The conditional pass
|
|
250
|
+
keeps the active lines and removes inactive branches from the stream seen by
|
|
251
|
+
later stages. Ordinary parsing then receives one effective source program.
|
|
252
|
+
|
|
253
|
+
## Parse Diagnostics
|
|
254
|
+
|
|
255
|
+
`src/syntax/parse-diagnostics.ts` contains shared helpers for syntax errors.
|
|
256
|
+
Diagnostic IDs come from `src/model/diagnostic.ts`. Use those helpers when
|
|
257
|
+
adding parse failures so source positions, severity and code shape stay
|
|
258
|
+
consistent.
|
|
259
|
+
|
|
260
|
+
Parser recovery matters for editor tooling. A user may have a half-written line
|
|
261
|
+
while typing. Tooling still needs symbols, diagnostics and register contract hints
|
|
262
|
+
for surrounding source, so parse errors should usually report a diagnostic and
|
|
263
|
+
let parsing continue.
|