"""Tests for the .hdcasm compiler.""" import os import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from hdc.assembler import AsmError, assemble, roundtrip_report, split_operands # noqa: E402 D = 2000 class TestLabels(unittest.TestCase): def test_forward_and_backward(self): src = """ jmp done loop: nop jmp loop done: halt """ prog = assemble(src, d=D) self.assertEqual(prog.labels["loop"], 1) self.assertEqual(prog.labels["done"], 3) self.assertEqual(prog.listing[0].tgt, 3) self.assertEqual(prog.listing[2].tgt, 1) def test_numeric_target(self): prog = assemble("jmp @2\nnop\nhalt", d=D) self.assertEqual(prog.listing[0].tgt, 2) def test_duplicate_label_rejected(self): with self.assertRaises(AsmError): assemble("loop:\n nop\nloop:\n nop", d=D) def test_undefined_label_rejected(self): with self.assertRaises(AsmError): assemble("jmp nowhere", d=D) def test_label_on_directive_line(self): prog = assemble('here: .str greeting "hello"\n ini: nop\n halt', d=D) self.assertEqual(prog.labels["ini"], 0) self.assertEqual(len(prog.instrs), 2) class TestDirectives(unittest.TestCase): SRC = """ .name out .data tbl 42 .str hello "hi there" ldi R0, 1 store out, R0 prints "hi there" halt """ def test_data_directory_populates_image(self): prog = assemble(self.SRC, d=D) self.assertEqual(prog.data["tbl"], "int:42") self.assertEqual(prog.data["hello"], "str:hi there") self.assertEqual(prog.cell_names, ["hello", "out", "tbl"]) self.assertIn("hi there", prog.strings) self.assertIn(42, prog.literals) self.assertEqual(len(prog.instrs), 4) def test_install_writes_cells(self): from hdc.memory import HDCStorage prog = assemble(self.SRC, d=D) store = HDCStorage(d=D) names = prog.install(store) self.assertEqual(names, ["code/0", "code/1", "code/2", "code/3"]) for name in names: self.assertIn(name, store) self.assertNotEqual(store.read(name), 0) self.assertIn("out", store) self.assertIn("tbl", store) def test_unknown_directive_rejected(self): with self.assertRaises(AsmError): assemble(".frobnicate x", d=D) class TestRoundTrip(unittest.TestCase): SRC = """ .str hello "hdc storage" .data acc 0 start: ldi R0, 5 ldi R1, 37 add R2, R0, R1 out R2 bind R3, R0, R1 sim R4, R3, R0 out R4 prints hello store acc, R2 load R5, acc out R5 halt """ def test_every_cell_decodes_to_its_source_opcode(self): prog = assemble(self.SRC, d=D) report = roundtrip_report(prog) n = len(prog.instrs) self.assertIn("decode-back check: %d/%d cells round-trip" % (n, n), report) for i, line in enumerate(prog.source): opcode = line.text.split()[0].upper() self.assertEqual(prog.listing[i].op, opcode, msg="cell %d" % i) self.assertGreater(prog.listing[i].confidence, 0.15) def test_source_lines_are_instruction_lines_only(self): prog = assemble(self.SRC, d=D) self.assertEqual(len(prog.source), len(prog.instrs)) def test_listing_text_contains_confidence(self): prog = assemble(self.SRC, d=D) text = prog.listing_text() self.assertIn("conf", text) self.assertIn("start:", text) class TestErrors(unittest.TestCase): def test_wrong_operand_count(self): with self.assertRaises(AsmError): assemble("add R0, R1", d=D) def test_unknown_opcode(self): with self.assertRaises(AsmError): assemble("frob R0", d=D) def test_bad_register(self): with self.assertRaises(AsmError): assemble("ldi R99, 1", d=D) def test_bad_literal(self): with self.assertRaises(AsmError): assemble("ldi R0, banana", d=D) def test_unquoted_string(self): with self.assertRaises(AsmError): assemble("prints hello", d=D) class TestTokenizer(unittest.TestCase): def test_commas_inside_quotes_survive(self): self.assertEqual( split_operands('"a, b", R0'), ['"a, b"', "R0"] ) def test_empty(self): self.assertEqual(split_operands(" "), []) def test_comments_stripped(self): prog = assemble("ldi R0, 5 ; set five\n halt # done", d=D) self.assertEqual(len(prog.instrs), 2) self.assertEqual(prog.listing[0].imm, 5) if __name__ == "__main__": unittest.main()