"""The native binary-logic IDE. A line-oriented front end to the whole machine: edit source, assemble it into hypervector cells, run and step it, inspect registers symbolically, read raw bit planes, call HDC-resident tools, and save the storage image. The IDE is not a separate program with its own model of the code — every view it shows is produced by the machine: disassembly is a cleanup decode of stored vectors, the register table's SYMBOL column is a cleanup query, and ``cell`` prints the bits that are literally resident in storage. When a decode is uncertain it says so instead of inventing a mnemonic. Commands (``help`` in the REPL prints this list): new [file] clear the source buffer open load a .hdcasm file into the buffer append append a source line insert insert before line n delete delete line n show [from to] list the buffer with line numbers save [file] write the buffer back out asm assemble the buffer into hypervectors run | go run to halt or the next breakpoint step [n] | next single step; next steps over a CALL bp set a breakpoint unbp clear a breakpoint bps list breakpoints regs registers with integer payload and nearest symbol sim [n] pairwise similarity of the first n registers disasm [start] [n] disassembly with decode confidence bits bit planes of a register cell bit planes of a storage cell mem [limit] storage listing sym cleanup memory contents trace [n] last n executed instructions stat storage statistics tools tool registry tool [args] call a tool directly adder [a b] run one addition through the HDC full adder shader HDC image pipeline: dim, edges, depth, normals net BitNet layer self-check opcodes instruction set reference decode decode an arbitrary hypervector reset reset registers; keep storage saveimg persist the storage image openimg load a storage image help this list quit leave """ from __future__ import annotations import os import shlex from .agent import ToolRegistry from .assembler import AsmError, assemble from .debugger import Debugger from .hypervec import DEFAULT_DIM, bits, hvc from .interpreter import Machine from .isa import DecodeError, decode_instr, opcode_table_text from .memory import HDCStorage DEMO_SOURCE = """\ ; --------------------------------------------------------------- ; A program that lives entirely in hyperdimensional storage. ; --------------------------------------------------------------- .str greeting "hdc storage online" .data accumulator 0 start: ldi R0, 5 ldi R1, 37 add R2, R0, R1 ; 42 out R2 prints greeting bind R3, R0, R1 ; bind the two operands sim R4, R3, R0 ; unrelated: ~0 out R4 bind R5, R3, R1 ; unbind recovers R0 sim R6, R5, R0 ; 1000 milli out R6 tool R7, R0, R1, hdc.similarity out R7 store accumulator, R2 load R8, accumulator out R8 halt """ class IDE: """Editor + assembler + machine + debugger in one console.""" def __init__( self, d: int = DEFAULT_DIM, storage: HDCStorage | None = None, out=print, ): self.d = d self.out = out self.storage = storage or HDCStorage(d=d, label="ide") self.registry = ToolRegistry(storage=self.storage, d=d) self.machine = Machine( storage=self.storage, d=d, tools=self.registry, echo=self._emit ) self.dbg = Debugger(self.machine) self.src: list[str] = [] self.program = None self.filename: str | None = None self.running = True self._printed = 0 # ------------------------------------------------------------- plumbing def _emit(self, text: str) -> None: self.out(" | " + text) def _flush_output(self) -> None: for line in self.machine.output[self._printed :]: self.out(" | " + line) self._printed = len(self.machine.output) # -------------------------------------------------------------- dispatch def execute(self, line: str) -> str: """Run one IDE command; returns text to display.""" line = line.strip() if not line or line.startswith("#"): return "" try: argv = shlex.split(line) except ValueError as exc: return "parse error: %s" % exc cmd = argv[0].lower() args = argv[1:] handler = getattr(self, "_cmd_" + cmd, None) aliases = {"go": "run", "next": "step_over", "quit": "quit", "exit": "quit"} if handler is None and cmd in aliases: handler = getattr(self, "_cmd_" + aliases[cmd], None) if handler is None: return "unknown command %r (try: help)" % cmd try: return handler(args) or "" except (AsmError, DecodeError, KeyError, ValueError, OSError) as exc: return "%s: %s" % (type(exc).__name__, exc) # ---------------------------------------------------------------- editor def _cmd_new(self, args) -> str: self.src = [] self.filename = args[0] if args else None self.program = None return "buffer cleared" def _cmd_open(self, args) -> str: if not args: return "usage: open " with open(args[0], "r", encoding="utf-8") as fh: self.src = fh.read().splitlines() self.filename = args[0] self.program = None return "loaded %d line(s) from %s" % (len(self.src), args[0]) def _cmd_append(self, args) -> str: if not args: return "usage: append " self.src.append(" ".join(args)) return "line %d appended" % len(self.src) def _cmd_insert(self, args) -> str: if len(args) < 2: return "usage: insert " n = int(args[0]) self.src.insert(max(0, n - 1), " ".join(args[1:])) return "inserted at %d" % n def _cmd_delete(self, args) -> str: if not args: return "usage: delete " n = int(args[0]) if not 1 <= n <= len(self.src): return "no line %d" % n removed = self.src.pop(n - 1) return "deleted: %s" % removed def _cmd_show(self, args) -> str: if not self.src: return "" lo = int(args[0]) if args else 1 hi = int(args[1]) if len(args) > 1 else len(self.src) rows = [] for i in range(lo, min(hi, len(self.src)) + 1): rows.append("%3d %s" % (i, self.src[i - 1])) return "\n".join(rows) def _cmd_save(self, args) -> str: path = args[0] if args else self.filename if not path: return "usage: save " with open(path, "w", encoding="utf-8") as fh: fh.write("\n".join(self.src) + "\n") self.filename = path return "wrote %d line(s) to %s" % (len(self.src), path) # ------------------------------------------------------------- assembling def _cmd_asm(self, args) -> str: source = "\n".join(self.src) if self.src else DEMO_SOURCE if not self.src: self.src = DEMO_SOURCE.splitlines() self.program = assemble(source, d=self.d) self.machine.load(self.program) self.dbg.attach_program(self.program) self._printed = 0 rows = [ "assembled %d cell(s), %d literal(s), %d string(s), %d cell name(s)" % ( len(self.program.instrs), len(self.program.literals), len(self.program.strings), len(self.program.cell_names), ), "weakest decode confidence: %.2f" % min(d.confidence for d in self.program.listing), self.program.listing_text(), ] return "\n".join(rows) # ---------------------------------------------------------------- running def _cmd_run(self, args=None) -> str: stop = self.dbg.continue_() self._flush_output() return "stopped: %s" % stop def _cmd_step(self, args) -> str: n = int(args[0]) if args else 1 last = None for _ in range(max(1, n)): last = self.dbg.step() self._flush_output() if last is None: return "machine is halted" return last.render() def _cmd_step_over(self, args) -> str: entry = self.dbg.step_over() self._flush_output() return entry.render() if entry else "machine is halted" def _cmd_reset(self, args) -> str: self.machine.reset() self._printed = 0 return "registers cleared; storage kept (%d cells)" % len(self.storage) # ------------------------------------------------------------ breakpoints def _cmd_bp(self, args) -> str: if not args: return "usage: bp " where = args[0] pc = self.dbg.add_break(int(where) if where.isdigit() else where) return "breakpoint at cell %d" % pc def _cmd_unbp(self, args) -> str: if not args: return "usage: unbp " where = args[0] ok = self.dbg.remove_break(int(where) if where.isdigit() else where) return "cleared" if ok else "no such breakpoint" def _cmd_bps(self, args) -> str: return "breakpoints: %s" % (sorted(self.dbg.breakpoints) or "none") # ------------------------------------------------------------------ views def _cmd_regs(self, args) -> str: return self.dbg.regs() def _cmd_sim(self, args) -> str: n = int(args[0]) if args else 8 return self.dbg.sim_table(list(range(min(n, len(self.machine.regs))))) def _cmd_disasm(self, args) -> str: start = int(args[0]) if args else None count = int(args[1]) if len(args) > 1 else 12 return self.dbg.disasm(start, count) def _cmd_bits(self, args) -> str: if not args: return "usage: bits " reg = args[0].upper().lstrip("R") return self.dbg.bits(int(reg)) def _cmd_cell(self, args) -> str: if not args: return "usage: cell " return self.dbg.cell_bits(args[0]) def _cmd_mem(self, args) -> str: limit = int(args[0]) if args else 24 return self.storage.dump(limit=limit) def _cmd_sym(self, args) -> str: syms = self.storage.symbols() if not syms: return "" return "\n".join("%3d %s" % (i, s) for i, s in enumerate(syms)) def _cmd_trace(self, args) -> str: n = int(args[0]) if args else 10 return self.dbg.trace_tail(n) def _cmd_stat(self, args) -> str: st = self.storage.stat() rows = ["%-14s %s" % (k, v) for k, v in st.items()] if self.program is not None: rows.append("%-14s %d" % ("program_cells", len(self.program.instrs))) rows.append( "%-14s %.2f" % ("min_decode_conf", min(d.confidence for d in self.program.listing)) ) return "\n".join(rows) def _cmd_tools(self, args) -> str: return self.registry.specs_text() + "\n\n" + self.registry.memory_report() def _cmd_tool(self, args) -> str: if not args: return "usage: tool [args...]" spec, conf = self.registry.resolve(args[0]) if spec is None: return "no tool resembling %r (best %.3f)" % (args[0], conf) values = [int(a, 0) for a in args[1:]] args_map = {} for (pname, kind), value in zip(spec.params, values): args_map[pname] = value result = self.registry.call_named(spec.name, args_map) shown = "0x%x" % result if spec.returns == "hv" else result return "%s (conf %.3f) -> %s" % (spec.name, conf, shown) def _cmd_adder(self, args) -> str: from .adder import HyperAdder, wrap a = int(args[0]) if args else 7 b = int(args[1]) if len(args) > 1 else 35 adder = HyperAdder(d=self.d) out = adder.add(a, b) return "\n".join( [ adder.table_text(), "", "ripple for %d + %d:" % (a, b), adder.trace_text(8), " ... %d more stages" % (adder.width - 8), "\nsum = %d (expected %d) -> %s" % ( adder.decode(out), wrap(a + b), "ok" if adder.decode(out) == wrap(a + b) else "MISMATCH", ), ] ) def _cmd_shader(self, args) -> str: from .shader import demo_report return demo_report(d=self.d) def _cmd_net(self, args) -> str: from .net import selfcheck report = selfcheck() return "\n".join("%-20s %s" % (k, v) for k, v in report.items()) def _cmd_opcodes(self, args) -> str: return opcode_table_text() def _cmd_decode(self, args) -> str: if not args: return "usage: decode " hv = int(args[0], 0) ctx = self.program.context() if self.program else None dec = decode_instr(hv, ctx) return "%s %s (conf %.2f, margins %s)" % ( dec.op, dec.regs, dec.confidence, {k: round(v, 3) for k, v in dec.margins.items()}, ) def _cmd_saveimg(self, args) -> str: path = args[0] if args else "hdcstorage.hdc" size = self.storage.save(path) return "saved %s (%d bytes, %d cells)" % (path, size, len(self.storage)) def _cmd_openimg(self, args) -> str: if not args: return "usage: openimg " self.storage = HDCStorage.load(args[0]) self.d = self.storage.d self.registry = ToolRegistry(storage=self.storage, d=self.d) self.machine = Machine( storage=self.storage, d=self.d, tools=self.registry, echo=self._emit ) self.dbg = Debugger(self.machine) return "loaded %r (%d cells, d=%d)" % (args[0], len(self.storage), self.d) def _cmd_help(self, args) -> str: return __doc__ or "" def _cmd_quit(self, args) -> str: self.running = False return "bye" # ------------------------------------------------------------------- loop def repl(self) -> int: self.out("HDCSTORAGE IDE d=%d cells=%d tools=%d" % (self.d, len(self.storage), len(self.registry.specs))) self.out('type "help" for commands, "asm" to assemble, "run" to execute') while self.running: try: line = input("hdc> ") except (EOFError, KeyboardInterrupt): self.out("") break text = self.execute(line) if text: self.out(text) return 0 def run_script(self, text: str) -> int: """Execute a batch of IDE commands (used by demos and tests).""" for line in text.splitlines(): text_out = self.execute(line) if text_out: self.out(text_out) return 0 def main(argv: list[str] | None = None) -> int: import sys argv = list(sys.argv[1:] if argv is None else argv) d = DEFAULT_DIM files = [] i = 0 while i < len(argv): if argv[i] == "--d": d = int(argv[i + 1]) i += 2 continue files.append(argv[i]) i += 1 ide = IDE(d=d) if files: path = files[0] if path.endswith(".hdc"): print(ide._cmd_openimg([path])) else: print(ide._cmd_open([path])) print(ide._cmd_asm([])) print(ide._cmd_run([])) if sys.stdin.isatty(): return ide.repl() return 0 if __name__ == "__main__": raise SystemExit(main())