@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.
- package/README.md +14 -0
- package/dist/src/assembly/import-visibility.js +108 -33
- package/dist/src/core/compile.js +98 -1
- package/dist/src/expansion/op-expand-selected.js +8 -1
- package/dist/src/expansion/op-expansion.d.ts +3 -0
- package/dist/src/expansion/op-expansion.js +47 -9
- package/dist/src/node/source-host.js +5 -2
- package/dist/src/outputs/d8-files.js +1 -0
- package/dist/src/outputs/types.d.ts +1 -0
- package/dist/src/source/instruction-chain.d.ts +5 -0
- package/dist/src/source/instruction-chain.js +75 -0
- package/dist/src/source/logical-lines.d.ts +1 -0
- package/dist/src/source/source-span.d.ts +1 -0
- package/dist/src/syntax/names.d.ts +18 -0
- package/dist/src/syntax/names.js +44 -0
- package/dist/src/syntax/parse-data-directives.d.ts +17 -0
- package/dist/src/syntax/parse-data-directives.js +147 -0
- package/dist/src/syntax/parse-declaration-directives.d.ts +18 -0
- package/dist/src/syntax/parse-declaration-directives.js +90 -0
- package/dist/src/syntax/parse-diagnostics.d.ts +8 -0
- package/dist/src/syntax/parse-diagnostics.js +15 -0
- package/dist/src/syntax/parse-directive-statement.d.ts +1 -5
- package/dist/src/syntax/parse-directive-statement.js +19 -259
- package/dist/src/syntax/parse-instruction-chain.d.ts +22 -0
- package/dist/src/syntax/parse-instruction-chain.js +62 -0
- package/dist/src/syntax/parse-layout-declarations.js +10 -18
- package/dist/src/syntax/parse-layout-expression.js +4 -3
- package/dist/src/syntax/parse-line.js +21 -31
- package/dist/src/syntax/parse-location-directives.d.ts +7 -0
- package/dist/src/syntax/parse-location-directives.js +15 -0
- package/dist/src/syntax/statement-classification.d.ts +2 -0
- package/dist/src/syntax/statement-classification.js +24 -0
- package/dist/src/tooling/case-style.js +42 -26
- package/docs/codebase/02-source-loading-and-parsing.md +28 -8
- package/docs/codebase/04-ops-and-register-contracts.md +24 -3
- package/docs/codebase/05-interfaces-and-output-artifacts.md +10 -0
- package/docs/codebase/06-verification-and-maintenance.md +9 -3
- package/docs/codebase/appendices/a-directory-file-reference.md +17 -10
- package/docs/codebase/appendices/b-compile-flow-reference.md +3 -2
- package/docs/codebase/index.md +4 -0
- package/package.json +1 -1
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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:
|
|
16
|
+
pattern: new RegExp(`^enum\\s+(${IDENTIFIER_PATTERN})\\s+(.+)$`),
|
|
13
17
|
parse: (line, match) => ({
|
|
14
18
|
items: [],
|
|
15
|
-
diagnostics: [
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
13
|
+
const labelWithStatement = new RegExp(`^(@?${LABEL_NAME_PATTERN}):\\s*(.+)$`).exec(text);
|
|
12
14
|
if (labelWithStatement) {
|
|
13
15
|
const rawLabel = labelWithStatement[1] ?? '';
|
|
14
|
-
const
|
|
15
|
-
|
|
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,
|
|
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:
|
|
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 =
|
|
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:
|
|
38
|
-
...(
|
|
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:
|
|
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:
|
|
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) =>
|
|
115
|
+
diagnostics: instruction.diagnostics.map((message) => parseLineError(line, message)),
|
|
110
116
|
};
|
|
111
117
|
}
|
|
112
118
|
if (instruction?.error) {
|
|
113
|
-
return { items: [], diagnostics: [
|
|
119
|
+
return { items: [], diagnostics: [parseLineError(line, instruction.error)] };
|
|
114
120
|
}
|
|
115
|
-
return { items: [], diagnostics: [
|
|
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:
|
|
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,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
|
+
}
|