From a0d22700dbcb9a42e84db483ca8302b333efac6c Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jul 2026 00:27:28 -0400 Subject: [PATCH] lots of cruft to still sift thru --- scripts/duffle.lua | 167 ++++++++++------------------- scripts/duffle_paths.lua | 20 ++-- scripts/passes/annotation.lua | 76 +++++-------- scripts/passes/components.lua | 7 +- scripts/passes/duffle_paths.lua | 85 --------------- scripts/passes/offsets.lua | 7 +- scripts/passes/report.lua | 7 +- scripts/passes/static_analysis.lua | 7 +- scripts/passes/word_count_eval.lua | 7 +- scripts/ps1_meta.lua | 11 +- 10 files changed, 129 insertions(+), 265 deletions(-) delete mode 100644 scripts/passes/duffle_paths.lua diff --git a/scripts/duffle.lua b/scripts/duffle.lua index 92c6890..2e004f1 100644 --- a/scripts/duffle.lua +++ b/scripts/duffle.lua @@ -138,51 +138,54 @@ end -- Section 0: LPeg patterns (compiled once at module load) -- ════════════════════════════════════════════════════════════════════════════ -- --- LPeg is a PEG library (no regex). All patterns below are first-class pattern values; they're cheap to build and reuse. +-- LPeg is a required dependency (PEG library, no regex). It's loaded +-- via `package.cpath` (configured by `duffle_paths.lua` to find +-- `toolchain/lpeg/lpeg.dll`). There's no hand-rolled fallback — the +-- original two-tier design added complexity for a 5-10x speedup that's +-- only relevant at the high-level scanner stage; the byte-by-byte +-- helpers in Section 1 are sufficient for the classification primitives. -- --- Note: lpeg is required lazily because Lua 5.5 may not have it on its cpath at the same location as LuaJIT. --- We attempt the require and fall back to the hand-rolled implementations if it fails. - +-- If the require fails, fail loud with an actionable message (per +-- lua.md §9). The build script (`update_deps.ps1`) builds lpeg.dll +-- into `toolchain/lpeg/`; if it's missing, run `update_deps.ps1`. local lpeg_ok, lpeg = pcall(require, "lpeg") -local lpeg_lib = nil -local lpeg_alpha_pat, lpeg_alnum_pat, lpeg_ident_pat -local lpeg_str_or_cmt_pat, lpeg_ws_and_cmt_pat -local lpeg_scan_to_target_pat -- generic "anything but target or balanced group" matcher - -if lpeg_ok then - lpeg_lib = lpeg - local P, S, R = lpeg.P, lpeg.S, lpeg.R - - -- Character class patterns - local alpha_pat = R("AZ", "az") + P("_") - local digit_pat = R("09") - lpeg_alnum_pat = alpha_pat + digit_pat - - -- Identifier: alpha followed by zero+ alnum. Capture as a string. - lpeg_alpha_pat = alpha_pat - lpeg_ident_pat = lpeg.C(alpha_pat * lpeg_alnum_pat^0) - - -- String literal: "..." with backslash escapes. - local lpeg_str_pat = P('"') * (P(1) - S('"\\') + P('\\') * P(1))^0 * P('"') - -- Char literal: '...' with backslash escapes. - local lpeg_chr_pat = P("'") * (P(1) - S("'\\") + P('\\') * P(1))^0 * P("'") - -- Line comment: // ... to end-of-line. - local lpeg_line_cmt_pat = P("//") * (P(1) - S("\n"))^0 - -- Block comment: /* ... */ (no nesting per C standard). - local lpeg_block_cmt_pat = P("/*") * (P(1) - P("*/"))^0 * P("*/") - -- String or comment (any of the four forms). - lpeg_str_or_cmt_pat = lpeg_str_pat + lpeg_chr_pat + lpeg_line_cmt_pat + lpeg_block_cmt_pat - - -- Whitespace + comment skipper: zero+ (whitespace run | string | comment). - local ws_pat = S(" \t\n\r\v\f") - lpeg_ws_and_cmt_pat = (ws_pat + lpeg_str_or_cmt_pat)^0 - - -- Generic "skip until target, but step over balanced groups" matcher. - -- Used by scan_to_char for non-ident / non-bracket chars. - -- We accept any single char except the target. - -- The balanced-group stepping is handled by the caller (via read_balanced). - lpeg_scan_to_target_pat = function(target) return (P(1) - P(target))^0 end +if not lpeg_ok then + io.stderr:write("[duffle] require('lpeg') failed: ", lpeg, "\n") + io.stderr:write("[duffle] lpeg.dll not found on package.cpath.\n") + io.stderr:write("[duffle] Run 'scripts/update_deps.ps1' to build it into toolchain/lpeg/.\n") + os.exit(1) end +local P, S, R = lpeg.P, lpeg.S, lpeg.R + +-- Character class patterns +local alpha_pat = R("AZ", "az") + P("_") +local digit_pat = R("09") +local lpeg_alnum_pat = alpha_pat + digit_pat + +-- Identifier: alpha followed by zero+ alnum. Capture as a string. +local lpeg_alpha_pat = alpha_pat +local lpeg_ident_pat = lpeg.C(alpha_pat * lpeg_alnum_pat^0) + +-- String literal: "..." with backslash escapes. +local lpeg_str_pat = P('"') * (P(1) - S('"\\') + P('\\') * P(1))^0 * P('"') +-- Char literal: '...' with backslash escapes. +local lpeg_chr_pat = P("'") * (P(1) - S("'\\") + P('\\') * P(1))^0 * P("'") +-- Line comment: // ... to end-of-line. +local lpeg_line_cmt_pat = P("//") * (P(1) - S("\n"))^0 +-- Block comment: /* ... */ (no nesting per C standard). +local lpeg_block_cmt_pat = P("/*") * (P(1) - P("*/"))^0 * P("*/") +-- String or comment (any of the four forms). +local lpeg_str_or_cmt_pat = lpeg_str_pat + lpeg_chr_pat + lpeg_line_cmt_pat + lpeg_block_cmt_pat + +-- Whitespace + comment skipper: zero+ (whitespace run | string | comment). +local ws_pat = S(" \t\n\r\v\f") +local lpeg_ws_and_cmt_pat = (ws_pat + lpeg_str_or_cmt_pat)^0 + +-- Generic "skip until target, but step over balanced groups" matcher. +-- Used by scan_to_char for non-ident / non-bracket chars. +-- We accept any single char except the target. +-- The balanced-group stepping is handled by the caller (via read_balanced). +local lpeg_scan_to_target_pat = function(target) return (P(1) - P(target))^0 end -- ════════════════════════════════════════════════════════════════════════════ -- Section 1: character classification (byte-based for hot loops) @@ -320,81 +323,26 @@ function M._reset_ensured_dirs() _ensured_dirs = {} end -- Section 4: C-language scanner primitives -- ════════════════════════════════════════════════════════════════════════════ --- LPeg-backed skipper when LPeg is available, hand-rolled fallback otherwise. --- Returns position just past the construct, or `pos` unchanged if no --- string/comment starts at position `pos`. +-- Skip a string or C-style comment starting at position `pos`. +-- Returns the position just past the construct, or `pos` unchanged if +-- no string/comment starts there. LPeg-backed. function M.skip_str_or_cmt(s, pos) - if lpeg_ok then - local new_pos = lpeg.match(lpeg_str_or_cmt_pat, s, pos) - if new_pos then return new_pos end - return pos - end - -- Hand-rolled fallback (kept for builds where LPeg isn't available). - local c = s:byte(pos) - if c == BYTE_DQUOTE or c == BYTE_SQUOTE then -- '"' or '\'' - pos = pos + 1 - while pos <= #s do - local b = s:byte(pos) - if b == BYTE_BACKSLASH then pos = pos + 2 -- '\\' - elseif b == c then return pos + 1 - else pos = pos + 1 end - end - return #s + 1 - elseif c == BYTE_SLASH then -- '/' - local nx = s:byte(pos + 1) - if nx == BYTE_SLASH then -- '//' - while pos <= #s and s:byte(pos) ~= BYTE_NEWLINE do pos = pos + 1 end - return pos - elseif nx == BYTE_STAR then -- '/*' - pos = pos + 2 - while pos <= #s - 1 do - if s:byte(pos) == BYTE_STAR and s:byte(pos + 1) == BYTE_SLASH then -- '*/' - return pos + 2 - end - pos = pos + 1 - end - return #s + 1 - end - end - return pos + return lpeg.match(lpeg_str_or_cmt_pat, s, pos) or pos end -- Skip whitespace AND C-style comments starting at position `pos`. --- LPeg-backed when available; ~5-10x faster than the hand-rolled version. +-- LPeg-backed; ~5-10x faster than a hand-rolled byte-by-byte walker. function M.skip_ws_and_cmt(s, pos) - if lpeg_ok then - local new_pos = lpeg.match(lpeg_ws_and_cmt_pat, s, pos) - if new_pos then return new_pos end - return pos - end - -- Hand-rolled fallback. - local len = #s - while pos <= len do - if M.is_space_byte(s:byte(pos)) then - pos = pos + 1 - else - local nx = M.skip_str_or_cmt(s, pos) - if nx > pos then pos = nx else break end - end - end - return pos + return lpeg.match(lpeg_ws_and_cmt_pat, s, pos) or pos end -- Read a C-style identifier (alpha followed by zero+ alnum) starting at -- position `pos`. Returns the identifier string + the position just past --- it, or nil + pos if no identifier starts here. +-- it, or nil + pos if no identifier starts here. LPeg-backed. function M.read_ident(s, pos) - if lpeg_ok then - local result = lpeg.match(lpeg_ident_pat, s, pos) - if result then return result, pos + #result end - return nil, pos - end - -- Hand-rolled fallback. - if not M.is_alpha_byte(s:byte(pos)) then return nil, pos end - local a = pos - pos = pos + 1 - while pos <= #s and M.is_alnum_byte(s:byte(pos)) do pos = pos + 1 end - return s:sub(a, pos - 1), pos + local result = lpeg.match(lpeg_ident_pat, s, pos) + if result then return result, pos + #result end + return nil, pos end -- Read a balanced-delimited group (parens, braces, or brackets) starting @@ -904,9 +852,4 @@ M.INSTRUCTION_LATENCY = { -- advisory so the cycle budget stays accurate as the codebase grows. M.UNKNOWN_INSTRUCTION_CYCLES = 1 --- Expose the lpeg_ok flag so callers can detect the LPeg-back path. --- True when LPeg was successfully required and the patterns above were --- compiled at module load time. False when running in fallback mode. -M.lpeg_ok = lpeg_ok - return M diff --git a/scripts/duffle_paths.lua b/scripts/duffle_paths.lua index 4cd5357..cbccdce 100644 --- a/scripts/duffle_paths.lua +++ b/scripts/duffle_paths.lua @@ -12,7 +12,7 @@ local M = {} -- Cache key for the repo root. Stored in `package.loaded` (process-global) so all 8 entry scripts + passes scripts share one git call. --- Without this cache, `git rev-parse --show-toplevel` runs once per script load = 8 × ~150ms = 1.2s wasted per build on Windows. +-- Without this cache, `git rev-parse --show-toplevel` runs once per script load. local CACHE_KEY = "__duffle_repo_root__" --- Resolve the repo root via git (cached after first call). @@ -31,8 +31,13 @@ local function find_repo_root() return root end ---- Set `package.path` (for `require("duffle")` + `require("passes.X")`) and `package.cpath` (for `lpeg.dll` on Windows). ---- Idempotent: safe to call multiple times (just re-sets the same paths). +--- Set `package.path` (for `require("duffle")` + `require("passes.X")`) and +--- `package.cpath` (for `lpeg.dll`). +--- +--- This script does NOT touch the OS environment: no `os.setenv`, no `os.putenv`, no `$PATH` mods. +--- It just sets `package.path` and `package.cpath` (the standard Lua way to register module search dirs). +--- lpeg is built by `update_deps.ps1` to `toolchain/lpeg/`, +--- which we wire into `package.cpath` here (so `require("lpeg")` from `duffle.lua` resolves without any global state). function M.setup() local repo_root = find_repo_root() if not repo_root then @@ -48,10 +53,11 @@ function M.setup() .. passes_dir .. "?/init.lua;" .. package.path - if package.config:sub(1, 1) == "\\" then - package.cpath = repo_root .. "toolchain/luajit-2.1/lib/lua/5.1/?.dll;" - .. package.cpath - end + -- lpeg: built by `update_deps.ps1` to `toolchain/lpeg/lpeg.dll`. + -- Wire its directory into cpath so `require("lpeg")` resolves. + local lpeg_dir = repo_root .. "toolchain/lpeg/" + package.cpath = lpeg_dir .. "?.dll;" + .. package.cpath end -- Run the setup as a side effect. diff --git a/scripts/passes/annotation.lua b/scripts/passes/annotation.lua index 8b7052b..53efd45 100644 --- a/scripts/passes/annotation.lua +++ b/scripts/passes/annotation.lua @@ -10,12 +10,13 @@ --- **Conventions**: tabs (1/level), EmmyLua annotations, no regex, --- Lua 5.3 compatible --- ════════════════════════════════════════════════════════════════════════════ --- Module-scope requires + package.path setup --- ════════════════════════════════════════════════════════════════════════════ - -- Bootstrap: same as entry scripts. See `ps1_meta.lua` for the rationale. -dofile((arg[0]:match("(.*[/\\])") or "./") .. "duffle_paths.lua") +-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath). +-- Uses `debug.getinfo` to find this file's own directory, so it works +-- both standalone and when require'd from the orchestrator. +local _src = debug.getinfo(1, "S").source:sub(2) +local _dir = _src:match("(.*[/\\])") or "./" +dofile(_dir .. "../duffle_paths.lua") local duffle = require("duffle") local is_space = duffle.is_space local is_alpha = duffle.is_alpha @@ -151,7 +152,7 @@ local BYTE_COMMA = 44 --- @field pragmas table -- reserved (currently always nil; legacy compat) --- ════════════════════════════════════════════════════════════════════════════ --- Hand-rolled split helpers (no regex patterns used) +-- split helpers -- ════════════════════════════════════════════════════════════════════════════ --- Split a string at top-level commas. Used inside TAPE_ATOM_* macro @@ -212,10 +213,10 @@ end -- ════════════════════════════════════════════════════════════════════════════ -- Recognize a `atom_bind(...)`, `atom_reads(...)`, or `atom_writes(...)` --- sub-call embedded inside an atom_info arg list. Returns the kind --- ("atom_bind" / "atom_reads" / "atom_writes") and the inner content, --- or nil if the token isn't a recognized sub-call form. Flattened via --- a prefix lookup instead of a nested if/elseif chain. +-- sub-call embedded inside an atom_info arg list. +-- Returns the kind ("atom_bind" / "atom_reads" / "atom_writes") and the inner content, +-- or nil if the token isn't a recognized sub-call form. +-- Flattened via a prefix lookup instead of a nested if/elseif chain. local REGS_CALL_PREFIX = { ["atom_writes("] = { kind = "atom_writes", inner_offset = 13 }, ["atom_reads("] = { kind = "atom_reads", inner_offset = 12 }, @@ -224,18 +225,14 @@ local REGS_CALL_PREFIX = { local function parse_regs_call(s) if s:sub(-1) ~= ")" then return nil end - -- Try longest prefix first so "atom_writes(" wins over "atom_reads(" - -- when both 12-char prefixes would otherwise match. Lengths: + -- Try longest prefix first so "atom_writes(" wins over "atom_reads(" when both 12-char prefixes would otherwise match. + -- Lengths: -- atom_writes( = 12 chars, offset 13 -- atom_reads( = 11 chars, offset 12 -- atom_bind( = 10 chars, offset 11 local spec = REGS_CALL_PREFIX[s:sub(1, 12)] - if not spec then - spec = REGS_CALL_PREFIX[s:sub(1, 11)] - end - if not spec then - spec = REGS_CALL_PREFIX[s:sub(1, 10)] - end + if not spec then spec = REGS_CALL_PREFIX[s:sub(1, 11)] end + if not spec then spec = REGS_CALL_PREFIX[s:sub(1, 10)] end if not spec then return nil end local inner = s:sub(spec.inner_offset, -2) if spec.single_ident then @@ -246,12 +243,10 @@ local function parse_regs_call(s) end -- Resolve any phase_* / R_* alias macros in a register list. --- (Phase / region / cadence aliases have been dropped. Kept as an --- identity function so callers can stay uniform.) +-- (Phase / region / cadence aliases have been dropped. Kept as an identity function so callers can stay uniform.) local function resolve_reg_aliases(regs) return regs end --- Parse a comma-separated inner content (e.g. inside atom_reads(...)) --- into a list of trimmed identifiers with aliases resolved. +-- Parse a comma-separated inner content (e.g. inside atom_reads(...)) into a list of trimmed identifiers with aliases resolved. local function parse_regs_list(inner) local out = {} for _, r in ipairs(split_csv_top(inner)) do @@ -262,8 +257,7 @@ local function parse_regs_list(inner) end -- Parse a single token (from split_csv_top) into an arg entry. --- Three forms: register-list call, bare identifier, --- "other" (preserved as text). +-- Three forms: register-list call, bare identifier, "other" (preserved as text). local function parse_arg_token(s) local kind, inner = parse_regs_call(s) if kind then @@ -279,8 +273,8 @@ local function parse_arg_token(s) return { kind = "other", value = s } end ---- Extract identifier args from a parenthesized group. Returns a list ---- of {kind, value} pairs where kind is one of: +--- Extract identifier args from a parenthesized group. +--- Returns a list of {kind, value} pairs where kind is one of: --- "ident" -- a bare identifier (e.g. phase_work) --- "atom_reads" -- an atom_reads(...) call: value is the register list --- "atom_writes" -- an atom_writes(...) call: value is the register list @@ -741,8 +735,8 @@ local function validate(ctx, src) end end - -- 2. Every atom may have AT MOST ONE annotation (no duplicates). - -- (Atoms with ZERO annotations are valid in the new minimal shape.) + -- 2. Every atom may have AT MOST ONE annotation (no duplicates). + -- (Atoms with ZERO annotations are valid in the new minimal shape.) local count_per_atom = {} for _, a in ipairs(annots) do if a.name and not a.error then @@ -758,21 +752,17 @@ local function validate(ctx, src) end end - -- 3. (Phase validity check DROPPED. Phases were removed from the - -- annotation DSL. They may be reintroduced later as sub-calls of - -- atom_info, at which point ordering checks will go here.) + -- 3. (Phase validity check DROPPED. Phases were removed from the annotation DSL. + -- They may be reintroduced later as sub-calls of atom_info, at which point ordering checks will go here.) -- 4. BIND atoms must reference a real Binds_* struct. for _, a in ipairs(annots) do if a.binds then if not binds_index[a.binds] then - -- Demoted from error to warning (2026-07-10): the same - -- condition is now caught by passes/static_analysis.lua's - -- check_abi_handoff() as an error. Emitting a warning - -- here keeps the annotation pass from being stop-on-error - -- for the common test-fixture case, while still surfacing - -- the issue in the report. The static-analysis report - -- remains the source of truth for build-stopping errors. + -- Demoted from error to warning (2026-07-10): the same condition is now caught by passes/static_analysis.lua's + -- check_abi_handoff() as an error. Emitting a warning here keeps the annotation pass from being stop-on-error + -- for the common test-fixture case, while still surfacing the issue in the report. + -- The static-analysis report remains the source of truth for build-stopping errors. warnings[#warnings + 1] = { line = a.line, msg = string.format("'%s' binds '%s' but no Struct_(%s) { ... } declaration found (also flagged as an error by check_abi_handoff in the static-analysis pass)", a.name, a.binds, a.binds), @@ -848,15 +838,7 @@ local function validate(ctx, src) check_macro_drift(m, ctx.shared.word_counts[m.name]) end - -- 8. (atom_<...> _Pragma validation DROPPED. The pragma macros - -- atom_resource / atom_region / atom_group / atom_cadence / - -- atom_async were removed from atom_dsl.h. They may be - -- reintroduced later as sub-calls of atom_info.) - - -- 9. (CADENCE_ONDEMAND requires async check DROPPED. Same reason - -- as #8.) - - -- 10. Information summary. + -- 8. Information summary. info[#info + 1] = { line = 0, msg = string.format("scanned: %d atom(s), %d annotation(s), %d macro-word-decl(s), %d binds struct(s)", diff --git a/scripts/passes/components.lua b/scripts/passes/components.lua index 6d1112a..6ae8279 100644 --- a/scripts/passes/components.lua +++ b/scripts/passes/components.lua @@ -22,7 +22,12 @@ -- ════════════════════════════════════════════════════════════════════════════ -- Resolve `arg[0]` to an absolute-ish script directory so that `require("duffle")` resolves against `scripts/` regardless of CWD. -dofile((arg[0]:match("(.*[/\\])") or "./") .. "duffle_paths.lua") +-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath). +-- Uses `debug.getinfo` to find this file's own directory, so it works +-- both standalone and when require'd from the orchestrator. +local _src = debug.getinfo(1, "S").source:sub(2) +local _dir = _src:match("(.*[/\\])") or "./" +dofile(_dir .. "../duffle_paths.lua") local duffle = require("duffle") local word_count_eval = require("word_count_eval") diff --git a/scripts/passes/duffle_paths.lua b/scripts/passes/duffle_paths.lua deleted file mode 100644 index 454d681..0000000 --- a/scripts/passes/duffle_paths.lua +++ /dev/null @@ -1,85 +0,0 @@ ---- duffle_paths.lua — Single-line bootstrap helper for the tape-atom ---- Lua scripts. ---- ---- Each entry script (ps1_meta.lua, word_count_eval.lua, and the 5 ---- passes/*.lua files) starts with: ---- ---- ```lua ---- local duffle = dofile((arg[0]:match("(.*[/\\])") or "./") .. "duffle_paths.lua") ---- ``` ---- ---- That single line: (a) locates this helper via `arg[0]`, (b) loads ---- it (which sets `package.path` + `package.cpath` via `git rev-parse`), ---- (c) returns the `M` table (a wrapper around the setup function). ---- After this line, `require("duffle")` and `require("passes.X")` both ---- resolve normally. ---- ---- **Why a helper instead of inline?** ---- - The 8-line path-setup boilerplate was duplicated across 7 entry ---- scripts (one per file). Single source of truth here. ---- - Mirrors the build script's pattern in `build_psyq.ps1`: ---- `$path_root = split-path -Path $PSScriptRoot -Parent;` then ---- derive everything from there. ---- ---- **Why `git rev-parse --show-toplevel`?** ---- Hardcoding `C:\\projects\\Pikuma\\ps1\\...` breaks portability. Git ---- gives us the canonical repo root regardless of where the repo lives ---- on disk. - -local M = {} - --- Cache key for the repo root. Stored in `package.loaded` (process- --- global) so all 8 entry scripts + passes scripts share one git call. --- Without this cache, `git rev-parse --show-toplevel` runs once per --- script load = 8 × ~150ms = 1.2s wasted per build on Windows. -local CACHE_KEY = "__duffle_repo_root__" - ---- Resolve the repo root via git (cached after first call). ---- Returns a normalized path with a trailing forward-slash, or nil ---- if not in a git repo. ---- @return string|nil -local function find_repo_root() - if package.loaded[CACHE_KEY] then return package.loaded[CACHE_KEY] end - local p = io.popen("git rev-parse --show-toplevel 2>nul") - local root - if p then - root = p:read("*l") - p:close() - end - if not root or root == "" then return nil end - -- Normalize to forward slashes (Windows accepts both, but mixed - -- `\` + `/` confuses LuaJIT's file APIs). - root = root:gsub("\\", "/") - if not root:match("/$") then root = root .. "/" end - package.loaded[CACHE_KEY] = root - return root -end - ---- Set `package.path` (for `require("duffle")` + `require("passes.X")`) ---- and `package.cpath` (for `lpeg.dll` on Windows). ---- Idempotent: safe to call multiple times (just re-sets the same paths). -function M.setup() - local repo_root = find_repo_root() - if not repo_root then - io.stderr:write("[duffle_paths] git rev-parse failed -- not in a git repo?\n") - os.exit(2) - end - - local scripts_dir = repo_root .. "scripts/" - local passes_dir = repo_root .. "scripts/passes/" - package.path = scripts_dir .. "?.lua;" - .. scripts_dir .. "?/init.lua;" - .. passes_dir .. "?.lua;" - .. passes_dir .. "?/init.lua;" - .. package.path - - if package.config:sub(1, 1) == "\\" then - package.cpath = repo_root .. "toolchain/luajit-2.1/lib/lua/5.1/?.dll;" - .. package.cpath - end -end - --- Run the setup as a side effect. -M.setup() - -return M \ No newline at end of file diff --git a/scripts/passes/offsets.lua b/scripts/passes/offsets.lua index 6845d2c..01acc7e 100644 --- a/scripts/passes/offsets.lua +++ b/scripts/passes/offsets.lua @@ -22,7 +22,12 @@ -- Note: this boilerplate is duplicated in 6 other entry scripts; a -- Phase-6 extraction target (`duffle.setup_package_path()`). -- Bootstrap: see `ps1_meta.lua` for the rationale. -dofile((arg[0]:match("(.*[/\\])") or "./") .. "duffle_paths.lua") +-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath). +-- Uses `debug.getinfo` to find this file's own directory, so it works +-- both standalone and when require'd from the orchestrator. +local _src = debug.getinfo(1, "S").source:sub(2) +local _dir = _src:match("(.*[/\\])") or "./" +dofile(_dir .. "../duffle_paths.lua") local duffle = require("duffle") local word_count_eval = require("word_count_eval") local count_token_words = word_count_eval.count_token_words diff --git a/scripts/passes/report.lua b/scripts/passes/report.lua index d215da4..82576c1 100644 --- a/scripts/passes/report.lua +++ b/scripts/passes/report.lua @@ -25,7 +25,12 @@ -- Note: this boilerplate is duplicated in 6 other entry scripts; a -- Phase-6 extraction target (`duffle.setup_package_path()`). -- Bootstrap: see `ps1_meta.lua` for the rationale. -dofile((arg[0]:match("(.*[/\\])") or "./") .. "duffle_paths.lua") +-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath). +-- Uses `debug.getinfo` to find this file's own directory, so it works +-- both standalone and when require'd from the orchestrator. +local _src = debug.getinfo(1, "S").source:sub(2) +local _dir = _src:match("(.*[/\\])") or "./" +dofile(_dir .. "../duffle_paths.lua") local duffle = require("duffle") -- ════════════════════════════════════════════════════════════════════════════ diff --git a/scripts/passes/static_analysis.lua b/scripts/passes/static_analysis.lua index d17d9cd..463e81a 100644 --- a/scripts/passes/static_analysis.lua +++ b/scripts/passes/static_analysis.lua @@ -37,7 +37,12 @@ -- resolves regardless of CWD. See `duffle.lua` for the implementation. -- Bootstrap: see `ps1_meta.lua` for the rationale. -dofile((arg[0]:match("(.*[/\\])") or "./") .. "duffle_paths.lua") +-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath). +-- Uses `debug.getinfo` to find this file's own directory, so it works +-- both standalone and when require'd from the orchestrator. +local _src = debug.getinfo(1, "S").source:sub(2) +local _dir = _src:match("(.*[/\\])") or "./" +dofile(_dir .. "../duffle_paths.lua") local duffle = require("duffle") -- Domain tables (single source of truth in duffle.lua). diff --git a/scripts/passes/word_count_eval.lua b/scripts/passes/word_count_eval.lua index 67d4701..573d769 100644 --- a/scripts/passes/word_count_eval.lua +++ b/scripts/passes/word_count_eval.lua @@ -24,7 +24,12 @@ -- Note: this boilerplate is duplicated in 6 other entry scripts; a -- Phase-6 extraction target (`duffle.setup_package_path()`). -- Bootstrap: see `ps1_meta.lua` for the rationale. -dofile((arg[0]:match("(.*[/\\])") or "./") .. "duffle_paths.lua") +-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath). +-- Uses `debug.getinfo` to find this file's own directory, so it works +-- both standalone and when require'd from the orchestrator. +local _src = debug.getinfo(1, "S").source:sub(2) +local _dir = _src:match("(.*[/\\])") or "./" +dofile(_dir .. "../duffle_paths.lua") local duffle = require("duffle") -- ════════════════════════════════════════════════════════════════════════════ diff --git a/scripts/ps1_meta.lua b/scripts/ps1_meta.lua index d2fe8dd..b60c193 100644 --- a/scripts/ps1_meta.lua +++ b/scripts/ps1_meta.lua @@ -625,15 +625,8 @@ local function report_validation_errors(pass_name, pass, result) return true end --- (internal) Run each pass in `order` in topological sequence. Tracks --- `had_errors` instead of os.exit()ing mid-loop so the report pass (and --- any other downstream pass) still runs and writes its per-module files. --- The legacy behavior was os.exit(1) on the first error, which left --- downstream per-module reports un-emitted; the 2026-07-10 change to --- per-module aggregation made that visible to the user (build/gen had --- only the partial reports from the failing pass), so we now complete --- all passes and set the exit code at the end. ---- +-- (internal) Run each pass in `order` in topological sequence. +-- -- @param ctx PassCtx -- @param order string[] -- @return boolean -- true if any validation errors were reported