hdcstorage / hdc /ide.py
AEUPH's picture
HDCSTORAGE: hyperdimensional computer, webOS and agentic MCP layer (stdlib-only Python)
ad474c9 verified
Raw History Blame Contribute Delete
16.6 kB
"""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 <file> load a .hdcasm file into the buffer
append <text> append a source line
insert <n> <text> insert before line n
delete <n> 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 <label|pc> set a breakpoint
unbp <label|pc> 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 <reg> bit planes of a register
cell <name> 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 <name> [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 <hex> decode an arbitrary hypervector
reset reset registers; keep storage
saveimg <file.hdc> persist the storage image
openimg <file.hdc> 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 <file.hdcasm>"
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 <source text>"
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> <source text>"
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>"
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 "<buffer empty>"
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 <file.hdcasm>"
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 <label|pc>"
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 <label|pc>"
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>"
reg = args[0].upper().lstrip("R")
return self.dbg.bits(int(reg))
def _cmd_cell(self, args) -> str:
if not args:
return "usage: cell <name>"
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 "<cleanup memory empty>"
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 <name> [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 <hex-or-integer>"
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 <file.hdc>"
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())