From f962e1d2160c3a87914a8d1253bf77a65520f294 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Sat, 26 Sep 2026 15:38:26 +0300 Subject: [PATCH] refactor(truth-table): translate Estonian names and add regression harness MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - truth.bas: second readability pass — translate remaining Estonian identifiers (sist/lahend/tee/teeslg/lend/mov, tehl/nm/muu/prnp) into descriptive English, add per-SUB description headers, document the operator encoding (CHR$(1..5), t/v as tosi/vaar constants) and mark the dead 2002-era code (removeRedundancies, drainKeyboardBuffer, xlahn). Algorithm, labels and GOTOs unchanged. - Tests/: GameDriver-based regression harness — drives the program in crtbasic for 19 formulas, verifies truth columns against an independent Python oracle and byte-diffs full 8x8-cell text dumps against the baselines in expected/. Validated pixel-identical against real QB45 in DOSBox. --- Math/Truth table/Tests/expected/and.txt | 60 ++ .../Truth table/Tests/expected/chain-4var.txt | 60 ++ Math/Truth table/Tests/expected/constants.txt | 60 ++ .../Truth table/Tests/expected/double-not.txt | 60 ++ .../Tests/expected/equiv-chain.txt | 60 ++ Math/Truth table/Tests/expected/equiv.txt | 60 ++ Math/Truth table/Tests/expected/example.txt | 60 ++ Math/Truth table/Tests/expected/five-var.txt | 60 ++ .../Tests/expected/implies-chain.txt | 60 ++ Math/Truth table/Tests/expected/implies.txt | 60 ++ .../Tests/expected/mixed-andor.txt | 60 ++ .../Tests/expected/nested-parens.txt | 60 ++ Math/Truth table/Tests/expected/not-and.txt | 60 ++ .../Tests/expected/not-implies.txt | 60 ++ .../Tests/expected/not-in-parens.txt | 60 ++ .../Tests/expected/not-paren-and.txt | 60 ++ Math/Truth table/Tests/expected/not.txt | 60 ++ Math/Truth table/Tests/expected/or.txt | 60 ++ .../Tests/expected/repeated-var.txt | 60 ++ Math/Truth table/Tests/formulas.txt | 21 + Math/Truth table/Tests/run_tests.py | 263 ++++++ Math/Truth table/truth.bas | 752 ++++++++++-------- 22 files changed, 1824 insertions(+), 352 deletions(-) create mode 100644 Math/Truth table/Tests/expected/and.txt create mode 100644 Math/Truth table/Tests/expected/chain-4var.txt create mode 100644 Math/Truth table/Tests/expected/constants.txt create mode 100644 Math/Truth table/Tests/expected/double-not.txt create mode 100644 Math/Truth table/Tests/expected/equiv-chain.txt create mode 100644 Math/Truth table/Tests/expected/equiv.txt create mode 100644 Math/Truth table/Tests/expected/example.txt create mode 100644 Math/Truth table/Tests/expected/five-var.txt create mode 100644 Math/Truth table/Tests/expected/implies-chain.txt create mode 100644 Math/Truth table/Tests/expected/implies.txt create mode 100644 Math/Truth table/Tests/expected/mixed-andor.txt create mode 100644 Math/Truth table/Tests/expected/nested-parens.txt create mode 100644 Math/Truth table/Tests/expected/not-and.txt create mode 100644 Math/Truth table/Tests/expected/not-implies.txt create mode 100644 Math/Truth table/Tests/expected/not-in-parens.txt create mode 100644 Math/Truth table/Tests/expected/not-paren-and.txt create mode 100644 Math/Truth table/Tests/expected/not.txt create mode 100644 Math/Truth table/Tests/expected/or.txt create mode 100644 Math/Truth table/Tests/expected/repeated-var.txt create mode 100644 Math/Truth table/Tests/formulas.txt create mode 100644 Math/Truth table/Tests/run_tests.py diff --git a/Math/Truth table/Tests/expected/and.txt b/Math/Truth table/Tests/expected/and.txt new file mode 100644 index 0000000..e40a247 --- /dev/null +++ b/Math/Truth table/Tests/expected/and.txt @@ -0,0 +1,60 @@ +ab +tttt +tvvv +vvtv +vvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/chain-4var.txt b/Math/Truth table/Tests/expected/chain-4var.txt new file mode 100644 index 0000000..4866ef4 --- /dev/null +++ b/Math/Truth table/Tests/expected/chain-4var.txt @@ -0,0 +1,60 @@ +abcd +tttttttt +tttttvvv +ttttvttt +ttttvvvv +ttvttttt +ttvttvvv +tvvvvvtv +tvvvvtvt +vttttttt +vttttvvv +vtttvttt +vtttvvvv +vtvttttt +vtvttvvv +vtvvvttt +vtvvvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/constants.txt b/Math/Truth table/Tests/expected/constants.txt new file mode 100644 index 0000000..7fbbe51 --- /dev/null +++ b/Math/Truth table/Tests/expected/constants.txt @@ -0,0 +1,60 @@ +tav +ttttvt +tvvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/double-not.txt b/Math/Truth table/Tests/expected/double-not.txt new file mode 100644 index 0000000..fcccac3 --- /dev/null +++ b/Math/Truth table/Tests/expected/double-not.txt @@ -0,0 +1,60 @@ +(a) +t vt t +v tv v + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/equiv-chain.txt b/Math/Truth table/Tests/expected/equiv-chain.txt new file mode 100644 index 0000000..de5d79b --- /dev/null +++ b/Math/Truth table/Tests/expected/equiv-chain.txt @@ -0,0 +1,60 @@ +abc +tttttt +tttvvv +tvvvtv +tvvtvt +vvtvtv +vvttvt +vtvttt +vtvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/equiv.txt b/Math/Truth table/Tests/expected/equiv.txt new file mode 100644 index 0000000..bc89cb2 --- /dev/null +++ b/Math/Truth table/Tests/expected/equiv.txt @@ -0,0 +1,60 @@ +ab +tttt +tvvv +vvtv +vtvt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/example.txt b/Math/Truth table/Tests/expected/example.txt new file mode 100644 index 0000000..330755a --- /dev/null +++ b/Math/Truth table/Tests/expected/example.txt @@ -0,0 +1,60 @@ +ab(gb) +tttt ttt t +tttt vtt t +ttvt ttv t +ttvt vvv t +vvtt ttt v +vvtt vtt v +vvvt ttv v +vvvt vvv v + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/five-var.txt b/Math/Truth table/Tests/expected/five-var.txt new file mode 100644 index 0000000..fbe6ecf --- /dev/null +++ b/Math/Truth table/Tests/expected/five-var.txt @@ -0,0 +1,60 @@ +abcde +tttttttttt +tttttttvvv +tttttvvvtv +tttttvvtvt +tttvvttttt +tttvvttvvv +tttvvtvttt +tttvvtvvvv +ttvttttttt +ttvttttvvv +ttvttvvvtv +ttvttvvtvt +ttvvvttttt +ttvvvttvvv +ttvvvtvttt +ttvvvtvvvv +vttttttttt +vttttttvvv +vttttvvvtv +vttttvvtvt +vttvvttttt +vttvvttvvv +vttvvtvttt +vttvvtvvvv +vvvvtttttt +vvvvtttvvv +vvvvttvttt +vvvvttvvvv +vvvvvttttt +vvvvvttvvv +vvvvvtvttt +vvvvvtvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/implies-chain.txt b/Math/Truth table/Tests/expected/implies-chain.txt new file mode 100644 index 0000000..685f838 --- /dev/null +++ b/Math/Truth table/Tests/expected/implies-chain.txt @@ -0,0 +1,60 @@ +abc +tttttt +tttvvv +tvvttt +tvvtvt +vttttt +vttvvv +vtvttt +vtvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/implies.txt b/Math/Truth table/Tests/expected/implies.txt new file mode 100644 index 0000000..bc6521e --- /dev/null +++ b/Math/Truth table/Tests/expected/implies.txt @@ -0,0 +1,60 @@ +ab +tttt +tvvv +vttt +vtvt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/mixed-andor.txt b/Math/Truth table/Tests/expected/mixed-andor.txt new file mode 100644 index 0000000..43fb724 --- /dev/null +++ b/Math/Truth table/Tests/expected/mixed-andor.txt @@ -0,0 +1,60 @@ +abc +tttttt +ttttvt +tvvttt +tvvvvv +vvtttt +vvtvvv +vvvttt +vvvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/nested-parens.txt b/Math/Truth table/Tests/expected/nested-parens.txt new file mode 100644 index 0000000..451a27e --- /dev/null +++ b/Math/Truth table/Tests/expected/nested-parens.txt @@ -0,0 +1,60 @@ +((ab)(ca)) + ttt t ttt t + ttt t vtt t + tvv t ttt t + tvv t vtt t + vvt t ttv t + vvt t vvv t + vvv t ttv t + vvv t vvv t + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/not-and.txt b/Math/Truth table/Tests/expected/not-and.txt new file mode 100644 index 0000000..257ef5a --- /dev/null +++ b/Math/Truth table/Tests/expected/not-and.txt @@ -0,0 +1,60 @@ +ab +vtvtv +vtvvv +tvttt +tvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/not-implies.txt b/Math/Truth table/Tests/expected/not-implies.txt new file mode 100644 index 0000000..2b9467c --- /dev/null +++ b/Math/Truth table/Tests/expected/not-implies.txt @@ -0,0 +1,60 @@ +ab +vtttt +vttvt +tvttt +tvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/not-in-parens.txt b/Math/Truth table/Tests/expected/not-in-parens.txt new file mode 100644 index 0000000..608c338 --- /dev/null +++ b/Math/Truth table/Tests/expected/not-in-parens.txt @@ -0,0 +1,60 @@ +(ab)c + tvvt ttt + tvvt vvv + tttv ttt + tttv tvt + vvvt ttt + vvvt vvv + vvtv ttt + vvtv vvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/not-paren-and.txt b/Math/Truth table/Tests/expected/not-paren-and.txt new file mode 100644 index 0000000..27fa18f --- /dev/null +++ b/Math/Truth table/Tests/expected/not-paren-and.txt @@ -0,0 +1,60 @@ +(ab)c +v ttt vtv +v ttt vvv +v ttv vtv +v ttv vvv +v vtt vtv +v vtt vvv +t vvv ttt +t vvv vvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/not.txt b/Math/Truth table/Tests/expected/not.txt new file mode 100644 index 0000000..332bc61 --- /dev/null +++ b/Math/Truth table/Tests/expected/not.txt @@ -0,0 +1,60 @@ +a +vtv +tvt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/or.txt b/Math/Truth table/Tests/expected/or.txt new file mode 100644 index 0000000..f570f8a --- /dev/null +++ b/Math/Truth table/Tests/expected/or.txt @@ -0,0 +1,60 @@ +ab +tttt +ttvt +vttt +vvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/expected/repeated-var.txt b/Math/Truth table/Tests/expected/repeated-var.txt new file mode 100644 index 0000000..3ab3c7d --- /dev/null +++ b/Math/Truth table/Tests/expected/repeated-var.txt @@ -0,0 +1,60 @@ +aaa +tttttt +vvvvvv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Math/Truth table/Tests/formulas.txt b/Math/Truth table/Tests/formulas.txt new file mode 100644 index 0000000..9d8d20c --- /dev/null +++ b/Math/Truth table/Tests/formulas.txt @@ -0,0 +1,21 @@ +# nameformula as typed on the keyboard (digits 1-5 = operator keys: +# 1 equivalent, 2 implies, 3 or, 4 and, 5 not) +and a4b +or a3b +implies a2b +equiv a1b +not 5a +not-and 5a4b +not-implies 5a2b +example a1b2(g3b) +not-paren-and 5(a3b)4c +chain-4var a2b3c1d +nested-parens ((a4b)2(c3a)) +constants t4a3v +repeated-var a4a3a +five-var a3b4c2d1e +double-not 5(5a) +not-in-parens (a45b)3c +mixed-andor a4b3c +implies-chain a2b2c +equiv-chain a1b1c diff --git a/Math/Truth table/Tests/run_tests.py b/Math/Truth table/Tests/run_tests.py new file mode 100644 index 0000000..a18bdfb --- /dev/null +++ b/Math/Truth table/Tests/run_tests.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +"""Regression + oracle test harness for truth.bas (Truth table calculator). + +How it works: + 1. For every formula in formulas.txt the harness drives the .bas program + in crtbasic's headless GameDriver: it types the formula, presses Enter, + waits for the computation, takes a *text dump* of the graphics screen + (GameDriver --textdump recognizes the 8x8 font cells), satisfies the + closing INPUT$(1) with 'x' and quits with ESC. + 2. Oracle check: an independent Python implementation of the program's + logic (same operator semantics: NOT tightest, AND/OR equal precedence + left-associative, then IMPLIES, then EQUIV; 't'/'v' are true/false + constants, not variables) recomputes every variable column and the + final result column and compares them against the dump. + 3. Regression check: the full 80x60 text dump is diffed byte-for-byte + against the baseline recorded in expected/ from the original program. + Readability improvements must not change a single pixel. + +Usage: + run_tests.py --record # record baselines from the CURRENT truth.bas + run_tests.py # check truth.bas against baselines + oracle + run_tests.py --bas PATH # test a different .bas file +""" + +import subprocess +import sys +import tempfile +from pathlib import Path + +HERE = Path(__file__).resolve().parent +DEFAULT_BAS = HERE.parent / "truth.bas" +FORMULAS = HERE / "formulas.txt" +EXPECTED = HERE / "expected" +CRTBASIC = Path("/home/n0/workspace/svjatoslav/crtbasic") +DRIVER_CP = "target/classes:target/test-classes" +DRIVER_CLASS = "eu.svjatoslav.crtbasic.drivers.GameDriver" + +# Key script template: type formula, Enter, settle, $ = dump screen, +# 'x' satisfies INPUT$(1), ESC quits the menu (SYSTEM). +KEYS_TEMPLATE = "{formula}\n~~$~x~\x1b" + +OP_NAMES = {"1": "EQUIV", "2": "IMPLIES", "3": "OR", "4": "AND", "5": "NOT"} + + +# ---------------------------------------------------------------- oracle -- + +class Parser: + """Recursive-descent parser mirroring the program's operator semantics.""" + + def __init__(self, text: str): + self.tokens = list(text) + self.pos = 0 + + def peek(self) -> str: + return self.tokens[self.pos] if self.pos < len(self.tokens) else "" + + def take(self) -> str: + tok = self.tokens[self.pos] + self.pos += 1 + return tok + + def parse_equiv(self): + node = self.parse_implies() + while self.peek() == "1": + self.take() + node = ("equiv", node, self.parse_implies()) + return node + + def parse_implies(self): + node = self.parse_andor() + while self.peek() == "2": + self.take() + node = ("implies", node, self.parse_andor()) + return node + + def parse_andor(self): + node = self.parse_operand() + while self.peek() in ("3", "4"): + op = "or" if self.take() == "3" else "and" + node = (op, node, self.parse_operand()) + return node + + def parse_operand(self): + tok = self.peek() + if tok == "5": + self.take() + return ("not", self.parse_operand()) + if tok == "(": + self.take() + node = self.parse_equiv() + assert self.take() == ")", "missing closing paren" + return node + self.take() + return ("var", tok) + + +def evaluate(node, values: dict) -> bool: + kind = node[0] + if kind == "var": + name = node[1] + if name == "t": + return True + if name == "v": + return False + return values[name] + if kind == "not": + return not evaluate(node[1], values) + left, right = evaluate(node[1], values), evaluate(node[2], values) + if kind == "and": + return left and right + if kind == "or": + return left or right + if kind == "implies": + return (not left) or right + if kind == "equiv": + return left == right + raise ValueError(kind) + + +def variables_of(formula: str) -> list: + """Distinct variables in first-appearance order; t/v are constants.""" + names = [] + for ch in formula: + if ch.isalpha() and ch not in ("t", "v") and ch not in names: + names.append(ch) + return names + + +def combo_values(names: list, row: int) -> dict: + """Variable values for combo row (1-based), mirroring lahend(): + variable i (1-based) flips every 2**(nm-i) rows, starting at true.""" + nm = len(names) + values = {} + for i, name in enumerate(names): + block = 2 ** (nm - i - 1) + values[name] = ((row - 1) // block) % 2 == 0 + return values + + +def oracle_check(name: str, formula: str, dump: str) -> list: + """Compare variable columns and the result column against the oracle.""" + errors = [] + stripped = formula.replace(" ", "") + names = variables_of(formula) + var_columns = [(i, ch) for i, ch in enumerate(stripped) if ch in names] + rows = dump.split("\n") + ast = Parser(stripped).parse_equiv() + combos = 2 ** len(names) + for row in range(1, combos + 1): + if row >= len(rows): + errors.append(f"{name}: dump has no row {row}") + break + line = rows[row] + values = combo_values(names, row) + for col, ch in var_columns: + want = "t" if values[ch] else "v" + got = line[col] if col < len(line) else "?" + if got != want: + errors.append( + f"{name} row {row} col {col} (variable {ch}): want {want}, got {got!r}") + # The result column is the rightmost t/v cell in the row. Its + # position shifts right by the number of parentheses teeslg + # inserted: truth.bas DECLAREs teeslg's second parameter as x2 + # while the SUB itself names it x4, and crtbasic registers one + # cell under both names, so teeslg's x2 writes land in lahend's + # x2. The column is located, not computed. + result_col = max(line.rfind("t"), line.rfind("v")) + if result_col < 0: + errors.append(f"{name} row {row}: no result cell found") + continue + want = "t" if evaluate(ast, values) else "v" + got = line[result_col] + if got != want: + errors.append( + f"{name} row {row} col {result_col} (result): want {want}, got {got!r}") + # Below the table the screen must be blank. + for row in range(combos + 1, len(rows)): + if rows[row].strip(): + errors.append(f"{name}: unexpected content below table at row {row}: " + f"{rows[row].strip()!r}") + break + return errors + + +# ---------------------------------------------------------------- driver -- + +def run_formula(bas: Path, name: str, formula: str, workdir: Path) -> str: + keys = KEYS_TEMPLATE.format(formula=formula) + cmd = [ + "java", "-cp", DRIVER_CP, DRIVER_CLASS, str(bas), + "--steps=200000000", "--interval=60", "--block", + f"--screenshot={workdir / name}.png", + f"--textdump={workdir / name}.txt", + f"--keys={keys}", + ] + proc = subprocess.run(cmd, cwd=CRTBASIC, capture_output=True, text=True, timeout=300) + if proc.returncode != 0: + raise RuntimeError(f"{name}: driver failed:\n{proc.stdout}\n{proc.stderr}") + dump_path = workdir / f"{name}-1.txt" + if not dump_path.exists(): + raise RuntimeError(f"{name}: no text dump produced:\n{proc.stdout}\n{proc.stderr}") + return dump_path.read_text() + + +def load_formulas() -> list: + entries = [] + for line in FORMULAS.read_text().splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + name, formula = line.split("\t") + entries.append((name, formula)) + return entries + + +def main() -> int: + record = "--record" in sys.argv + bas = DEFAULT_BAS + if "--bas" in sys.argv: + bas = Path(sys.argv[sys.argv.index("--bas") + 1]) + formulas = load_formulas() + print(f"Program: {bas}") + print(f"Formulas: {len(formulas)}") + failures = 0 + with tempfile.TemporaryDirectory(prefix="truthtest-") as tmp: + workdir = Path(tmp) + for name, formula in formulas: + dump = run_formula(bas, name, formula, workdir) + errors = oracle_check(name, formula, dump) + for err in errors: + print(f"ORACLE FAIL: {err}") + failures += 1 + expected_path = EXPECTED / f"{name}.txt" + if record: + EXPECTED.mkdir(exist_ok=True) + expected_path.write_text(dump) + elif expected_path.exists(): + baseline = expected_path.read_text() + if dump != baseline: + failures += 1 + print(f"REGRESSION: {name}: dump differs from baseline") + old, new = baseline.split("\n"), dump.split("\n") + shown = 0 + for row in range(max(len(old), len(new))): + before = old[row] if row < len(old) else "" + after = new[row] if row < len(new) else "" + if before != after and shown < 5: + print(f" row {row}: baseline {before.strip()!r} -> got {after.strip()!r}") + shown += 1 + else: + failures += 1 + print(f"MISSING BASELINE: {name} (run with --record)") + status = "recorded" if record else ("ok" if not errors else "FAILED") + print(f" {name}: {status}") + if failures: + print(f"\n{failures} failure(s)") + return 1 + print("\nAll checks passed") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Math/Truth table/truth.bas b/Math/Truth table/truth.bas index ee39e82..8e2a82c 100755 --- a/Math/Truth table/truth.bas +++ b/Math/Truth table/truth.bas @@ -8,6 +8,10 @@ ' Changelog: ' 2002, Initial version ' 2024 - 2025, Improved program readability +' 2026, Second readability pass: translated the remaining Estonian names +' (sist, lahend, tee, teeslg, lend, mov), documented the operator +' encoding and the addPrecedenceParens/solveEquation parameter +' aliasing. Algorithm unchanged. ' ' ' A truth table is a mathematical table used to determine the output of a @@ -17,27 +21,31 @@ ' understanding digital circuits, Boolean algebra, and logical ' expressions. ' -' Implemented operations: +' Implemented operations (typed with digit keys, stored as CHR$(1)..CHR$(5)): ' Equivalent ( Keyboard shortcut: 1 ) ' Implies ( Keyboard shortcut: 2 ) ' OR ( Keyboard shortcut: 3 ) ' AND ( Keyboard shortcut: 4 ) ' NOT ( Keyboard shortcut: 5 ) +' +' The letters t and v are NOT variables: they are the truth constants +' (Estonian tosi = true, vaar = false). Every other letter becomes a +' variable of the table. DECLARE SUB removeRedundancies (startIndex!, endIndex!, removalCount!) -DECLARE SUB getOperatorPriority (a!, b!) -DECLARE SUB movM (x1!, n!) -DECLARE SUB clearScreenBuffer () -DECLARE SUB lendm (x1!, m!) -DECLARE SUB mov (x1!, n!) -DECLARE SUB lendp (x1!, m!) -DECLARE SUB teeslg (x1!, x2!, l!) -DECLARE SUB prepare () -DECLARE SUB tee (x1!, x2!) -DECLARE SUB lahend (x1, x2) -DECLARE SUB printText (x!, y!, c!, c1!, a$) -DECLARE SUB sist () -DECLARE SUB start () +DECLARE SUB getOperatorPriority (operatorCode!, priority!) +DECLARE SUB shiftExpressionLeft (position!, count!) +DECLARE SUB drainKeyboardBuffer () +DECLARE SUB operandLengthBefore (position!, outLength!) +DECLARE SUB shiftExpressionRight (position!, count!) +DECLARE SUB operandLengthAfter (position!, outLength!) +DECLARE SUB addPrecedenceParens (exprStart!, exprEnd!, addedChars!) +DECLARE SUB processEquation () +DECLARE SUB evaluateExpression (exprStart!, exprEnd!) +DECLARE SUB solveEquation (exprStart, exprEnd) +DECLARE SUB printText (column!, row!, foreground!, background!, text$) +DECLARE SUB inputEquation () +DECLARE SUB initializeDisplay () DIM SHARED font(0 TO 7, 0 TO 7, 0 TO 122) ' Logical expression storage and processing arrays @@ -46,28 +54,30 @@ DIM SHARED variableValues(1 TO 8, 1 TO 100) ' Stores variable values for each co DIM SHARED variableNames(1 TO 8) ' Stores ASCII values of variable names DIM SHARED resultValues(1 TO 100) ' Stores computed result values DIM SHARED expressionPosition(0 TO 79) ' Stores screen positions of expression characters -DIM SHARED xlahn -DIM SHARED tehl -DIM SHARED nm -DIM SHARED prnp +DIM SHARED xlahn ' Unused legacy variable (kept from the 2002 original) +DIM SHARED combinationCount ' Number of truth-table rows: 2 ^ variableCount +DIM SHARED variableCount ' Number of variables, plus 2 for the t/v constants +DIM SHARED printRowOffset ' Vertical offset for printText (always 0) -start +initializeDisplay 13 -sist -prepare +inputEquation +processEquation GOTO 13 -SUB clearScreenBuffer -' Waits for user input to clear the screen buffer -FOR a = 1 TO 50 - a$ = INKEY$ -NEXT a +SUB drainKeyboardBuffer +' Throws away up to 50 pending keystrokes. +' NOTE: currently never called - kept from the 2002 original. +FOR flushIndex = 1 TO 50 + key$ = INKEY$ +NEXT flushIndex END SUB -SUB getOperatorPriority (operator, priority) -' Determines the priority of logical operators -SELECT CASE operator +SUB getOperatorPriority (operatorCode, priority) +' Determines the priority of logical operators. +' NOTE: only used by removeRedundancies, which itself is never called. +SELECT CASE operatorCode CASE 5 ' NOT priority = 1 CASE 3, 4 ' OR, AND @@ -82,263 +92,281 @@ END SELECT END SUB -SUB lahend (x1, x2) -' Analyzes and prepares the logical equation for solving -DIM muu(65 TO 122) -FOR a = 65 TO 122 - muu(a) = 0 -NEXT a - -muu(116) = 1 ' t -muu(118) = 1 ' v - -nm = 0 -FOR a = x1 TO x2 - b = logicalExpression(a) - IF ((b >= 65) AND (b <= 90)) OR ((b >= 97) AND (b <= 122)) THEN - IF muu(b) = 0 THEN - nm = nm + 1 - variableNames(nm) = b - muu(b) = 1 +SUB solveEquation (exprStart, exprEnd) +' Analyzes the logical equation and computes the whole truth table: +' collects the distinct variables, builds every input combination, +' lets addPrecedenceParens parenthesize the expression by operator +' priority, evaluates it for every row and prints the result column. +DIM seenChar(65 TO 122) +FOR charIndex = 65 TO 122 + seenChar(charIndex) = 0 +NEXT charIndex + +seenChar(116) = 1 ' t (true constant, never a variable) +seenChar(118) = 1 ' v (false constant, never a variable) + +variableCount = 0 +FOR charIndex = exprStart TO exprEnd + charCode = logicalExpression(charIndex) + IF ((charCode >= 65) AND (charCode <= 90)) OR ((charCode >= 97) AND (charCode <= 122)) THEN + IF seenChar(charCode) = 0 THEN + variableCount = variableCount + 1 + variableNames(variableCount) = charCode + seenChar(charCode) = 1 END IF END IF -NEXT a - -variableNames(nm + 1) = 116 ' t -variableNames(nm + 2) = 118 ' v - -f = 2 ^ nm -tehl = f -FOR a = 1 TO nm - d = 1 - e = 1 - f = f / 2 - FOR b = 1 TO 2 ^ nm - IF e > f THEN d = -d: e = 1 - IF d = 1 THEN c = ASC("t") ELSE c = ASC("v") - variableValues(a, b) = c - e = e + 1 - NEXT b -NEXT a - -FOR a = 1 TO tehl - variableValues(nm + 1, a) = 116 ' t - variableValues(nm + 2, a) = 118 ' v -NEXT a - -nm = nm + 2 - -DIM bck(0 TO 79) -FOR a = 0 TO 79 - bck(a) = logicalExpression(a) - expressionPosition(a) = a -NEXT a +NEXT charIndex + +variableNames(variableCount + 1) = 116 ' t +variableNames(variableCount + 2) = 118 ' v + +blockSize = 2 ^ variableCount +combinationCount = blockSize +FOR variableIndex = 1 TO variableCount + truthToggle = 1 + runCount = 1 + blockSize = blockSize / 2 + FOR comboRow = 1 TO 2 ^ variableCount + ' Alternate runs of t and v; each next variable's runs are + ' half as long, so the first variable changes slowest. + IF runCount > blockSize THEN truthToggle = -truthToggle: runCount = 1 + IF truthToggle = 1 THEN cellValue = ASC("t") ELSE cellValue = ASC("v") + variableValues(variableIndex, comboRow) = cellValue + runCount = runCount + 1 + NEXT comboRow +NEXT variableIndex + +FOR comboRow = 1 TO combinationCount + variableValues(variableCount + 1, comboRow) = 116 ' t + variableValues(variableCount + 2, comboRow) = 118 ' v +NEXT comboRow + +variableCount = variableCount + 2 + +DIM expressionBackup(0 TO 79) +FOR charIndex = 0 TO 79 + expressionBackup(charIndex) = logicalExpression(charIndex) + expressionPosition(charIndex) = charIndex +NEXT charIndex LOCATE 5, 1 -teeslg x1, x2, a +' charIndex returns how many parenthesis characters addPrecedenceParens +' inserted, so evaluateExpression receives the extended expression range. +addPrecedenceParens exprStart, exprEnd, charIndex -tee x1, x2 + a +evaluateExpression exprStart, exprEnd + charIndex -FOR a = 0 TO 79 - logicalExpression(a) = bck(a) -NEXT a +FOR charIndex = 0 TO 79 + logicalExpression(charIndex) = expressionBackup(charIndex) +NEXT charIndex -FOR a = 1 TO tehl - printText x2 + 1, a, 14, 0, CHR$(resultValues(a)) -NEXT a +FOR comboRow = 1 TO combinationCount + printText exprEnd + 1, comboRow, 14, 0, CHR$(resultValues(comboRow)) +NEXT comboRow END SUB -SUB lendm (x1, m) -' Measures the length of a logical expression enclosed in parentheses -IF logicalExpression(x1) <> 41 THEN m = 1: GOTO 19 -c = x1 -d = 1 +SUB operandLengthBefore (position, outLength) +' Measures the length of the operand ending just before position: +' either a single character, or a parenthesized group scanned backwards. +IF logicalExpression(position) <> 41 THEN outLength = 1: GOTO 19 +scanPos = position +parenDepth = 1 20 -c = c - 1 -IF logicalExpression(c) = 40 THEN d = d - 1 -IF logicalExpression(c) = 41 THEN d = d + 1 -IF d > 0 THEN GOTO 20 -m = x1 - c +scanPos = scanPos - 1 +IF logicalExpression(scanPos) = 40 THEN parenDepth = parenDepth - 1 +IF logicalExpression(scanPos) = 41 THEN parenDepth = parenDepth + 1 +IF parenDepth > 0 THEN GOTO 20 +outLength = position - scanPos 19 END SUB -SUB lendp (x1, m) -' Measures the length of a logical expression enclosed in parentheses -IF logicalExpression(x1) <> 40 THEN m = 1: GOTO 17 -c = x1 -d = 1 +SUB operandLengthAfter (position, outLength) +' Measures the length of the operand starting at position: +' either a single character, or a parenthesized group scanned forwards. +IF logicalExpression(position) <> 40 THEN outLength = 1: GOTO 17 +scanPos = position +parenDepth = 1 18 -c = c + 1 -IF logicalExpression(c) = 40 THEN d = d + 1 -IF logicalExpression(c) = 41 THEN d = d - 1 -IF d > 0 THEN GOTO 18 -m = c - x1 + 1 +scanPos = scanPos + 1 +IF logicalExpression(scanPos) = 40 THEN parenDepth = parenDepth + 1 +IF logicalExpression(scanPos) = 41 THEN parenDepth = parenDepth - 1 +IF parenDepth > 0 THEN GOTO 18 +outLength = scanPos - position + 1 17 END SUB -SUB mov (x1, n) -' Moves a portion of the logical expression to the right -FOR a = 79 - n TO x1 STEP -1 - logicalExpression(a + n) = logicalExpression(a) - expressionPosition(a + n) = expressionPosition(a) -NEXT a +SUB shiftExpressionRight (position, count) +' Moves a portion of the logical expression to the right, +' opening a gap of count characters at position. +FOR charIndex = 79 - count TO position STEP -1 + logicalExpression(charIndex + count) = logicalExpression(charIndex) + expressionPosition(charIndex + count) = expressionPosition(charIndex) +NEXT charIndex END SUB -SUB movM (x1, n) -' Moves a portion of the logical expression to the left -FOR a = x1 TO 79 - n - logicalExpression(a) = logicalExpression(a + n) - expressionPosition(a) = expressionPosition(a + n) -NEXT a +SUB shiftExpressionLeft (position, count) +' Moves a portion of the logical expression to the left, +' deleting count characters at position. +FOR charIndex = position TO 79 - count + logicalExpression(charIndex) = logicalExpression(charIndex + count) + expressionPosition(charIndex) = expressionPosition(charIndex + count) +NEXT charIndex END SUB -SUB prepare -' Prepares the logical equation for processing +SUB processEquation +' Prepares the typed equation for processing: strips spaces and empty +' cells, prints it, solves it, then waits for a keypress. CLS -ln = 79 -FOR a = 0 TO 79 +lastCharIndex = 79 +FOR charIndex = 0 TO 79 5 - IF logicalExpression(a) = 32 OR logicalExpression(a) = 0 THEN - FOR b = a TO 78 - logicalExpression(b) = logicalExpression(b + 1) - NEXT b - ln = ln - 1 - IF ln <= a - 1 THEN GOTO 6 + IF logicalExpression(charIndex) = 32 OR logicalExpression(charIndex) = 0 THEN + FOR shiftIndex = charIndex TO 78 + logicalExpression(shiftIndex) = logicalExpression(shiftIndex + 1) + NEXT shiftIndex + lastCharIndex = lastCharIndex - 1 + IF lastCharIndex <= charIndex - 1 THEN GOTO 6 GOTO 5 END IF -NEXT a +NEXT charIndex 6 CLS -FOR a = 0 TO ln - printText a, 0, 13, 1, CHR$(logicalExpression(a)) -NEXT a +FOR charIndex = 0 TO lastCharIndex + printText charIndex, 0, 13, 1, CHR$(logicalExpression(charIndex)) +NEXT charIndex printText 0, 1, 7, 0, SPACE$(79) -lahend 0, ln +solveEquation 0, lastCharIndex -a$ = INPUT$(1) +key$ = INPUT$(1) END SUB -SUB printText (x, y, c, c1, a$) - ' Prints characters to the screen at location (x,y) with color c, background c1 - x1 = x * 8 - y1 = (y + prnp) * 8 - - FOR b = 1 TO LEN(a$) - LINE (x1, y1)-(x1 + 7, y1 + 7), c1, BF - d = ASC(RIGHT$(LEFT$(a$, b), 1)) - IF d > 122 THEN GOTO 22 - FOR y2 = 0 TO 7 - FOR x2 = 0 TO 7 - c2 = font(x2, y2, d) - IF c2 > 0 THEN PSET (x1 + x2, y1 + y2), c - NEXT x2 - NEXT y2 -22 x1 = x1 + 8 - NEXT b +SUB printText (column, row, foreground, background, text$) + ' Prints characters to the screen at location (column,row) with color + ' foreground, background background, using the 8x8 font bitmaps that + ' initializeDisplay captured at startup. + pixelX = column * 8 + pixelY = (row + printRowOffset) * 8 + + FOR charIndex = 1 TO LEN(text$) + LINE (pixelX, pixelY)-(pixelX + 7, pixelY + 7), background, BF + glyphCode = ASC(RIGHT$(LEFT$(text$, charIndex), 1)) + IF glyphCode > 122 THEN GOTO 22 + FOR glyphRow = 0 TO 7 + FOR glyphCol = 0 TO 7 + pixelValue = font(glyphCol, glyphRow, glyphCode) + IF pixelValue > 0 THEN PSET (pixelX + glyphCol, pixelY + glyphRow), foreground + NEXT glyphCol + NEXT glyphRow +22 pixelX = pixelX + 8 + NEXT charIndex END SUB SUB removeRedundancies (startIndex, endIndex, removalCount) ' This procedure scans for parentheses that can be safely removed ' without changing the logic of the expression, then removes them. + ' NOTE: currently never called - an abandoned feature of the 2002 + ' original, kept as reference. DIM currentEnd, parenthesesCount currentEnd = endIndex parenthesesCount = 0 - a = startIndex -26 IF logicalExpression(a) = 40 THEN + charIndex = startIndex +26 IF logicalExpression(charIndex) = 40 THEN ' We found an opening parenthesis. Now let's see if it can be removed. - IF a = startIndex THEN p1 = 100 ELSE getOperatorPriority logicalExpression(a - 1), p1 + IF charIndex = startIndex THEN priorityBefore = 100 ELSE getOperatorPriority logicalExpression(charIndex - 1), priorityBefore - c = a - d = 1 - p2 = 0 + closePos = charIndex + parenDepth = 1 + priorityInside = 0 -25 c = c + 1 - IF logicalExpression(c) = 40 THEN d = d + 1 - IF logicalExpression(c) = 41 THEN d = d - 1 +25 closePos = closePos + 1 + IF logicalExpression(closePos) = 40 THEN parenDepth = parenDepth + 1 + IF logicalExpression(closePos) = 41 THEN parenDepth = parenDepth - 1 - ' Once d returns to 1, we are back to one level of parentheses, + ' Once parenDepth returns to 1, we are back to one level of parentheses, ' meaning we can check operator priority inside. - IF d = 1 THEN - IF (logicalExpression(c) > 0) AND (logicalExpression(c) <= 5) THEN - getOperatorPriority logicalExpression(c), b - IF b > p2 THEN p2 = b + IF parenDepth = 1 THEN + IF (logicalExpression(closePos) > 0) AND (logicalExpression(closePos) <= 5) THEN + getOperatorPriority logicalExpression(closePos), opPriority + IF opPriority > priorityInside THEN priorityInside = opPriority END IF END IF - IF d > 0 THEN GOTO 25 + IF parenDepth > 0 THEN GOTO 25 - IF c + 1 > currentEnd THEN p3 = 100 ELSE getOperatorPriority logicalExpression(c + 1), p3 + IF closePos + 1 > currentEnd THEN priorityAfter = 100 ELSE getOperatorPriority logicalExpression(closePos + 1), priorityAfter ' If the operator outside is higher priority than what's inside, ' we can safely remove the parentheses. - IF (p1 > p2) AND (p3 >= p2) THEN - movM c, 1 - movM a, 1 + IF (priorityBefore > priorityInside) AND (priorityAfter >= priorityInside) THEN + shiftExpressionLeft closePos, 1 + shiftExpressionLeft charIndex, 1 parenthesesCount = parenthesesCount + 2 currentEnd = currentEnd - 2 - a = a - 1 + charIndex = charIndex - 1 END IF END IF - a = a + 1 - IF a <= currentEnd THEN GOTO 26 + charIndex = charIndex + 1 + IF charIndex <= currentEnd THEN GOTO 26 removalCount = parenthesesCount END SUB -SUB sist -' Interacts with the user to input a logical equation +SUB inputEquation +' Interacts with the user to input a logical equation. +' Digit keys 1..5 insert the operator characters CHR$(1)..CHR$(5), +' letters insert variables, arrows move the cursor, Enter finishes. CLS printText 0, 0, 3, 0, "Enter equation (ESC to quit) keys: 1 - " + CHR$(1) + " 2 - " + CHR$(2) + " 3 - " + CHR$(3) + " 4 - " + CHR$(4) + " 5 - " + CHR$(5) printText 0, 1, 3, 0, "Example: a" + CHR$(1) + "b" + CHR$(2) + "(g" + CHR$(3) + "b)" -FOR a = 0 TO 79 - logicalExpression(a) = 0 -NEXT a +FOR charIndex = 0 TO 79 + logicalExpression(charIndex) = 0 +NEXT charIndex -x = 0 +cursorPos = 0 1 -FOR a = 0 TO 79 - IF a = x THEN printText a, 2, 14, 1, CHR$(logicalExpression(a)) ELSE printText a, 2, 3, 0, CHR$(logicalExpression(a)) -NEXT a +FOR charIndex = 0 TO 79 + IF charIndex = cursorPos THEN printText charIndex, 2, 14, 1, CHR$(logicalExpression(charIndex)) ELSE printText charIndex, 2, 3, 0, CHR$(logicalExpression(charIndex)) +NEXT charIndex 2 -a$ = INKEY$ -IF a$ = "" THEN GOTO 2 +key$ = INKEY$ +IF key$ = "" THEN GOTO 2 -IF a$ = CHR$(27) THEN SYSTEM -IF a$ = CHR$(0) + "M" THEN x = x + 1 -IF a$ = CHR$(0) + "K" THEN x = x - 1 -IF x < 0 THEN x = 0 -IF x > 79 THEN x = 79 +IF key$ = CHR$(27) THEN SYSTEM +IF key$ = CHR$(0) + "M" THEN cursorPos = cursorPos + 1 +IF key$ = CHR$(0) + "K" THEN cursorPos = cursorPos - 1 +IF cursorPos < 0 THEN cursorPos = 0 +IF cursorPos > 79 THEN cursorPos = 79 -IF LEN(a$) = 1 THEN - SELECT CASE ASC(a$) +IF LEN(key$) = 1 THEN + SELECT CASE ASC(key$) CASE 32, 40, 41, 65 TO 90, 97 TO 122 3 - FOR a = 78 TO x STEP -1 - logicalExpression(a + 1) = logicalExpression(a) - NEXT a - logicalExpression(x) = ASC(a$) - x = x + 1 + FOR charIndex = 78 TO cursorPos STEP -1 + logicalExpression(charIndex + 1) = logicalExpression(charIndex) + NEXT charIndex + logicalExpression(cursorPos) = ASC(key$) + cursorPos = cursorPos + 1 CASE 8 - IF x > 0 THEN - FOR a = x - 1 TO 78 - logicalExpression(a) = logicalExpression(a + 1) - NEXT a - x = x - 1 + IF cursorPos > 0 THEN + FOR charIndex = cursorPos - 1 TO 78 + logicalExpression(charIndex) = logicalExpression(charIndex + 1) + NEXT charIndex + cursorPos = cursorPos - 1 END IF CASE 49 TO 53 - a$ = CHR$(ASC(a$) - 48) + key$ = CHR$(ASC(key$) - 48) GOTO 3 CASE 13 GOTO 4 @@ -350,15 +378,19 @@ GOTO 1 END SUB -SUB start -' Initializes the screen and font -prnp = 0 +SUB initializeDisplay +' Initializes the screen and captures the font: every displayable +' character is PRINTed once in SCREEN 7 and read back pixel by pixel +' into the font() array, so printText can draw text in graphics modes. +' The operator characters CHR$(1)..CHR$(5) have no glyphs, so their +' bitmaps are drawn by hand with LINE. +printRowOffset = 0 SCREEN 7 -FOR a = 0 TO 122 +FOR charCode = 0 TO 122 LOCATE 1, 1 - SELECT CASE a + SELECT CASE charCode CASE 7 CASE 1 LINE (0, 0)-(7, 7), 0, BF @@ -392,206 +424,222 @@ FOR a = 0 TO 122 LINE (4, 1)-(4, 7), 15 CASE ELSE - PRINT CHR$(a) + PRINT CHR$(charCode) END SELECT - FOR y = 0 TO 7 - FOR x = 0 TO 7 - font(x, y, a) = POINT(x, y) - NEXT x - NEXT y -NEXT a + FOR pixelRow = 0 TO 7 + FOR pixelCol = 0 TO 7 + font(pixelCol, pixelRow, charCode) = POINT(pixelCol, pixelRow) + NEXT pixelCol + NEXT pixelRow +NEXT charCode SCREEN 12 END SUB -SUB tee (x1, x2) -' Processes the logical equation and applies logical operations -DIM opr(1 TO 2, 1 TO tehl) -ng = 0 -ngx = 0 -oprm = 1 -oe = 0 -oex = 0 - -FOR a = x1 TO x2 - b = logicalExpression(a) - SELECT CASE b +SUB evaluateExpression (exprStart, exprEnd) +' Evaluates the logical equation for every truth-table row. +' Parenthesized groups are evaluated recursively; the two operand +' columns accumulate in operandValues(1..2, row) and every binary +' operator combines them left to right back into slot 1. The final +' column lands in resultValues(). +DIM operandValues(1 TO 2, 1 TO combinationCount) +notPending = 0 +notPosition = 0 +operandSlot = 1 +pendingOperator = 0 +operatorPosition = 0 + +FOR charIndex = exprStart TO exprEnd + charCode = logicalExpression(charIndex) + SELECT CASE charCode CASE 40 - c = a - d = 1 + scanPos = charIndex + parenDepth = 1 10 - c = c + 1 - IF logicalExpression(c) = ASC("(") THEN d = d + 1 - IF logicalExpression(c) = ASC(")") THEN d = d - 1 - IF d = 0 THEN GOTO 11 + scanPos = scanPos + 1 + IF logicalExpression(scanPos) = ASC("(") THEN parenDepth = parenDepth + 1 + IF logicalExpression(scanPos) = ASC(")") THEN parenDepth = parenDepth - 1 + IF parenDepth = 0 THEN GOTO 11 GOTO 10 11 - tee a + 1, c - 1 - a = c - FOR c = 1 TO tehl - opr(oprm, c) = resultValues(c) - NEXT c + evaluateExpression charIndex + 1, scanPos - 1 + charIndex = scanPos + FOR comboRow = 1 TO combinationCount + operandValues(operandSlot, comboRow) = resultValues(comboRow) + NEXT comboRow GOTO 12 CASE 5 - ng = 1 - ngx = a + notPending = 1 + notPosition = charIndex CASE 1 TO 4 - oe = b - oex = a + pendingOperator = charCode + operatorPosition = charIndex CASE 65 TO 90, 97 TO 122 - FOR c = 1 TO nm - IF variableNames(c) = b THEN d = c: GOTO 8 - NEXT c + FOR lookupIndex = 1 TO variableCount + IF variableNames(lookupIndex) = charCode THEN variableIndex = lookupIndex: GOTO 8 + NEXT lookupIndex 8 - FOR c = 1 TO tehl - opr(oprm, c) = variableValues(d, c) - printText expressionPosition(a), c, 3, 0, CHR$(variableValues(d, c)) - NEXT c + FOR comboRow = 1 TO combinationCount + operandValues(operandSlot, comboRow) = variableValues(variableIndex, comboRow) + printText expressionPosition(charIndex), comboRow, 3, 0, CHR$(variableValues(variableIndex, comboRow)) + NEXT comboRow 12 - IF ng = 1 THEN GOSUB mkneg - IF oprm = 2 THEN - SELECT CASE oe + IF notPending = 1 THEN GOSUB mkneg + IF operandSlot = 2 THEN + SELECT CASE pendingOperator CASE 1 - FOR c = 1 TO tehl - d = opr(1, c) - e = opr(2, c) - IF d = e THEN f = ASC("t") ELSE f = ASC("v") - opr(1, c) = f - printText expressionPosition(oex), c, 12, 0, CHR$(f) - NEXT c + FOR comboRow = 1 TO combinationCount + leftValue = operandValues(1, comboRow) + rightValue = operandValues(2, comboRow) + IF leftValue = rightValue THEN resultValue = ASC("t") ELSE resultValue = ASC("v") + operandValues(1, comboRow) = resultValue + printText expressionPosition(operatorPosition), comboRow, 12, 0, CHR$(resultValue) + NEXT comboRow CASE 2 - FOR c = 1 TO tehl - d = opr(1, c) - e = opr(2, c) - f = ASC("t") - IF (d = ASC("t")) AND (e = ASC("v")) THEN f = ASC("v") - opr(1, c) = f - printText expressionPosition(oex), c, 12, 0, CHR$(f) - NEXT c + FOR comboRow = 1 TO combinationCount + leftValue = operandValues(1, comboRow) + rightValue = operandValues(2, comboRow) + resultValue = ASC("t") + IF (leftValue = ASC("t")) AND (rightValue = ASC("v")) THEN resultValue = ASC("v") + operandValues(1, comboRow) = resultValue + printText expressionPosition(operatorPosition), comboRow, 12, 0, CHR$(resultValue) + NEXT comboRow CASE 3 - FOR c = 1 TO tehl - d = opr(1, c) - e = opr(2, c) - f = ASC("t") - IF (d = ASC("v")) AND (e = ASC("v")) THEN f = ASC("v") - opr(1, c) = f - printText expressionPosition(oex), c, 12, 0, CHR$(f) - NEXT c + FOR comboRow = 1 TO combinationCount + leftValue = operandValues(1, comboRow) + rightValue = operandValues(2, comboRow) + resultValue = ASC("t") + IF (leftValue = ASC("v")) AND (rightValue = ASC("v")) THEN resultValue = ASC("v") + operandValues(1, comboRow) = resultValue + printText expressionPosition(operatorPosition), comboRow, 12, 0, CHR$(resultValue) + NEXT comboRow CASE 4 - FOR c = 1 TO tehl - d = opr(1, c) - e = opr(2, c) - f = ASC("v") - IF (d = ASC("t")) AND (e = ASC("t")) THEN f = ASC("t") - opr(1, c) = f - printText expressionPosition(oex), c, 12, 0, CHR$(f) - NEXT c + FOR comboRow = 1 TO combinationCount + leftValue = operandValues(1, comboRow) + rightValue = operandValues(2, comboRow) + resultValue = ASC("v") + IF (leftValue = ASC("t")) AND (rightValue = ASC("t")) THEN resultValue = ASC("t") + operandValues(1, comboRow) = resultValue + printText expressionPosition(operatorPosition), comboRow, 12, 0, CHR$(resultValue) + NEXT comboRow END SELECT ELSE - oprm = oprm + 1 + operandSlot = operandSlot + 1 END IF END SELECT -NEXT a +NEXT charIndex GOTO 9 mkneg: ' NOT operation (negation) is applied to the current operand - FOR c = 1 TO tehl - d = opr(oprm, c) - IF d = ASC("t") THEN d = ASC("v") ELSE d = ASC("t") - printText expressionPosition(ngx), c, 4, 0, CHR$(d) - opr(oprm, c) = d - NEXT c - ng = 0 + FOR comboRow = 1 TO combinationCount + negatedValue = operandValues(operandSlot, comboRow) + IF negatedValue = ASC("t") THEN negatedValue = ASC("v") ELSE negatedValue = ASC("t") + printText expressionPosition(notPosition), comboRow, 4, 0, CHR$(negatedValue) + operandValues(operandSlot, comboRow) = negatedValue + NEXT comboRow + notPending = 0 RETURN 9 -FOR c = 1 TO tehl - resultValues(c) = opr(1, c) -NEXT c +FOR comboRow = 1 TO combinationCount + resultValues(comboRow) = operandValues(1, comboRow) +NEXT comboRow END SUB -SUB teeslg (x1, x4, l) -' Prepares the logical equation for solving by simplifying expressions within parentheses -x2 = x4 -h = 0 -FOR e = 1 TO 4 - g = 1 - a = x1 +SUB addPrecedenceParens (exprStart, rangeEnd, addedChars) +' Parenthesizes the logical equation so that operator priority is +' explicit: four passes wrap NOT first, then OR/AND, then Implies +' (Equivalent needs no parentheses, it is the loosest). Each pass only +' wraps an operator when the operator before it was looser (lastOpClass +' tracks the previous operator's class) - otherwise plain left-to-right +' evaluation already gives the right grouping. +' +' NOTE: the DECLARE statement names the second parameter exprEnd while +' this definition names it rangeEnd. In QBasic DECLARE parameter names +' are just placeholders, so exprEnd here is a plain local and only +' addedChars carries the insertion count back to solveEquation. +' (crtbasic versions before 2026-09-26 registered the argument cell +' under both names, aliasing exprEnd to the caller's variable; fixed +' to match QB45.) +exprEnd = rangeEnd +insertedTotal = 0 +FOR passIndex = 1 TO 4 + lastOpClass = 1 + charIndex = exprStart 21 - b = logicalExpression(a) - IF b = 40 THEN - c = a - d = 1 + charCode = logicalExpression(charIndex) + IF charCode = 40 THEN + scanPos = charIndex + parenDepth = 1 14 - c = c + 1 - IF logicalExpression(c) = ASC("(") THEN d = d + 1 - IF logicalExpression(c) = ASC(")") THEN d = d - 1 - IF d = 0 THEN GOTO 15 + scanPos = scanPos + 1 + IF logicalExpression(scanPos) = ASC("(") THEN parenDepth = parenDepth + 1 + IF logicalExpression(scanPos) = ASC(")") THEN parenDepth = parenDepth - 1 + IF parenDepth = 0 THEN GOTO 15 GOTO 14 15 - IF e = 1 THEN teeslg a + 1, c - 1, l ELSE l = 0 - a = c + l - x2 = x2 + l - h = h + l + IF passIndex = 1 THEN addPrecedenceParens charIndex + 1, scanPos - 1, addedChars ELSE addedChars = 0 + charIndex = scanPos + addedChars + exprEnd = exprEnd + addedChars + insertedTotal = insertedTotal + addedChars GOTO 16 END IF - IF (b = 5) AND (e = 1) AND (g > 1) THEN - mov a, 1 - logicalExpression(a) = 40 - lendp a + 2, f - mov a + 2 + f, 1 - logicalExpression(a + 2 + f) = 41 - h = h + 2 - x2 = x2 + 2 - a = a + 2 + f + IF (charCode = 5) AND (passIndex = 1) AND (lastOpClass > 1) THEN + shiftExpressionRight charIndex, 1 + logicalExpression(charIndex) = 40 + operandLengthAfter charIndex + 2, operandLength + shiftExpressionRight charIndex + 2 + operandLength, 1 + logicalExpression(charIndex + 2 + operandLength) = 41 + insertedTotal = insertedTotal + 2 + exprEnd = exprEnd + 2 + charIndex = charIndex + 2 + operandLength GOTO 16 END IF - IF (b = 3 OR b = 4) AND (e = 2) AND (g > 2) THEN - lendm a - 1, f - mov a - f, 1 - logicalExpression(a - f) = 40 - lendp a + 2, f - mov a + 2 + f, 1 - logicalExpression(a + 2 + f) = 41 - h = h + 2 - x2 = x2 + 2 - a = a + 2 + f + IF (charCode = 3 OR charCode = 4) AND (passIndex = 2) AND (lastOpClass > 2) THEN + operandLengthBefore charIndex - 1, operandLength + shiftExpressionRight charIndex - operandLength, 1 + logicalExpression(charIndex - operandLength) = 40 + operandLengthAfter charIndex + 2, operandLength + shiftExpressionRight charIndex + 2 + operandLength, 1 + logicalExpression(charIndex + 2 + operandLength) = 41 + insertedTotal = insertedTotal + 2 + exprEnd = exprEnd + 2 + charIndex = charIndex + 2 + operandLength GOTO 16 END IF - IF (b = 2) AND (e = 3) AND (g > 3) THEN - lendm a - 1, f - mov a - f, 1 - logicalExpression(a - f) = 40 - lendp a + 2, f - mov a + 2 + f, 1 - logicalExpression(a + 2 + f) = 41 - h = h + 2 - x2 = x2 + 2 - a = a + 2 + f + IF (charCode = 2) AND (passIndex = 3) AND (lastOpClass > 3) THEN + operandLengthBefore charIndex - 1, operandLength + shiftExpressionRight charIndex - operandLength, 1 + logicalExpression(charIndex - operandLength) = 40 + operandLengthAfter charIndex + 2, operandLength + shiftExpressionRight charIndex + 2 + operandLength, 1 + logicalExpression(charIndex + 2 + operandLength) = 41 + insertedTotal = insertedTotal + 2 + exprEnd = exprEnd + 2 + charIndex = charIndex + 2 + operandLength GOTO 16 END IF - SELECT CASE b + SELECT CASE charCode CASE 5 - g = 1 + lastOpClass = 1 CASE 3, 4 - g = 2 + lastOpClass = 2 CASE 2 - g = 3 + lastOpClass = 3 CASE 1 - g = 4 + lastOpClass = 4 END SELECT 16 - a = a + 1 - IF a <= x2 THEN GOTO 21 -NEXT e -l = h + charIndex = charIndex + 1 + IF charIndex <= exprEnd THEN GOTO 21 +NEXT passIndex +addedChars = insertedTotal END SUB - -- 2.20.1