@jhlagado/azm 0.2.6 → 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.
- package/README.md +170 -69
- package/dist/src/api-artifacts.d.ts +20 -0
- package/dist/src/api-artifacts.js +165 -0
- package/dist/src/api-compile.d.ts +8 -2
- package/dist/src/api-compile.js +31 -230
- package/dist/src/api-register-contracts.d.ts +9 -0
- package/dist/src/api-register-contracts.js +77 -0
- package/dist/src/api-tooling.d.ts +2 -2
- package/dist/src/api-tooling.js +1 -1
- package/dist/src/assembly/address-planning.d.ts +1 -2
- package/dist/src/assembly/address-planning.js +119 -218
- package/dist/src/assembly/address-symbols.d.ts +12 -0
- package/dist/src/assembly/address-symbols.js +118 -0
- package/dist/src/assembly/fixup-emission.js +30 -48
- package/dist/src/assembly/program-emission.js +163 -164
- package/dist/src/cli/artifact-files.d.ts +15 -0
- package/dist/src/cli/artifact-files.js +86 -0
- package/dist/src/cli/parse-args.d.ts +6 -5
- package/dist/src/cli/parse-args.js +162 -136
- package/dist/src/cli/run.js +4 -1
- package/dist/src/cli/usage.d.ts +1 -0
- package/dist/src/cli/usage.js +33 -0
- package/dist/src/cli/write-artifacts.js +18 -91
- package/dist/src/core/compile.js +51 -274
- package/dist/src/core/conditional-assembly.d.ts +6 -0
- package/dist/src/core/conditional-assembly.js +181 -0
- package/dist/src/expansion/op-constant-expression.d.ts +3 -0
- package/dist/src/expansion/op-constant-expression.js +52 -0
- package/dist/src/expansion/op-expand-selected.d.ts +5 -0
- package/dist/src/expansion/op-expand-selected.js +143 -0
- package/dist/src/expansion/op-expansion.d.ts +5 -53
- package/dist/src/expansion/op-expansion.js +85 -815
- package/dist/src/expansion/op-instruction-instantiation.d.ts +3 -0
- package/dist/src/expansion/op-instruction-instantiation.js +194 -0
- package/dist/src/expansion/op-local-labels.d.ts +8 -0
- package/dist/src/expansion/op-local-labels.js +166 -0
- package/dist/src/expansion/op-operand-splitting.d.ts +1 -0
- package/dist/src/expansion/op-operand-splitting.js +44 -0
- package/dist/src/expansion/op-operands.d.ts +53 -0
- package/dist/src/expansion/op-operands.js +66 -0
- package/dist/src/expansion/op-selection.d.ts +18 -0
- package/dist/src/expansion/op-selection.js +172 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/model/diagnostic.d.ts +4 -0
- package/dist/src/model/diagnostic.js +4 -0
- package/dist/src/outputs/asm80-expression-evaluation.d.ts +10 -0
- package/dist/src/outputs/asm80-expression-evaluation.js +75 -0
- package/dist/src/outputs/asm80-expressions.d.ts +5 -0
- package/dist/src/outputs/asm80-expressions.js +47 -0
- package/dist/src/outputs/asm80-instruction-operands.d.ts +16 -0
- package/dist/src/outputs/asm80-instruction-operands.js +38 -0
- package/dist/src/outputs/asm80-instructions.d.ts +5 -0
- package/dist/src/outputs/asm80-instructions.js +272 -0
- package/dist/src/outputs/asm80-ld-operands.d.ts +10 -0
- package/dist/src/outputs/asm80-ld-operands.js +157 -0
- package/dist/src/outputs/asm80-strings.d.ts +4 -0
- package/dist/src/outputs/asm80-strings.js +14 -0
- package/dist/src/outputs/d8-files.d.ts +10 -0
- package/dist/src/outputs/d8-files.js +103 -0
- package/dist/src/outputs/d8-helpers.d.ts +21 -0
- package/dist/src/outputs/d8-helpers.js +136 -0
- package/dist/src/outputs/hex.js +26 -18
- package/dist/src/outputs/types.d.ts +16 -10
- package/dist/src/outputs/write-asm80.js +68 -597
- package/dist/src/outputs/write-d8.js +6 -216
- package/dist/src/register-contracts/accept-output.d.ts +2 -0
- package/dist/src/register-contracts/analyze-helpers.d.ts +29 -0
- package/dist/src/register-contracts/analyze-helpers.js +162 -0
- package/dist/src/{register-care → register-contracts}/analyze.d.ts +6 -6
- package/dist/src/register-contracts/analyze.js +73 -0
- package/dist/src/register-contracts/annotate.d.ts +11 -0
- package/dist/src/{register-care → register-contracts}/annotate.js +3 -3
- package/dist/src/register-contracts/annotations.d.ts +8 -0
- package/dist/src/{register-care → register-contracts}/annotations.js +3 -3
- package/dist/src/register-contracts/boundaryHints.d.ts +3 -0
- package/dist/src/register-contracts/boundaryHints.js +24 -0
- package/dist/src/register-contracts/carriers.d.ts +2 -0
- package/dist/src/register-contracts/constants.d.ts +4 -0
- package/dist/src/register-contracts/constants.js +51 -0
- package/dist/src/register-contracts/controlFlow.d.ts +5 -0
- package/dist/src/register-contracts/controlFlow.js +55 -0
- package/dist/src/register-contracts/fix.d.ts +11 -0
- package/dist/src/{register-care → register-contracts}/fix.js +47 -30
- package/dist/src/register-contracts/instruction-head.d.ts +2 -0
- package/dist/src/register-contracts/instruction-head.js +3 -0
- package/dist/src/register-contracts/instruction-operands.d.ts +3 -0
- package/dist/src/register-contracts/instruction-operands.js +101 -0
- package/dist/src/register-contracts/instruction-predicates.d.ts +6 -0
- package/dist/src/register-contracts/instruction-predicates.js +44 -0
- package/dist/src/register-contracts/interfaceContracts.d.ts +2 -0
- package/dist/src/register-contracts/interfaceContracts.js +68 -0
- package/dist/src/register-contracts/liveness.d.ts +3 -0
- package/dist/src/{register-care → register-contracts}/liveness.js +111 -79
- package/dist/src/register-contracts/operand-register-name.d.ts +2 -0
- package/dist/src/register-contracts/operand-register-name.js +13 -0
- package/dist/src/{register-care → register-contracts}/profiles.d.ts +5 -5
- package/dist/src/{register-care → register-contracts}/profiles.js +13 -2
- package/dist/src/register-contracts/programModel-boundaries.d.ts +6 -0
- package/dist/src/register-contracts/programModel-boundaries.js +64 -0
- package/dist/src/register-contracts/programModel-routines.d.ts +7 -0
- package/dist/src/register-contracts/programModel-routines.js +128 -0
- package/dist/src/register-contracts/programModel.d.ts +3 -0
- package/dist/src/register-contracts/programModel.js +14 -0
- package/dist/src/register-contracts/report.d.ts +5 -0
- package/dist/src/{register-care → register-contracts}/report.js +34 -17
- package/dist/src/register-contracts/routine-summaries.d.ts +6 -0
- package/dist/src/{register-care → register-contracts}/routine-summaries.js +11 -1
- package/dist/src/register-contracts/smartCommentBlocks.d.ts +5 -0
- package/dist/src/register-contracts/smartCommentBlocks.js +30 -0
- package/dist/src/register-contracts/smartCommentParsing.d.ts +3 -0
- package/dist/src/register-contracts/smartCommentParsing.js +80 -0
- package/dist/src/register-contracts/smartComments.d.ts +5 -0
- package/dist/src/register-contracts/smartComments.js +92 -0
- package/dist/src/register-contracts/summaries.d.ts +12 -0
- package/dist/src/{register-care → register-contracts}/summaries.js +7 -7
- package/dist/src/register-contracts/summary-boundary.d.ts +2 -0
- package/dist/src/register-contracts/summary-boundary.js +40 -0
- package/dist/src/register-contracts/summary-contract.d.ts +2 -0
- package/dist/src/register-contracts/summary-contract.js +45 -0
- package/dist/src/register-contracts/summary-result.d.ts +7 -0
- package/dist/src/register-contracts/summary-result.js +122 -0
- package/dist/src/register-contracts/summary-state.d.ts +23 -0
- package/dist/src/register-contracts/summary-state.js +88 -0
- package/dist/src/register-contracts/summary-token-transfer.d.ts +3 -0
- package/dist/src/register-contracts/summary-token-transfer.js +67 -0
- package/dist/src/register-contracts/summary.d.ts +3 -0
- package/dist/src/register-contracts/summary.js +266 -0
- package/dist/src/register-contracts/tooling.d.ts +57 -0
- package/dist/src/{register-care → register-contracts}/tooling.js +8 -6
- package/dist/src/register-contracts/types.d.ts +188 -0
- package/dist/src/semantics/binary-operators.d.ts +2 -0
- package/dist/src/semantics/binary-operators.js +15 -0
- package/dist/src/semantics/byte-functions.d.ts +2 -0
- package/dist/src/semantics/byte-functions.js +7 -0
- package/dist/src/semantics/constant-operator-types.d.ts +10 -0
- package/dist/src/semantics/constant-operator-types.js +1 -0
- package/dist/src/semantics/constant-operators.d.ts +3 -0
- package/dist/src/semantics/constant-operators.js +3 -0
- package/dist/src/semantics/diagnostics.d.ts +3 -0
- package/dist/src/semantics/diagnostics.js +10 -0
- package/dist/src/semantics/expression-evaluation.d.ts +11 -19
- package/dist/src/semantics/expression-evaluation.js +22 -334
- package/dist/src/semantics/layout-evaluation.d.ts +23 -0
- package/dist/src/semantics/layout-evaluation.js +202 -0
- package/dist/src/semantics/layout-format.d.ts +5 -0
- package/dist/src/semantics/layout-format.js +31 -0
- package/dist/src/semantics/layout-path.d.ts +24 -0
- package/dist/src/semantics/layout-path.js +58 -0
- package/dist/src/semantics/unary-operators.d.ts +2 -0
- package/dist/src/semantics/unary-operators.js +8 -0
- package/dist/src/source/line-comment-scanner.d.ts +1 -0
- package/dist/src/source/line-comment-scanner.js +51 -0
- package/dist/src/source/strip-line-comment.js +8 -44
- package/dist/src/syntax/directive-aliases.js +36 -22
- package/dist/src/syntax/expression-tokenizer.d.ts +30 -0
- package/dist/src/syntax/expression-tokenizer.js +310 -0
- package/dist/src/syntax/parse-directive-statement.d.ts +14 -0
- package/dist/src/syntax/parse-directive-statement.js +307 -0
- package/dist/src/syntax/parse-expression.d.ts +2 -2
- package/dist/src/syntax/parse-expression.js +7 -568
- package/dist/src/syntax/parse-layout-declarations.d.ts +9 -0
- package/dist/src/syntax/parse-layout-declarations.js +180 -0
- package/dist/src/syntax/parse-layout-expression.d.ts +5 -0
- package/dist/src/syntax/parse-layout-expression.js +175 -0
- package/dist/src/syntax/parse-line.js +4 -272
- package/dist/src/syntax/parse-token-expression.d.ts +3 -0
- package/dist/src/syntax/parse-token-expression.js +133 -0
- package/dist/src/tooling/case-style.js +47 -30
- package/dist/src/z80/effect-groups.d.ts +38 -0
- package/dist/src/z80/effect-groups.js +265 -0
- package/dist/src/z80/effect-units.d.ts +18 -0
- package/dist/src/z80/effect-units.js +165 -0
- package/dist/src/z80/effects.d.ts +1 -1
- package/dist/src/z80/effects.js +94 -557
- package/dist/src/z80/encode-core.d.ts +2 -0
- package/dist/src/z80/encode-core.js +42 -0
- package/dist/src/z80/encode-ld-helpers.d.ts +25 -0
- package/dist/src/z80/encode-ld-helpers.js +172 -0
- package/dist/src/z80/encode-ld.d.ts +2 -0
- package/dist/src/z80/encode-ld.js +285 -0
- package/dist/src/z80/encode.js +190 -542
- package/dist/src/z80/ld-support.d.ts +3 -0
- package/dist/src/z80/ld-support.js +146 -0
- package/dist/src/z80/operand-split-state.d.ts +8 -0
- package/dist/src/z80/operand-split-state.js +46 -0
- package/dist/src/z80/operand-split.d.ts +1 -0
- package/dist/src/z80/operand-split.js +13 -0
- package/dist/src/z80/parse-basic.d.ts +4 -0
- package/dist/src/z80/parse-basic.js +39 -0
- package/dist/src/z80/parse-branch.d.ts +4 -0
- package/dist/src/z80/parse-branch.js +218 -0
- package/dist/src/z80/parse-conditions.d.ts +6 -0
- package/dist/src/z80/parse-conditions.js +10 -0
- package/dist/src/z80/parse-exchange.d.ts +2 -0
- package/dist/src/z80/parse-exchange.js +30 -0
- package/dist/src/z80/parse-instruction.js +224 -1010
- package/dist/src/z80/parse-io-control.d.ts +5 -0
- package/dist/src/z80/parse-io-control.js +108 -0
- package/dist/src/z80/parse-ld.d.ts +2 -0
- package/dist/src/z80/parse-ld.js +83 -0
- package/dist/src/z80/parse-operands.d.ts +41 -0
- package/dist/src/z80/parse-operands.js +259 -0
- package/docs/reference/cli.md +42 -35
- package/docs/reference/tooling-api.md +20 -16
- package/package.json +1 -1
- package/dist/src/register-care/accept-output.d.ts +0 -2
- package/dist/src/register-care/analyze.js +0 -166
- package/dist/src/register-care/annotate.d.ts +0 -11
- package/dist/src/register-care/annotations.d.ts +0 -8
- package/dist/src/register-care/boundaryHints.d.ts +0 -3
- package/dist/src/register-care/boundaryHints.js +0 -80
- package/dist/src/register-care/carriers.d.ts +0 -2
- package/dist/src/register-care/controlFlow.d.ts +0 -5
- package/dist/src/register-care/controlFlow.js +0 -38
- package/dist/src/register-care/fix.d.ts +0 -11
- package/dist/src/register-care/instruction-shape.d.ts +0 -11
- package/dist/src/register-care/instruction-shape.js +0 -129
- package/dist/src/register-care/liveness.d.ts +0 -3
- package/dist/src/register-care/programModel.d.ts +0 -3
- package/dist/src/register-care/programModel.js +0 -266
- package/dist/src/register-care/report.d.ts +0 -5
- package/dist/src/register-care/routine-summaries.d.ts +0 -6
- package/dist/src/register-care/smartComments.d.ts +0 -5
- package/dist/src/register-care/smartComments.js +0 -243
- package/dist/src/register-care/summaries.d.ts +0 -12
- package/dist/src/register-care/summary.d.ts +0 -3
- package/dist/src/register-care/summary.js +0 -474
- package/dist/src/register-care/tooling.d.ts +0 -43
- package/dist/src/register-care/types.d.ts +0 -172
- /package/dist/src/{register-care → register-contracts}/accept-output.js +0 -0
- /package/dist/src/{register-care → register-contracts}/carriers.js +0 -0
- /package/dist/src/{register-care → register-contracts}/sourceText.d.ts +0 -0
- /package/dist/src/{register-care → register-contracts}/sourceText.js +0 -0
- /package/dist/src/{register-care → register-contracts}/types.js +0 -0
|
@@ -1,161 +1,61 @@
|
|
|
1
|
-
import {
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
if (
|
|
588
|
-
return
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
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
|
|
623
|
-
const
|
|
624
|
-
if (
|
|
625
|
-
return
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
return [];
|
|
98
|
+
return parts.length === 2
|
|
99
|
+
? { instruction: { mnemonic, bit, operand } }
|
|
100
|
+
: parseIndexedBitDestination(mnemonic, bit, operand, parts[2] ?? '');
|
|
631
101
|
}
|
|
632
|
-
function
|
|
633
|
-
|
|
634
|
-
|
|
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 (
|
|
638
|
-
return
|
|
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
|
|
643
|
-
|
|
644
|
-
|
|
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
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
652
|
-
|
|
120
|
+
const destination = parseRegister8Operand(destinationText);
|
|
121
|
+
if (destination) {
|
|
122
|
+
return { instruction: { mnemonic, bit, operand, destination } };
|
|
653
123
|
}
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
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
|
|
661
|
-
const
|
|
662
|
-
const
|
|
663
|
-
if (
|
|
664
|
-
return
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
|
703
|
-
const
|
|
704
|
-
if (
|
|
705
|
-
|
|
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
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
|
793
|
-
const
|
|
794
|
-
if (
|
|
795
|
-
return
|
|
232
|
+
function parseAccumulatorAluSource(mnemonic, sourceText) {
|
|
233
|
+
const source = parseAluOperand(sourceText);
|
|
234
|
+
if (!source) {
|
|
235
|
+
return { error: `invalid ${mnemonic.toUpperCase()} operand: ${sourceText}` };
|
|
796
236
|
}
|
|
797
|
-
|
|
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
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
|
256
|
+
return {
|
|
257
|
+
error: `add ${targetIndex16.toUpperCase()}, rr supports BC/DE/SP and same-index pair only`,
|
|
258
|
+
};
|
|
991
259
|
}
|
|
992
|
-
function
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
|
1043
|
-
|
|
1044
|
-
|
|
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
|
|
1066
|
-
|
|
1067
|
-
|
|
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
|
}
|