@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
@@ -5,6 +5,7 @@ export interface LogicalLine {
5
5
  readonly text: string;
6
6
  readonly sourceUnit?: string;
7
7
  readonly sourceRelation?: SourceRelation;
8
+ readonly sourceUnitRelation?: SourceRelation;
8
9
  }
9
10
  export type SourceRelation = 'entry' | 'include' | 'import';
10
11
  export declare function scanLogicalLines(source: SourceFile): LogicalLine[];
@@ -4,4 +4,5 @@ export interface SourceSpan {
4
4
  readonly column: number;
5
5
  readonly sourceUnit?: string;
6
6
  readonly sourceRelation?: 'entry' | 'include' | 'import';
7
+ readonly sourceUnitRelation?: 'entry' | 'include' | 'import';
7
8
  }
@@ -0,0 +1,18 @@
1
+ export declare const IDENTIFIER_PATTERN = "[A-Za-z_][A-Za-z0-9_]*";
2
+ export declare const LABEL_NAME_PATTERN = "[A-Za-z_.$?][A-Za-z0-9_.$?]*";
3
+ export interface ParsedEntryLabel {
4
+ readonly rawLabel: string;
5
+ readonly name: string;
6
+ readonly isEntry: boolean;
7
+ }
8
+ export interface ParsedLeadingLabel extends ParsedEntryLabel {
9
+ readonly labelColumn: number;
10
+ readonly statementText: string;
11
+ readonly statementColumn: number;
12
+ }
13
+ export declare function isIdentifier(text: string): boolean;
14
+ export declare function isLabelName(text: string): boolean;
15
+ export declare function parseEntryLabel(text: string): ParsedEntryLabel | undefined;
16
+ export declare function normalizeEntryLabelName(raw: string): string;
17
+ export declare function hasLeadingLabel(text: string): boolean;
18
+ export declare function parseLeadingLabel(text: string, column: number): ParsedLeadingLabel | undefined;
@@ -0,0 +1,44 @@
1
+ export const IDENTIFIER_PATTERN = '[A-Za-z_][A-Za-z0-9_]*';
2
+ export const LABEL_NAME_PATTERN = '[A-Za-z_.$?][A-Za-z0-9_.$?]*';
3
+ const IDENTIFIER_RE = new RegExp(`^${IDENTIFIER_PATTERN}$`);
4
+ const LABEL_NAME_RE = new RegExp(`^${LABEL_NAME_PATTERN}$`);
5
+ const ENTRY_LABEL_RE = new RegExp(`^@?${LABEL_NAME_PATTERN}$`);
6
+ const LEADING_LABEL_RE = new RegExp(`^(@?${LABEL_NAME_PATTERN}):\\s*(.*)$`);
7
+ export function isIdentifier(text) {
8
+ return IDENTIFIER_RE.test(text);
9
+ }
10
+ export function isLabelName(text) {
11
+ return LABEL_NAME_RE.test(text);
12
+ }
13
+ export function parseEntryLabel(text) {
14
+ if (!ENTRY_LABEL_RE.test(text))
15
+ return undefined;
16
+ return {
17
+ rawLabel: text,
18
+ name: normalizeEntryLabelName(text),
19
+ isEntry: text.startsWith('@'),
20
+ };
21
+ }
22
+ export function normalizeEntryLabelName(raw) {
23
+ return raw.startsWith('@') ? raw.slice(1) : raw;
24
+ }
25
+ export function hasLeadingLabel(text) {
26
+ return new RegExp(`^@?${LABEL_NAME_PATTERN}:`).test(text);
27
+ }
28
+ export function parseLeadingLabel(text, column) {
29
+ const match = LEADING_LABEL_RE.exec(text);
30
+ if (!match)
31
+ return undefined;
32
+ const rawLabel = match[1] ?? '';
33
+ const parsed = parseEntryLabel(rawLabel);
34
+ if (!parsed)
35
+ return undefined;
36
+ const statementText = match[2] ?? '';
37
+ const statementOffset = text.indexOf(statementText, rawLabel.length + 1);
38
+ return {
39
+ ...parsed,
40
+ labelColumn: column,
41
+ statementText,
42
+ statementColumn: column + (statementOffset === -1 ? text.length : statementOffset),
43
+ };
44
+ }
@@ -0,0 +1,17 @@
1
+ import type { LogicalLine } from '../source/logical-lines.js';
2
+ import type { ParseLineResult } from './parse-line.js';
3
+ export declare function parseDataDirective(line: LogicalLine, directiveText: string, valueText: string, span: {
4
+ readonly sourceName: string;
5
+ readonly line: number;
6
+ readonly column: number;
7
+ }): ParseLineResult;
8
+ export declare function parseDsDirective(line: LogicalLine, valueText: string, span: {
9
+ readonly sourceName: string;
10
+ readonly line: number;
11
+ readonly column: number;
12
+ }): ParseLineResult;
13
+ export declare function parseStringDataDirective(line: LogicalLine, directive: 'cstr' | 'istr' | 'pstr', valueText: string, span: {
14
+ readonly sourceName: string;
15
+ readonly line: number;
16
+ readonly column: number;
17
+ }): ParseLineResult;
@@ -0,0 +1,147 @@
1
+ import { parseWholeQuotedString } from './parse-declaration-directives.js';
2
+ import { parseLineError } from './parse-diagnostics.js';
3
+ import { parseExpression, parseTypeExpr } from './parse-expression.js';
4
+ export function parseDataDirective(line, directiveText, valueText, span) {
5
+ const directive = directiveText.slice(1).toLowerCase();
6
+ const parts = splitValueList(valueText);
7
+ const values = directive === 'db'
8
+ ? parts.map(parseDataValue).filter((value) => value !== undefined)
9
+ : parts.map(parseExpression).filter((value) => value !== undefined);
10
+ if (values.length !== parts.length) {
11
+ return {
12
+ items: [],
13
+ diagnostics: [parseLineError(line, `invalid .${directive} value list`)],
14
+ };
15
+ }
16
+ return {
17
+ items: directive === 'db'
18
+ ? [{ kind: 'db', values: values, span }]
19
+ : [{ kind: 'dw', values: values, span }],
20
+ diagnostics: [],
21
+ };
22
+ }
23
+ export function parseDsDirective(line, valueText, span) {
24
+ const parts = splitValueList(valueText);
25
+ const listDiagnostic = validateDsValueList(line, parts);
26
+ if (listDiagnostic) {
27
+ return { items: [], diagnostics: [listDiagnostic] };
28
+ }
29
+ const sizeResult = parseDsSize(line, parts[0] ?? '');
30
+ if (sizeResult.diagnostic) {
31
+ return { items: [], diagnostics: [sizeResult.diagnostic] };
32
+ }
33
+ const fillResult = parseDsFill(line, parts[1]);
34
+ if (fillResult.diagnostic) {
35
+ return { items: [], diagnostics: [fillResult.diagnostic] };
36
+ }
37
+ return {
38
+ items: [
39
+ fillResult.fill === undefined
40
+ ? { kind: 'ds', size: sizeResult.size, span }
41
+ : { kind: 'ds', size: sizeResult.size, fill: fillResult.fill, span },
42
+ ],
43
+ diagnostics: [],
44
+ };
45
+ }
46
+ export function parseStringDataDirective(line, directive, valueText, span) {
47
+ const value = parseQuotedString(valueText);
48
+ if (value === undefined) {
49
+ return {
50
+ items: [],
51
+ diagnostics: [parseLineError(line, `.${directive} expects one double-quoted string`)],
52
+ };
53
+ }
54
+ return { items: [{ kind: 'string-data', directive, value, span }], diagnostics: [] };
55
+ }
56
+ function validateDsValueList(line, parts) {
57
+ return parts.length < 1 || parts.length > 2
58
+ ? parseLineError(line, `invalid .ds value list`)
59
+ : undefined;
60
+ }
61
+ function parseDsSize(line, sizeText) {
62
+ const size = parseTypeSizeExpression(sizeText) ?? parseExpression(sizeText);
63
+ if (!size) {
64
+ return {
65
+ diagnostic: parseLineError(line, `invalid .ds size: ${sizeText}`),
66
+ };
67
+ }
68
+ return { size };
69
+ }
70
+ function parseDsFill(line, fillText) {
71
+ if (fillText === undefined)
72
+ return { fill: undefined };
73
+ const fill = parseExpression(fillText);
74
+ if (!fill) {
75
+ return {
76
+ diagnostic: parseLineError(line, `invalid .ds fill: ${fillText}`),
77
+ };
78
+ }
79
+ return { fill };
80
+ }
81
+ function parseTypeSizeExpression(text) {
82
+ const typeExpr = parseTypeExpr(text);
83
+ return typeExpr ? { kind: 'type-size', typeExpr } : undefined;
84
+ }
85
+ function splitValueList(text) {
86
+ const values = [];
87
+ let state = { quote: undefined, escaped: false, parenDepth: 0 };
88
+ let start = 0;
89
+ for (let index = 0; index < text.length; index += 1) {
90
+ if (isValueSeparator(text[index] ?? '', state)) {
91
+ values.push(text.slice(start, index));
92
+ start = index + 1;
93
+ continue;
94
+ }
95
+ state = scanValueListChar(text[index] ?? '', state);
96
+ }
97
+ values.push(text.slice(start));
98
+ return values;
99
+ }
100
+ function isValueSeparator(char, state) {
101
+ return char === ',' && state.quote === undefined && state.parenDepth === 0;
102
+ }
103
+ function scanValueListChar(char, state) {
104
+ const escapedState = scanEscapedValueListChar(char, state);
105
+ if (escapedState)
106
+ return escapedState;
107
+ const quotedState = scanQuotedValueListChar(char, state);
108
+ if (quotedState)
109
+ return quotedState;
110
+ return scanParenthesizedValueListChar(char, state);
111
+ }
112
+ function scanEscapedValueListChar(char, state) {
113
+ if (state.escaped)
114
+ return { ...state, escaped: false };
115
+ if (char === '\\' && state.quote !== undefined)
116
+ return { ...state, escaped: true };
117
+ return undefined;
118
+ }
119
+ function scanQuotedValueListChar(char, state) {
120
+ if (char !== '"' && char !== "'")
121
+ return undefined;
122
+ return { ...state, quote: state.quote === char ? undefined : (state.quote ?? char) };
123
+ }
124
+ function scanParenthesizedValueListChar(char, state) {
125
+ if (state.quote !== undefined)
126
+ return state;
127
+ if (char === '(')
128
+ return { ...state, parenDepth: state.parenDepth + 1 };
129
+ if (char === ')')
130
+ return { ...state, parenDepth: Math.max(0, state.parenDepth - 1) };
131
+ return state;
132
+ }
133
+ function parseQuotedString(text) {
134
+ const input = text.trim();
135
+ if (input[0] !== '"' || input[input.length - 1] !== '"') {
136
+ return undefined;
137
+ }
138
+ return parseWholeQuotedString(input);
139
+ }
140
+ function parseDataValue(text) {
141
+ const expression = parseExpression(text);
142
+ if (expression) {
143
+ return expression;
144
+ }
145
+ const value = parseWholeQuotedString(text);
146
+ return value === undefined ? undefined : { kind: 'string-fragment', value };
147
+ }
@@ -0,0 +1,18 @@
1
+ import type { LogicalLine } from '../source/logical-lines.js';
2
+ import type { ParseLineResult } from './parse-line.js';
3
+ export declare function parseColonDeclaration(line: LogicalLine, name: string, statementText: string, span: {
4
+ readonly sourceName: string;
5
+ readonly line: number;
6
+ readonly column: number;
7
+ }): ParseLineResult | undefined;
8
+ export declare function parseEquItem(line: LogicalLine, name: string, expressionText: string, span: {
9
+ readonly sourceName: string;
10
+ readonly line: number;
11
+ readonly column: number;
12
+ }): ParseLineResult;
13
+ export declare function parseEnumItem(line: LogicalLine, name: string, membersText: string, span: {
14
+ readonly sourceName: string;
15
+ readonly line: number;
16
+ readonly column: number;
17
+ }): ParseLineResult;
18
+ export declare function parseWholeQuotedString(text: string): string | undefined;
@@ -0,0 +1,90 @@
1
+ import { isIdentifier } from './names.js';
2
+ import { parseLineError } from './parse-diagnostics.js';
3
+ import { parseExpression } from './parse-expression.js';
4
+ export function parseColonDeclaration(line, name, statementText, span) {
5
+ const equ = /^\.equ\s+(.+)$/.exec(statementText);
6
+ if (equ) {
7
+ return parseEquItem(line, name, equ[1] ?? '', span);
8
+ }
9
+ const enumDecl = /^\.enum\s+(.+)$/.exec(statementText);
10
+ if (enumDecl) {
11
+ return parseEnumItem(line, name, enumDecl[1] ?? '', span);
12
+ }
13
+ return undefined;
14
+ }
15
+ export function parseEquItem(line, name, expressionText, span) {
16
+ const stringValue = parseWholeQuotedString(expressionText.trim());
17
+ const expression = stringValue !== undefined && stringValue.length > 1
18
+ ? { kind: 'number', value: 0 }
19
+ : parseExpression(expressionText);
20
+ if (!expression) {
21
+ return {
22
+ items: [],
23
+ diagnostics: [parseLineError(line, `invalid .equ expression: ${expressionText}`)],
24
+ };
25
+ }
26
+ return {
27
+ items: [
28
+ {
29
+ kind: 'equ',
30
+ name,
31
+ expression,
32
+ ...(stringValue !== undefined && stringValue.length > 1 ? { stringValue } : {}),
33
+ span,
34
+ },
35
+ ],
36
+ diagnostics: [],
37
+ };
38
+ }
39
+ export function parseEnumItem(line, name, membersText, span) {
40
+ const rawMembers = membersText.split(',').map((member) => member.trim());
41
+ if (membersText.trim().length === 0 || rawMembers.some((member) => member.length === 0)) {
42
+ return {
43
+ items: [],
44
+ diagnostics: [parseLineError(line, `invalid enum member list`)],
45
+ };
46
+ }
47
+ const members = [];
48
+ const diagnostics = [];
49
+ for (const member of rawMembers) {
50
+ if (!isIdentifier(member)) {
51
+ diagnostics.push(parseLineError(line, `Invalid enum member name "${member}": expected <identifier>.`));
52
+ continue;
53
+ }
54
+ members.push(member);
55
+ }
56
+ if (diagnostics.length > 0) {
57
+ return { items: [], diagnostics };
58
+ }
59
+ return { items: [{ kind: 'enum', name, members, span }], diagnostics: [] };
60
+ }
61
+ export function parseWholeQuotedString(text) {
62
+ return parseQuotedStringWithQuotes(text, new Set(['"', "'"]));
63
+ }
64
+ function parseQuotedStringWithQuotes(text, allowedQuotes) {
65
+ const input = text.trim();
66
+ const quote = input[0];
67
+ if (!quote || !allowedQuotes.has(quote) || input[input.length - 1] !== quote) {
68
+ return undefined;
69
+ }
70
+ return parseQuotedStringContent(input, quote);
71
+ }
72
+ function parseQuotedStringContent(input, quote) {
73
+ let value = '';
74
+ for (let index = 1; index < input.length - 1; index += 1) {
75
+ const char = input[index] ?? '';
76
+ if (char === '\\') {
77
+ if (index + 1 >= input.length - 1) {
78
+ return undefined;
79
+ }
80
+ value += input[index + 1] ?? '';
81
+ index += 1;
82
+ continue;
83
+ }
84
+ if (char === quote) {
85
+ return undefined;
86
+ }
87
+ value += char;
88
+ }
89
+ return value;
90
+ }
@@ -3,10 +3,18 @@ type ParseDiagLocation = {
3
3
  line: number;
4
4
  column: number;
5
5
  };
