@jhlagado/azm 0.2.7 → 0.2.8

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 (235) hide show
  1. package/README.md +170 -69
  2. package/dist/src/api-artifacts.d.ts +20 -0
  3. package/dist/src/api-artifacts.js +165 -0
  4. package/dist/src/api-compile.d.ts +8 -2
  5. package/dist/src/api-compile.js +31 -230
  6. package/dist/src/api-register-contracts.d.ts +9 -0
  7. package/dist/src/api-register-contracts.js +77 -0
  8. package/dist/src/api-tooling.d.ts +2 -2
  9. package/dist/src/api-tooling.js +1 -1
  10. package/dist/src/assembly/address-planning.d.ts +1 -2
  11. package/dist/src/assembly/address-planning.js +119 -218
  12. package/dist/src/assembly/address-symbols.d.ts +12 -0
  13. package/dist/src/assembly/address-symbols.js +118 -0
  14. package/dist/src/assembly/fixup-emission.js +30 -48
  15. package/dist/src/assembly/program-emission.js +163 -164
  16. package/dist/src/cli/artifact-files.d.ts +15 -0
  17. package/dist/src/cli/artifact-files.js +86 -0
  18. package/dist/src/cli/parse-args.d.ts +6 -5
  19. package/dist/src/cli/parse-args.js +162 -136
  20. package/dist/src/cli/run.js +4 -1
  21. package/dist/src/cli/usage.d.ts +1 -0
  22. package/dist/src/cli/usage.js +33 -0
  23. package/dist/src/cli/write-artifacts.js +18 -91
  24. package/dist/src/core/compile.js +51 -274
  25. package/dist/src/core/conditional-assembly.d.ts +6 -0
  26. package/dist/src/core/conditional-assembly.js +181 -0
  27. package/dist/src/expansion/op-constant-expression.d.ts +3 -0
  28. package/dist/src/expansion/op-constant-expression.js +52 -0
  29. package/dist/src/expansion/op-expand-selected.d.ts +5 -0
  30. package/dist/src/expansion/op-expand-selected.js +143 -0
  31. package/dist/src/expansion/op-expansion.d.ts +5 -53
  32. package/dist/src/expansion/op-expansion.js +85 -815
  33. package/dist/src/expansion/op-instruction-instantiation.d.ts +3 -0
  34. package/dist/src/expansion/op-instruction-instantiation.js +194 -0
  35. package/dist/src/expansion/op-local-labels.d.ts +8 -0
  36. package/dist/src/expansion/op-local-labels.js +166 -0
  37. package/dist/src/expansion/op-operand-splitting.d.ts +1 -0
  38. package/dist/src/expansion/op-operand-splitting.js +44 -0
  39. package/dist/src/expansion/op-operands.d.ts +53 -0
  40. package/dist/src/expansion/op-operands.js +66 -0
  41. package/dist/src/expansion/op-selection.d.ts +18 -0
  42. package/dist/src/expansion/op-selection.js +172 -0
  43. package/dist/src/index.d.ts +2 -1
  44. package/dist/src/index.js +1 -1
  45. package/dist/src/model/diagnostic.d.ts +4 -0
  46. package/dist/src/model/diagnostic.js +4 -0
  47. package/dist/src/outputs/asm80-expression-evaluation.d.ts +10 -0
  48. package/dist/src/outputs/asm80-expression-evaluation.js +75 -0
  49. package/dist/src/outputs/asm80-expressions.d.ts +5 -0
  50. package/dist/src/outputs/asm80-expressions.js +47 -0
  51. package/dist/src/outputs/asm80-instruction-operands.d.ts +16 -0
  52. package/dist/src/outputs/asm80-instruction-operands.js +38 -0
  53. package/dist/src/outputs/asm80-instructions.d.ts +5 -0
  54. package/dist/src/outputs/asm80-instructions.js +272 -0
  55. package/dist/src/outputs/asm80-ld-operands.d.ts +10 -0
  56. package/dist/src/outputs/asm80-ld-operands.js +157 -0
  57. package/dist/src/outputs/asm80-strings.d.ts +4 -0
  58. package/dist/src/outputs/asm80-strings.js +14 -0
  59. package/dist/src/outputs/d8-files.d.ts +10 -0
  60. package/dist/src/outputs/d8-files.js +103 -0
  61. package/dist/src/outputs/d8-helpers.d.ts +21 -0
  62. package/dist/src/outputs/d8-helpers.js +136 -0
  63. package/dist/src/outputs/hex.js +26 -18
  64. package/dist/src/outputs/types.d.ts +16 -10
  65. package/dist/src/outputs/write-asm80.js +68 -597
  66. package/dist/src/outputs/write-d8.js +6 -216
  67. package/dist/src/register-contracts/accept-output.d.ts +2 -0
  68. package/dist/src/register-contracts/analyze-helpers.d.ts +29 -0
  69. package/dist/src/register-contracts/analyze-helpers.js +162 -0
  70. package/dist/src/{register-care → register-contracts}/analyze.d.ts +6 -6
  71. package/dist/src/register-contracts/analyze.js +73 -0
  72. package/dist/src/register-contracts/annotate.d.ts +11 -0
  73. package/dist/src/{register-care → register-contracts}/annotate.js +3 -3
  74. package/dist/src/register-contracts/annotations.d.ts +8 -0
  75. package/dist/src/{register-care → register-contracts}/annotations.js +3 -3
  76. package/dist/src/register-contracts/boundaryHints.d.ts +3 -0
  77. package/dist/src/register-contracts/boundaryHints.js +24 -0
  78. package/dist/src/register-contracts/carriers.d.ts +2 -0
  79. package/dist/src/register-contracts/constants.d.ts +4 -0
  80. package/dist/src/register-contracts/constants.js +51 -0
  81. package/dist/src/register-contracts/controlFlow.d.ts +5 -0
  82. package/dist/src/register-contracts/controlFlow.js +55 -0
  83. package/dist/src/register-contracts/fix.d.ts +11 -0
  84. package/dist/src/{register-care → register-contracts}/fix.js +47 -30
  85. package/dist/src/register-contracts/instruction-head.d.ts +2 -0
  86. package/dist/src/register-contracts/instruction-head.js +3 -0
  87. package/dist/src/register-contracts/instruction-operands.d.ts +3 -0
  88. package/dist/src/register-contracts/instruction-operands.js +101 -0
  89. package/dist/src/register-contracts/instruction-predicates.d.ts +6 -0
  90. package/dist/src/register-contracts/instruction-predicates.js +44 -0
  91. package/dist/src/register-contracts/interfaceContracts.d.ts +2 -0
  92. package/dist/src/register-contracts/interfaceContracts.js +68 -0
  93. package/dist/src/register-contracts/liveness.d.ts +3 -0
  94. package/dist/src/{register-care → register-contracts}/liveness.js +111 -79
  95. package/dist/src/register-contracts/operand-register-name.d.ts +2 -0
  96. package/dist/src/register-contracts/operand-register-name.js +13 -0
  97. package/dist/src/{register-care → register-contracts}/profiles.d.ts +5 -5
  98. package/dist/src/{register-care → register-contracts}/profiles.js +2 -2
  99. package/dist/src/register-contracts/programModel-boundaries.d.ts +6 -0
  100. package/dist/src/register-contracts/programModel-boundaries.js +64 -0
  101. package/dist/src/register-contracts/programModel-routines.d.ts +7 -0
  102. package/dist/src/register-contracts/programModel-routines.js +128 -0
  103. package/dist/src/register-contracts/programModel.d.ts +3 -0
  104. package/dist/src/register-contracts/programModel.js +14 -0
  105. package/dist/src/register-contracts/report.d.ts +5 -0
  106. package/dist/src/{register-care → register-contracts}/report.js +34 -17
  107. package/dist/src/register-contracts/routine-summaries.d.ts +6 -0
  108. package/dist/src/{register-care → register-contracts}/routine-summaries.js +11 -1
  109. package/dist/src/register-contracts/smartCommentBlocks.d.ts +5 -0
  110. package/dist/src/register-contracts/smartCommentBlocks.js +30 -0
  111. package/dist/src/register-contracts/smartCommentParsing.d.ts +3 -0
  112. package/dist/src/register-contracts/smartCommentParsing.js +80 -0
  113. package/dist/src/register-contracts/smartComments.d.ts +5 -0
  114. package/dist/src/register-contracts/smartComments.js +92 -0
  115. package/dist/src/register-contracts/summaries.d.ts +12 -0
  116. package/dist/src/{register-care → register-contracts}/summaries.js +7 -7
  117. package/dist/src/register-contracts/summary-boundary.d.ts +2 -0
  118. package/dist/src/register-contracts/summary-boundary.js +40 -0
  119. package/dist/src/register-contracts/summary-contract.d.ts +2 -0
  120. package/dist/src/register-contracts/summary-contract.js +45 -0
  121. package/dist/src/register-contracts/summary-result.d.ts +7 -0
  122. package/dist/src/register-contracts/summary-result.js +122 -0
  123. package/dist/src/register-contracts/summary-state.d.ts +23 -0
  124. package/dist/src/register-contracts/summary-state.js +88 -0
  125. package/dist/src/register-contracts/summary-token-transfer.d.ts +3 -0
  126. package/dist/src/register-contracts/summary-token-transfer.js +67 -0
  127. package/dist/src/register-contracts/summary.d.ts +3 -0
  128. package/dist/src/register-contracts/summary.js +266 -0
  129. package/dist/src/register-contracts/tooling.d.ts +57 -0
  130. package/dist/src/{register-care → register-contracts}/tooling.js +8 -6
  131. package/dist/src/register-contracts/types.d.ts +188 -0
  132. package/dist/src/semantics/binary-operators.d.ts +2 -0
  133. package/dist/src/semantics/binary-operators.js +15 -0
  134. package/dist/src/semantics/byte-functions.d.ts +2 -0
  135. package/dist/src/semantics/byte-functions.js +7 -0
  136. package/dist/src/semantics/constant-operator-types.d.ts +10 -0
  137. package/dist/src/semantics/constant-operator-types.js +1 -0
  138. package/dist/src/semantics/constant-operators.d.ts +3 -0
  139. package/dist/src/semantics/constant-operators.js +3 -0
  140. package/dist/src/semantics/diagnostics.d.ts +3 -0
  141. package/dist/src/semantics/diagnostics.js +10 -0
  142. package/dist/src/semantics/expression-evaluation.d.ts +11 -19
  143. package/dist/src/semantics/expression-evaluation.js +22 -334
  144. package/dist/src/semantics/layout-evaluation.d.ts +23 -0
  145. package/dist/src/semantics/layout-evaluation.js +202 -0
  146. package/dist/src/semantics/layout-format.d.ts +5 -0
  147. package/dist/src/semantics/layout-format.js +31 -0
  148. package/dist/src/semantics/layout-path.d.ts +24 -0
  149. package/dist/src/semantics/layout-path.js +58 -0
  150. package/dist/src/semantics/unary-operators.d.ts +2 -0
  151. package/dist/src/semantics/unary-operators.js +8 -0
  152. package/dist/src/source/line-comment-scanner.d.ts +1 -0
  153. package/dist/src/source/line-comment-scanner.js +51 -0
  154. package/dist/src/source/strip-line-comment.js +8 -44
  155. package/dist/src/syntax/directive-aliases.js +36 -22
  156. package/dist/src/syntax/expression-tokenizer.d.ts +30 -0
  157. package/dist/src/syntax/expression-tokenizer.js +310 -0
  158. package/dist/src/syntax/parse-directive-statement.d.ts +14 -0
  159. package/dist/src/syntax/parse-directive-statement.js +307 -0
  160. package/dist/src/syntax/parse-expression.d.ts +2 -2
  161. package/dist/src/syntax/parse-expression.js +7 -568
  162. package/dist/src/syntax/parse-layout-declarations.d.ts +9 -0
  163. package/dist/src/syntax/parse-layout-declarations.js +180 -0
  164. package/dist/src/syntax/parse-layout-expression.d.ts +5 -0
  165. package/dist/src/syntax/parse-layout-expression.js +175 -0
  166. package/dist/src/syntax/parse-line.js +4 -272
  167. package/dist/src/syntax/parse-token-expression.d.ts +3 -0
  168. package/dist/src/syntax/parse-token-expression.js +133 -0
  169. package/dist/src/tooling/case-style.js +47 -30
  170. package/dist/src/z80/effect-groups.d.ts +38 -0
  171. package/dist/src/z80/effect-groups.js +265 -0
  172. package/dist/src/z80/effect-units.d.ts +18 -0
  173. package/dist/src/z80/effect-units.js +165 -0
  174. package/dist/src/z80/effects.d.ts +1 -1
  175. package/dist/src/z80/effects.js +94 -557
  176. package/dist/src/z80/encode-core.d.ts +2 -0
  177. package/dist/src/z80/encode-core.js +42 -0
  178. package/dist/src/z80/encode-ld-helpers.d.ts +25 -0
  179. package/dist/src/z80/encode-ld-helpers.js +172 -0
  180. package/dist/src/z80/encode-ld.d.ts +2 -0
  181. package/dist/src/z80/encode-ld.js +285 -0
  182. package/dist/src/z80/encode.js +190 -542
  183. package/dist/src/z80/ld-support.d.ts +3 -0
  184. package/dist/src/z80/ld-support.js +146 -0
  185. package/dist/src/z80/operand-split-state.d.ts +8 -0
  186. package/dist/src/z80/operand-split-state.js +46 -0
  187. package/dist/src/z80/operand-split.d.ts +1 -0
  188. package/dist/src/z80/operand-split.js +13 -0
  189. package/dist/src/z80/parse-basic.d.ts +4 -0
  190. package/dist/src/z80/parse-basic.js +39 -0
  191. package/dist/src/z80/parse-branch.d.ts +4 -0
  192. package/dist/src/z80/parse-branch.js +218 -0
  193. package/dist/src/z80/parse-conditions.d.ts +6 -0
  194. package/dist/src/z80/parse-conditions.js +10 -0
  195. package/dist/src/z80/parse-exchange.d.ts +2 -0
  196. package/dist/src/z80/parse-exchange.js +30 -0
  197. package/dist/src/z80/parse-instruction.js +224 -1010
  198. package/dist/src/z80/parse-io-control.d.ts +5 -0
  199. package/dist/src/z80/parse-io-control.js +108 -0
  200. package/dist/src/z80/parse-ld.d.ts +2 -0
  201. package/dist/src/z80/parse-ld.js +83 -0
  202. package/dist/src/z80/parse-operands.d.ts +41 -0
  203. package/dist/src/z80/parse-operands.js +259 -0
  204. package/docs/reference/cli.md +42 -35
  205. package/docs/reference/tooling-api.md +20 -16
  206. package/package.json +1 -1
  207. package/dist/src/register-care/accept-output.d.ts +0 -2
  208. package/dist/src/register-care/analyze.js +0 -166
  209. package/dist/src/register-care/annotate.d.ts +0 -11
  210. package/dist/src/register-care/annotations.d.ts +0 -8
  211. package/dist/src/register-care/boundaryHints.d.ts +0 -3
  212. package/dist/src/register-care/boundaryHints.js +0 -80
  213. package/dist/src/register-care/carriers.d.ts +0 -2
  214. package/dist/src/register-care/controlFlow.d.ts +0 -5
  215. package/dist/src/register-care/controlFlow.js +0 -38
  216. package/dist/src/register-care/fix.d.ts +0 -11
  217. package/dist/src/register-care/instruction-shape.d.ts +0 -11
  218. package/dist/src/register-care/instruction-shape.js +0 -129
  219. package/dist/src/register-care/liveness.d.ts +0 -3
  220. package/dist/src/register-care/programModel.d.ts +0 -3
  221. package/dist/src/register-care/programModel.js +0 -266
  222. package/dist/src/register-care/report.d.ts +0 -5
  223. package/dist/src/register-care/routine-summaries.d.ts +0 -6
  224. package/dist/src/register-care/smartComments.d.ts +0 -5
  225. package/dist/src/register-care/smartComments.js +0 -243
  226. package/dist/src/register-care/summaries.d.ts +0 -12
  227. package/dist/src/register-care/summary.d.ts +0 -3
  228. package/dist/src/register-care/summary.js +0 -474
  229. package/dist/src/register-care/tooling.d.ts +0 -43
  230. package/dist/src/register-care/types.d.ts +0 -172
  231. /package/dist/src/{register-care → register-contracts}/accept-output.js +0 -0
  232. /package/dist/src/{register-care → register-contracts}/carriers.js +0 -0
  233. /package/dist/src/{register-care → register-contracts}/sourceText.d.ts +0 -0
  234. /package/dist/src/{register-care → register-contracts}/sourceText.js +0 -0
  235. /package/dist/src/{register-care → register-contracts}/types.js +0 -0
