From: Thomas Walker Lynch Date: Thu, 27 Nov 2025 07:19:58 +0000 (+0000) Subject: checkpoint before rewrite due to apparent patch misalignment X-Git-Url: https://git.reasoningtechnology.com/style/rt_dark_doc.css?a=commitdiff_plain;h=5145f106cc694de37db579a14324c0d08c344e04;p=Harmony.git checkpoint before rewrite due to apparent patch misalignment --- diff --git a/shared/authored/dir-walk/BreadthMachine.py b/shared/authored/dir-walk/BreadthMachine.py new file mode 100755 index 0000000..5f20fbc --- /dev/null +++ b/shared/authored/dir-walk/BreadthMachine.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +from __future__ import annotations + +import sys +from typing import List, Tuple + +import meta +from TreeMachine import TreeMachine + + +class BreadthMachine: + """ + BreadthMachine — breadth-first directory walker built on TreeMachine. + + External usage: + + bm = BreadthMachine(root_dp) + while True: + path, props, addr = bm.read() + # ... process node ... + if not bm.can_step(): break + bm.step() + """ + + def __init__(self, root_dp: str | None = None) -> None: + if root_dp is None: root_dp = "." + self.root_dp: str = root_dp + self._tree_machine: TreeMachine = TreeMachine(root_dp) + self._has_node: bool = False + + # Bootstrap: move to the first node, if any. + if self._tree_machine.context_depth() > 0: + self._tree_machine.step() + self._has_node = True + + # ------------------------------------------------------------------ + # External interface + # ------------------------------------------------------------------ + def read(self) -> Tuple[str, List[str], Tuple[int, int]]: + """ + Read the current node. + + Returns: + (path_relative_to_root, property_list, address) + """ + if not self._has_node: + raise RuntimeError("BreadthMachine.read() with no current node.") + return self._tree_machine.read() + + def can_step(self) -> bool: + """ + True if there is another node after the current one. + + Logic: + + - If current node is a non-empty directory, we can always push("back") + and eventually visit its children. + + - Otherwise, delegate to TreeMachine.can_step(), which ripples through + all Contexts to see whether any Context is not rightmost. + """ + if not self._has_node: + return False + + _path, prop_list, _addr = self._tree_machine.read() + is_non_empty_dir = ("directory" in prop_list and "empty" not in prop_list) + + if is_non_empty_dir: + return True + + return self._tree_machine.can_step() + + def step(self) -> None: + """ + Advance to the next node (breadth-first flavor). + + Steps: + + 1. If current node is a non-empty directory, push it to the BACK of + the context queue. (We do NOT descend immediately.) + + 2. If current context is not rightmost, step within it. + + 3. If current context is rightmost, pop contexts until a context is + found that can step, then step there. + """ + if not self.can_step(): + raise RuntimeError("BreadthMachine.step() called when can_step() is False.") + + _path, prop_list, _addr = self._tree_machine.read() + is_non_empty_dir = ("directory" in prop_list and "empty" not in prop_list) + + # 1. Queue non-empty directory for later (back of the queue). + if is_non_empty_dir: + self._tree_machine.push_current_directory("back") + + # 2. Try to step within current context. + if not self._tree_machine.rightmost(): + self._tree_machine.step() + return + + # 3. Current context is rightmost -> pop until we can step. + while True: + self._tree_machine.pop_context() + if self._tree_machine.context_depth() == 0: + # Defensive: should not happen if can_step() was True. + self._has_node = False + return + if not self._tree_machine.rightmost(): + self._tree_machine.step() + return + # Still rightmost in new front context; keep popping. + + # ------------------------------------------------------------------ + # Introspection helper + # ------------------------------------------------------------------ + def has_node(self) -> bool: + """True if there is a current node to read.""" + return self._has_node and self._tree_machine.context_depth() > 0 + + +def build_breadth_machine(root_dp: str) -> BreadthMachine: + """Worker: construct and return a BreadthMachine for root_dp.""" + return BreadthMachine(root_dp) + + +def CLI(argv: List[str]) -> int: + """ + Command-line interface for BreadthMachine. + + Usage: + + ./BreadthMachine.py [root_dp] + + If root_dp is omitted, "." is used. + """ + root_dp = argv[1] if len(argv) > 1 else "." + bm = build_breadth_machine(root_dp) + + print("BreadthMachine") + meta.version() + print(f"# root = {root_dp!r}") + + if not bm.has_node(): + print("# (no nodes)") + return 0 + + while True: + path, _props, _addr = bm.read() + print(path) + if not bm.can_step(): break + bm.step() + + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI(sys.argv)) diff --git a/shared/authored/dir-walk/DepthMachine.py b/shared/authored/dir-walk/DepthMachine.py new file mode 100755 index 0000000..5d30f9f --- /dev/null +++ b/shared/authored/dir-walk/DepthMachine.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +from __future__ import annotations + +import sys +from typing import List, Tuple + +import meta +from TreeMachine import TreeMachine + + +class DepthMachine: + """ + DepthMachine — depth-first directory walker built on TreeMachine. + + External usage: + + dm = DepthMachine(root_dp) + while True: + path, props, addr = dm.read() + # ... process node ... + if not dm.can_step(): break + dm.step() + """ + + def __init__(self, root_dp: str | None = None) -> None: + if root_dp is None: root_dp = "." + self.root_dp: str = root_dp + self._tree_machine: TreeMachine = TreeMachine(root_dp) + self._has_node: bool = False + + # Bootstrap: move to the first node, if any. + if self._tree_machine.context_depth() > 0: + self._tree_machine.step() + self._has_node = True + + # ------------------------------------------------------------------ + # External interface + # ------------------------------------------------------------------ + def read(self) -> Tuple[str, List[str], Tuple[int, int]]: + """ + Read the current node. + + Returns: + (path_relative_to_root, property_list, address) + """ + if not self._has_node: + raise RuntimeError("DepthMachine.read() with no current node.") + return self._tree_machine.read() + + def can_step(self) -> bool: + """ + True if there is another node after the current one. + + Logic: + + - If current node is a non-empty directory, we can always push("front") + and descend into it. + + - Otherwise, delegate to TreeMachine.can_step(), which ripples over + all Contexts to see whether any Context is not rightmost. + """ + if not self._has_node: + return False + + _path, prop_list, _addr = self._tree_machine.read() + is_non_empty_dir = ("directory" in prop_list and "empty" not in prop_list) + + if is_non_empty_dir: + return True + + return self._tree_machine.can_step() + + def step(self) -> None: + """ + Advance to the next node. + + Cases: + + A) Current node is a non-empty directory: + - push it to the FRONT of the context queue + - step into its first child. + + B) Current node is not a non-empty directory AND current context + is not rightmost: + - step within current context. + + C) Current node is not a non-empty directory AND current context + is rightmost: + - pop contexts until a non-rightmost context is found, + then step within that context. + """ + if not self.can_step(): + raise RuntimeError("DepthMachine.step() called when can_step() is False.") + + _path, prop_list, _addr = self._tree_machine.read() + is_non_empty_dir = ("directory" in prop_list and "empty" not in prop_list) + + # Case A: non-empty directory -> push front + descend. + if is_non_empty_dir: + self._tree_machine.push_current_directory("front") + # New front Context is the directory we just pushed; step into its first child. + self._tree_machine.step() + return + + # Case B: not a non-empty dir and not rightmost -> simple step. + if not self._tree_machine.rightmost(): + self._tree_machine.step() + return + + # Case C: not a non-empty dir and rightmost -> pop until we can step. + while True: + self._tree_machine.pop_context() + if self._tree_machine.context_depth() == 0: + # Defensive: should not happen if can_step() was True. + self._has_node = False + return + if not self._tree_machine.rightmost(): + self._tree_machine.step() + return + # Still rightmost in new front context; keep popping. + + # ------------------------------------------------------------------ + # Introspection helper + # ------------------------------------------------------------------ + def has_node(self) -> bool: + """True if there is a current node to read.""" + return self._has_node and self._tree_machine.context_depth() > 0 + + +def build_depth_machine(root_dp: str) -> DepthMachine: + """Worker: construct and return a DepthMachine for root_dp.""" + return DepthMachine(root_dp) + + +def CLI(argv: List[str]) -> int: + """ + Command-line interface for DepthMachine. + + Usage: + + ./DepthMachine.py [root_dp] + + If root_dp is omitted, "." is used. + """ + root_dp = argv[1] if len(argv) > 1 else "." + dm = build_depth_machine(root_dp) + + print("DepthMachine") + meta.version() + print(f"# root = {root_dp!r}") + + if not dm.has_node(): + print("# (no nodes)") + return 0 + + # Knuth-style loop: read, test, step. + while True: + path, _props, _addr = dm.read() + print(path) + if not dm.can_step(): break + dm.step() + + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI(sys.argv)) diff --git a/shared/authored/dir-walk/Queue.py b/shared/authored/dir-walk/Queue.py index 2597423..da84999 100755 --- a/shared/authored/dir-walk/Queue.py +++ b/shared/authored/dir-walk/Queue.py @@ -99,6 +99,15 @@ class Queue(Generic[T]): else: self._queue_list = list(initial_iter) + def snapshot(self) -> list: + """ + Return a shallow copy of the items in queue order (front to back). + + This is intended for read-only traversal by higher-level logic + such as TreeMachine.can_step(). + """ + return list(self._items) + # ---------------------------------------------------------------------- # Basic properties # ---------------------------------------------------------------------- diff --git a/shared/authored/dir-walk/TreeMachine.py b/shared/authored/dir-walk/TreeMachine.py index 6e5eaf2..9057c3b 100755 --- a/shared/authored/dir-walk/TreeMachine.py +++ b/shared/authored/dir-walk/TreeMachine.py @@ -66,6 +66,35 @@ class Context: new_ctx._started = self._started return new_ctx + def can_step(self) -> bool: + """ + True if a step/pop sequence would find another node without + exhausting the entire context queue. + + Implementation: + + - If the queue is empty, there is nothing left to visit. + - Otherwise, walk the Contexts from front to back: + + * If we find any Context that is NOT rightmost, then there + exists some directory in the tree where a step can advance + (possibly after one or more pops). + + * If ALL Contexts report rightmost(), then any attempt to + step/pop would eventually pop the entire queue, so there + is no next node. + """ + if self._context_queue.is_empty(): + return False + + ctx_list = self._context_queue.snapshot() + for ctx in ctx_list: + if not ctx.rightmost(): + return True + + # Reached the back; every Context is rightmost. + return False + def rightmost(self) -> bool: """ True when this context cannot be stepped further without error. diff --git a/shared/authored/dir-walk/conventions.org b/shared/authored/dir-walk/conventions.org new file mode 100644 index 0000000..9f18ecc --- /dev/null +++ b/shared/authored/dir-walk/conventions.org @@ -0,0 +1,323 @@ +1. **2025-11-26 14:58:00Z** + +2. Here are cleaned-up versions of `DepthMachine.py` and `BreadthMachine.py`: + +* Single `if __name__ == "__main__":` branch. +* A `CLI` function that: + + * unpacks `argv`, + * calls a worker, + * returns an `int`. +* A small worker function that can be called directly from other code without going through CLI packing/parsing. + +--- + +### `DepthMachine.py` + +```python +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +from __future__ import annotations + +import sys +from typing import Any, List, Optional + +import meta +from TapeMachine import TapeMachine +from TreeMachine import TreeMachine + + +def _depth_debug(dm: Any, msg: str) -> None: + """Emit a DepthMachine-related debug message if meta.DEBUG has 'DepthMachine'.""" + if "DepthMachine" not in meta.DEBUG: return + print(f"[DepthMachine {hex(id(dm))}] {msg}") + + +class DepthMachine(TapeMachine): + """ + DepthMachine — depth-first directory walker as a TapeMachine. + + - Wraps a TreeMachine. + - For each visited node, appends its relative path to a tape. + - For directories with children, pushes a Context with where_tag 'front'. + """ + + def __init__(self, root_dp: Optional[str] = None) -> None: + if root_dp is None: root_dp = "." + node_list: List[str] = self._build_node_list(root_dp) + if not node_list: + raise ValueError("DepthMachine requires at least one node under root.") + _depth_debug(self, f"init root_dp={root_dp!r}, n_nodes={len(node_list)}") + super().__init__(node_list) + + def _build_node_list(self, root_dp: str) -> List[str]: + """Build a depth-first ordered list of node paths under root_dp.""" + tm = TreeMachine(root_dp) + nodes: List[str] = [] + + while tm.context_depth() > 0: + tm.step() + value, prop_list, _addr = tm.read() + nodes.append(value) + + if "directory" in prop_list and "empty" not in prop_list: + tm.push_current_directory("front") + + if tm.rightmost(): + tm.pop_context() + + return nodes + + +def build_depth_machine(root_dp: str) -> DepthMachine: + """Worker: construct and return a DepthMachine for root_dp.""" + return DepthMachine(root_dp) + + +def CLI(argv: List[str]) -> int: + """Command-line interface wrapper for DepthMachine.""" + root_dp = argv[1] if len(argv) > 1 else "." + dm = build_depth_machine(root_dp) + + print("DepthMachine") + meta.version() + print(f"# root = {root_dp!r}") + print(f"# length = {dm.length}") + dm.rewind() + while True: + print(dm.read()) + if not dm.can_step(1): break + dm.step(1) + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI(sys.argv)) +``` + +--- + +### `BreadthMachine.py` + +```python +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +from __future__ import annotations + +import sys +from typing import Any, List, Optional + +import meta +from TapeMachine import TapeMachine +from TreeMachine import TreeMachine + + +def _breadth_debug(bm: Any, msg: str) -> None: + """Emit a BreadthMachine-related debug message if meta.DEBUG has 'BreadthMachine'.""" + if "BreadthMachine" not in meta.DEBUG: return + print(f"[BreadthMachine {hex(id(bm))}] {msg}") + + +class BreadthMachine(TapeMachine): + """ + BreadthMachine — breadth-first directory walker as a TapeMachine. + + - Wraps a TreeMachine. + - For each visited node, appends its relative path to a tape. + - For directories with children, pushes a Context with where_tag 'back'. + """ + + def __init__(self, root_dp: Optional[str] = None) -> None: + if root_dp is None: root_dp = "." + node_list: List[str] = self._build_node_list(root_dp) + if not node_list: + raise ValueError("BreadthMachine requires at least one node under root.") + _breadth_debug(self, f"init root_dp={root_dp!r}, n_nodes={len(node_list)}") + super().__init__(node_list) + + def _build_node_list(self, root_dp: str) -> List[str]: + """Build a breadth-first ordered list of node paths under root_dp.""" + tm = TreeMachine(root_dp) + nodes: List[str] = [] + + while tm.context_depth() > 0: + tm.step() + value, prop_list, _addr = tm.read() + nodes.append(value) + + if "directory" in prop_list and "empty" not in prop_list: + tm.push_current_directory("back") + + if tm.rightmost(): + tm.pop_context() + + return nodes + + +def build_breadth_machine(root_dp: str) -> BreadthMachine: + """Worker: construct and return a BreadthMachine for root_dp.""" + return BreadthMachine(root_dp) + + +def CLI(argv: List[str]) -> int: + """Command-line interface wrapper for BreadthMachine.""" + root_dp = argv[1] if len(argv) > 1 else "." + bm = build_breadth_machine(root_dp) + + print("BreadthMachine") + meta.version() + print(f"# root = {root_dp!r}") + print(f"# length = {bm.length}") + bm.rewind() + while True: + print(bm.read()) + if not bm.can_step(1): break + bm.step(1) + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI(sys.argv)) +``` + +You can now: + +```bash +./DepthMachine.py +./DepthMachine.py /some/path +./BreadthMachine.py +./BreadthMachine.py /some/path +``` + +And from another Python module: + +```python +from DepthMachine import build_depth_machine + +dm = build_depth_machine("/some/path") +dm.rewind() +while True: + print(dm.read()) + if not dm.can_step(1): break + dm.step(1) +``` + +--- + +3. **RT conventions we’ve touched in this session** + +Here’s a quick index of the conventions that surfaced so far, so we don’t have to rediscover them later: + +1. **Indentation and layout** + + * Two-space indent. + * `indent-tabs-mode: nil` in the file header. + * Multi-line functions kept visually tight; formal parameter lists on a single line where they comfortably fit. + * Short, “inline” style for simple methods when reasonable. + +2. **Naming style** + + * PascalCase for type / class names: `TapeMachine`, `TreeMachine`, `DepthMachine`, `BreadthMachine`, `Context`, `Queue`. + * snake_case for functions, methods, and variables: `can_step`, `cue_end`, `build_depth_machine`, `root_dp`. + * DEBUG helpers: `_tape_debug`, `_tm_debug`, `_depth_debug`, `_breadth_debug`. + +3. **Path suffix conventions** + + * `*_dp` → directory path (unspecified relative vs absolute). + * `*_dpa` → absolute directory path. + * `*_dpr` → relative directory path. + * `*_fp` → file path (unspecified). + * `*_fpa` / `*_fpr` → absolute / relative file paths. + * `*_fs_nod_pr` / `*_fs_nod_pa` → file-system node paths (relative / absolute) when type (file/dir/other) isn’t fixed. + +4. **TapeMachine semantics** + + * First-rest pattern: + + * Tape must be non-empty. + * Head (`_head_position`) always on a valid cell; starts at `0`. + * `can_step(n)` checks bounds; `step(n)` assumes caller checked. + * `read()` returns *just* the current value. + * `step()` returns nothing; it just moves the head. + * `cue()` is the public accessor for `_head_position`. + * `rewind()` sets cue to `0`. + * `cue_end()` sets cue to `length-1`. + * `entangle()` creates another machine sharing the same tape but with its own head. + +5. **Context / Queue conventions** + + * Context objects hold: + + * `dir_dpr` (relative directory path, `'.'` for root), + * `level` (depth, root == 1), + * child traversal state. + * `Context` internally uses a `TapeMachine` for child names; no separate index bookkeeping. + * Contexts are only created for directories with **non-empty** child lists. + * `Queue` holds `Context` objects and supports tagged pushes: + + * `"front"` (depth-ish), + * `"back"` (breadth-ish), + * `"throw-away"` (discard). + * `Context.clone()` uses entangled `TapeMachine` for children. + +6. **TreeMachine semantics** + + * Root stored as absolute `root_dpa`; external API uses paths relative to root. + * `step()` advances within the current `Context` and sets the current node. + * `read()` returns `(value, property_list, address)` where: + + * `value` is a `fs_nod_pr` relative path, + * `property_list` ⊆ `{"directory","file","other","empty"}`, + * `address` is `(level, index)`; `level` not derived from path text. + * `rightmost()` checks if current context has any more nodes. + * `push_current_directory(where_tag)` enqueues non-empty directory contexts. + * `pop_context()` drops current context and clears the current head. + * `context_depth()` returns the active context depth. + * `entangle()` clones the context queue and current node/address, using `Context.clone()`. + +7. **CLI vs worker separation** + + * Worker functions take structured arguments (e.g. `root_dp: str`) and do the real work: + + * `build_depth_machine(root_dp)`, + * `build_breadth_machine(root_dp)`. + * `CLI(argv)`: + + * unpacks `argv`, + * calls the worker, + * handles printing / side effects, + * returns an `int` status. + * `if __name__ == "__main__": raise SystemExit(CLI(sys.argv))` – single branch. + +8. **Debugging conventions** + + * `meta.DEBUG` is a **set** of tags; use `add`, `discard`, membership tests. + * Debug helpers: + + * Check presence of tag (`"TapeMachine"`, `"TreeMachine"`, `"Queue"`, `"DepthMachine"`, `"BreadthMachine"`, etc.). + * Print with class name and `hex(id(instance))` prefix: + + * `[TapeMachine 0x...] msg` + * `[TreeMachine 0x...] msg` + * Tests can enable tags with: + + ```python + meta.DEBUG.add("TapeMachine") + meta.DEBUG.add("TreeMachine") + ``` + +9. **Versioning via `meta`** + + * `meta` holds at least: + + * `DEBUG` (set of tags), + * `version()` that prints `"version "`. + * Modules print version in tests / CLI to track which build is running. + +If you’d like, next step could be a tiny “course” doc that shows: + +* raw `TreeMachine` usage, +* `DepthMachine` / `BreadthMachine` as derived tapes, +* and a short example of entangling and doing a sub-search without losing place.