6
+ type ParseDiagLine = {
7
+ readonly sourceName: string;
8
+ readonly line: number;
9
+ readonly text: string;
10
+ };
6
11
  /** Push a parse diagnostic with Next default code/severity (`AZMN_PARSE` / error). */
7
12
  export declare function parseDiag(diagnostics: Diagnostic[], sourceName: string, message: string, where?: ParseDiagLocation): void;
8
13
  /** Push a parse diagnostic at an explicit 1-based line/column. */
9
14
  export declare function parseDiagAt(diagnostics: Diagnostic[], sourceName: string, message: string, line: number, column: number): void;
10
15
  /** Push a diagnostic with explicit code, severity, and optional location. */
11
16
  export declare function parseDiagAtWithId(diagnostics: Diagnostic[], sourceName: string, code: DiagnosticId | string, severity: DiagnosticSeverity, message: string, where?: ParseDiagLocation): void;
17
+ /** Return a parse diagnostic at the first non-whitespace column of a source line. */
18
+ export declare function parseLineError(line: ParseDiagLine, message: string): Diagnostic;
19
+ export declare function firstNonWhitespaceColumn(text: string): number;
12
20
  export {};
@@ -16,3 +16,18 @@ export function parseDiagAtWithId(diagnostics, sourceName, code, severity, messa
16
16
  ...(where ? { line: where.line, column: where.column } : {}),
17
17
  });