@@ -1,50 +1,63 @@
1
- import { encodeZ80Instruction } from '../z80/encode.js';
2
- import { parseZ80Instruction } from '../z80/parse-instruction.js';
3
1
  import { stripLineComment } from '../source/strip-line-comment.js';
4
2
  import { parseLogicalLine } from '../syntax/parse-line.js';
5
- import { parseExpression } from '../syntax/parse-expression.js';
3
+ import { expandSelectedOp } from './op-expand-selected.js';
4
+ import { splitOperands } from './op-operand-splitting.js';
5
+ import { parseOpMatcher, parseOpOperand, } from './op-operands.js';
6
6
  export function collectOps(lines, diagnostics, parseOptions = {}) {
7
7
  const ops = new Map();
8
8
  const opLineIndexes = new Set();
9
9
  for (let index = 0; index < lines.length; index += 1) {
10
10
  const line = lines[index];
11
- if (isTopLevelEnd(line.text)) {
11
+ if (isTopLevelEnd(line.text))
12
12
  break;
13
- }
14
- const opHeader = /^op\s+([A-Za-z_][A-Za-z0-9_]*)\s*\((.*)\)\s*$/i.exec(stripLineComment(line.text).trim());
15
- if (!opHeader) {
13
+ const header = parseOpHeader(line, diagnostics);
14
+ if (!header)
16
15
  continue;
17
- }
18
- const name = opHeader[1] ?? '';
19
- const params = parseOpParams(opHeader[2] ?? '', line, diagnostics);
20
- const paramNames = new Set(params.map((param) => param.name));
21
- const body = [];
22
- let terminated = false;
23
16
  opLineIndexes.add(index);
24
- for (index += 1; index < lines.length; index += 1) {
25
- opLineIndexes.add(index);
26
- const bodyLine = lines[index];
27
- const bodyText = stripLineComment(bodyLine.text).trim();
28
- if (/^end\s*$/i.test(bodyText)) {
29
- terminated = true;
30
- break;
31
- }
32
- const template = parseOpBodyTemplate(bodyLine, paramNames, diagnostics, parseOptions);
33
- if (template) {
34
- body.push(template);
35
- }
36
- }
37
- if (!terminated) {
38
- diagnostics.push(parseDiagnostic(line, `op ${name} missing end`));
39
- }
40
- if (params.length > 0 || terminated) {
41
- const overloads = ops.get(name) ?? [];
42
- overloads.push({ name, params, body, sourceName: line.sourceName, line: line.line });
43
- ops.set(name, overloads);
44
- }
17
+ const collected = collectOpBody(lines, index + 1, header.params, diagnostics, parseOptions, opLineIndexes);
18
+ index = collected.endIndex;
19
+ recordCollectedOp(ops, header, collected, line, diagnostics);
45
20
  }
46
21
  return { ops, opLineIndexes };
47
22
  }
