@jhlagado/azm 0.2.10 → 0.2.12

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 (41) hide show
  1. package/README.md +14 -0
  2. package/dist/src/assembly/import-visibility.js +108 -33
  3. package/dist/src/core/compile.js +98 -1
  4. package/dist/src/expansion/op-expand-selected.js +8 -1
  5. package/dist/src/expansion/op-expansion.d.ts +3 -0
  6. package/dist/src/expansion/op-expansion.js +47 -9
  7. package/dist/src/node/source-host.js +5 -2
  8. package/dist/src/outputs/d8-files.js +1 -0
  9. package/dist/src/outputs/types.d.ts +1 -0
  10. package/dist/src/source/instruction-chain.d.ts +5 -0
  11. package/dist/src/source/instruction-chain.js +75 -0
  12. package/dist/src/source/logical-lines.d.ts +1 -0
  13. package/dist/src/source/source-span.d.ts +1 -0
  14. package/dist/src/syntax/names.d.ts +18 -0
  15. package/dist/src/syntax/names.js +44 -0
  16. package/dist/src/syntax/parse-data-directives.d.ts +17 -0
  17. package/dist/src/syntax/parse-data-directives.js +147 -0
  18. package/dist/src/syntax/parse-declaration-directives.d.ts +18 -0
  19. package/dist/src/syntax/parse-declaration-directives.js +90 -0
  20. package/dist/src/syntax/parse-diagnostics.d.ts +8 -0
  21. package/dist/src/syntax/parse-diagnostics.js +15 -0
  22. package/dist/src/syntax/parse-directive-statement.d.ts +1 -5
  23. package/dist/src/syntax/parse-directive-statement.js +19 -259
  24. package/dist/src/syntax/parse-instruction-chain.d.ts +22 -0
  25. package/dist/src/syntax/parse-instruction-chain.js +62 -0
  26. package/dist/src/syntax/parse-layout-declarations.js +10 -18
  27. package/dist/src/syntax/parse-layout-expression.js +4 -3
  28. package/dist/src/syntax/parse-line.js +21 -31
  29. package/dist/src/syntax/parse-location-directives.d.ts +7 -0
  30. package/dist/src/syntax/parse-location-directives.js +15 -0
  31. package/dist/src/syntax/statement-classification.d.ts +2 -0
  32. package/dist/src/syntax/statement-classification.js +24 -0
  33. package/dist/src/tooling/case-style.js +42 -26
  34. package/docs/codebase/02-source-loading-and-parsing.md +28 -8
  35. package/docs/codebase/04-ops-and-register-contracts.md +24 -3
  36. package/docs/codebase/05-interfaces-and-output-artifacts.md +10 -0
  37. package/docs/codebase/06-verification-and-maintenance.md +9 -3
  38. package/docs/codebase/appendices/a-directory-file-reference.md +17 -10
  39. package/docs/codebase/appendices/b-compile-flow-reference.md +3 -2
  40. package/docs/codebase/index.md +4 -0
  41. package/package.json +1 -1
