@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.
Files changed (29) hide show
  1. package/README.md +68 -6
  2. package/dist/src/api-compile.js +27 -0
  3. package/dist/src/assembly/assemble-program.js +5 -0
  4. package/dist/src/assembly/import-visibility.d.ts +3 -0
  5. package/dist/src/assembly/import-visibility.js +204 -0
  6. package/dist/src/node/source-host.js +40 -13
  7. package/dist/src/outputs/write-asm80.js +4 -0
  8. package/dist/src/register-contracts/programModel-routines.js +33 -17
  9. package/dist/src/source/logical-lines.d.ts +3 -0
  10. package/dist/src/source/source-span.d.ts +2 -0
  11. package/dist/src/syntax/parse-directive-statement.d.ts +1 -6
  12. package/dist/src/syntax/parse-directive-statement.js +3 -1
  13. package/dist/src/syntax/parse-layout-declarations.js +11 -2
  14. package/dist/src/syntax/parse-line.js +18 -2
  15. package/dist/src/tooling/api.js +1 -1
  16. package/docs/codebase/01-orientation-and-repository-layout.md +192 -0
  17. package/docs/codebase/02-source-loading-and-parsing.md +263 -0
  18. package/docs/codebase/03-assembly-and-z80-emission.md +251 -0
  19. package/docs/codebase/04-ops-and-register-contracts.md +237 -0
  20. package/docs/codebase/05-interfaces-and-output-artifacts.md +253 -0
  21. package/docs/codebase/06-verification-and-maintenance.md +202 -0
  22. package/docs/codebase/appendices/a-directory-file-reference.md +253 -0
  23. package/docs/codebase/appendices/b-compile-flow-reference.md +103 -0
  24. package/docs/codebase/appendices/c-public-surface-reference.md +106 -0
  25. package/docs/codebase/appendices/index.md +16 -0
  26. package/docs/codebase/index.md +46 -0
  27. package/package.json +2 -3
  28. package/docs/reference/cli.md +0 -158
  29. 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: { sourceName: line.sourceName, line: line.line, column: firstColumn(line.text) },
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: { sourceName: line.sourceName, line: line.line, column: firstColumn(line.text) },
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 = { sourceName: line.sourceName, line: line.line, column: firstColumn(line.text) };
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: [{ kind: 'label', name: labelName, ...(isEntry ? { isEntry: true } : {}), span }, ...parsedStatement.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
  }
@@ -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.