mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-07-13 04:41:26 -07:00
lots of cruft to still sift thru
This commit is contained in:
+55
-112
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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")
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user