18
18
  }
19
+ /** Return a parse diagnostic at the first non-whitespace column of a source line. */
20
+ export function parseLineError(line, message) {
21
+ return {
22
+ code: 'AZMN_PARSE',
23
+ severity: 'error',
24
+ message,
25
+ sourceName: line.sourceName,
26
+ line: line.line,
27
+ column: firstNonWhitespaceColumn(line.text),
28
+ };
29
+ }
30
+ export function firstNonWhitespaceColumn(text) {
31
+ const match = /\S/.exec(text);
32
+ return match ? match.index + 1 : 1;
33
+ }
@@ -1,9 +1,5 @@
1
1
  import type { LogicalLine } from '../source/logical-lines.js';
2
2
  import type { SourceSpan } from '../source/source-span.js';
3
3
  import type { ParseLineResult } from './parse-line.js';
4
+ export { parseColonDeclaration } from './parse-declaration-directives.js';
4
5
  export declare function parseDirectiveStatement(line: LogicalLine, text: string, span: SourceSpan): ParseLineResult | undefined;
5
- export declare function parseColonDeclaration(line: LogicalLine, name: string, statementText: string, span: {
6
- readonly sourceName: string;
7
- readonly line: number;
8
- readonly column: number;
9
- }): ParseLineResult | undefined;