@jhlagado/azm 0.2.11 → 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.
@@ -1,14 +1,22 @@
1
1
  import { diagnostic } from '../semantics/diagnostics.js';
2
2
  export function validateImportVisibility(items, diagnostics) {
3
- const labels = collectLabelVisibility(items);
3
+ const symbols = collectSymbolVisibility(items);
4
4
  for (const item of items) {
5
- validateItemReferences(item, labels, diagnostics);
5
+ validateItemReferences(item, symbols, diagnostics);
6
6
  }
7
7
  }
8
- function collectLabelVisibility(items) {
8
+ function collectSymbolVisibility(items) {
9
9
  const labels = new Map();
10
+ const exactSymbols = new Set();
10
11
  const importedSourceUnits = importedUnitNames(items);
12
+ const symbolConflicts = buildSymbolConflictIndex(items);
11
13
  for (const item of items) {
14
+ for (const name of exactSymbolNames(item)) {
15
+ exactSymbols.add(name);
16
+ }
17
+ }
18
+ for (let index = 0; index < items.length; index += 1) {
19
+ const item = items[index];
12
20
  if (item.kind !== 'label')
13
21
  continue;
14
22
  labels.set(item.name, {
@@ -16,14 +24,75 @@ function collectLabelVisibility(items) {
16
24
  definingSourceUnit: item.span.sourceUnit,
17
25
  definingSourceName: item.span.sourceName,
18
26
  public: isPublicLabel(item, importedSourceUnits),
27
+ duplicateName: hasAddressPlanningNameConflict(item.name, symbolConflicts, items, index),
19
28
  });
20
29
  }
21
- return labels;
30
+ return { labels, exactSymbols };
31
+ }
32
+ function buildSymbolConflictIndex(items) {
33
+ const exact = new Map();
34
+ const declarationLower = new Map();
35
+ for (const item of items) {
36
+ for (const name of exactSymbolNames(item)) {
37
+ exact.set(name, (exact.get(name) ?? 0) + 1);
38
+ }
39
+ const declarationName = caseInsensitiveDeclarationName(item);
40
+ if (declarationName !== undefined) {
41
+ const key = declarationName.toLowerCase();
42
+ declarationLower.set(key, (declarationLower.get(key) ?? 0) + 1);
43
+ }
44
+ }
45
+ return { exact, declarationLower };
46
+ }
47
+ function hasAddressPlanningNameConflict(labelName, conflicts, items, labelIndex) {
48
+ return ((conflicts.exact.get(labelName) ?? 0) > 1 ||
49
+ (conflicts.declarationLower.get(labelName.toLowerCase()) ?? 0) > 0 ||
50
+ hasReportedEnumMemberConflict(labelName, items, labelIndex));
51
+ }
52
+ function hasReportedEnumMemberConflict(labelName, items, labelIndex) {
53
+ const lowerName = labelName.toLowerCase();
54
+ for (let index = 0; index < items.length; index += 1) {
55
+ const item = items[index];
56
+ if (item.kind !== 'enum')
57
+ continue;
58
+ for (const memberName of qualifiedEnumMemberNames(item)) {
59
+ if (memberName === labelName)
60
+ return true;
61
+ if (index > labelIndex && memberName.toLowerCase() === lowerName)
62
+ return true;
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+ function exactSymbolNames(item) {
68
+ switch (item.kind) {
69
+ case 'label':
70
+ case 'equ':
71
+ return [item.name];
72
+ case 'enum':
73
+ return qualifiedEnumMemberNames(item);
74
+ default:
75
+ return [];
76
+ }
77
+ }
78
+ function qualifiedEnumMemberNames(item) {
79
+ return item.kind === 'enum' ? item.members.map((member) => `${item.name}.${member}`) : [];
80
+ }
81
+ function caseInsensitiveDeclarationName(item) {
82
+ switch (item.kind) {
83
+ case 'enum':
84
+ case 'type':
85
+ case 'type-alias':
86
+ return item.name;
87
+ default:
88
+ return undefined;
89
+ }
22
90
  }
23
91
  function importedUnitNames(items) {
24
92
  const units = new Set();
25
93
  for (const item of items) {
26
- if (item.span.sourceRelation === 'import' && item.span.sourceUnit !== undefined) {
94
+ if (item.span.sourceUnitRelation === 'import' &&
95
+ item.span.sourceUnit !== undefined) {
27
96
  units.add(item.span.sourceUnit);
28
97
  }
29
98
  }
@@ -34,39 +103,39 @@ function isPublicLabel(item, importedSourceUnits) {
34
103
  item.span.sourceUnit === undefined ||
35
104
  !importedSourceUnits.has(item.span.sourceUnit));
36
105
  }
37
- function validateItemReferences(item, labels, diagnostics) {
106
+ function validateItemReferences(item, symbols, diagnostics) {
38
107
  switch (item.kind) {
39
108
  case 'org':
40
- validateExpression(item.expression, item.span, labels, diagnostics);
109
+ validateExpression(item.expression, item.span, symbols, diagnostics);
41
110
  return;
42
111
  case 'equ':
43
- validateExpression(item.expression, item.span, labels, diagnostics);
112
+ validateExpression(item.expression, item.span, symbols, diagnostics);
44
113
  return;
45
114
  case 'db':
46
115
  for (const value of item.values) {
47
- validateDataValue(value, item.span, labels, diagnostics);
116
+ validateDataValue(value, item.span, symbols, diagnostics);
48
117
  }
49
118
  return;
50
119
  case 'dw':
51
120
  for (const value of item.values) {
52
- validateExpression(value, item.span, labels, diagnostics);
121
+ validateExpression(value, item.span, symbols, diagnostics);
53
122
  }
54
123
  return;
55
124
  case 'ds':
56
- validateExpression(item.size, item.span, labels, diagnostics);
125
+ validateExpression(item.size, item.span, symbols, diagnostics);
57
126
  if (item.fill !== undefined) {
58
- validateExpression(item.fill, item.span, labels, diagnostics);
127
+ validateExpression(item.fill, item.span, symbols, diagnostics);
59
128
  }
60
129
  return;
61
130
  case 'align':
62
- validateExpression(item.alignment, item.span, labels, diagnostics);
131
+ validateExpression(item.alignment, item.span, symbols, diagnostics);
63
132
  return;
64
133
  case 'binfrom':
65
134
  case 'binto':
66
- validateExpression(item.expression, item.span, labels, diagnostics);
135
+ validateExpression(item.expression, item.span, symbols, diagnostics);
67
136
  return;
68
137
  case 'instruction':
69
- validateInstruction(item.instruction, item.span, labels, diagnostics);
138
+ validateInstruction(item.instruction, item.span, symbols, diagnostics);
70
139
  return;
71
140
  case 'label':
72
141
  case 'comment':
@@ -78,14 +147,14 @@ function validateItemReferences(item, labels, diagnostics) {
78
147
  return;
79
148
  }
80
149
  }
81
- function validateDataValue(value, span, labels, diagnostics) {
150
+ function validateDataValue(value, span, symbols, diagnostics) {
82
151
  if ('kind' in value && value.kind === 'string-fragment')
83
152
  return;
84
- validateExpression(value, span, labels, diagnostics);
153
+ validateExpression(value, span, symbols, diagnostics);
85
154
  }
86
- function validateInstruction(instruction, span, labels, diagnostics) {
155
+ function validateInstruction(instruction, span, symbols, diagnostics) {
87
156
  for (const expression of instructionExpressions(instruction)) {
88
- validateExpression(expression, span, labels, diagnostics);
157
+ validateExpression(expression, span, symbols, diagnostics);
89
158
  }
90
159
  }
91
160
  function instructionExpressions(instruction) {
@@ -154,49 +223,55 @@ function operandExpressions(operand) {
154
223
  return [];
155
224
  }
156
225
  }
157
- function validateExpression(expression, span, labels, diagnostics) {
226
+ function validateExpression(expression, span, symbols, diagnostics) {
158
227
  switch (expression.kind) {
159
228
  case 'symbol':
160
- validateSymbolReference(expression.name, span, labels, diagnostics);
229
+ validateSymbolReference(expression.name, span, symbols, diagnostics);
161
230
  return;
162
231
  case 'byte-function':
163
232
  case 'unary':
164
- validateExpression(expression.expression, span, labels, diagnostics);
233
+ validateExpression(expression.expression, span, symbols, diagnostics);
165
234
  return;
166
235
  case 'binary':
167
- validateExpression(expression.left, span, labels, diagnostics);
168
- validateExpression(expression.right, span, labels, diagnostics);
236
+ validateExpression(expression.left, span, symbols, diagnostics);
237
+ validateExpression(expression.right, span, symbols, diagnostics);
169
238
  return;
170
239
  case 'layout-cast':
171
- validateExpression(expression.base, span, labels, diagnostics);
240
+ validateExpression(expression.base, span, symbols, diagnostics);
172
241
  for (const part of expression.path) {
173
242
  if (part.kind === 'index') {
174
- validateExpression(part.expression, span, labels, diagnostics);
243
+ validateExpression(part.expression, span, symbols, diagnostics);
175
244
  }
176
245
  }
177
246
  return;
178
247
  case 'number':
179
248
  case 'current-location':
180
- case 'type-size':
181
249
  case 'sizeof':
182
250
  case 'offset':
183
251
  return;
252
+ case 'type-size':
253
+ if (expression.typeExpr.length === undefined) {
254
+ validateSymbolReference(expression.typeExpr.name, span, symbols, diagnostics);
255
+ }
256
+ return;
184
257
  }
185
258
  }
186
- function validateSymbolReference(name, referenceSpan, labels, diagnostics) {
187
- const label = lookupLabel(labels, name);
188
- if (!label || label.public)
259
+ function validateSymbolReference(name, referenceSpan, symbols, diagnostics) {
260
+ const label = lookupLabel(symbols, name);
261
+ if (!label || label.duplicateName || label.public)
189
262
  return;
190
263
  if (referenceSpan.sourceUnit === label.definingSourceUnit)
191
264
  return;
192
265
  diagnostics.push(diagnostic(referenceSpan, `symbol "${name}" is private to ${label.definingSourceName}; export it with @${label.name} or keep the reference inside that file`));
193
266
  }
194
- function lookupLabel(labels, name) {
195
- const direct = labels.get(name);
267
+ function lookupLabel(symbols, name) {
268
+ const direct = symbols.labels.get(name);
196
269
  if (direct)
197
270
  return direct;
271
+ if (symbols.exactSymbols.has(name))
272
+ return undefined;
198
273
  const lowerName = name.toLowerCase();
199
- for (const [key, label] of labels) {
274
+ for (const [key, label] of symbols.labels) {
200
275
  if (key.toLowerCase() === lowerName)
201
276
  return label;
202
277
  }
@@ -158,6 +158,7 @@ function spanAt(line, column) {
158
158
  column,
159
159
  ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
160
160
  ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
161
+ ...(line.sourceUnitRelation !== undefined ? { sourceUnitRelation: line.sourceUnitRelation } : {}),
161
162
  };
162
163
  }
163
164
  function firstColumn(text) {
@@ -60,7 +60,14 @@ function expandTemplateItem(ops, item, bindings, line, diagnostics, overload, ex
60
60
  }
61
61
  function opEmittedSource(line) {
62
62
  return {
63
- span: { sourceName: line.sourceName, line: line.line, column: firstColumn(line.text) },
63
+ span: {
64
+ sourceName: line.sourceName,
65
+ line: line.line,
66
+ column: firstColumn(line.text),
67
+ ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
68
+ ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
69
+ ...(line.sourceUnitRelation !== undefined ? { sourceUnitRelation: line.sourceUnitRelation } : {}),
70
+ },
64
71
  kind: 'macro',
65
72
  };
66
73
  }
@@ -6,6 +6,9 @@ export type LogicalLineLike = {
6
6
  readonly sourceName: string;
7
7
  readonly line: number;
8
8
  readonly text: string;
9
+ readonly sourceUnit?: string;
10
+ readonly sourceRelation?: 'entry' | 'include' | 'import';
11
+ readonly sourceUnitRelation?: 'entry' | 'include' | 'import';
9
12
  };
10
13
  interface OpParam {
11
14
  readonly name: string;
@@ -30,6 +30,7 @@ export async function expandSourceForTooling(options) {
30
30
  sourceStack: [],
31
31
  sourceUnit: entryFile,
32
32
  sourceRelation: 'entry',
33
+ sourceUnitRelation: 'entry',
33
34
  ...(options.preloadedText !== undefined ? { preloadedText: options.preloadedText } : {}),
34
35
  ...(options.signal !== undefined ? { signal: options.signal } : {}),
35
36
  });
@@ -60,7 +61,7 @@ async function expandFile(options) {
60
61
  recordLineComment(options.sourceLineComments, line);
61
62
  const directive = parseSourceLoadDirective(line.text);
62
63
  if (!directive) {
63
- output.push(withSourceOwnership(line, options.sourceUnit, options.sourceRelation));
64
+ output.push(withSourceOwnership(line, options.sourceUnit, options.sourceRelation, options.sourceUnitRelation));
64
65
  continue;
65
66
  }
66
67
  const result = await resolveSourcePath(sourcePath, directive.path, options.includeDirs);
@@ -88,6 +89,7 @@ async function expandFile(options) {
88
89
  sourceStack: [...options.sourceStack, { sourcePath }],
89
90
  sourceUnit: directive.kind === 'import' ? result.resolved : options.sourceUnit,
90
91
  sourceRelation: directive.kind,
92
+ sourceUnitRelation: directive.kind === 'import' ? 'import' : options.sourceUnitRelation,
91
93
  });
92
94
  if (included !== undefined) {
93
95
  output.push(...included);
@@ -95,11 +97,12 @@ async function expandFile(options) {
95
97
  }
96
98
  return output;
97
99
  }
98
- function withSourceOwnership(line, sourceUnit, sourceRelation) {
100
+ function withSourceOwnership(line, sourceUnit, sourceRelation, sourceUnitRelation) {
99
101
  return {
100
102
  ...line,
101
103
  sourceUnit,
102
104
  sourceRelation,
105
+ sourceUnitRelation,
103
106
  };
104
107
  }
105
108
  function recordLineComment(comments, line) {
@@ -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
  }
@@ -112,6 +112,7 @@ function spanForLine(line) {
112
112
  column: firstNonWhitespaceColumn(line.text),
113
113
  ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
114
114
  ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
115
+ ...(line.sourceUnitRelation !== undefined ? { sourceUnitRelation: line.sourceUnitRelation } : {}),
115
116
  };
116
117
  }
117
118
  function skipToLayoutEnd(lines, index, directive) {
@@ -127,5 +127,6 @@ function spanForLine(line) {
127
127
  column: firstNonWhitespaceColumn(line.text),
128
128
  ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
129
129
  ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
130
+ ...(line.sourceUnitRelation !== undefined ? { sourceUnitRelation: line.sourceUnitRelation } : {}),
130
131
  };
131
132
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhlagado/azm",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "AZM assembler for the Z80 family (Node.js CLI)",
5
5
  "license": "GPL-3.0-only",
6
6
  "engines": {