@@ -1,7 +1,11 @@
1
- import { parseExpression, parseTypeExpr } from './parse-expression.js';
1
+ import { IDENTIFIER_PATTERN, LABEL_NAME_PATTERN } from './names.js';
2
+ import { parseEnumItem, parseEquItem } from './parse-declaration-directives.js';
3
+ import { firstNonWhitespaceColumn } from './parse-diagnostics.js';
4
+ import { parseDataDirective, parseDsDirective, parseStringDataDirective, } from './parse-data-directives.js';
5
+ import { parseExpressionDirective } from './parse-location-directives.js';
2
6
  const DIRECTIVE_PARSERS = [
3
7
  {
4
- pattern: /^([A-Za-z_.$?][A-Za-z0-9_.$?]*)\s+\.equ\s+(.+)$/,
8
+ pattern: new RegExp(`^(${LABEL_NAME_PATTERN})\\s+\\.equ\\s+(.+)$`),
5
9
  parse: (line, match, span) => parseEquItem(line, match[1] ?? '', match[2] ?? '', span),
6
10
  },
7
11
  {
@@ -9,14 +13,23 @@ const DIRECTIVE_PARSERS = [
9
13
  parse: (line, match, span) => parseExpressionDirective(line, 'org', match[1] ?? '', span),
10
14
  },
11
15
  {
12
- pattern: /^enum\s+([A-Za-z_][A-Za-z0-9_]*)\s+(.+)$/,
16
+ pattern: new RegExp(`^enum\\s+(${IDENTIFIER_PATTERN})\\s+(.+)$`),
13
17
  parse: (line, match) => ({
14
18
  items: [],
15
- diagnostics: [parseError(line, `Use "${match[1] ?? ''} .enum ..." for enums.`)],
19
+ diagnostics: [
20
+ {
21
+ severity: 'error',
22
+ code: 'AZMN_PARSE',
23
+ message: `Use "${match[1] ?? ''} .enum ..." for enums.`,
24
+ sourceName: line.sourceName,
25
+ line: line.line,
26
+ column: firstNonWhitespaceColumn(line.text),
27
+ },
28
+ ],
16
29
  }),
17
30
  },
18
31
  {
19
- pattern: /^([A-Za-z_][A-Za-z0-9_]*)\s+\.enum\s+(.+)$/,
32
+ pattern: new RegExp(`^(${IDENTIFIER_PATTERN})\\s+\\.enum\\s+(.+)$`),
20
33
  parse: (line, match, span) => parseEnumItem(line, match[1] ?? '', match[2] ?? '', span),
21
34
  },
22
35
  {
@@ -44,6 +57,7 @@ const DIRECTIVE_PARSERS = [
44
57
  parse: (line, match, span) => parseStringDataDirective(line, (match[1] ?? '').slice(1).toLowerCase(), match[2] ?? '', span),
45
58
  },
46
59
  ];
60
+ export { parseColonDeclaration } from './parse-declaration-directives.js';
47
61
  export function parseDirectiveStatement(line, text, span) {
48
62
  for (const parser of DIRECTIVE_PARSERS) {
49
63
  const match = parser.pattern.exec(text);
@@ -53,257 +67,3 @@ export function parseDirectiveStatement(line, text, span) {
53
67
  }
54
68
  return undefined;
55
69
  }
56
- export function parseColonDeclaration(line, name, statementText, span) {
57
- const equ = /^\.equ\s+(.+)$/.exec(statementText);
58
- if (equ) {
59
- return parseEquItem(line, name, equ[1] ?? '', span);
60
- }
61
- const enumDecl = /^\.enum\s+(.+)$/.exec(statementText);
62
- if (enumDecl) {
63
- return parseEnumItem(line, name, enumDecl[1] ?? '', span);
64
- }
65
- return undefined;
66
- }
67
- function parseEquItem(line, name, expressionText, span) {
68
- const stringValue = parseWholeQuotedString(expressionText.trim());
69
- const expression = stringValue !== undefined && stringValue.length > 1
70
- ? { kind: 'number', value: 0 }
71
- : parseExpression(expressionText);
72
- if (!expression) {
73
- return {
74
- items: [],
75
- diagnostics: [parseError(line, `invalid .equ expression: ${expressionText}`)],
76
- };
77
- }
78
- return {
79
- items: [
80
- {
81
- kind: 'equ',
82
- name,
83
- expression,
84
- ...(stringValue !== undefined && stringValue.length > 1 ? { stringValue } : {}),
85
- span,
86
- },
87
- ],
88
- diagnostics: [],
89
- };
90
- }
91
- function parseExpressionDirective(line, kind, expressionText, span) {
92
- const expression = parseExpression(expressionText);
93
- if (!expression) {
94
- return {
95
- items: [],
96
- diagnostics: [parseError(line, `invalid .${kind} expression: ${expressionText}`)],
97
- };
98
- }
99
- if (kind === 'align') {
100
- return { items: [{ kind, alignment: expression, span }], diagnostics: [] };
101
- }
102
- return { items: [{ kind, expression, span }], diagnostics: [] };
103
- }
104
- function parseDataDirective(line, directiveText, valueText, span) {
105
- const directive = directiveText.slice(1).toLowerCase();
106
- const parts = splitValueList(valueText);
107
- const values = directive === 'db'
108
- ? parts.map(parseDataValue).filter((value) => value !== undefined)
109
- : parts.map(parseExpression).filter((value) => value !== undefined);
110
- if (values.length !== parts.length) {
111
- return {
112
- items: [],
113
- diagnostics: [parseError(line, `invalid .${directive} value list`)],
114
- };
115
- }
116
- return {
117
- items: directive === 'db'
118
- ? [{ kind: 'db', values: values, span }]
119
- : [{ kind: 'dw', values: values, span }],
120
- diagnostics: [],
121
- };
122
- }
123
- function parseDsDirective(line, valueText, span) {
124
- const parts = splitValueList(valueText);
125
- const listDiagnostic = validateDsValueList(line, parts);
126
- if (listDiagnostic) {
127
- return { items: [], diagnostics: [listDiagnostic] };
128
- }
129
- const sizeResult = parseDsSize(line, parts[0] ?? '');
130
- if (sizeResult.diagnostic) {
131
- return { items: [], diagnostics: [sizeResult.diagnostic] };
132
- }
133
- const fillResult = parseDsFill(line, parts[1]);
134
- if (fillResult.diagnostic) {
135
- return { items: [], diagnostics: [fillResult.diagnostic] };
136
- }
137
- return {
138
- items: [
139
- fillResult.fill === undefined
140
- ? { kind: 'ds', size: sizeResult.size, span }
141
- : { kind: 'ds', size: sizeResult.size, fill: fillResult.fill, span },
142
- ],
143
- diagnostics: [],
144
- };
145
- }
146
- function validateDsValueList(line, parts) {
147
- return parts.length < 1 || parts.length > 2
148
- ? parseError(line, `invalid .ds value list`)
149
- : undefined;
150
- }
151
- function parseDsSize(line, sizeText) {
152
- const size = parseTypeSizeExpression(sizeText) ?? parseExpression(sizeText);
153
- if (!size) {
154
- return {
155
- diagnostic: parseError(line, `invalid .ds size: ${sizeText}`),
156
- };
157
- }
158
- return { size };
159
- }
160
- function parseDsFill(line, fillText) {
161
- if (fillText === undefined)
162
- return { fill: undefined };
163
- const fill = parseExpression(fillText);
164
- if (!fill) {
165
- return {
166
- diagnostic: parseError(line, `invalid .ds fill: ${fillText}`),
167
- };
168
- }
169
- return { fill };
170
- }
171
- function parseStringDataDirective(line, directive, valueText, span) {
172
- const value = parseQuotedString(valueText);
173
- if (value === undefined) {
174
- return {
175
- items: [],
176
- diagnostics: [parseError(line, `.${directive} expects one double-quoted string`)],
177
- };
178
- }
179
- return { items: [{ kind: 'string-data', directive, value, span }], diagnostics: [] };
180
- }
181
- function parseEnumItem(line, name, membersText, span) {
182
- const rawMembers = membersText.split(',').map((member) => member.trim());
183
- if (membersText.trim().length === 0 || rawMembers.some((member) => member.length === 0)) {
184
- return {
185
- items: [],
186
- diagnostics: [parseError(line, `invalid enum member list`)],
187
- };
188
- }
189
- const members = [];
190
- const diagnostics = [];
191
- for (const member of rawMembers) {
192
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(member)) {
193
- diagnostics.push(parseError(line, `Invalid enum member name "${member}": expected <identifier>.`));
194
- continue;
195
- }
196
- members.push(member);
197
- }
198
- if (diagnostics.length > 0) {
199
- return { items: [], diagnostics };
200
- }
201
- return { items: [{ kind: 'enum', name, members, span }], diagnostics: [] };
202
- }
203
- function parseTypeSizeExpression(text) {
204
- const typeExpr = parseTypeExpr(text);
205
- return typeExpr ? { kind: 'type-size', typeExpr } : undefined;
206
- }
207
- function splitValueList(text) {
208
- const values = [];
209
- let state = { quote: undefined, escaped: false, parenDepth: 0 };
210
- let start = 0;
211
- for (let index = 0; index < text.length; index += 1) {
212
- if (isValueSeparator(text[index] ?? '', state)) {
213
- values.push(text.slice(start, index));
214
- start = index + 1;
215
- continue;
216
- }
217
- state = scanValueListChar(text[index] ?? '', state);
218
- }
219
- values.push(text.slice(start));
220
- return values;
221
- }
222
- function isValueSeparator(char, state) {
223
- return char === ',' && state.quote === undefined && state.parenDepth === 0;
224
- }
225
- function scanValueListChar(char, state) {
226
- const escapedState = scanEscapedValueListChar(char, state);
227
- if (escapedState)
228
- return escapedState;
229
- const quotedState = scanQuotedValueListChar(char, state);
230
- if (quotedState)
231
- return quotedState;
232
- return scanParenthesizedValueListChar(char, state);
233
- }
234
- function scanEscapedValueListChar(char, state) {
235
- if (state.escaped)
236
- return { ...state, escaped: false };
237
- if (char === '\\' && state.quote !== undefined)
238
- return { ...state, escaped: true };
239
- return undefined;
240
- }
241
- function scanQuotedValueListChar(char, state) {
242
- if (char !== '"' && char !== "'")
243
- return undefined;
244
- return { ...state, quote: state.quote === char ? undefined : (state.quote ?? char) };
245
- }
246
- function scanParenthesizedValueListChar(char, state) {
247
- if (state.quote !== undefined)
248
- return state;
249
- if (char === '(')
250
- return { ...state, parenDepth: state.parenDepth + 1 };
251
- if (char === ')')
252
- return { ...state, parenDepth: Math.max(0, state.parenDepth - 1) };
253
- return state;
254
- }
255
- function parseQuotedString(text) {
256
- return parseQuotedStringWithQuotes(text, new Set(['"']));
257
- }
258
- function parseWholeQuotedString(text) {
259
- return parseQuotedStringWithQuotes(text, new Set(['"', "'"]));
260
- }
261
- function parseQuotedStringWithQuotes(text, allowedQuotes) {
262
- const input = text.trim();
263
- const quote = input[0];
264
- if (!quote || !allowedQuotes.has(quote) || input[input.length - 1] !== quote) {
265
- return undefined;
266
- }
267
- return parseQuotedStringContent(input, quote);
268
- }
269
- function parseQuotedStringContent(input, quote) {
270
- let value = '';
271
- for (let index = 1; index < input.length - 1; index += 1) {
272
- const char = input[index] ?? '';
273
- if (char === '\\') {
274
- if (index + 1 >= input.length - 1) {
275
- return undefined;
276
- }
277
- value += input[index + 1] ?? '';
278
- index += 1;
279
- continue;
280
- }
281
- if (char === quote) {
282
- return undefined;
283
- }
284
- value += char;
285
- }
286
- return value;
287
- }
288
- function parseDataValue(text) {
289
- const expression = parseExpression(text);
290
- if (expression) {
291
- return expression;
292
- }
293
- const value = parseWholeQuotedString(text);
294
- return value === undefined ? undefined : { kind: 'string-fragment', value };
295
- }
296
- function firstColumn(text) {
297
- const match = /\S/.exec(text);
298
- return match ? match.index + 1 : 1;
299
- }
300
- function parseError(line, message) {
301
- return {
302
- severity: 'error',
303
- code: 'AZMN_PARSE',
304
- message,
305
- sourceName: line.sourceName,
306
- line: line.line,
307
- column: firstColumn(line.text),
308
- };
309
- }
@@ -0,0 +1,22 @@
1
+ import type { Diagnostic } from '../model/diagnostic.js';
2
+ import type { ParsedLeadingLabel } from './names.js';
3
+ interface InstructionChainLine {
4
+ readonly text: string;
5
+ }
6
+ export interface ParseChainStatementResult<TItem> {
7
+ readonly items: readonly TItem[];
8
+ readonly diagnostics: readonly Diagnostic[];
9
+ }
10
+ export interface ParseInstructionChainOptions<TLine extends InstructionChainLine, TItem> {
11
+ readonly line: TLine;
12
+ readonly parseStatement: (line: TLine, statementText: string, statementColumn: number) => ParseChainStatementResult<TItem>;
13
+ readonly makeLabelItem: (label: ParsedLeadingLabel, line: TLine) => TItem;
14
+ readonly makeDiagnostic: (line: TLine, column: number, message: string) => Diagnostic;
15
+ readonly appendLineComment?: (items: TItem[], line: TLine) => void;
16
+ }
17
+ export interface ParseInstructionChainResult<TItem> {
18
+ readonly items: readonly TItem[];
19
+ readonly diagnostics: readonly Diagnostic[];
20
+ }
21
+ export declare function parseInstructionChain<TLine extends InstructionChainLine, TItem>(options: ParseInstructionChainOptions<TLine, TItem>): ParseInstructionChainResult<TItem> | undefined;
22
+ export {};
@@ -0,0 +1,62 @@
1
+ import { splitInstructionChain } from '../source/instruction-chain.js';
2
+ import { hasLeadingLabel, parseLeadingLabel } from './names.js';
3
+ import { isChainedDirectiveOrDeclaration } from './statement-classification.js';
4
+ export function parseInstructionChain(options) {
5
+ const segments = splitInstructionChain(options.line.text);
6
+ if (segments === undefined)
7
+ return undefined;
8
+ const items = [];
9
+ const diagnostics = [];
10
+ for (let index = 0; index < segments.length; index += 1) {
11
+ const segment = segments[index];
12
+ const parsed = parseInstructionChainSegment(options, segment.text, segment.column, index);
13
+ diagnostics.push(...parsed.diagnostics);
14
+ items.push(...parsed.items);
15
+ }
16
+ options.appendLineComment?.(items, options.line);
17
+ return { items, diagnostics };
18
+ }
19
+ function parseInstructionChainSegment(options, text, column, segmentIndex) {
20
+ if (text.length === 0) {
21
+ return {
22
+ items: [],
23
+ diagnostics: [
24
+ options.makeDiagnostic(options.line, column, 'empty instruction segment in chained line'),
25
+ ],
26
+ };
27
+ }
28
+ if (segmentIndex > 0 && hasLeadingLabel(text)) {
29
+ return {
30
+ items: [],
31
+ diagnostics: [
32
+ options.makeDiagnostic(options.line, column, 'labels are only allowed before the first chained instruction'),
33
+ ],
34
+ };
35
+ }
36
+ const labeled = segmentIndex === 0 ? parseLeadingLabel(text, column) : undefined;
37
+ const statementText = labeled?.statementText ?? text;
38
+ const statementColumn = labeled?.statementColumn ?? column;
39
+ if (statementText.length === 0) {
40
+ return {
41
+ items: [],
42
+ diagnostics: [
43
+ options.makeDiagnostic(options.line, statementColumn, 'empty instruction segment in chained line'),
44
+ ],
45
+ };
46
+ }
47
+ if (isChainedDirectiveOrDeclaration(statementText)) {
48
+ return {
49
+ items: [],
50
+ diagnostics: [
51
+ options.makeDiagnostic(options.line, statementColumn, 'directives must be on their own line; chained lines only support instructions and ops'),
52
+ ],
53
+ };
54
+ }
55
+ const items = [];
56
+ if (labeled) {
57
+ items.push(options.makeLabelItem(labeled, options.line));
58
+ }
59
+ const statement = options.parseStatement(options.line, statementText, statementColumn);
60
+ items.push(...statement.items);
61
+ return { items, diagnostics: statement.diagnostics };
62
+ }
@@ -1,4 +1,6 @@
1
1
  import { stripLineComment } from '../source/strip-line-comment.js';
2
+ import { IDENTIFIER_PATTERN } from './names.js';
3
+ import { firstNonWhitespaceColumn, parseLineError } from './parse-diagnostics.js';
2
4
  import { parseTypeExpr } from './parse-expression.js';
3
5
  export function parseLayoutDeclarationAt(lines, index) {
4
6
  const line = lines[index];
@@ -9,7 +11,7 @@ export function parseLayoutDeclarationAt(lines, index) {
9
11
  if (typeAlias !== undefined) {
10
12
  return { consumedUntilIndex: index, ...typeAlias };
11
13
  }
12
- const prefixLayoutHeader = /^\.(type|union)\s+([A-Za-z_][A-Za-z0-9_]*)\s*$/.exec(text);
14
+ const prefixLayoutHeader = new RegExp(`^\\.(type|union)\\s+(${IDENTIFIER_PATTERN})\\s*$`).exec(text);
13
15
  if (prefixLayoutHeader) {
14
16
  const directive = prefixLayoutHeader[1] ?? 'type';
15
17
  return {
@@ -26,7 +28,7 @@ export function parseLayoutDeclarationAt(lines, index) {
26
28
  return parseLayoutBlock(lines, index, layoutHeader);
27
29
  }
28
30
  function parseTypeAlias(line, text) {
29
- const nameLeftTypeAlias = /^([A-Za-z_][A-Za-z0-9_]*)(?::\s*|\s+)\.typealias\s+(.+)$/.exec(text);
31
+ const nameLeftTypeAlias = new RegExp(`^(${IDENTIFIER_PATTERN})(?::\\s*|\\s+)\\.typealias\\s+(.+)$`).exec(text);
30
32
  if (nameLeftTypeAlias) {
31
33
  const typeExprText = nameLeftTypeAlias[2] ?? '';
32
34
  const typeExpr = parseTypeExpr(typeExprText);
@@ -43,7 +45,7 @@ function parseTypeAlias(line, text) {
43
45
  diagnostics: [],
44
46
  };
45
47
  }
46
- const oldTypeAlias = /^\.type\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+)$/.exec(text);
48
+ const oldTypeAlias = new RegExp(`^\\.type\\s+(${IDENTIFIER_PATTERN})\\s*=\\s*(.+)$`).exec(text);
47
49
  if (oldTypeAlias) {
48
50
  return {
49
51
  diagnostics: [
@@ -54,7 +56,7 @@ function parseTypeAlias(line, text) {
54
56
  return undefined;
55
57
  }
56
58
  function parseNameLeftLayoutHeader(text) {
57
- const match = /^([A-Za-z_][A-Za-z0-9_]*)(?::\s*|\s+)\.(type|union)\s*$/.exec(text);
59
+ const match = new RegExp(`^(${IDENTIFIER_PATTERN})(?::\\s*|\\s+)\\.(type|union)\\s*$`).exec(text);
58
60
  return match
59
61
  ? {
60
62
  directive: match[2] ?? '',
@@ -107,9 +109,10 @@ function spanForLine(line) {
107
109
  return {
108
110
  sourceName: line.sourceName,
109
111
  line: line.line,
110
- column: firstColumn(line.text),
112
+ column: firstNonWhitespaceColumn(line.text),
111
113
  ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
112
114
  ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
115
+ ...(line.sourceUnitRelation !== undefined ? { sourceUnitRelation: line.sourceUnitRelation } : {}),
113
116
  };
114
117
  }
115
118
  function skipToLayoutEnd(lines, index, directive) {
@@ -122,7 +125,7 @@ function skipToLayoutEnd(lines, index, directive) {
122
125
  return index;
123
126
  }
124
127
  function parseLayoutField(text) {
125
- const match = /^([A-Za-z_][A-Za-z0-9_]*)\s+(\.(?:field|byte|word|addr))(?:\s+(.+))?$/.exec(text);
128
+ const match = new RegExp(`^(${IDENTIFIER_PATTERN})\\s+(\\.(?:field|byte|word|addr))(?:\\s+(.+))?$`).exec(text);
126
129
  if (!match) {
127
130
  return undefined;
128
131
  }
@@ -174,16 +177,5 @@ function scalarFieldSize(typeName) {
174
177
  }
175
178
  }
176
179
  function parseDiagnostic(line, message) {
177
- return {
178
- severity: 'error',
179
- code: 'AZMN_PARSE',
180
- message,
181
- sourceName: line.sourceName,
182
- line: line.line,
183
- column: firstColumn(line.text),
184
- };
185
- }
186
- function firstColumn(text) {
187
- const match = /\S/.exec(text);
188
- return match ? match.index + 1 : 1;
180
+ return parseLineError(line, message);
189
181
  }
@@ -1,6 +1,7 @@
1
+ import { IDENTIFIER_PATTERN } from './names.js';
1
2
  export function parseTypeExpr(text) {
2
3
  const trimmed = text.trim();
3
- const match = /^([A-Za-z_][A-Za-z0-9_]*)(?:\[\s*([0-9]+)\s*\])?$/.exec(trimmed);
4
+ const match = new RegExp(`^(${IDENTIFIER_PATTERN})(?:\\[\\s*([0-9]+)\\s*\\])?$`).exec(trimmed);
4
5
  if (!match) {
5
6
  return undefined;
6
7
  }
@@ -118,7 +119,7 @@ function parseLayoutCastPathPart(text, parseNestedExpression) {
118
119
  : parseLayoutCastIndex(text, parseNestedExpression);
119
120
  }
120
121
  function parseLayoutCastField(text) {
121
- const field = /^\.([A-Za-z_][A-Za-z0-9_]*)/.exec(text);
122
+ const field = new RegExp(`^\\.(${IDENTIFIER_PATTERN})`).exec(text);
122
123
  return field ? { part: { kind: 'field', name: field[1] ?? '' }, rest: text.slice(field[0].length) } : undefined;
123
124
  }
124
125
  function parseLayoutCastIndex(text, parseNestedExpression) {
@@ -168,7 +169,7 @@ function parseOffsetIndex(text) {
168
169
  : undefined;
169
170
  }
170
171
  function parseOffsetField(text) {
171
- const field = /^[A-Za-z_][A-Za-z0-9_]*/.exec(text);
172
+ const field = new RegExp(`^${IDENTIFIER_PATTERN}`).exec(text);
172
173
  return field
173
174
  ? { part: { kind: 'field', name: field[0] }, rest: text.slice(field[0].length) }
174
175
  : undefined;
@@ -1,5 +1,7 @@
1
1
  import { extractLineComment, stripLineComment } from '../source/strip-line-comment.js';
2
2
  import { normalizeDirectiveAlias } from './directive-aliases.js';
3
+ import { LABEL_NAME_PATTERN, parseEntryLabel } from './names.js';
4
+ import { firstNonWhitespaceColumn, parseLineError } from './parse-diagnostics.js';
3
5
  import { parseColonDeclaration, parseDirectiveStatement } from './parse-directive-statement.js';
4
6
  import { parseZ80Instruction } from '../z80/parse-instruction.js';
5
7
  export function parseLogicalLine(line, options = {}) {
@@ -8,34 +10,38 @@ export function parseLogicalLine(line, options = {}) {
8
10
  return commentOnlyLine(line);
9
11
  }
10
12
  const span = spanForLine(line);
11
- const labelWithStatement = /^(@?[A-Za-z_.$?][A-Za-z0-9_.$?]*):\s*(.+)$/.exec(text);
13
+ const labelWithStatement = new RegExp(`^(@?${LABEL_NAME_PATTERN}):\\s*(.+)$`).exec(text);
12
14
  if (labelWithStatement) {
13
15
  const rawLabel = labelWithStatement[1] ?? '';
14
- const labelName = normalizeEntryLabelName(rawLabel);
15
- const isEntry = rawLabel.startsWith('@');
16
+ const label = parseEntryLabel(rawLabel);
17
+ if (!label)
18
+ return withLineComment(line, parseCanonicalStatement(line, text, span));
16
19
  const statementText = labelWithStatement[2] ?? '';
17
- const declaration = parseColonDeclaration(line, labelName, statementText, span);
20
+ const declaration = parseColonDeclaration(line, label.name, statementText, span);
18
21
  if (declaration) {
19
22
  return withLineComment(line, declaration);
20
23
  }
21
24
  const parsedStatement = parseCanonicalStatement(line, statementText, span);
22
25
  return withLineComment(line, {
23
26
  items: [
24
- { kind: 'label', name: labelName, ...(isEntry ? { isEntry: true } : {}), span },
27
+ { kind: 'label', name: label.name, ...(label.isEntry ? { isEntry: true } : {}), span },
25
28
  ...parsedStatement.items,
26
29
  ],
27
30
  diagnostics: parsedStatement.diagnostics,
28
31
  });
29
32
  }
30
- const labelOnly = /^(@?[A-Za-z_.$?][A-Za-z0-9_.$?]*):$/.exec(text);
33
+ const labelOnly = new RegExp(`^(@?${LABEL_NAME_PATTERN}):$`).exec(text);
31
34
  if (labelOnly) {
32
35
  const rawLabel = labelOnly[1] ?? '';
36
+ const label = parseEntryLabel(rawLabel);
37
+ if (!label)
38
+ return withLineComment(line, parseCanonicalStatement(line, text, span));
33
39
  return withLineComment(line, {
34
40
  items: [
35
41
  {
36
42
  kind: 'label',
37
- name: normalizeEntryLabelName(rawLabel),
38
- ...(rawLabel.startsWith('@') ? { isEntry: true } : {}),
43
+ name: label.name,
44
+ ...(label.isEntry ? { isEntry: true } : {}),
39
45
  span,
40
46
  },
41
47
  ],
@@ -58,7 +64,7 @@ function commentOnlyLine(line) {
58
64
  span: {
59
65
  sourceName: line.sourceName,
60
66
  line: line.line,
61
- column: firstColumn(line.text),
67
+ column: firstNonWhitespaceColumn(line.text),
62
68
  ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
63
69
  ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
64
70
  },
@@ -82,7 +88,7 @@ function withLineComment(line, result) {
82
88
  span: {
83
89
  sourceName: line.sourceName,
84
90
  line: line.line,
85
- column: firstColumn(line.text),
91
+ column: firstNonWhitespaceColumn(line.text),
86
92
  ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
87
93
  ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
88
94
  },
@@ -106,37 +112,21 @@ function parseCanonicalStatement(line, text, span) {
106
112
  if (instruction?.diagnostics && instruction.diagnostics.length > 0) {
107
113
  return {
108
114
  items: [],
109
- diagnostics: instruction.diagnostics.map((message) => parseError(line, message)),
115
+ diagnostics: instruction.diagnostics.map((message) => parseLineError(line, message)),
110
116
  };
111
117
  }
112
118
  if (instruction?.error) {
113
- return { items: [], diagnostics: [parseError(line, instruction.error)] };
119
+ return { items: [], diagnostics: [parseLineError(line, instruction.error)] };
114
120
  }
115
- return { items: [], diagnostics: [parseError(line, `unsupported source line: ${text}`)] };
121
+ return { items: [], diagnostics: [parseLineError(line, `unsupported source line: ${text}`)] };
116
122
  }
117
123
  function spanForLine(line) {
118
124
  return {
119
125
  sourceName: line.sourceName,
120
126
  line: line.line,
121
- column: firstColumn(line.text),
127
+ column: firstNonWhitespaceColumn(line.text),
122
128
  ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
123
129
  ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
124
- };
125
- }
126
- function normalizeEntryLabelName(raw) {
127
- return raw.startsWith('@') ? raw.slice(1) : raw;
128
- }
129
- function firstColumn(text) {
130
- const match = /\S/.exec(text);
131
- return match ? match.index + 1 : 1;
132
- }
133
- function parseError(line, message) {
134
- return {
135
- severity: 'error',
136
- code: 'AZMN_PARSE',
137
- message,
138
- sourceName: line.sourceName,
139
- line: line.line,
140
- column: firstColumn(line.text),
130
+ ...(line.sourceUnitRelation !== undefined ? { sourceUnitRelation: line.sourceUnitRelation } : {}),
141
131
  };
142
132
  }
@@ -0,0 +1,7 @@
1
+ import type { LogicalLine } from '../source/logical-lines.js';
2
+ import type { ParseLineResult } from './parse-line.js';
3
+ export declare function parseExpressionDirective(line: LogicalLine, kind: 'align' | 'binfrom' | 'binto' | 'org', expressionText: string, span: {
4
+ readonly sourceName: string;
5
+ readonly line: number;
6
+ readonly column: number;
7
+ }): ParseLineResult;
@@ -0,0 +1,15 @@
1
+ import { parseLineError } from './parse-diagnostics.js';
2
+ import { parseExpression } from './parse-expression.js';
3
+ export function parseExpressionDirective(line, kind, expressionText, span) {
4
+ const expression = parseExpression(expressionText);
5
+ if (!expression) {
6
+ return {
7
+ items: [],
8
+ diagnostics: [parseLineError(line, `invalid .${kind} expression: ${expressionText}`)],
9
+ };
10
+ }
11
+ if (kind === 'align') {
12
+ return { items: [{ kind, alignment: expression, span }], diagnostics: [] };
13
+ }
14
+ return { items: [{ kind, expression, span }], diagnostics: [] };
15
+ }
@@ -0,0 +1,2 @@
1
+ export declare function isChainedDirectiveOrDeclaration(text: string): boolean;
2
+ export declare function isPotentialOpInvocationStatement(text: string): boolean;
@@ -0,0 +1,24 @@
1
+ import { IDENTIFIER_PATTERN, LABEL_NAME_PATTERN } from './names.js';
2
+ const CHAIN_DECLARATION_RE = new RegExp(`^${LABEL_NAME_PATTERN}\\s+\\.?(?:equ|enum|type|union|typealias)\\b`, 'i');
3
+ const IDENTIFIER_STATEMENT_RE = new RegExp(`^${IDENTIFIER_PATTERN}(?:\\s+.*)?$`);
4
+ const EQU_DECLARATION_RE = new RegExp(`^${IDENTIFIER_PATTERN}\\s+\\.?equ\\b`, 'i');
5
+ const LAYOUT_DECLARATION_RE = new RegExp(`^${IDENTIFIER_PATTERN}\\s+\\.(?:enum|type|union|typealias|field|byte|word|addr)\\b`);
6
+ export function isChainedDirectiveOrDeclaration(text) {
7
+ return (/^\./.test(text) ||
8
+ /^(?:org|equ|db|dw|ds|align|include|import|binfrom|binto|cstr|pstr|istr|end|enum|type|union|field|byte|word|addr|endtype|endunion|typealias|if|else|endif|op)\b/i.test(text) ||
9
+ CHAIN_DECLARATION_RE.test(text));
10
+ }
11
+ export function isPotentialOpInvocationStatement(text) {
12
+ if (!IDENTIFIER_STATEMENT_RE.test(text))
13
+ return false;
14
+ if (EQU_DECLARATION_RE.test(text))
15
+ return false;
16
+ if (LAYOUT_DECLARATION_RE.test(text))
17
+ return false;
18
+ if (/^(?:op|end|enum|type|union|field|byte|word|addr)\b/i.test(text))
19
+ return false;
20
+ if (/^(?:org|equ|db|dw|ds|align|include|binfrom|binto|cstr|pstr|istr)\b/i.test(text)) {
21
+ return false;
22
+ }
23
+ return true;
24
+ }