Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | 139x 139x 834x 240x 139x 25x 9x 2x 1x 5x 1x 187x 159x 139x 1x 7x 1x 3x 1x 1x 2x 8x 3x 51x 157x 2x 5x 2x 3x 4x 6x 1x 139x 139x 139x 139x 117x 117x 117x 117x 17x 117x 665x 665x 665x 665x 106x 106x 559x 559x 559x 559x 46x 46x 513x 70x 70x 443x 161x 161x 3842x 282x 280x 280x 1789x 117x 349x 344x 344x 115x 115x 229x 5x 5x 5x 224x 4x 4x 4x 220x 6x 6x 6x 214x 67x 67x 67x 59x 59x 147x 147x 141x 141x 117x 2130x 1796x 1796x 1787x 1787x 151x 151x 151x 258x 117x 117x 117x 100x | /**
* Breakpoint conditions. Names: the registers A X Y S PC P, the flags C Z I D V N (1 or 0), `value` and `old`.
* `?addr` reads a byte without side effects. Numbers: &FF, $FF, 0xFF or decimal. Operators, loosest first:
* `|| &&`, then BBC BASIC's bitwise `OR EOR AND` (C's `| ^ &`), `= == <> !=`, `< <= > >=`, `+ -`, and the unary
* `! NOT - ?`. As in BBC BASIC, true is -1, so a comparison ANDed with a value keeps the value; unlike it, NOT is
* logical, the same as `!`.
*/
const AddressMask = 0xffff;
const True = -1;
const flag = (bit) => (cpu) => (cpu.p.asByte() & bit ? 1 : 0);
const truth = (test) => (test ? True : 0);
const Variables = new Map([
["A", (cpu) => cpu.a],
["X", (cpu) => cpu.x],
["Y", (cpu) => cpu.y],
["S", (cpu) => cpu.s],
["PC", (cpu, pc) => pc],
["P", (cpu) => cpu.p.asByte()],
["C", flag(0x01)],
["Z", flag(0x02)],
["I", flag(0x04)],
["D", flag(0x08)],
["V", flag(0x40)],
["N", flag(0x80)],
["VALUE", (cpu, pc, value) => value],
["OLD", (cpu, pc, value, old) => old],
]);
const BinaryLevels = [
new Map([["||", (a, b) => truth(a || b)]]),
new Map([["&&", (a, b) => truth(a && b)]]),
new Map([
["|", (a, b) => a | b],
["OR", (a, b) => a | b],
]),
new Map([
["^", (a, b) => a ^ b],
["EOR", (a, b) => a ^ b],
]),
new Map([
["&", (a, b) => a & b],
["AND", (a, b) => a & b],
]),
new Map([
["==", (a, b) => truth(a === b)],
["=", (a, b) => truth(a === b)],
["!=", (a, b) => truth(a !== b)],
["<>", (a, b) => truth(a !== b)],
]),
new Map([
["<", (a, b) => truth(a < b)],
["<=", (a, b) => truth(a <= b)],
[">", (a, b) => truth(a > b)],
[">=", (a, b) => truth(a >= b)],
]),
new Map([
["+", (a, b) => a + b],
["-", (a, b) => a - b],
]),
];
const Symbols = ["==", "!=", "<>", "<=", ">=", "&&", "||", "<", ">", "=", "&", "|", "^", "!", "?", "+", "-", "(", ")"];
const HexNumber = /^(?:&|\$|0x)([0-9a-f]+)/i;
const DecimalNumber = /^[0-9]+/;
const Word = /^[a-z_][a-z0-9_]*/i;
/**
* Compiles a breakpoint condition to a function of the CPU, the address of the instruction hitting it, the byte read
* or written (the opcode, for an instruction) and the byte there before a write (the byte read, for a read), which is
* non-zero when the condition holds. Throws, naming the column, if `text` is not one.
* @param {string} text
* @returns {(cpu: object, pc: number, value: number, old: number) => number}
*/
export function compileCondition(text) {
let at = 0;
let token = null;
let tokenAt = 0;
const fail = (what) => {
throw new Error(`Bad condition "${text}": ${what} at column ${tokenAt + 1}`);
};
// A & where a value is due starts an &-prefixed hex number; after a value it is the operator.
const advance = (valueDue) => {
while (at < text.length && /\s/.test(text[at])) at++;
tokenAt = at;
const rest = text.slice(at);
if (!rest) {
token = null;
return;
}
const hex = HexNumber.exec(rest);
const decimal = DecimalNumber.exec(rest);
const word = Word.exec(rest);
if (hex && (valueDue || rest[0] !== "&")) {
token = { number: parseInt(hex[1], 16) };
at += hex[0].length;
} else if (decimal) {
token = { number: parseInt(decimal[0], 10) };
at += decimal[0].length;
} else if (word) {
token = { word: word[0].toUpperCase() };
at += word[0].length;
} else {
const symbol = Symbols.find((s) => rest.startsWith(s));
if (!symbol) fail(`unexpected "${rest[0]}"`);
token = { symbol };
at += symbol.length;
}
};
const operatorName = () => token?.symbol ?? token?.word;
const value = () => {
if (!token) fail("expected a value");
const { number, word, symbol } = token;
if (number !== undefined) {
advance(false);
return () => number;
}
if (word === "NOT" || symbol === "!") {
advance(true);
const operand = value();
return (...args) => truth(!operand(...args));
}
if (symbol === "-") {
advance(true);
const operand = value();
return (...args) => -operand(...args);
}
if (symbol === "?") {
advance(true);
const address = value();
return (cpu, ...rest) => cpu.peekmem(address(cpu, ...rest) & AddressMask);
}
if (symbol === "(") {
advance(true);
const inner = binary(0);
if (token?.symbol !== ")") fail('expected ")"');
advance(false);
return inner;
}
const variable = Variables.get(word);
if (!variable) fail(word ? `unknown name "${word}"` : `unexpected "${symbol}"`);
advance(false);
return variable;
};
const binary = (level) => {
if (level === BinaryLevels.length) return value();
let left = binary(level + 1);
for (;;) {
const apply = BinaryLevels[level].get(operatorName());
if (!apply) return left;
advance(true);
const lhs = left;
const rhs = binary(level + 1);
left = (...args) => apply(lhs(...args), rhs(...args));
}
};
advance(true);
const condition = binary(0);
if (token) fail(`unexpected "${operatorName() ?? token.number}"`);
return condition;
}
|