23
+ function parseOpHeader(line, diagnostics) {
24
+ const opHeader = /^op\s+([A-Za-z_][A-Za-z0-9_]*)\s*\((.*)\)\s*$/i.exec(stripLineComment(line.text).trim());
25
+ if (!opHeader)
26
+ return undefined;
27
+ return { name: opHeader[1] ?? '', params: parseOpParams(opHeader[2] ?? '', line, diagnostics) };
28
+ }
29
+ function collectOpBody(lines, startIndex, params, diagnostics, parseOptions, opLineIndexes) {
30
+ const paramNames = new Set(params.map((param) => param.name));
31
+ const body = [];
32
+ for (let index = startIndex; index < lines.length; index += 1) {
33
+ opLineIndexes.add(index);
34
+ const bodyLine = lines[index];
35
+ if (isOpEnd(bodyLine.text)) {
36
+ return { body, terminated: true, endIndex: index };
37
+ }
38
+ const template = parseOpBodyTemplate(bodyLine, paramNames, diagnostics, parseOptions);
39
+ if (template)
40
+ body.push(template);
41
+ }
42
+ return { body, terminated: false, endIndex: lines.length };
43
+ }
44
+ function recordCollectedOp(ops, header, collected, line, diagnostics) {
45
+ if (!collected.terminated) {
46
+ diagnostics.push(parseDiagnostic(line, `op ${header.name} missing end`));
47
+ }
48
+ if (header.params.length === 0 && !collected.terminated)
49
+ return;
50
+ ops.set(header.name, [
51
+ ...(ops.get(header.name) ?? []),
52
+ {
53
+ name: header.name,
54
+ params: header.params,
55
+ body: collected.body,
56
+ sourceName: line.sourceName,
57
+ line: line.line,
58
+ },
59
+ ]);
60
+ }
48
61
  export function parseOpInvocation(line) {
49
62
  const text = stripLineComment(line.text).trim();
50
63
  const match = /^([A-Za-z_][A-Za-z0-9_]*)(?:\s+(.+))?$/.exec(text);
@@ -62,66 +75,6 @@ export function parseOpInvocation(line) {
62
75
  export function expandOpInvocation(ops, overloads, operands, line, diagnostics) {
63
76
  return expandSelectedOp(ops, overloads, operands, line, diagnostics, []);
64
77
  }
65
- function expandSelectedOp(ops, overloads, operands, line, diagnostics, stack) {
66
- const name = overloads[0]?.name ?? '<unknown>';
67
- const cycleStart = stack.findIndex((entry) => entry.name.toLowerCase() === name.toLowerCase());
68
- if (cycleStart !== -1) {
69
- diagnostics.push(parseDiagnostic(line, [
70
- `Cyclic op expansion detected for "${name}".`,
71
- `expansion chain: ${[...stack.slice(cycleStart), overloads[0]].map(formatOpChainEntry).join(' -> ')}`,
72
- ].join('\n')));
73
- return [];
74
- }
75
- const selection = selectOpOverload(overloads, operands);
76
- if (selection.kind !== 'selected') {
77
- diagnostics.push(parseDiagnostic(line, formatOpSelectionDiagnostic(selection, overloads, operands)));
78
- return [];
79
- }
80
- const bindings = new Map();
81
- selection.overload.params.forEach((param, index) => {
82
- bindings.set(param.name, operands[index]);
83
- });
84
- const expanded = [];
85
- const localLabelMap = buildLocalLabelMap(selection.overload, line);
86
- const expansionStack = [...stack, selection.overload];
87
- const emittedSource = {
88
- span: { sourceName: line.sourceName, line: line.line, column: firstColumn(line.text) },
89
- kind: 'macro',
90
- };
91
- for (const item of selection.overload.body) {
92
- if (item.kind === 'source-items') {
93
- expanded.push(...renameSourceItems(item.items, localLabelMap).map((renamed) => stampOpSource(renamed, emittedSource)));
94
- continue;
95
- }
96
- const concreteOperands = instantiateTemplateOperands(item, bindings);
97
- if (!concreteOperands) {
98
- diagnostics.push(parseDiagnostic(line, `invalid op expansion in "${selection.overload.name}"`));
99
- continue;
100
- }
101
- const nested = ops.get(item.mnemonic);
102
- if (nested) {
103
- expanded.push(...expandSelectedOp(ops, nested, concreteOperands, line, diagnostics, expansionStack));
104
- continue;
105
- }
106
- const instruction = instantiateTemplateInstruction(item, concreteOperands);
107
- if (!instruction || encodeZ80Instruction(instruction).size === 0) {
108
- reportInvalidOpExpansion(line, diagnostics, selection.overload, expansionStack, item, concreteOperands);
109
- continue;
110
- }
111
- expanded.push({
112
- kind: 'instruction',
113
- instruction: renameInstructionExpressions(instruction, localLabelMap),
114
- span: emittedSource.span,
115
- emittedSource,
116
- });
117
- }
118
- return expanded;
119
- }
120
- function stampOpSource(item, emittedSource) {
121
- if (item.kind !== 'instruction')
122
- return item;
123
- return { ...item, emittedSource };
124
- }
125
78
  function parseOpParams(text, line, diagnostics) {
126
79
  const trimmed = text.trim();
127
80
  if (trimmed.length === 0) {
@@ -149,70 +102,48 @@ function parseOpParams(text, line, diagnostics) {
149
102
  }
150
103
  return params;
151
104
  }
152
- function parseOpMatcher(text) {
153
- if (/^reg8$/i.test(text)) {
154
- return { kind: 'reg8' };
155
- }
156
- if (/^reg16$/i.test(text)) {
157
- return { kind: 'reg16' };
158
- }
159
- if (/^imm8$/i.test(text)) {
160
- return { kind: 'imm8' };
161
- }
162
- if (/^imm16$/i.test(text)) {
163
- return { kind: 'imm16' };
164
- }
165
- if (/^cc$/i.test(text)) {
166
- return { kind: 'cc' };
167
- }
168
- if (/^idx16$/i.test(text)) {
169
- return { kind: 'idx16' };
170
- }
171
- if (/^ea$/i.test(text)) {
172
- return { kind: 'ea' };
173
- }
174
- if (/^mem8$/i.test(text)) {
175
- return { kind: 'mem8' };
176
- }
177
- if (/^mem16$/i.test(text)) {
178
- return { kind: 'mem16' };
179
- }
180
- if (/^[A-Za-z][A-Za-z0-9_]*$/.test(text)) {
181
- return { kind: 'fixed', token: text.toUpperCase() };
182
- }
183
- return undefined;
184
- }
185
105
  function parseOpBodyTemplate(line, paramNames, diagnostics, parseOptions) {
186
106
  const text = stripLineComment(line.text).trim();
187
- if (text.length === 0) {
107
+ if (text.length === 0)
188
108
  return undefined;
109
+ const template = parseTemplateInstructionCandidate(text, paramNames);
110
+ if (template && templateContainsParam(template)) {
111
+ return template;
189
112
  }
113
+ const parsedSource = parseOpBodySourceItems(line, diagnostics, parseOptions, template !== undefined);
114
+ if (parsedSource)
115
+ return parsedSource;
116
+ return template;
117
+ }
118
+ function parseTemplateInstructionCandidate(text, paramNames) {
190
119
  const instruction = /^([A-Za-z_][A-Za-z0-9_]*)(?:\s+(.+))?$/.exec(text);
191
- if (instruction) {
192
- const operandText = instruction[2] ?? '';
193
- const operands = operandText.trim().length === 0
194
- ? []
195
- : splitOperands(operandText).map((operand) => parseTemplateOperand(operand, paramNames));
196
- if (operands.every((operand) => operand !== undefined)) {
197
- if (!operands.some((operand) => operand?.kind === 'param' || operand?.kind === 'port-param')) {
198
- const result = parseLogicalLine(line, parseOptions);
199
- if (result.items.length > 0 || !isUnsupportedSourceLine(result.diagnostics)) {
200
- diagnostics.push(...result.diagnostics);
201
- return result.items.length > 0
202
- ? { kind: 'source-items', items: result.items }
203
- : undefined;
204
- }
205
- }
206
- return {
207
- kind: 'instruction',
208
- mnemonic: (instruction[1] ?? '').toLowerCase(),
209
- operands: operands,
210
- };
211
- }
212
- }
120
+ if (!instruction)
121
+ return undefined;
122
+ const operands = parseTemplateOperands(instruction[2] ?? '', paramNames);
123
+ return operands
124
+ ? { kind: 'instruction', mnemonic: (instruction[1] ?? '').toLowerCase(), operands }
125
+ : undefined;
126
+ }
127
+ function parseTemplateOperands(operandText, paramNames) {
128
+ const operands = operandText.trim().length === 0
129
+ ? []
130
+ : splitOperands(operandText).map((operand) => parseTemplateOperand(operand, paramNames));
131
+ return operands.every((operand) => operand !== undefined)
132
+ ? operands
133
+ : undefined;
134
+ }
135
+ function templateContainsParam(template) {
136
+ return template.operands.some((operand) => operand.kind === 'param' || operand.kind === 'port-param');
137
+ }
138
+ function parseOpBodySourceItems(line, diagnostics, parseOptions, suppressUnsupportedLineDiagnostic) {
213
139
  const result = parseLogicalLine(line, parseOptions);
140
+ if (suppressUnsupportedLineDiagnostic && isUnsupportedSourceLine(result.diagnostics)) {
141
+ return undefined;
142
+ }
214
143
  diagnostics.push(...result.diagnostics);
215
- return result.items.length > 0 ? { kind: 'source-items', items: result.items } : undefined;
144
+ if (result.items.length === 0)
145
+ return undefined;
146
+ return { kind: 'source-items', items: result.items };
216
147
  }
217
148
  function isUnsupportedSourceLine(diagnostics) {
218
149
  return (diagnostics.length === 1 &&
@@ -231,673 +162,12 @@ function parseTemplateOperand(text, paramNames) {
231
162
  const operand = parseOpOperand(trimmed);
232
163
  return operand ? { kind: 'literal', operand } : undefined;
233
164
  }
234
- function parseOpOperand(text) {
235
- const trimmed = text.trim();
236
- if (/^(A|B|C|D|E|H|L)$/i.test(trimmed)) {
237
- return { kind: 'reg8', register: trimmed.toLowerCase(), text: trimmed.toUpperCase() };
238
- }
239
- if (/^(BC|DE|HL|SP)$/i.test(trimmed)) {
240
- return { kind: 'reg16', register: trimmed.toLowerCase(), text: trimmed.toUpperCase() };
241
- }
242
- if (/^\(HL\)$/i.test(trimmed)) {
243
- return { kind: 'reg-indirect', register: 'hl', text: '(HL)' };
244
- }
245
- const indexed = parseIndexedOperand(trimmed);
246
- if (indexed) {
247
- return indexed;
248
- }
249
- if (trimmed.startsWith('(') && trimmed.endsWith(')')) {
250
- const expression = parseExpression(trimmed.slice(1, -1).trim());
251
- return expression ? { kind: 'mem-abs', expression, text: trimmed } : undefined;
252
- }
253
- const expression = parseExpression(trimmed);
254
- return expression ? { kind: 'imm', expression, text: trimmed } : undefined;
255
- }
256
- function selectOpOverload(overloads, operands) {
257
- const arityMatches = overloads.filter((overload) => overload.params.length === operands.length);
258
- if (arityMatches.length === 0) {
259
- return { kind: 'arity_mismatch' };
260
- }
261
- const matches = arityMatches.filter((overload) => overload.params.every((param, index) => matcherMatchesOperand(param.matcher, operands[index])));
262
- if (matches.length === 0) {
263
- return { kind: 'no_match', candidates: arityMatches };
264
- }
265
- if (matches.length === 1) {
266
- return { kind: 'selected', overload: matches[0] };
267
- }
268
- const selected = mostSpecificOverload(matches, operands);
269
- return selected
270
- ? { kind: 'selected', overload: selected }
271
- : { kind: 'ambiguous', candidates: matches };
272
- }
273
- function mostSpecificOverload(overloads, operands) {
274
- for (const candidate of overloads) {
275
- let beatsAll = true;
276
- for (const other of overloads) {
277
- if (candidate === other) {
278
- continue;
279
- }
280
- if (compareOverloadSpecificity(candidate, other, operands) !== 'x') {
281
- beatsAll = false;
282
- break;
283
- }
284
- }
285
- if (beatsAll) {
286
- return candidate;
287
- }
288
- }
289
- return undefined;
290
- }
291
- function compareOverloadSpecificity(x, y, operands) {
292
- let xBetter = 0;
293
- let yBetter = 0;
294
- for (let index = 0; index < operands.length; index += 1) {
295
- const comparison = compareMatcherSpecificity(x.params[index].matcher, y.params[index].matcher, operands[index]);
296
- if (comparison === 'x') {
297
- xBetter += 1;
298
- }
299
- else if (comparison === 'y') {
300
- yBetter += 1;
301
- }
302
- }
303
- if (xBetter > 0 && yBetter === 0)
304
- return 'x';
305
- if (yBetter > 0 && xBetter === 0)
306
- return 'y';
307
- if (xBetter === 0 && yBetter === 0)
308
- return 'equal';
309
- return 'incomparable';
310
- }
311
- function compareMatcherSpecificity(x, y, operand) {
312
- if (x.kind === y.kind)
313
- return 'equal';
314
- if (x.kind === 'fixed' && fixedTokenBeatsMatcher(x, y, operand))
315
- return 'x';
316
- if (y.kind === 'fixed' && fixedTokenBeatsMatcher(y, x, operand))
317
- return 'y';
318
- if (x.kind === 'imm8' && y.kind === 'imm16' && operand.kind === 'imm') {
319
- return expressionFitsKnownImm8(operand.expression) ? 'x' : 'equal';
320
- }
321
- if (x.kind === 'imm16' && y.kind === 'imm8' && operand.kind === 'imm') {
322
- return expressionFitsKnownImm8(operand.expression) ? 'y' : 'equal';
323
- }
324
- return 'equal';
325
- }
326
- function fixedTokenBeatsMatcher(fixed, other, operand) {
327
- return ((other.kind === 'reg8' && operand.kind === 'reg8' && operand.text === fixed.token) ||
328
- (other.kind === 'reg16' && operand.kind === 'reg16' && operand.text === fixed.token) ||
329
- (other.kind === 'cc' &&
330
- isConditionToken(fixed.token) &&
331
- operand.text.toUpperCase() === fixed.token));
332
- }
333
- function matcherMatchesOperand(matcher, operand) {
334
- switch (matcher.kind) {
335
- case 'reg8':
336
- return operand.kind === 'reg8';
337
- case 'reg16':
338
- return operand.kind === 'reg16';
339
- case 'imm8':
340
- return operand.kind === 'imm' && expressionFitsKnownImm8(operand.expression);
341
- case 'imm16':
342
- return operand.kind === 'imm' && expressionFitsKnownImm16(operand.expression);
343
- case 'cc':
344
- return isConditionToken(operand.text);
345
- case 'idx16':
346
- return operand.kind === 'indexed';
347
- case 'ea':
348
- return operand.kind === 'imm';
349
- case 'mem8':
350
- case 'mem16':
351
- return (operand.kind === 'reg-indirect' || operand.kind === 'mem-abs' || operand.kind === 'indexed');
352
- case 'fixed':
353
- return operand.text.toUpperCase() === matcher.token;
354
- }
355
- }
356
- function formatOpSelectionDiagnostic(selection, overloads, operands) {
357
- const name = overloads[0]?.name ?? '<unknown>';
358
- const operandSummary = `call-site operands: (${operands.map(formatOpOperand).join(', ')})`;
359
- switch (selection.kind) {
360
- case 'arity_mismatch':
361
- return [
362
- `No op overload of "${name}" accepts ${operands.length} operand(s).`,
363
- 'available overloads:',
364
- ...overloads.map((overload) => ` - ${formatOpSignature(overload)}`),
365
- ].join('\n');
366
- case 'no_match': {
367
- return [
368
- `No matching op overload for "${name}" with provided operands.`,
369
- operandSummary,
370
- 'available overloads:',
371
- ...selection.candidates.map((candidate) => {
372
- const mismatch = firstMismatchReason(candidate, operands);
373
- return ` - ${formatOpSignatureWithLocation(candidate)}${mismatch ? ` ; ${mismatch}` : ''}`;
374
- }),
375
- ].join('\n');
376
- }
377
- case 'ambiguous':
378
- return [
379
- `Ambiguous op overload for "${name}" (${selection.candidates.length} matches).`,
380
- operandSummary,
381
- 'equally specific candidates:',
382
- ...selection.candidates.map((candidate) => ` - ${formatOpSignatureWithLocation(candidate)}`),
383
- ].join('\n');
384
- }
385
- }
386
- function firstMismatchReason(overload, operands) {
387
- for (let index = 0; index < overload.params.length; index += 1) {
388
- const param = overload.params[index];
389
- const operand = operands[index];
390
- if (!matcherMatchesOperand(param.matcher, operand)) {
391
- return `${param.name}: ${matcherMismatchReason(param.matcher, operand)}`;
392
- }
393
- }
394
- return undefined;
395
- }
396
- function matcherMismatchReason(matcher, operand) {
397
- switch (matcher.kind) {
398
- case 'reg8':
399
- return `expects reg8, got ${formatOpOperand(operand)}`;
400
- case 'reg16':
401
- return `expects reg16, got ${formatOpOperand(operand)}`;
402
- case 'imm8':
403
- return `expects imm8, got ${formatOpOperand(operand)}`;
404
- case 'imm16':
405
- return `expects imm16, got ${formatOpOperand(operand)}`;
406
- case 'cc':
407
- return `expects condition token NZ/Z/NC/C/PO/PE/P/M, got ${formatOpOperand(operand)}`;
408
- case 'idx16':
409
- return `expects IX/IY indexed memory operand, got ${formatOpOperand(operand)}`;
410
- case 'ea':
411
- return `expects ea, got ${formatOpOperand(operand)}`;
412
- case 'mem8':
413
- return `expects mem8 dereference, got ${formatOpOperand(operand)}`;
414
- case 'mem16':
415
- return `expects mem16 dereference, got ${formatOpOperand(operand)}`;
416
- case 'fixed':
417
- return `expects ${matcher.token}, got ${formatOpOperand(operand)}`;
418
- }
419
- }
420
- function formatOpSignature(op) {
421
- return `${op.name}(${op.params.map((param) => `${param.name} ${formatMatcher(param.matcher)}`).join(', ')})`;
422
- }
423
- function formatOpSignatureWithLocation(op) {
424
- return `${formatOpSignature(op)} (${op.sourceName}:${op.line})`;
425
- }
426
- function formatOpChainEntry(op) {
427
- return `${op.name} (${op.sourceName}:${op.line})`;
428
- }
429
- function formatMatcher(matcher) {
430
- return matcher.kind === 'fixed' ? matcher.token : matcher.kind;
431
- }
432
- function formatOpOperand(operand) {
433
- return operand.kind === 'imm' ? operand.text : operand.text.toUpperCase();
434
- }
435
- function formatExpandedInstruction(mnemonic, operands) {
436
- return `${mnemonic} ${operands.map(formatOpOperand).join(', ')}`.trim();
437
- }
438
- function reportInvalidOpExpansion(line, diagnostics, overload, expansionStack, item, concreteOperands) {
439
- const expandedInstruction = formatExpandedInstruction(item.mnemonic, concreteOperands);
440
- const underlying = expandedInstructionUnderlyingError(item, concreteOperands);
441
- if (underlying) {
442
- diagnostics.push(parseDiagnostic(line, underlying));
443
- }
444
- diagnostics.push(parseDiagnostic(line, formatInvalidOpExpansionDiagnostic(overload, expandedInstruction, expansionStack)));
445
- }
446
- function expandedInstructionUnderlyingError(item, concreteOperands) {
447
- const instruction = instantiateTemplateInstruction(item, concreteOperands);
448
- if (instruction && encodeZ80Instruction(instruction).size > 0) {
449
- return undefined;
450
- }
451
- if (instruction?.mnemonic === 'ld' || item.mnemonic === 'ld') {
452
- return 'ld expects a supported register/memory/immediate transfer form';
453
- }
454
- const parsed = parseZ80Instruction(formatExpandedInstruction(item.mnemonic, concreteOperands));
455
- return parsed?.error;
456
- }
457
- function formatInvalidOpExpansionDiagnostic(overload, expandedInstruction, expansionStack) {
458
- return [
459
- `Invalid op expansion in "${overload.name}" at call site.`,
460
- `expanded instruction: ${expandedInstruction}`,
461
- `op definition: ${overload.sourceName}:${overload.line}`,
462
- `expansion chain: ${expansionStack.map(formatOpChainEntry).join(' -> ')}`,
463
- ].join('\n');
464
- }
465
- function instantiateTemplateInstruction(item, concrete) {
466
- const mnemonic = item.mnemonic;
467
- if (mnemonic === 'ld' && concrete.length === 2) {
468
- const target = toZ80Operand(concrete[0]);
469
- const source = toZ80Operand(concrete[1]);
470
- return target && source ? { mnemonic: 'ld', target, source } : undefined;
471
- }
472
- if (mnemonic === 'in' && concrete.length === 2 && concrete[0]?.kind === 'reg8') {
473
- const port = toPortOperand(concrete[1]);
474
- return port
475
- ? {
476
- mnemonic: 'in',
477
- target: { kind: 'reg8', register: concrete[0].register },
478
- port,
479
- }
480
- : undefined;
481
- }
482
- if (mnemonic === 'out' && concrete.length === 2) {
483
- const port = toPortOperand(concrete[0]);
484
- const source = concrete[1]?.kind === 'reg8' ? concrete[1] : undefined;
485
- return port && source
486
- ? {
487
- mnemonic: 'out',
488
- port,
489
- source: { kind: 'reg8', register: source.register },
490
- }
491
- : undefined;
492
- }
493
- if ((mnemonic === 'inc' || mnemonic === 'dec') && concrete.length === 1) {
494
- const operand = toIncDecOperand(concrete[0]);
495
- return operand ? { mnemonic, operand } : undefined;
496
- }
497
- if (mnemonic === 'jp' && concrete.length === 1 && concrete[0]?.kind === 'imm') {
498
- return { mnemonic: 'jp', expression: concrete[0].expression };
499
- }
500
- if (mnemonic === 'jp' &&
501
- concrete.length === 2 &&
502
- isConditionToken(concrete[0]?.text ?? '') &&
503
- concrete[1]?.kind === 'imm') {
504
- return {
505
- mnemonic: 'jp-cc',
506
- condition: concrete[0].text.toLowerCase(),
507
- expression: concrete[1].expression,
508
- };
509
- }
510
- if (mnemonic === 'jr' && concrete.length === 1 && concrete[0]?.kind === 'imm') {
511
- return { mnemonic: 'jr', expression: concrete[0].expression };
512
- }
513
- if (mnemonic === 'jr' &&
514
- concrete.length === 2 &&
515
- isRelativeConditionToken(concrete[0]?.text ?? '') &&
516
- concrete[1]?.kind === 'imm') {
517
- return {
518
- mnemonic: 'jr-cc',
519
- condition: concrete[0].text.toLowerCase(),
520
- expression: concrete[1].expression,
521
- };
522
- }
523
- if (isAluMnemonic(mnemonic) && concrete.length === 1) {
524
- const source = toZ80Operand(concrete[0]);
525
- return source ? { mnemonic, source } : undefined;
526
- }
527
- if (isAluMnemonic(mnemonic) && concrete.length === 2 && concrete[0]?.kind === 'reg8') {
528
- if (concrete[0].register !== 'a') {
529
- return undefined;
530
- }
531
- const source = toZ80Operand(concrete[1]);
532
- return source ? { mnemonic, source } : undefined;
533
- }
534
- if ((mnemonic === 'add' || mnemonic === 'adc' || mnemonic === 'sbc') &&
535
- concrete.length === 2 &&
536
- concrete[0]?.kind === 'reg16' &&
537
- concrete[1]?.kind === 'reg16') {
538
- const target = toZ80Operand(concrete[0]);
539
- const source = toZ80Operand(concrete[1]);
540
- return target?.kind === 'reg16' && source?.kind === 'reg16'
541
- ? { mnemonic, target, source }
542
- : undefined;
543
- }
544
- return parseExpandedInstruction(mnemonic, concrete);
545
- }
546
- function instantiateTemplateOperands(item, bindings) {
547
- const operands = item.operands.map((operand) => operand.kind === 'param'
548
- ? bindings.get(operand.name)
549
- : operand.kind === 'port-param'
550
- ? parenthesizedOperandFromBinding(item.mnemonic, bindings.get(operand.name))
551
- : operand.operand);
552
- if (operands.some((operand) => operand === undefined)) {
553
- return undefined;
554
- }
555
- return operands;
556
- }
557
- function toZ80Operand(operand) {
558
- switch (operand.kind) {
559
- case 'reg8':
560
- return { kind: 'reg8', register: operand.register };
561
- case 'reg16':
562
- return { kind: 'reg16', register: operand.register };
563
- case 'reg-indirect':
564
- return { kind: 'reg-indirect', register: operand.register };
565
- case 'mem-abs':
566
- return { kind: 'mem-abs', expression: operand.expression };
567
- case 'indexed':
568
- return {
569
- kind: 'indexed',
570
- register: operand.register,
571
- displacement: operand.displacement,
572
- };
573
- case 'imm':
574
- return { kind: 'imm', expression: operand.expression };
575
- }
576
- }
577
- function toPortOperand(operand) {
578
- if (operand.kind === 'reg8' && operand.register === 'c') {
579
- return { kind: 'c' };
580
- }
581
- return operand.kind === 'imm' ? { kind: 'imm', expression: operand.expression } : undefined;
582
- }
583
- function toIncDecOperand(operand) {
584
- switch (operand.kind) {
585
- case 'reg8':
586
- return { kind: 'reg8', register: operand.register };
587
- case 'reg16':
588
- return { kind: 'reg16', register: operand.register };
589
- case 'reg-indirect':
590
- return { kind: 'reg-indirect', register: operand.register };
591
- case 'indexed':
592
- return {
593
- kind: 'indexed',
594
- register: operand.register,
595
- displacement: operand.displacement,
596
- };
597
- case 'imm':
598
- return undefined;
599
- }
600
- }
601
- function parenthesizedOperandFromBinding(mnemonic, operand) {
602
- if (operand?.kind !== 'imm') {
603
- return undefined;
604
- }
605
- return mnemonic === 'in' || mnemonic === 'out'
606
- ? operand
607
- : { kind: 'mem-abs', expression: operand.expression, text: `(${operand.text})` };
608
- }
609
- function buildLocalLabelMap(op, line) {
610
- const map = new Map();
611
- let ordinal = 0;
612
- for (const item of op.body) {
613
- if (item.kind !== 'source-items') {
614
- continue;
615
- }
616
- for (const sourceItem of item.items) {
617
- if (sourceItem.kind === 'label' && !map.has(sourceItem.name)) {
618
- map.set(sourceItem.name, `__azm_op_${op.name}_${sourceItem.name}_${line.line}_${ordinal}`);
619
- ordinal += 1;
620
- }
621
- }
622
- }
623
- return map;
624
- }
625
- function renameSourceItems(items, localLabelMap) {
626
- if (localLabelMap.size === 0) {
627
- return items;
628
- }
629
- return items.map((item) => renameSourceItem(item, localLabelMap));
630
- }
631
- function renameSourceItem(item, localLabelMap) {
632
- switch (item.kind) {
633
- case 'label':
634
- return { ...item, name: localLabelMap.get(item.name) ?? item.name };
635
- case 'org':
636
- case 'binfrom':
637
- case 'binto':
638
- return { ...item, expression: renameExpression(item.expression, localLabelMap) };
639
- case 'equ':
640
- return { ...item, expression: renameExpression(item.expression, localLabelMap) };
641
- case 'db':
642
- return {
643
- ...item,
644
- values: item.values.map((value) => value.kind === 'string-fragment' ? value : renameExpression(value, localLabelMap)),
645
- };
646
- case 'dw':
647
- return {
648
- ...item,
649
- values: item.values.map((value) => renameExpression(value, localLabelMap)),
650
- };
651
- case 'ds':
652
- return item.fill
653
- ? {
654
- ...item,
655
- size: renameExpression(item.size, localLabelMap),
656
- fill: renameExpression(item.fill, localLabelMap),
657
- }
658
- : { ...item, size: renameExpression(item.size, localLabelMap) };
659
- case 'align':
660
- return { ...item, alignment: renameExpression(item.alignment, localLabelMap) };
661
- case 'instruction':
662
- return {
663
- ...item,
664
- instruction: renameInstructionExpressions(item.instruction, localLabelMap),
665
- };
666
- case 'comment':
667
- case 'end':
668
- case 'enum':
669
- case 'type':
670
- case 'type-alias':
671
- case 'string-data':
672
- return item;
673
- }
674
- }
675
- function renameInstructionExpressions(instruction, localLabelMap) {
676
- if (localLabelMap.size === 0) {
677
- return instruction;
678
- }
679
- switch (instruction.mnemonic) {
680
- case 'ld':
681
- return {
682
- ...instruction,
683
- target: renameOperandExpression(instruction.target, localLabelMap),
684
- source: renameOperandExpression(instruction.source, localLabelMap),
685
- };
686
- case 'ld-a-imm':
687
- return {
688
- ...instruction,
689
- expression: renameExpression(instruction.expression, localLabelMap),
690
- };
691
- case 'in':
692
- return { ...instruction, port: renamePortExpression(instruction.port, localLabelMap) };
693
- case 'out':
694
- return { ...instruction, port: renamePortExpression(instruction.port, localLabelMap) };
695
- case 'add':
696
- case 'adc':
697
- case 'sub':
698
- case 'sbc':
699
- if ('target' in instruction) {
700
- return instruction;
701
- }
702
- return {
703
- ...instruction,
704
- source: renameOperandExpression(instruction.source, localLabelMap),
705
- };
706
- case 'and':
707
- case 'or':
708
- case 'xor':
709
- case 'cp':
710
- return { ...instruction, source: renameOperandExpression(instruction.source, localLabelMap) };
711
- case 'jp':
712
- case 'call':
713
- case 'jr':
714
- case 'djnz':
715
- return {
716
- ...instruction,
717
- expression: renameExpression(instruction.expression, localLabelMap),
718
- };
719
- case 'jp-cc':
720
- case 'call-cc':
721
- case 'jr-cc':
722
- return {
723
- ...instruction,
724
- expression: renameExpression(instruction.expression, localLabelMap),
725
- };
726
- default:
727
- return instruction;
728
- }
729
- }
730
- function renameOperandExpression(operand, localLabelMap) {
731
- switch (operand.kind) {
732
- case 'imm':
733
- return { ...operand, expression: renameExpression(operand.expression, localLabelMap) };
734
- case 'mem-abs':
735
- return { ...operand, expression: renameExpression(operand.expression, localLabelMap) };
736
- case 'indexed':
737
- return { ...operand, displacement: renameExpression(operand.displacement, localLabelMap) };
738
- default:
739
- return operand;
740
- }
741
- }
742
- function renamePortExpression(port, localLabelMap) {
743
- return port.kind === 'imm'
744
- ? { ...port, expression: renameExpression(port.expression, localLabelMap) }
745
- : port;
746
- }
747
- function renameExpression(expression, localLabelMap) {
748
- switch (expression.kind) {
749
- case 'symbol':
750
- return { ...expression, name: localLabelMap.get(expression.name) ?? expression.name };
751
- case 'unary':
752
- return { ...expression, expression: renameExpression(expression.expression, localLabelMap) };
753
- case 'binary':
754
- return {
755
- ...expression,
756
- left: renameExpression(expression.left, localLabelMap),
757
- right: renameExpression(expression.right, localLabelMap),
758
- };
759
- case 'layout-cast':
760
- return { ...expression, base: renameExpression(expression.base, localLabelMap) };
761
- default:
762
- return expression;
763
- }
764
- }
765
- function isAluMnemonic(mnemonic) {
766
- return /^(add|adc|sub|sbc|and|or|xor|cp)$/.test(mnemonic);
767
- }
768
- function expressionFitsKnownImm8(expression) {
769
- const value = expressionConstantValue(expression);
770
- return value === undefined || (value >= -0x80 && value <= 0xff);
771
- }
772
- function expressionFitsKnownImm16(expression) {
773
- const value = expressionConstantValue(expression);
774
- return value === undefined || (value >= -0x8000 && value <= 0xffff);
775
- }
776
- function parseIndexedOperand(text) {
777
- const trimmed = text.trim();
778
- if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
779
- return undefined;
780
- }
781
- const inner = trimmed.slice(1, -1).trim();
782
- const match = /^(IX|IY)(?:\s*([+-])\s*(.+))?$/i.exec(inner);
783
- if (!match) {
784
- return undefined;
785
- }
786
- const displacementText = match[3] ?? '0';
787
- const displacement = parseExpression(match[2] === '-' ? `-${displacementText}` : displacementText);
788
- return displacement
789
- ? {
790
- kind: 'indexed',
791
- register: (match[1] ?? '').toLowerCase(),
792
- displacement,
793
- text: trimmed,
794
- }
795
- : undefined;
796
- }
797
- function isConditionToken(text) {
798
- return /^(NZ|Z|NC|C|PO|PE|P|M)$/i.test(text);
799
- }
800
- function isRelativeConditionToken(text) {
801
- return /^(NZ|Z|NC|C)$/i.test(text);
802
- }
803
- function parseExpandedInstruction(mnemonic, operands) {
804
- const text = formatExpandedInstruction(mnemonic, operands);
805
- const result = parseLogicalLine({ sourceName: '<op-expansion>', line: 1, text });
806
- if (result.diagnostics.length > 0 || result.items.length !== 1) {
807
- return undefined;
808
- }
809
- const item = result.items[0];
810
- return item?.kind === 'instruction' ? item.instruction : undefined;
811
- }
812
- function expressionConstantValue(expression) {
813
- switch (expression.kind) {
814
- case 'number':
815
- return expression.value;
816
- case 'unary': {
817
- const value = expressionConstantValue(expression.expression);
818
- if (value === undefined)
819
- return undefined;
820
- switch (expression.operator) {
821
- case '+':
822
- return value;
823
- case '-':
824
- return -value;
825
- case '~':
826
- return ~value;
827
- }
828
- }
829
- case 'binary': {
830
- const left = expressionConstantValue(expression.left);
831
- const right = expressionConstantValue(expression.right);
832
- if (left === undefined || right === undefined)
833
- return undefined;
834
- switch (expression.operator) {
835
- case '+':
836
- return left + right;
837
- case '-':
838
- return left - right;
839
- case '*':
840
- return left * right;
841
- case '/':
842
- return right === 0 ? undefined : Math.trunc(left / right);
843
- case '%':
844
- return right === 0 ? undefined : left % right;
845
- case '&':
846
- return left & right;
847
- case '^':
848
- return left ^ right;
849
- case '|':
850
- return left | right;
851
- case '<<':
852
- return left << right;
853
- case '>>':
854
- return left >> right;
855
- }
856
- }
857
- default:
858
- return undefined;
859
- }
860
- }
861
- function splitOperands(text) {
862
- const values = [];
863
- let depth = 0;
864
- let quote;
865
- let escaped = false;
866
- let start = 0;
867
- for (let index = 0; index < text.length; index += 1) {
868
- const char = text[index];
869
- if (escaped) {
870
- escaped = false;
871
- continue;
872
- }
873
- if (char === '\\' && quote) {
874
- escaped = true;
875
- continue;
876
- }
877
- if (char === '"' || char === "'") {
878
- quote = quote === char ? undefined : (quote ?? char);
879
- continue;
880
- }
881
- if (quote) {
882
- continue;
883
- }
884
- if (char === '(') {
885
- depth += 1;
886
- }
887
- else if (char === ')') {
888
- depth -= 1;
889
- }
890
- else if (char === ',' && depth === 0) {
891
- values.push(text.slice(start, index).trim());
892
- start = index + 1;
893
- }
894
- }
895
- values.push(text.slice(start).trim());
896
- return values;
897
- }
898
165
  function isTopLevelEnd(text) {
899
166
  return /^(?:\.end|end)\s*$/i.test(stripLineComment(text).trim());
900
167
  }
168
+ function isOpEnd(text) {
169
+ return /^end\s*$/i.test(stripLineComment(text).trim());
170
+ }
901
171
  function parseDiagnostic(line, message) {
902
172
  return {
903
173
  severity: 'error',