TM_ND_A and TM_ND_DB machines
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Tue, 2 Dec 2025 07:56:39 +0000 (07:56 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Tue, 2 Dec 2025 07:56:39 +0000 (07:56 +0000)
shared/authored/dir-walk/JSON.py
shared/authored/dir-walk/TM_ND_A.py [new file with mode: 0644]
shared/authored/dir-walk/TM_ND_A_DB.py [new file with mode: 0755]
shared/authored/dir-walk/TapeMachine.py [deleted file]
shared/authored/dir-walk/meta.py
shared/authored/dir-walk/temp.py [deleted file]

index f944cfb..f69716d 100755 (executable)
@@ -3,13 +3,55 @@
 
 from __future__ import annotations
 from typing import Any
+import sys
 import json as pyjson
+import meta
 import duck
-import TapeMachine
+import TM_ND_A
 
 import builtins
 p = builtins.print
 
+COLOR = {
+  "RESET"  : "\033[0m"
+ ,"OP"     : "\033[38;5;39m"
+ ,"RET"    : "\033[38;5;82m"
+}
+
+def _color(key: str) -> str:
+  """
+  Return ANSI color code for key if COLOR debug is enabled and
+  stdout is a TTY, otherwise empty string.
+  """
+  if "COLOR" not in meta.DEBUG: return ""
+  if not sys.stdout.isatty():  return ""
+  return COLOR.get(key ,"")
+
+
+# ----------------------------------------------------------------------
+def debug(
+  msg: str
+  ,value: Any = None
+  ,indent: int = 0
+) -> None:
+  """
+  Single-line debug helper:
+
+    - If value is None:  "op(args)"
+    - Else:              "op(args) -> value"
+  """
+  if "TM_ND_A_DB" not in meta.DEBUG:
+    return
+
+  prefix = " " * indent
+  op = f"{_color('OP')}{msg}{_color('RESET')}"
+  if value is None:
+    out = op
+  else:
+    rv = f"{_color('RET')}{value}{_color('RESET')}"
+    out = f"{op} -> {rv}"
+
+  builtins.print(prefix + out)
 
 # ----------------------------------------------------------------------
 def to_JSON_TM(
diff --git a/shared/authored/dir-walk/TM_ND_A.py b/shared/authored/dir-walk/TM_ND_A.py
new file mode 100644 (file)
index 0000000..0dbccef
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+from __future__ import annotations
+from typing import Any
+import duck
+
+class TM_ND_A:
+  """
+  TM_ND_A
+
+    TM  Tape Machine
+    ND  Non Destructive, no cell deletion or insertion
+    A   Address supports addressing of cells
+
+    Machine does not support status, so if the machine exists, the tape has
+    at least one cell.
+
+    Head starts on cell 0 and by contract with the user is always on a valid cell.
+    I.e. it is the user's responsibility to check `can_step` before stepping.
+  """
+
+  # ----------------------------------------------------------------------
+  def __init__(self ,tape):
+    duck.require(duck.class_of(tape) ,"__len__" ,"__getitem__" ,"__setitem__")
+    self._extent: int = len(tape) - 1
+    self._tape = tape
+    self._head_position: int = 0
+
+  # ----------------------------------------------------------------------
+  def        extent(self) -> int:  return self._extent
+  def          tape(self) -> Any:  return self._tape
+  def         where(self) -> int:  return self._head_position
+  def  cue_leftmost(self) -> None: self._head_position = 0
+  def cue_rightmost(self) -> None: self._head_position = self._extent
+  def           cue(self ,new_position: int) -> int: self._head_position = new_position
+  def      can_step(self ,n: int = 1) -> bool: return 0 <= self._head_position + n <= self._extent
+  def          step(self ,n: int = 1) -> None: self._head_position += n
+  def          read(self ,n: int = 0) -> Any:  return self._tape[self._head_position + n]
+  def         write(self ,value: Any ,n: int = 0) -> None: self._tape[self._head_position + n] = value
+
+  # because this is an ND machine it is safe to entangle heads
+  def entangle(self) -> "TM_ND_A":
+    tm = object.__new__(TM_ND_A)
+    tm._tape = self._tape
+    tm._extent = self._extent
+    tm._head_position = self._head_position
+    return tm
+
diff --git a/shared/authored/dir-walk/TM_ND_A_DB.py b/shared/authored/dir-walk/TM_ND_A_DB.py
new file mode 100755 (executable)
index 0000000..33d9ae9
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+from __future__ import annotations
+from typing import Any
+
+import TM_ND_A
+import JSON
+import meta
+
+
+# ----------------------------------------------------------------------
+_STACK_DEPTH = 0
+
+def _indent() -> int:
+  return _STACK_DEPTH * 2
+
+
+# ----------------------------------------------------------------------
+class TM_ND_A_DB:
+  """
+  Debug wrapper for TM_ND_A.
+
+  For each method:
+
+    - Calls the underlying TM_ND_A method.
+    - Emits a single-line debug trace via JSON.debug:
+
+        name(args) -> result     (for methods with a result)
+        name(args)               (for void-returning methods)
+
+  Nested calls (like entangle() creating a new TM_ND_A_DB) are
+  indented by stack depth.
+  """
+
+  # --------------------------------------------------------------------
+  def __init__(self ,tape: Any):
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    self._tm = TM_ND_A.TM_ND_A(tape)
+    self._id = f"{id(self)}" 
+    JSON.debug(f"{self._id} __init__({tape})" ,value="<init>" ,indent=_indent())
+    _STACK_DEPTH -= 1
+
+  # --------------------------------------------------------------------
+  def extent(self) -> int:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    r = self._tm.extent()
+    JSON.debug(f"{self._id} extent()" ,value=r ,indent=_indent())
+    _STACK_DEPTH -= 1
+    return r
+
+  def tape(self) -> Any:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    r = self._tm.tape()
+    JSON.debug(f"{self._id} tape()" ,value=r ,indent=_indent())
+    _STACK_DEPTH -= 1
+    return r
+
+  def where(self) -> int:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    r = self._tm.where()
+    JSON.debug(f"{self._id} where()" ,value=r ,indent=_indent())
+    _STACK_DEPTH -= 1
+    return r
+
+  # --------------------------------------------------------------------
+  def cue_leftmost(self) -> None:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    self._tm.cue_leftmost()
+    JSON.debug(f"{self._id} cue_leftmost()" ,value=None ,indent=_indent())
+    _STACK_DEPTH -= 1
+
+  def cue_rightmost(self) -> None:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    self._tm.cue_rightmost()
+    JSON.debug(f"{self._id} cue_rightmost()" ,value=None ,indent=_indent())
+    _STACK_DEPTH -= 1
+
+  def cue(self ,new_position: int) -> int:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    r = self._tm.cue(new_position)
+    JSON.debug(f"{self._id} cue({new_position})" ,value=r ,indent=_indent())
+    _STACK_DEPTH -= 1
+    return r
+
+  # --------------------------------------------------------------------
+  def can_step(self ,n: int = 1) -> bool:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    r = self._tm.can_step(n)
+    JSON.debug(f"{self._id} can_step({n})" ,value=r ,indent=_indent())
+    _STACK_DEPTH -= 1
+    return r
+
+  def step(self ,n: int = 1) -> None:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    self._tm.step(n)
+    JSON.debug(f"{self._id} step({n})" ,value=None ,indent=_indent())
+    _STACK_DEPTH -= 1
+
+  # --------------------------------------------------------------------
+  def read(self ,n: int = 0) -> Any:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    r = self._tm.read(n)
+    JSON.debug(f"{self._id} read({n})" ,value=r ,indent=_indent())
+    _STACK_DEPTH -= 1
+    return r
+
+  def write(self ,value: Any ,n: int = 0) -> None:
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+    self._tm.write(value ,n)
+    JSON.debug(f"{self._id} write(n={n}, value={value})" ,value=None ,indent=_indent())
+    _STACK_DEPTH -= 1
+
+  # --------------------------------------------------------------------
+  def entangle(self) -> "TM_ND_A_DB":
+    global _STACK_DEPTH
+    _STACK_DEPTH += 1
+
+    # Child __init__() will run its own debug line with its ID.
+    child = TM_ND_A_DB(self._tm.tape())
+
+    # Copy state
+    child._tm._tape = self._tm._tape
+    child._tm._extent = self._tm._extent
+    child._tm._head_position = self._tm._head_position
+
+    # Parent reports child ID as return value
+    JSON.debug(f"{self._id} entangle()" ,value=child._id ,indent=_indent())
+
+    _STACK_DEPTH -= 1
+    return child
+
+# ----------------------------------------------------------------------
+def CLI() -> None:
+  meta.DEBUG.add("TM_ND_A_DB")
+
+  JSON.print("=== TM_ND_A_DB DEBUG DEMO ===")
+
+  tm = TM_ND_A_DB([10 ,20 ,30])
+
+  tm.extent()
+  tm.read()
+  tm.step()
+  tm.read()
+  tm.write(777)
+  tm.entangle()
+
+  JSON.print("=== END DEBUG DEMO ===")
+
+
+# ----------------------------------------------------------------------
+if __name__ == "__main__":
+  CLI()
diff --git a/shared/authored/dir-walk/TapeMachine.py b/shared/authored/dir-walk/TapeMachine.py
deleted file mode 100644 (file)
index 0dbccef..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-from __future__ import annotations
-from typing import Any
-import duck
-
-class TM_ND_A:
-  """
-  TM_ND_A
-
-    TM  Tape Machine
-    ND  Non Destructive, no cell deletion or insertion
-    A   Address supports addressing of cells
-
-    Machine does not support status, so if the machine exists, the tape has
-    at least one cell.
-
-    Head starts on cell 0 and by contract with the user is always on a valid cell.
-    I.e. it is the user's responsibility to check `can_step` before stepping.
-  """
-
-  # ----------------------------------------------------------------------
-  def __init__(self ,tape):
-    duck.require(duck.class_of(tape) ,"__len__" ,"__getitem__" ,"__setitem__")
-    self._extent: int = len(tape) - 1
-    self._tape = tape
-    self._head_position: int = 0
-
-  # ----------------------------------------------------------------------
-  def        extent(self) -> int:  return self._extent
-  def          tape(self) -> Any:  return self._tape
-  def         where(self) -> int:  return self._head_position
-  def  cue_leftmost(self) -> None: self._head_position = 0
-  def cue_rightmost(self) -> None: self._head_position = self._extent
-  def           cue(self ,new_position: int) -> int: self._head_position = new_position
-  def      can_step(self ,n: int = 1) -> bool: return 0 <= self._head_position + n <= self._extent
-  def          step(self ,n: int = 1) -> None: self._head_position += n
-  def          read(self ,n: int = 0) -> Any:  return self._tape[self._head_position + n]
-  def         write(self ,value: Any ,n: int = 0) -> None: self._tape[self._head_position + n] = value
-
-  # because this is an ND machine it is safe to entangle heads
-  def entangle(self) -> "TM_ND_A":
-    tm = object.__new__(TM_ND_A)
-    tm._tape = self._tape
-    tm._extent = self._extent
-    tm._head_position = self._head_position
-    return tm
-
index 299aef7..e4f8bd8 100644 (file)
@@ -17,7 +17,7 @@ _MINOR = 2
 #   "TreeMachine" -> enable debug for TreeMachine operations.
 #
 DEBUG: set[str] = set()
-
+#DEBUG.add("COLOR")
 
 def version() -> None:
   """
diff --git a/shared/authored/dir-walk/temp.py b/shared/authored/dir-walk/temp.py
deleted file mode 100644 (file)
index c546377..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-
-  to_JSON:
-    For each cell of the tape:
-    1. If the value has a to_JSON() method, use value.to_JSON().
-    2. If the value is a JSON primitive (str,int,float,bool,None), output it directly.
-    3. If the value is a Python primitive that is not a JSON primitive, stringify it
-       with JSON-safe escaping.
-    4. Otherwise: error — cannot represent in JSON.
-
-  # ----------------------------------------------------------------------
-  # JSON representation
-  # ----------------------------------------------------------------------
-
-  @classmethod
-  def is_a_JSON_primitive(
-    cls
-    ,value: Any
-  ) -> bool:
-    return (
-      isinstance(value ,(str ,int ,float ,bool))
-      or value is None
-    )
-
-  @classmethod
-  def has_to_JSON(
-    cls
-    ,value: Any
-  ) -> bool:
-    return hasattr(value ,"to_JSON") and callable(value.to_JSON)
-
-  @classmethod
-  def normalize_value(
-    cls
-    ,value: Any
-  ) -> Any:
-    """
-    Normalize a tape cell value into JSON-compatible output.
-
-    Rules:
-      1. If the value has to_JSON, call it.
-      2. If the value is a JSON primitive, return it.
-      3. If the value is a Python primitive but not JSON-safe, stringify it with escaping.
-      4. Else: raise TypeError.
-    """
-    # Rule 1: to_JSON
-    if cls.has_to_JSON(value):
-      return value.to_JSON()
-
-    # Rule 2: JSON primitive
-    if cls.is_a_JSON_primitive(value):
-      return value
-
-    # Rule 3: Python primitive → stringify
-    # Note: repr() is safe because JSONPretty will escape the resulting string.
-    if isinstance(value ,(bytes ,bytearray)):
-      # represent bytes safely in hex
-      hexval = value.hex()
-      return f"<bytes len={len(value)} hex={hexval}>"
-
-    if isinstance(value ,(complex ,range)):
-      return repr(value)
-
-    # Rule 4: No idea what this is
-    raise TypeError(f"TapeMachine cannot JSON-normalize value: {value!r} of type {type(value)}")
-
-  def to_JSON(
-    self
-  ) -> dict[str ,Any]:
-    """
-    JSON representation:
-
-      {
-        "TapeMachine": "<hex id>"
-        ,"head": <int>
-        ,"Tape": [ <Value0> ,<Value1> , ... ]
-      }
-
-    Where each Value is JSON-normalized by normalize_value().
-    """
-    normalized = []
-    for raw in self._tape:
-      normalized.append(self.normalize_value(raw))
-
-    return {
-      "TapeMachine": hex(id(self))
-      ,"head": self._head_position
-      ,"Tape": normalized
-    }
-
-def _tape_debug(
-  tm: Any
-  ,msg: str
-) -> None:
-  """Emit a TapeMachine-related debug message if meta.DEBUG has 'TapeMachine'."""
-  if "TapeMachine" not in meta.DEBUG: return
-  print(f"[TapeMachine {hex(id(tm))}] {msg}")
-
-