@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,161 +1,61 @@
1
- import { parseExpression } from '../syntax/parse-expression.js';
1
+ import { splitInstructionOperands } from './operand-split.js';
2
+ import { aluImm8RangeError, halfIndexFamilyFromRegister, indexedBracketError, parseAluOperand, parseBitIndex, parseCbOperand, parseIncDecOperand, parseIndexHalfRegister, parseIndexRegister16, parseRegister16Operand, parseRegister8Operand, parseStackRegister, } from './parse-operands.js';
3
+ import { parseCallInstruction, parseJumpInstruction, parseRelativeBranchInstruction, } from './parse-branch.js';
4
+ import { parseNoOperandCoreInstruction, parseNopInstruction, parseRetInstruction, } from './parse-basic.js';
5
+ import { parseExchangeInstruction } from './parse-exchange.js';
6
+ import { parseInputInstruction, parseInterruptModeInstruction, parseOutputInstruction, parseRstInstruction, } from './parse-io-control.js';
7
+ import { parseLdInstruction } from './parse-ld.js';
8
+ const INSTRUCTION_PARSERS = [
9
+ parseNopInstruction,
10
+ parseRetInstruction,
11
+ parseNoOperandCoreInstruction,
12
+ parseInputInstruction,
13
+ parseOutputInstruction,
14
+ parseInterruptModeInstruction,
15
+ parseRstInstruction,
16
+ parseExchangeInstruction,
17
+ parseIncDecInstruction,
18
+ parseStackInstruction,
19
+ parseLdInstruction,
20
+ parseBitLikeInstruction,
21
+ parseRotateShiftInstruction,
22
+ parseAccumulatorAluInstruction,
23
+ parseUnaryAluInstruction,
24
+ parseJumpInstruction,
25
+ parseCallInstruction,
26
+ parseRelativeBranchInstruction,
27
+ ];
2
28
  export function parseZ80Instruction(text) {
3
- const nop = /^NOP(?:\s+(.*))?$/i.exec(text);
4
- if (nop) {
5
- return nop[1] === undefined
6
- ? { instruction: { mnemonic: 'nop' } }
7
- : { error: 'nop expects no operands' };
8
- }
9
- const ret = /^RET(?:\s+(.*))?$/i.exec(text);
10
- if (ret) {
11
- const operandText = ret[1] ?? '';
12
- if (operandText.trim().length === 0) {
13
- return { instruction: { mnemonic: 'ret' } };
14
- }
15
- const parts = splitInstructionOperands(operandText);
16
- if (parts.length !== 1) {
17
- return { error: 'ret expects no operands or one condition code' };
18
- }
19
- const condition = parseCondition(parts[0] ?? '');
20
- return condition
21
- ? { instruction: { mnemonic: 'ret-cc', condition } }
22
- : { error: 'ret cc expects a valid condition code' };
23
- }
24
- const noOperandCore = /^(DI|EI|SCF|CCF|CPL|DAA|EXX|HALT|RLCA|RRCA|RLA|RRA|NEG|RRD|RLD|LDI|LDIR|LDD|LDDR|CPI|CPIR|CPD|CPDR|INI|INIR|IND|INDR|OUTI|OTIR|OUTD|OTDR|RETI|RETN)(?:\s+(.*))?$/i.exec(text);
25
- if (noOperandCore) {
26
- const mnemonic = (noOperandCore[1] ?? '').toLowerCase();
27
- return noOperandCore[2] === undefined
28
- ? { instruction: { mnemonic } }
29
- : { error: `${mnemonic} expects no operands` };
30
- }
31
- const input = /^IN(?:\s+(.*))?$/i.exec(text);
32
- if (input) {
33
- const operandText = input[1] ?? '';
34
- const parts = splitInstructionOperands(operandText);
35
- if (operandText.trim().length === 0 || parts.length > 2) {
36
- return { error: 'in expects one or two operands' };
37
- }
38
- if (parts.length === 1) {
39
- const port = parsePortOperand(parts[0] ?? '');
40
- return port?.kind === 'c'
41
- ? { instruction: { mnemonic: 'in', port } }
42
- : { error: 'in (c) is the only one-operand in form' };
43
- }
44
- const target = parseRegister8Operand(parts[0] ?? '');
45
- if (!target) {
46
- return parseIndexHalfRegister(parts[0] ?? '')
47
- ? { error: 'in destination must use plain reg8 B/C/D/E/H/L/A' }
48
- : { error: 'in expects a reg8 destination' };
49
- }
50
- const port = parsePortOperand(parts[1] ?? '');
51
- if (!port) {
52
- return { error: 'in expects a port operand (c) or (imm8)' };
53
- }
54
- if (port.kind === 'imm' && target.register !== 'a') {
55
- return { error: 'in a,(n) immediate port form requires destination A' };
56
- }
57
- return { instruction: { mnemonic: 'in', target, port } };
58
- }
59
- const output = /^OUT(?:\s+(.*))?$/i.exec(text);
60
- if (output) {
61
- const operandText = output[1] ?? '';
62
- const parts = splitInstructionOperands(operandText);
63
- if (operandText.trim().length === 0 || parts.length !== 2) {
64
- return { error: 'out expects two operands' };
65
- }
66
- const port = parsePortOperand(parts[0] ?? '');
67
- if (!port) {
68
- return { error: 'out expects a port operand (c) or (imm8)' };
69
- }
70
- const source = parseRegister8Operand(parts[1] ?? '');
71
- if (source) {
72
- if (port.kind === 'imm' && source.register !== 'a') {
73
- return { error: 'out (n),a immediate port form requires source A' };
74
- }
75
- return { instruction: { mnemonic: 'out', port, source } };
76
- }
77
- if (parseIndexHalfRegister(parts[1] ?? '')) {
78
- return { error: 'out source must use plain reg8 B/C/D/E/H/L/A' };
79
- }
80
- const zero = parseConstantExpression(parts[1] ?? '');
81
- if (zero !== undefined && port.kind === 'c') {
82
- return zero === 0
83
- ? { instruction: { mnemonic: 'out', port, source: { kind: 'zero' } } }
84
- : { error: 'out (c), n immediate form supports n=0 only' };
85
- }
86
- return { error: 'out expects a reg8 source' };
87
- }
88
- const im = /^IM(?:\s+(.*))?$/i.exec(text);
89
- if (im) {
90
- const operandText = im[1] ?? '';
91
- const parts = splitInstructionOperands(operandText);
92
- if (operandText.trim().length === 0 || parts.length !== 1) {
93
- return { error: 'im expects one operand' };
94
- }
95
- const mode = parseConstantExpression(parts[0] ?? '');
96
- if (mode !== 0 && mode !== 1 && mode !== 2) {
97
- return { error: 'im expects 0, 1, or 2' };
98
- }
99
- return { instruction: { mnemonic: 'im', mode } };
100
- }
101
- const rst = /^RST(?:\s+(.*))?$/i.exec(text);
102
- if (rst) {
103
- const operandText = rst[1] ?? '';
104
- const parts = splitInstructionOperands(operandText);
105
- if (operandText.trim().length === 0 || parts.length !== 1) {
106
- return { error: 'rst expects one operand' };
107
- }
108
- const vector = parseConstantExpression(parts[0] ?? '');
109
- if (!isRstVector(vector)) {
110
- return { error: 'rst expects an imm8 multiple of 8 (0..56)' };
111
- }
112
- return { instruction: { mnemonic: 'rst', vector } };
113
- }
114
- const exchange = /^EX\s+(.+)$/i.exec(text);
115
- if (exchange) {
116
- const operandText = exchange[1] ?? '';
117
- const parts = splitInstructionOperands(operandText);
118
- if (parts.length !== 2) {
119
- return { error: 'ex expects two operands' };
120
- }
121
- const left = (parts[0] ?? '').toLowerCase();
122
- const right = (parts[1] ?? '').toLowerCase();
123
- if ((left === 'af' && right === "af'") || (left === "af'" && right === 'af')) {
124
- return { instruction: { mnemonic: 'ex', form: 'af-af' } };
125
- }
126
- if (left === 'de' && right === 'hl') {
127
- return { instruction: { mnemonic: 'ex', form: 'de-hl' } };
128
- }
129
- if (left === '(sp)' && right === 'hl') {
130
- return { instruction: { mnemonic: 'ex', form: 'sp-hl' } };
131
- }
132
- if ((left === '(sp)' && right === 'ix') || (left === 'ix' && right === '(sp)')) {
133
- return { instruction: { mnemonic: 'ex', form: 'sp-ix' } };
134
- }
135
- if ((left === '(sp)' && right === 'iy') || (left === 'iy' && right === '(sp)')) {
136
- return { instruction: { mnemonic: 'ex', form: 'sp-iy' } };
137
- }
138
- return {
139
- error: `ex supports "AF, AF'", "DE, HL", "(SP), HL", "(SP), IX", and "(SP), IY" only`,
140
- };
29
+ for (const parser of INSTRUCTION_PARSERS) {
30
+ const result = parser(text);
31
+ if (result)
32
+ return result;
141
33
  }
34
+ return undefined;
35
+ }
36
+ function parseIncDecInstruction(text) {
142
37
  const incDec = /^(INC|DEC)(?:\s+(.*))?$/i.exec(text);
143
38
  if (incDec) {
144
39
  const mnemonic = (incDec[1] ?? '').toLowerCase();
145
- const operandText = incDec[2] ?? '';
146
- const parts = splitInstructionOperands(operandText);
147
- if (operandText.trim().length === 0 || parts.length !== 1) {
148
- return { error: `${mnemonic} expects one operand` };
149
- }
150
- const indexedBracket = indexedBracketError(parts[0] ?? '');
151
- if (indexedBracket) {
152
- return { error: indexedBracket };
153
- }
154
- const operand = parseIncDecOperand(parts[0] ?? '');
155
- return operand
156
- ? { instruction: { mnemonic, operand } }
157
- : { error: `${mnemonic} expects r8/rr/(hl) operand` };
40
+ return parseIncDecOperands(mnemonic, incDec[2] ?? '');
158
41
  }
42
+ return undefined;
43
+ }
44
+ function parseIncDecOperands(mnemonic, operandText) {
45
+ const parts = splitInstructionOperands(operandText);
46
+ if (operandText.trim().length === 0 || parts.length !== 1) {
47
+ return { error: `${mnemonic} expects one operand` };
48
+ }
49
+ const indexedBracket = indexedBracketError(parts[0] ?? '');
50
+ if (indexedBracket) {
51
+ return { error: indexedBracket };
52
+ }
53
+ const operand = parseIncDecOperand(parts[0] ?? '');
54
+ return operand
55
+ ? { instruction: { mnemonic, operand } }
56
+ : { error: `${mnemonic} expects r8/rr/(hl) operand` };
57
+ }
58
+ function parseStackInstruction(text) {
159
59
  const stack = /^(PUSH|POP)(?:\s+(.*))?$/i.exec(text);
160
60
  if (stack) {
161
61
  const mnemonic = (stack[1] ?? '').toLowerCase();
@@ -169,900 +69,214 @@ export function parseZ80Instruction(text) {
169
69
  ? { instruction: { mnemonic, register } }
170
70
  : { error: `${mnemonic} supports BC/DE/HL/AF/IX/IY only` };
171
71
  }
172
- const ld = /^LD\s+(.+)$/i.exec(text);
173
- if (ld) {
174
- const operandText = ld[1] ?? '';
175
- const parts = splitInstructionOperands(operandText);
176
- if (parts.length !== 2) {
177
- return { error: 'ld expects two operands' };
178
- }
179
- const indexedBracket = indexedBracketError(parts[0] ?? '') ?? indexedBracketError(parts[1] ?? '');
180
- if (indexedBracket) {
181
- return { error: indexedBracket };
182
- }
183
- const leftText = parts[0] ?? '';
184
- const rightText = parts[1] ?? '';
185
- if (/^AF$/i.test(leftText) || /^AF$/i.test(rightText)) {
186
- return { error: 'ld does not support AF in this form' };
187
- }
188
- const target = parseLdOperand(leftText);
189
- const source = parseLdOperand(rightText);
190
- if (!target || !source) {
191
- const operandDiagnostics = [
192
- ...invalidLdOperandDiagnostics(leftText),
193
- ...invalidLdOperandDiagnostics(rightText),
194
- ];
195
- if (operandDiagnostics.length > 0) {
196
- const error = operandDiagnostics[operandDiagnostics.length - 1];
197
- return {
198
- error,
199
- diagnostics: operandDiagnostics,
200
- };
201
- }
202
- return { error: 'ld expects a supported register/memory/immediate transfer form' };
203
- }
204
- if (target.kind === 'reg8' &&
205
- target.register !== 'a' &&
206
- source.kind === 'reg-indirect' &&
207
- source.register !== 'hl') {
208
- return { error: 'ld r8, (bc/de) supports destination A only' };
209
- }
210
- if (target.kind === 'reg-indirect' &&
211
- source.kind === 'reg8' &&
212
- source.register !== 'a' &&
213
- target.register !== 'hl') {
214
- return { error: 'ld (bc/de), r8 supports source A only' };
215
- }
216
- if (target.kind === 'reg-half-index' && source.kind === 'reg-indirect') {
217
- return {
218
- error: `ld ${target.register.toUpperCase()}, source expects (ix+disp)`,
219
- };
220
- }
221
- const unsupportedReason = unsupportedLdReason(target, source);
222
- if (unsupportedReason) {
223
- return { error: unsupportedReason };
224
- }
225
- if (!isSupportedLd(target, source)) {
226
- return { error: 'ld expects a supported register/memory/immediate transfer form' };
227
- }
228
- return { instruction: { mnemonic: 'ld', target, source } };
229
- }
72
+ return undefined;
73
+ }
74
+ function parseBitLikeInstruction(text) {
230
75
  const bitLike = /^(BIT|RES|SET)(?:\s+(.*))?$/i.exec(text);
231
76
  if (bitLike) {
232
77
  const mnemonic = (bitLike[1] ?? '').toLowerCase();
233
- const operandText = bitLike[2] ?? '';
234
- const parts = splitInstructionOperands(operandText);
235
- if (operandText.trim().length === 0 || parts.length < 2) {
236
- return {
237
- error: mnemonic === 'bit'
238
- ? 'bit expects two operands'
239
- : `${mnemonic} expects two operands, or three with indexed source + reg8 destination`,
240
- };
241
- }
242
- if (mnemonic === 'bit' && parts.length !== 2) {
243
- return { error: 'bit expects two operands' };
244
- }
245
- if (mnemonic !== 'bit' && parts.length > 3) {
246
- return {
247
- error: `${mnemonic} expects two operands, or three with indexed source + reg8 destination`,
248
- };
249
- }
250
- const bit = parseBitIndex(parts[0] ?? '');
251
- if (bit === undefined) {
252
- return { error: `${mnemonic} expects bit index 0..7` };
253
- }
254
- const operand = parseCbOperand(parts[1] ?? '');
255
- if (!operand) {
256
- return { error: `${mnemonic} expects reg8 or (hl)` };
257
- }
258
- if (parts.length === 2) {
259
- return { instruction: { mnemonic, bit, operand } };
260
- }
261
- if (operand.kind !== 'indexed') {
262
- return { error: `${mnemonic} b,(ix/iy+disp),r requires an indexed memory source` };
263
- }
264
- const destination = parseRegister8Operand(parts[2] ?? '');
265
- if (destination) {
266
- return { instruction: { mnemonic, bit, operand, destination } };
267
- }
268
- const halfDestination = parseIndexHalfRegister(parts[2] ?? '');
269
- if (halfDestination) {
270
- return halfIndexFamilyFromRegister(halfDestination) === operand.register
271
- ? {
272
- error: `${mnemonic} indexed destination must use plain reg8 B/C/D/E/H/L/A`,
273
- }
274
- : {
275
- error: `${mnemonic} indexed destination family must match source index base`,
276
- };
277
- }
278
- return { error: `${mnemonic} b,(ix/iy+disp),r expects reg8 destination` };
279
- }
280
- const rotateShift = /^(RLC|RRC|RL|RR|SLA|SRA|SLL|SLS|SRL)(?:\s+(.*))?$/i.exec(text);
281
- if (rotateShift) {
282
- const mnemonic = (rotateShift[1] ?? '').toLowerCase();
283
- const operandText = rotateShift[2] ?? '';
284
- const parts = splitInstructionOperands(operandText);
285
- if (operandText.trim().length === 0) {
286
- return {
287
- error: `${mnemonic} expects one operand, or two with indexed source + reg8 destination`,
288
- };
289
- }
290
- if (parts.length > 2) {
291
- return {
292
- error: `${mnemonic} expects one operand, or two with indexed source + reg8 destination`,
293
- };
294
- }
295
- if (parts.length === 2) {
296
- const operand = parseCbOperand(parts[0] ?? '');
297
- if (operand?.kind !== 'indexed') {
298
- return { error: `${mnemonic} two-operand form requires (ix/iy+disp) source` };
299
- }
300
- const destination = parseRegister8Operand(parts[1] ?? '');
301
- if (destination) {
302
- return { instruction: { mnemonic, operand, destination } };
303
- }
304
- const halfDestination = parseIndexHalfRegister(parts[1] ?? '');
305
- if (halfDestination) {
306
- return halfIndexFamilyFromRegister(halfDestination) === operand.register
307
- ? {
308
- error: `${mnemonic} indexed destination must use plain reg8 B/C/D/E/H/L/A`,
309
- }
310
- : {
311
- error: `${mnemonic} indexed destination family must match source index base`,
312
- };
313
- }
314
- return { error: `${mnemonic} (ix/iy+disp),r expects reg8 destination` };
315
- }
316
- if (parts.length !== 1) {
317
- return {
318
- error: `${mnemonic} expects one operand, or two with indexed source + reg8 destination`,
319
- };
320
- }
321
- const operand = parseCbOperand(parts[0] ?? '');
322
- return operand
323
- ? { instruction: { mnemonic, operand } }
324
- : { error: `${mnemonic} expects reg8 or (hl)` };
325
- }
326
- const accumulatorAlu = /^(ADD|ADC|SBC)(?:\s+(.*))?$/i.exec(text);
327
- if (accumulatorAlu) {
328
- const mnemonic = (accumulatorAlu[1] ?? '').toLowerCase();
329
- const operandText = accumulatorAlu[2] ?? '';
330
- if (operandText.trim().length === 0) {
331
- return {
332
- error: mnemonic === 'add'
333
- ? 'add expects two operands'
334
- : `${mnemonic} expects one operand, two with destination A, or HL,rr form`,
335
- };
336
- }
337
- const parts = splitInstructionOperands(operandText);
338
- if (parts.length !== 2) {
339
- return {
340
- error: mnemonic === 'add'
341
- ? 'add expects two operands'
342
- : `${mnemonic} expects one operand, two with destination A, or HL,rr form`,
343
- };
344
- }
345
- const target = parseRegister8Operand(parts[0] ?? '');
346
- if (target?.register === 'a') {
347
- const source = parseAluOperand(parts[1] ?? '');
348
- if (!source) {
349
- return { error: `invalid ${mnemonic.toUpperCase()} operand: ${parts[1] ?? ''}` };
350
- }
351
- const imm8Error = source.kind === 'imm' ? aluImm8RangeError(source.expression, mnemonic) : undefined;
352
- if (imm8Error) {
353
- return { error: imm8Error };
354
- }
355
- return { instruction: { mnemonic, source } };
356
- }
357
- const target16 = parseRegister16Operand(parts[0] ?? '');
358
- if (target16?.register === 'hl') {
359
- const source = parseRegister16Operand(parts[1] ?? '');
360
- return source
361
- ? { instruction: { mnemonic: mnemonic, target: target16, source } }
362
- : { error: `${mnemonic} HL, rr expects BC/DE/HL/SP` };
363
- }
364
- const targetIndex16 = parseIndexRegister16(parts[0] ?? '');
365
- if (mnemonic === 'add' && targetIndex16) {
366
- const target = { kind: 'reg-index16', register: targetIndex16 };
367
- const source16 = parseRegister16Operand(parts[1] ?? '');
368
- if (source16 && source16.register !== 'hl') {
369
- return { instruction: { mnemonic, target, source: source16 } };
370
- }
371
- const sourceIndex16 = parseIndexRegister16(parts[1] ?? '');
372
- if (sourceIndex16 === targetIndex16) {
373
- return {
374
- instruction: {
375
- mnemonic,
376
- target,
377
- source: { kind: 'reg-index16', register: sourceIndex16 },
378
- },
379
- };
380
- }
381
- return {
382
- error: `add ${targetIndex16.toUpperCase()}, rr supports BC/DE/SP and same-index pair only`,
383
- };
384
- }
385
- return mnemonic === 'add'
386
- ? { error: 'add expects destination A, HL, IX, or IY' }
387
- : { error: `${mnemonic} expects destination A or HL` };
388
- }
389
- const alu = /^(SUB|AND|OR|XOR|CP)(?:\s+(.*))?$/i.exec(text);
390
- if (alu) {
391
- const mnemonic = (alu[1] ?? '').toLowerCase();
392
- const operandText = alu[2] ?? '';
393
- if (operandText.trim().length === 0) {
394
- return { error: `${mnemonic} expects one operand, or two with destination A` };
395
- }
396
- const parts = splitInstructionOperands(operandText);
397
- if (parts.length === 2) {
398
- const target = parseRegister8Operand(parts[0] ?? '');
399
- if (target?.register === 'a') {
400
- const source = parseAluOperand(parts[1] ?? '');
401
- if (!source) {
402
- return { error: `invalid ${mnemonic.toUpperCase()} operand: ${parts[1] ?? ''}` };
403
- }
404
- const imm8Error = source.kind === 'imm' ? aluImm8RangeError(source.expression, mnemonic) : undefined;
405
- if (imm8Error) {
406
- return { error: imm8Error };
407
- }
408
- return { instruction: { mnemonic, source } };
409
- }
410
- return { error: `${mnemonic} two-operand form requires destination A` };
411
- }
412
- if (parts.length !== 1) {
413
- return { error: `${mnemonic} expects one operand, or two with destination A` };
414
- }
415
- const source = parseAluOperand(parts[0] ?? '');
416
- if (!source) {
417
- return { error: `invalid ${mnemonic.toUpperCase()} operand: ${operandText}` };
418
- }
419
- const imm8Error = source.kind === 'imm' ? aluImm8RangeError(source.expression, mnemonic) : undefined;
420
- if (imm8Error) {
421
- return { error: imm8Error };
422
- }
423
- return { instruction: { mnemonic, source } };
424
- }
425
- const jump = /^JP(?:\s+(.*))?$/i.exec(text);
426
- if (jump) {
427
- const operandText = jump[1] ?? '';
428
- const parts = splitInstructionOperands(operandText);
429
- if (operandText.trim().length === 0) {
430
- return {
431
- error: 'jp expects one operand (nn/(hl)/(ix)/(iy)) or two operands (cc, nn)',
432
- };
433
- }
434
- if (parts.length === 2) {
435
- const condition = parseCondition(parts[0] ?? '');
436
- if (!condition) {
437
- return { error: 'jp cc expects valid condition code NZ/Z/NC/C/PO/PE/P/M' };
438
- }
439
- const targetText = parts[1] ?? '';
440
- if (/^\(.*\)$/.test(targetText.trim())) {
441
- return { error: 'jp cc, nn does not support indirect targets' };
442
- }
443
- const expression = parseAbsoluteBranchTarget(targetText);
444
- return expression
445
- ? { instruction: { mnemonic: 'jp-cc', condition, expression } }
446
- : { error: 'jp cc, nn expects imm16' };
447
- }
448
- if (parts.length !== 1) {
449
- return {
450
- error: 'jp expects one operand (nn/(hl)/(ix)/(iy)) or two operands (cc, nn)',
451
- };
452
- }
453
- const single = parts[0] ?? '';
454
- const condition = parseCondition(single);
455
- if (condition) {
456
- return { error: 'jp cc, nn expects two operands (cc, nn)' };
457
- }
458
- const indirect = parseJumpIndirect(single);
459
- if (indirect) {
460
- return { instruction: { mnemonic: 'jp-indirect', register: indirect } };
461
- }
462
- if (/^\(.*\)$/.test(single.trim())) {
463
- return { error: 'jp indirect form supports (hl), (ix), or (iy) only' };
464
- }
465
- if (isRegisterName(single)) {
466
- if (/^(HL|IX|IY)$/i.test(single.trim())) {
467
- return { error: 'jp indirect form requires parentheses; use (hl), (ix), or (iy)' };
468
- }
469
- return { error: 'jp does not support register targets; use imm16' };
470
- }
471
- const expression = parseExpression(single);
472
- return expression
473
- ? { instruction: { mnemonic: 'jp', expression } }
474
- : { error: `invalid JP target: ${single}` };
475
- }
476
- const call = /^CALL(?:\s+(.*))?$/i.exec(text);
477
- if (call) {
478
- const operandText = call[1] ?? '';
479
- const parts = splitInstructionOperands(operandText);
480
- if (operandText.trim().length === 0) {
481
- return { error: 'call expects one operand (nn) or two operands (cc, nn)' };
482
- }
483
- if (parts.length === 2) {
484
- const condition = parseCondition(parts[0] ?? '');
485
- if (!condition) {
486
- return { error: 'call cc expects valid condition code NZ/Z/NC/C/PO/PE/P/M' };
487
- }
488
- const targetText = parts[1] ?? '';
489
- if (/^\(.*\)$/.test(targetText.trim())) {
490
- return { error: 'call cc, nn does not support indirect targets' };
491
- }
492
- const expression = parseAbsoluteBranchTarget(targetText);
493
- return expression
494
- ? { instruction: { mnemonic: 'call-cc', condition, expression } }
495
- : { error: 'call cc, nn expects imm16' };
496
- }
497
- if (parts.length !== 1) {
498
- return { error: 'call expects one operand (nn) or two operands (cc, nn)' };
499
- }
500
- const single = parts[0] ?? '';
501
- const condition = parseCondition(single);
502
- if (condition) {
503
- return { error: 'call cc, nn expects two operands (cc, nn)' };
504
- }
505
- if (/^\(.*\)$/.test(single.trim())) {
506
- return { error: 'call does not support indirect targets; use imm16' };
507
- }
508
- if (isRegisterName(single)) {
509
- return { error: 'call does not support register targets; use imm16' };
510
- }
511
- const expression = parseExpression(single);
512
- return expression
513
- ? { instruction: { mnemonic: 'call', expression } }
514
- : { error: `invalid CALL target: ${single}` };
515
- }
516
- const relativeBranch = /^(JR|DJNZ)(?:\s+(.*))?$/i.exec(text);
517
- if (relativeBranch) {
518
- const mnemonic = (relativeBranch[1] ?? '').toLowerCase();
519
- const operandText = (relativeBranch[2] ?? '').trim();
520
- if (operandText.length === 0) {
521
- return {
522
- error: mnemonic === 'djnz'
523
- ? 'djnz expects one operand (disp8)'
524
- : 'jr expects one operand (disp8) or two operands (cc, disp8)',
525
- };
526
- }
527
- const parts = splitInstructionOperands(operandText);
528
- if (mnemonic === 'djnz') {
529
- if (parts.length !== 1) {
530
- return { error: 'djnz expects one operand (disp8)' };
531
- }
532
- const targetText = parts[0] ?? '';
533
- const targetError = relativeDispTargetError(targetText, {
534
- indirect: 'djnz does not support indirect targets; expects disp8',
535
- register: 'djnz does not support register targets; expects disp8',
536
- });
537
- if (targetError) {
538
- return { error: targetError };
539
- }
540
- const expression = parseExpression(targetText);
541
- return expression
542
- ? { instruction: { mnemonic: 'djnz', expression } }
543
- : { error: 'djnz expects disp8' };
544
- }
545
- if (parts.length === 1) {
546
- const targetText = parts[0] ?? '';
547
- const targetError = relativeDispTargetError(targetText, {
548
- indirect: 'jr does not support indirect targets; expects disp8',
549
- register: 'jr does not support register targets; expects disp8',
550
- });
551
- if (targetError) {
552
- return { error: targetError };
553
- }
554
- if (parseRelativeCondition(targetText)) {
555
- return { error: 'jr cc, disp expects two operands (cc, disp8)' };
556
- }
557
- const expression = parseExpression(targetText);
558
- return expression
559
- ? { instruction: { mnemonic: 'jr', expression } }
560
- : { error: 'jr expects disp8' };
561
- }
562
- if (parts.length === 2) {
563
- const condition = parseRelativeCondition(parts[0] ?? '');
564
- if (!condition) {
565
- return { error: 'jr cc expects valid condition code NZ/Z/NC/C' };
566
- }
567
- const targetText = parts[1] ?? '';
568
- const targetError = relativeDispTargetError(targetText, {
569
- indirect: 'jr cc, disp does not support indirect targets',
570
- register: 'jr cc, disp does not support register targets; expects disp8',
571
- });
572
- if (targetError) {
573
- return { error: targetError };
574
- }
575
- const expression = parseExpression(targetText);
576
- return expression
577
- ? { instruction: { mnemonic: 'jr-cc', condition, expression } }
578
- : { error: 'jr cc, disp expects disp8' };
579
- }
580
- return { error: 'jr expects one operand (disp8) or two operands (cc, disp8)' };
78
+ return parseBitLikeOperands(mnemonic, bitLike[2] ?? '');
581
79
  }
582
80
  return undefined;
583
81
  }
584
- function parseLdOperand(text) {
585
- const trimmed = text.trim();
586
- const indexed = parseIndexedOperand(trimmed);
587
- if (indexed) {
588
- return indexed;
589
- }
590
- const memory = /^\((BC|DE|HL)\)$/i.exec(trimmed);
591
- if (memory) {
592
- return {
593
- kind: 'reg-indirect',
594
- register: (memory[1] ?? '').toLowerCase(),
595
- };
82
+ function parseBitLikeOperands(mnemonic, operandText) {
83
+ const parts = splitInstructionOperands(operandText);
84
+ const arityError = bitLikeArityError(mnemonic, operandText, parts.length);
85
+ if (arityError)
86
+ return { error: arityError };
87
+ const bit = parseBitIndex(parts[0] ?? '');
88
+ if (bit === undefined) {
89
+ return { error: `${mnemonic} expects bit index 0..7` };
596
90
  }
597
- if (trimmed.startsWith('(') && trimmed.endsWith(')')) {
598
- const expression = parseExpression(trimmed.slice(1, -1).trim());
599
- return expression ? { kind: 'mem-abs', expression } : undefined;
600
- }
601
- if (/^(A|B|C|D|E|H|L)$/i.test(trimmed)) {
602
- return { kind: 'reg8', register: trimmed.toLowerCase() };
603
- }
604
- const index16 = parseIndexRegister16(trimmed);
605
- if (index16) {
606
- return { kind: 'reg-index16', register: index16 };
607
- }
608
- const half = parseIndexHalfRegister(trimmed);
609
- if (half) {
610
- return { kind: 'reg-half-index', register: half };
611
- }
612
- if (/^(BC|DE|HL|SP)$/i.test(trimmed)) {
613
- return parseRegister16Operand(trimmed);
614
- }
615
- const special8 = parseSpecialRegister8(trimmed);
616
- if (special8) {
617
- return { kind: 'special8', register: special8 };
618
- }
619
- const expression = parseExpression(trimmed);
620
- return expression ? { kind: 'imm', expression } : undefined;
91
+ return parseBitLikeTarget(mnemonic, bit, parts);
621
92
  }
622
- function invalidLdOperandDiagnostics(text) {
623
- const trimmed = text.trim();
624
- if (trimmed === '?') {
625
- return ['Invalid imm expression: ?', 'Unsupported operand: ?'];
93
+ function parseBitLikeTarget(mnemonic, bit, parts) {
94
+ const operand = parseCbOperand(parts[1] ?? '');
95
+ if (!operand) {
96
+ return { error: `${mnemonic} expects reg8 or (hl)` };
626
97
  }
627
- if (trimmed.startsWith("'") && parseExpression(trimmed) === undefined) {
628
- return [`Invalid imm expression: ${trimmed}`];
629
- }
630
- return [];
98
+ return parts.length === 2
99
+ ? { instruction: { mnemonic, bit, operand } }
100
+ : parseIndexedBitDestination(mnemonic, bit, operand, parts[2] ?? '');
631
101
  }
632
- function aluImm8RangeError(expression, mnemonic) {
633
- const value = constantExpressionValue(expression);
634
- if (value === undefined) {
635
- return undefined;
102
+ function bitLikeArityError(mnemonic, operandText, partCount) {
103
+ if (operandText.trim().length === 0 || partCount < 2) {
104
+ return bitLikeOperandCountError(mnemonic);
636
105
  }
637
- if (value < -128 || value > 255) {
638
- return `${mnemonic} expects imm8`;
106
+ if (mnemonic === 'bit' && partCount !== 2) {
107
+ return 'bit expects two operands';
639
108
  }
640
- return undefined;
109
+ return mnemonic !== 'bit' && partCount > 3 ? bitLikeOperandCountError(mnemonic) : undefined;
641
110
  }
642
- function parseRelativeCondition(text) {
643
- const trimmed = text.trim().toLowerCase();
644
- return /^(nz|z|nc|c)$/.test(trimmed) ? trimmed : undefined;
111
+ function bitLikeOperandCountError(mnemonic) {
112
+ return mnemonic === 'bit'
113
+ ? 'bit expects two operands'
114
+ : `${mnemonic} expects two operands, or three with indexed source + reg8 destination`;
645
115
  }
646
- function relativeDispTargetError(text, messages) {
647
- const trimmed = text.trim();
648
- if (/^\(.*\)$/.test(trimmed)) {
649
- return messages.indirect;
116
+ function parseIndexedBitDestination(mnemonic, bit, operand, destinationText) {
117
+ if (operand.kind !== 'indexed') {
118
+ return { error: `${mnemonic} b,(ix/iy+disp),r requires an indexed memory source` };
650
119
  }
651
- if (isRegisterName(trimmed)) {
652
- return messages.register;
120
+ const destination = parseRegister8Operand(destinationText);
121
+ if (destination) {
122
+ return { instruction: { mnemonic, bit, operand, destination } };
653
123
  }
654
- const expression = parseExpression(trimmed);
655
- if (expression?.kind === 'symbol' && isRegisterName(expression.name)) {
656
- return messages.register;
124
+ const destinationError = indexedDestinationError(mnemonic, destinationText, operand.register);
125
+ return { error: destinationError ?? `${mnemonic} b,(ix/iy+disp),r expects reg8 destination` };
126
+ }
127
+ function parseRotateShiftInstruction(text) {
128
+ const rotateShift = /^(RLC|RRC|RL|RR|SLA|SRA|SLL|SLS|SRL)(?:\s+(.*))?$/i.exec(text);
129
+ if (rotateShift) {
130
+ const mnemonic = (rotateShift[1] ?? '').toLowerCase();
131
+ return parseRotateShiftOperands(mnemonic, rotateShift[2] ?? '');
657
132
  }
658
133
  return undefined;
659
134
  }
660
- function parseAluOperand(text) {
661
- const trimmed = text.trim();
662
- const indexed = parseIndexedOperand(trimmed);
663
- if (indexed) {
664
- return indexed;
665
- }
666
- const memory = /^\(HL\)$/i.exec(trimmed);
667
- if (memory) {
668
- return { kind: 'reg-indirect', register: 'hl' };
669
- }
670
- if (trimmed.startsWith('(') && trimmed.endsWith(')')) {
135
+ function parseRotateShiftOperands(mnemonic, operandText) {
136
+ const parts = splitInstructionOperands(operandText);
137
+ const arityError = rotateShiftArityError(mnemonic, operandText, parts.length);
138
+ if (arityError)
139
+ return { error: arityError };
140
+ if (parts.length === 2) {
141
+ return parseIndexedRotateShift(mnemonic, parts[0] ?? '', parts[1] ?? '');
142
+ }
143
+ const operand = parseCbOperand(parts[0] ?? '');
144
+ return operand
145
+ ? { instruction: { mnemonic, operand } }
146
+ : { error: `${mnemonic} expects reg8 or (hl)` };
147
+ }
148
+ function rotateShiftArityError(mnemonic, operandText, partCount) {
149
+ const message = `${mnemonic} expects one operand, or two with indexed source + reg8 destination`;
150
+ return operandText.trim().length === 0 || partCount < 1 || partCount > 2 ? message : undefined;
151
+ }
152
+ function parseIndexedRotateShift(mnemonic, operandText, destinationText) {
153
+ const operand = parseCbOperand(operandText);
154
+ if (operand?.kind !== 'indexed') {
155
+ return { error: `${mnemonic} two-operand form requires (ix/iy+disp) source` };
156
+ }
157
+ const destination = parseRegister8Operand(destinationText);
158
+ if (destination) {
159
+ return { instruction: { mnemonic, operand, destination } };
160
+ }
161
+ const destinationError = indexedDestinationError(mnemonic, destinationText, operand.register);
162
+ return { error: destinationError ?? `${mnemonic} (ix/iy+disp),r expects reg8 destination` };
163
+ }
164
+ function indexedDestinationError(mnemonic, destinationText, sourceIndex) {
165
+ const halfDestination = parseIndexHalfRegister(destinationText);
166
+ if (!halfDestination) {
671
167
  return undefined;
672
168
  }
673
- const register = parseRegister8Operand(trimmed);
674
- if (register) {
675
- return register;
676
- }
677
- const half = parseIndexHalfRegister(trimmed);
678
- if (half) {
679
- return { kind: 'reg-half-index', register: half };
680
- }
681
- const expression = parseExpression(trimmed);
682
- return expression ? { kind: 'imm', expression } : undefined;
683
- }
684
- function parseCbOperand(text) {
685
- const trimmed = text.trim();
686
- const indexed = parseIndexedOperand(trimmed);
687
- if (indexed) {
688
- return indexed;
689
- }
690
- if (/^\(HL\)$/i.test(trimmed)) {
691
- return { kind: 'reg-indirect', register: 'hl' };
692
- }
693
- return parseRegister8Operand(trimmed);
694
- }
695
- function parseRegister8Operand(text) {
696
- const trimmed = text.trim();
697
- if (/^(A|B|C|D|E|H|L)$/i.test(trimmed)) {
698
- return { kind: 'reg8', register: trimmed.toLowerCase() };
699
- }
700
- return undefined;
169
+ return halfIndexFamilyFromRegister(halfDestination) === sourceIndex
170
+ ? `${mnemonic} indexed destination must use plain reg8 B/C/D/E/H/L/A`
171
+ : `${mnemonic} indexed destination family must match source index base`;
701
172
  }
702
- function parseRegister16Operand(text) {
703
- const trimmed = text.trim();
704
- if (/^(BC|DE|HL|SP)$/i.test(trimmed)) {
705
- return { kind: 'reg16', register: trimmed.toLowerCase() };
173
+ function parseAccumulatorAluInstruction(text) {
174
+ const accumulatorAlu = /^(ADD|ADC|SBC)(?:\s+(.*))?$/i.exec(text);
175
+ if (accumulatorAlu) {
176
+ const mnemonic = (accumulatorAlu[1] ?? '').toLowerCase();
177
+ return parseAccumulatorAluOperands(mnemonic, accumulatorAlu[2] ?? '');
706
178
  }
707
179
  return undefined;
708
180
  }
709
- function parseIncDecOperand(text) {
710
- const trimmed = text.trim();
711
- const indexed = parseIndexedOperand(trimmed);
712
- if (indexed) {
713
- return indexed;
714
- }
715
- if (/^\(HL\)$/i.test(trimmed)) {
716
- return { kind: 'reg-indirect', register: 'hl' };
717
- }
718
- const register8 = parseRegister8Operand(trimmed);
719
- if (register8) {
720
- return register8;
721
- }
722
- const register16 = parseRegister16Operand(trimmed);
723
- if (register16) {
724
- return register16;
725
- }
726
- const index16 = parseIndexRegister16(trimmed);
727
- if (index16) {
728
- return { kind: 'reg16', register: index16 };
729
- }
730
- const half = parseIndexHalfRegister(trimmed);
731
- return half ? { kind: 'reg-half-index', register: half } : undefined;
732
- }
733
- function parseIndexRegister16(text) {
734
- const trimmed = text.trim();
735
- return /^(IX|IY)$/i.test(trimmed) ? trimmed.toLowerCase() : undefined;
736
- }
737
- function parseIndexHalfRegister(text) {
738
- const trimmed = text.trim();
739
- return /^(IXH|IXL|IYH|IYL)$/i.test(trimmed)
740
- ? trimmed.toLowerCase()
741
- : undefined;
742
- }
743
- function halfIndexFamilyFromRegister(register) {
744
- return register.startsWith('ix') ? 'ix' : 'iy';
745
- }
746
- function parseSpecialRegister8(text) {
747
- const trimmed = text.trim();
748
- return /^(I|R)$/i.test(trimmed) ? trimmed.toLowerCase() : undefined;
749
- }
750
- function parseStackRegister(text) {
751
- const trimmed = text.trim();
752
- return /^(BC|DE|HL|AF|IX|IY)$/i.test(trimmed)
753
- ? trimmed.toLowerCase()
754
- : undefined;
755
- }
756
- function parseIndexedOperand(text) {
757
- const trimmed = text.trim();
758
- if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
759
- return undefined;
760
- }
761
- const inner = trimmed.slice(1, -1).trim();
762
- const match = /^(IX|IY)(?:\s*([+-])\s*(.+))?$/i.exec(inner);
763
- if (!match) {
764
- return undefined;
765
- }
766
- const register = (match[1] ?? '').toLowerCase();
767
- const sign = match[2];
768
- const displacementText = match[3] ?? '0';
769
- const parsed = parseExpression(sign === '-' ? `-${displacementText}` : displacementText);
770
- if (!parsed) {
181
+ function parseAccumulatorAluOperands(mnemonic, operandText) {
182
+ const arityError = accumulatorAluArityError(mnemonic, operandText);
183
+ if (arityError)
184
+ return { error: arityError };
185
+ const parts = splitInstructionOperands(operandText);
186
+ if (parts.length !== 2)
187
+ return { error: accumulatorAluOperandCountError(mnemonic) };
188
+ return parseAccumulatorAluTarget(mnemonic, parts[0] ?? '', parts[1] ?? '');
189
+ }
190
+ function accumulatorAluArityError(mnemonic, operandText) {
191
+ return operandText.trim().length === 0 ? accumulatorAluOperandCountError(mnemonic) : undefined;
192
+ }
193
+ function accumulatorAluOperandCountError(mnemonic) {
194
+ return mnemonic === 'add'
195
+ ? 'add expects two operands'
196
+ : `${mnemonic} expects one operand, two with destination A, or HL,rr form`;
197
+ }
198
+ function parseAccumulatorAluTarget(mnemonic, targetText, sourceText) {
199
+ for (const parser of ACCUMULATOR_ALU_TARGET_PARSERS) {
200
+ const result = parser(mnemonic, targetText, sourceText);
201
+ if (result)
202
+ return result;
203
+ }
204
+ return mnemonic === 'add'
205
+ ? { error: 'add expects destination A, HL, IX, or IY' }
206
+ : { error: `${mnemonic} expects destination A or HL` };
207
+ }
208
+ const ACCUMULATOR_ALU_TARGET_PARSERS = [
209
+ parseAccumulatorRegisterAlu,
210
+ parseHlRegisterAlu,
211
+ parseIndexRegisterAdd,
212
+ ];
213
+ function parseAccumulatorRegisterAlu(mnemonic, targetText, sourceText) {
214
+ const target = parseRegister8Operand(targetText);
215
+ return target?.register === 'a' ? parseAccumulatorAluSource(mnemonic, sourceText) : undefined;
216
+ }
217
+ function parseHlRegisterAlu(mnemonic, targetText, sourceText) {
218
+ const target16 = parseRegister16Operand(targetText);
219
+ if (target16?.register !== 'hl')
771
220
  return undefined;
772
- }
773
- return { kind: 'indexed', register, displacement: parsed };
774
- }
775
- function parsePortOperand(text) {
776
- const trimmed = text.trim();
777
- if (/^\(C\)$/i.test(trimmed)) {
778
- return { kind: 'c' };
779
- }
780
- if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
781
- return undefined;
782
- }
783
- const expression = parseExpression(trimmed.slice(1, -1).trim());
784
- return expression ? { kind: 'imm', expression } : undefined;
785
- }
786
- function indexedBracketError(text) {
787
- const match = /^\(?\s*((IX|IY)\s*\[\s*.+?\s*\])\s*\)?$/i.exec(text.trim());
788
- return match
789
- ? `Indexed memory operands use (ix+disp)/(iy+disp), not ${match[1]?.toLowerCase().replace(/\s+/g, '')}.`
221
+ const source = parseRegister16Operand(sourceText);
222
+ return source
223
+ ? { instruction: { mnemonic: mnemonic, target: target16, source } }
224
+ : { error: `${mnemonic} HL, rr expects BC/DE/HL/SP` };
225
+ }
226
+ function parseIndexRegisterAdd(mnemonic, targetText, sourceText) {
227
+ const targetIndex16 = parseIndexRegister16(targetText);
228
+ return mnemonic === 'add' && targetIndex16
229
+ ? parseIndexedAdd(targetIndex16, sourceText)
790
230
  : undefined;
791
231
  }
792
- function parseAbsoluteBranchTarget(text) {
793
- const trimmed = text.trim();
794
- if (/^\(.*\)$/.test(trimmed) || isRegisterName(trimmed)) {
795
- return undefined;
232
+ function parseAccumulatorAluSource(mnemonic, sourceText) {
233
+ const source = parseAluOperand(sourceText);
234
+ if (!source) {
235
+ return { error: `invalid ${mnemonic.toUpperCase()} operand: ${sourceText}` };
796
236
  }
797
- return parseExpression(trimmed);
798
- }
799
- function parseCondition(text) {
800
- const trimmed = text.trim();
801
- return /^(NZ|Z|NC|C|PO|PE|P|M)$/i.test(trimmed)
802
- ? trimmed.toLowerCase()
803
- : undefined;
804
- }
805
- function parseJumpIndirect(text) {
806
- const indirect = /^\((HL|IX|IY)\)$/i.exec(text.trim());
807
- return indirect ? (indirect[1] ?? '').toLowerCase() : undefined;
237
+ const imm8Error = source.kind === 'imm' ? aluImm8RangeError(source.expression, mnemonic) : undefined;
238
+ return imm8Error ? { error: imm8Error } : { instruction: { mnemonic, source } };
808
239
  }
809
- function isRegisterName(text) {
810
- return /^(A|B|C|D|E|H|L|I|R|AF|BC|DE|HL|SP|IX|IY|IXH|IXL|IYH|IYL)$/i.test(text.trim());
811
- }
812
- function parseConstantExpression(text) {
813
- const expression = parseExpression(text);
814
- return expression ? constantExpressionValue(expression) : undefined;
815
- }
816
- function isRstVector(value) {
817
- return (value === 0 ||
818
- value === 8 ||
819
- value === 16 ||
820
- value === 24 ||
821
- value === 32 ||
822
- value === 40 ||
823
- value === 48 ||
824
- value === 56);
825
- }
826
- function parseBitIndex(text) {
827
- const value = parseConstantExpression(text);
828
- return isBitIndex(value) ? value : undefined;
829
- }
830
- function isBitIndex(value) {
831
- return (value === 0 ||
832
- value === 1 ||
833
- value === 2 ||
834
- value === 3 ||
835
- value === 4 ||
836
- value === 5 ||
837
- value === 6 ||
838
- value === 7);
839
- }
840
- function constantExpressionValue(expression) {
841
- switch (expression.kind) {
842
- case 'number':
843
- return expression.value;
844
- case 'unary':
845
- return constantUnaryExpressionValue(expression);
846
- case 'binary':
847
- return constantBinaryExpressionValue(expression);
848
- case 'symbol':
849
- case 'current-location':
850
- return undefined;
851
- }
852
- }
853
- function constantUnaryExpressionValue(expression) {
854
- const value = constantExpressionValue(expression.expression);
855
- if (value === undefined) {
856
- return undefined;
857
- }
858
- switch (expression.operator) {
859
- case '+':
860
- return value;
861
- case '-':
862
- return -value;
863
- case '~':
864
- return ~value;
865
- }
866
- }
867
- function constantBinaryExpressionValue(expression) {
868
- const left = constantExpressionValue(expression.left);
869
- const right = constantExpressionValue(expression.right);
870
- if (left === undefined || right === undefined) {
871
- return undefined;
872
- }
873
- switch (expression.operator) {
874
- case '+':
875
- return left + right;
876
- case '-':
877
- return left - right;
878
- case '*':
879
- return left * right;
880
- case '/':
881
- return Math.trunc(left / right);
882
- case '%':
883
- return left % right;
884
- case '&':
885
- return left & right;
886
- case '^':
887
- return left ^ right;
888
- case '|':
889
- return left | right;
890
- case '<<':
891
- return left << right;
892
- case '>>':
893
- return left >> right;
894
- }
895
- }
896
- function splitInstructionOperands(text) {
897
- const values = [];
898
- let depth = 0;
899
- let quote;
900
- let escaped = false;
901
- let start = 0;
902
- for (let index = 0; index < text.length; index += 1) {
903
- const char = text[index];
904
- if (escaped) {
905
- escaped = false;
906
- continue;
907
- }
908
- if (char === '\\' && quote) {
909
- escaped = true;
910
- continue;
911
- }
912
- if ((char === '"' || char === "'") &&
913
- !(char === "'" && quote === undefined && /[A-Za-z0-9_]/.test(text[index - 1] ?? ''))) {
914
- quote = quote === char ? undefined : (quote ?? char);
915
- continue;
916
- }
917
- if (quote) {
918
- continue;
919
- }
920
- if (char === '(') {
921
- depth += 1;
922
- }
923
- else if (char === ')') {
924
- depth -= 1;
925
- }
926
- else if (char === ',' && depth === 0) {
927
- values.push(text.slice(start, index));
928
- start = index + 1;
929
- }
930
- }
931
- values.push(text.slice(start));
932
- return values.map((value) => value.trim());
933
- }
934
- function isSupportedLd(target, source) {
935
- if (isSupportedSpecialRegisterLd(target, source)) {
936
- return true;
937
- }
938
- if (isSupportedHalfIndexLd(target, source)) {
939
- return true;
940
- }
941
- if (target.kind === 'reg8' && (source.kind === 'reg8' || source.kind === 'imm')) {
942
- return true;
943
- }
944
- if (target.kind === 'reg8' && source.kind === 'indexed') {
945
- return true;
946
- }
947
- if (target.kind === 'indexed' && (source.kind === 'reg8' || source.kind === 'imm')) {
948
- return true;
949
- }
950
- if (target.kind === 'reg16' && source.kind === 'imm') {
951
- return true;
952
- }
953
- if (target.kind === 'reg16' &&
954
- source.kind === 'reg16' &&
955
- isLegacyReg16ByteTransferPair(target.register, source.register)) {
956
- return true;
240
+ function parseIndexedAdd(targetIndex16, sourceText) {
241
+ const target = { kind: 'reg-index16', register: targetIndex16 };
242
+ const source16 = parseRegister16Operand(sourceText);
243
+ if (source16 && source16.register !== 'hl') {
244
+ return { instruction: { mnemonic: 'add', target, source: source16 } };
957
245
  }
958
- if (target.kind === 'reg-index16' && source.kind === 'imm') {
959
- return true;
960
- }
961
- if (target.kind === 'reg16' &&
962
- target.register === 'sp' &&
963
- (source.kind === 'reg16' || source.kind === 'reg-index16') &&
964
- (source.register === 'hl' || source.register === 'ix' || source.register === 'iy')) {
965
- return true;
966
- }
967
- if ((target.kind === 'reg8' || target.kind === 'reg16' || target.kind === 'reg-index16') &&
968
- source.kind === 'mem-abs' &&
969
- (target.kind !== 'reg8' || target.register === 'a')) {
970
- return true;
971
- }
972
- if (target.kind === 'mem-abs' &&
973
- (source.kind === 'reg16' ||
974
- source.kind === 'reg-index16' ||
975
- (source.kind === 'reg8' && source.register === 'a'))) {
976
- return true;
977
- }
978
- if (target.kind === 'reg8' && target.register === 'a' && source.kind === 'reg-indirect') {
979
- return true;
980
- }
981
- if (target.kind === 'reg-indirect' && source.kind === 'reg8' && source.register === 'a') {
982
- return true;
983
- }
984
- if (target.kind === 'reg-indirect' && target.register === 'hl' && source.kind === 'reg8') {
985
- return true;
986
- }
987
- if (target.kind === 'reg-indirect' && target.register === 'hl' && source.kind === 'imm') {
988
- return true;
246
+ const sourceIndex16 = parseIndexRegister16(sourceText);
247
+ if (sourceIndex16 === targetIndex16) {
248
+ return {
249
+ instruction: {
250
+ mnemonic: 'add',
251
+ target,
252
+ source: { kind: 'reg-index16', register: sourceIndex16 },
253
+ },
254
+ };
989
255
  }
990
- return target.kind === 'reg8' && source.kind === 'reg-indirect' && source.register === 'hl';
256
+ return {
257
+ error: `add ${targetIndex16.toUpperCase()}, rr supports BC/DE/SP and same-index pair only`,
258
+ };
991
259
  }
992
- function unsupportedLdReason(target, source) {
993
- if (isMemoryOperand(target) && isMemoryOperand(source)) {
994
- return 'ld does not support memory-to-memory transfers';
995
- }
996
- if (hasHalfIndexRegister(target, source)) {
997
- if (!isSameIndexHalfFamily(target, source)) {
998
- return 'ld between IX* and IY* byte registers is not supported';
999
- }
1000
- if (usesPlainHlCounterpart(target, source)) {
1001
- return 'ld with IX*/IY* does not support plain H/L counterpart operands';
1002
- }
1003
- }
1004
- if (target.kind === 'reg-index16' &&
1005
- source.kind === 'reg-index16' &&
1006
- target.register !== source.register) {
1007
- return 'ld rr, rr supports SP <- HL/IX/IY only';
1008
- }
1009
- if (target.kind === 'reg16' &&
1010
- source.kind !== 'imm' &&
1011
- (source.kind === 'reg16' || source.kind === 'reg-index16')) {
1012
- if (target.register === 'sp' &&
1013
- (source.register === 'hl' || source.register === 'ix' || source.register === 'iy')) {
1014
- return undefined;
1015
- }
1016
- if (source.kind === 'reg16' &&
1017
- isLegacyReg16ByteTransferPair(target.register, source.register)) {
1018
- return undefined;
1019
- }
1020
- return 'ld rr, rr supports SP <- HL/IX/IY only';
260
+ function parseUnaryAluInstruction(text) {
261
+ const alu = /^(SUB|AND|OR|XOR|CP)(?:\s+(.*))?$/i.exec(text);
262
+ if (alu) {
263
+ const mnemonic = (alu[1] ?? '').toLowerCase();
264
+ return parseUnaryAluOperands(mnemonic, alu[2] ?? '');
1021
265
  }
1022
266
  return undefined;
1023
267
  }
1024
- function isLegacyReg16ByteTransferPair(target, source) {
1025
- return ((target === 'hl' && source === 'de') ||
1026
- (target === 'bc' && source === 'de'));
1027
- }
1028
- function isSupportedSpecialRegisterLd(target, source) {
1029
- return ((target.kind === 'special8' && source.kind === 'reg8' && source.register === 'a') ||
1030
- (target.kind === 'reg8' && target.register === 'a' && source.kind === 'special8'));
1031
- }
1032
- function isMemoryOperand(operand) {
1033
- return (operand.kind === 'reg-indirect' || operand.kind === 'indexed' || operand.kind === 'mem-abs');
1034
- }
1035
- function isSupportedHalfIndexLd(target, source) {
1036
- if (!hasHalfIndexRegister(target, source)) {
1037
- return false;
1038
- }
1039
- if (!isSameIndexHalfFamily(target, source) || usesPlainHlCounterpart(target, source)) {
1040
- return false;
268
+ function parseUnaryAluOperands(mnemonic, operandText) {
269
+ const parts = splitInstructionOperands(operandText);
270
+ if (operandText.trim().length === 0 || (parts.length !== 1 && parts.length !== 2)) {
271
+ return { error: `${mnemonic} expects one operand, or two with destination A` };
1041
272
  }
1042
- return isHalfIndexCompatibleByteOperand(target) && isHalfIndexCompatibleByteOperand(source);
1043
- }
1044
- function hasHalfIndexRegister(target, source) {
1045
- return target.kind === 'reg-half-index' || source.kind === 'reg-half-index';
1046
- }
1047
- function isSameIndexHalfFamily(target, source) {
1048
- const targetFamily = indexHalfFamily(target);
1049
- const sourceFamily = indexHalfFamily(source);
1050
- return !targetFamily || !sourceFamily || targetFamily === sourceFamily;
1051
- }
1052
- function indexHalfFamily(operand) {
1053
- if (operand.kind !== 'reg-half-index') {
1054
- return undefined;
1055
- }
1056
- return operand.register.startsWith('ix') ? 'ix' : 'iy';
1057
- }
1058
- function usesPlainHlCounterpart(target, source) {
1059
- return ((target.kind === 'reg-half-index' && isPlainHlReg8(source)) ||
1060
- (source.kind === 'reg-half-index' && isPlainHlReg8(target)));
1061
- }
1062
- function isPlainHlReg8(operand) {
1063
- return operand.kind === 'reg8' && (operand.register === 'h' || operand.register === 'l');
273
+ return parts.length === 2
274
+ ? parseTwoOperandUnaryAlu(mnemonic, parts[0] ?? '', parts[1] ?? '')
275
+ : parseAccumulatorAluSource(mnemonic, parts[0] ?? '');
1064
276
  }
1065
- function isHalfIndexCompatibleByteOperand(operand) {
1066
- return (operand.kind === 'reg-half-index' ||
1067
- (operand.kind === 'reg8' && operand.register !== 'h' && operand.register !== 'l'));
277
+ function parseTwoOperandUnaryAlu(mnemonic, targetText, sourceText) {
278
+ const target = parseRegister8Operand(targetText);
279
+ return target?.register === 'a'
280
+ ? parseAccumulatorAluSource(mnemonic, sourceText)
281
+ : { error: `${mnemonic} two-operand form requires destination A` };
1068
282
  }