feat(selfhost): quote/graft hygiene API for macros and mono
Add Ast_Graft/Clone/Quote helpers, HIR Lcx_GraftSourceFile, and force definition-site paths on monomorphized bodies. Smoke checks Array mono #line stays in lib while Main stays isolated.
This commit is contained in:
@@ -233,3 +233,6 @@ test-selfhost-smoke: selfhost
|
|||||||
@echo "=== Selfhost smoke (move_field + multi-file #line) ==="
|
@echo "=== Selfhost smoke (move_field + multi-file #line) ==="
|
||||||
@chmod +x tools/smoke_selfhost.sh
|
@chmod +x tools/smoke_selfhost.sh
|
||||||
@tools/smoke_selfhost.sh
|
@tools/smoke_selfhost.sh
|
||||||
|
@echo "=== Graft / quote hygiene smoke ==="
|
||||||
|
@chmod +x tools/smoke_graft_hygiene.sh
|
||||||
|
@tools/smoke_graft_hygiene.sh
|
||||||
|
|||||||
+19
-5
@@ -1,7 +1,7 @@
|
|||||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-19
|
> **Дата:** 2026-07-19
|
||||||
> **Текущо:** v0.5.x — **LSP 0.15 type hierarchy**, CI `make test`, fixed-point
|
> **Текущо:** v0.5.x — quote/graft hygiene, LSP 0.15, CI, fixed-point
|
||||||
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -850,9 +850,23 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Сесия 55 (macro / quote hygiene foundation)
|
||||||
|
|
||||||
|
1. **AST graft API** (force overwrite): `Ast_GraftExpr/Stmt/Block/PatternFile`
|
||||||
|
2. **Clone**: `Ast_CloneExpr/Stmt/Block/Pattern` (+ list / match arms)
|
||||||
|
3. **Quote policies**:
|
||||||
|
- `Ast_QuoteDefSite` — clone, keep `sourceFile` (macro body / template)
|
||||||
|
- `Ast_QuoteCallSite` / `Ast_QuoteStmtCallSite` — clone + graft call-site path
|
||||||
|
4. **Helpers**: `Ast_SetExprLoc` / `Ast_ExprSourceFile`
|
||||||
|
5. **HIR**: `Lcx_GraftSourceFile`; mono instances force-graft `genDecl.sourceFile`
|
||||||
|
6. Smoke: `tools/smoke_graft_hygiene.sh` (Array mono → lib; Main no leak)
|
||||||
|
7. Wired into `make test-selfhost-smoke`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Следващи стъпки
|
## Следващи стъпки
|
||||||
|
|
||||||
1. Macro / quote hygiene using Expr.sourceFile grafts
|
1. Parenthesize binary ops in CBE for full C precedence safety
|
||||||
2. Parenthesize binary ops in CBE for full C precedence safety
|
2. CI matrix (macOS) or split jobs for faster PR feedback
|
||||||
3. CI matrix (macOS) or split jobs for faster PR feedback
|
3. Type hierarchy for multi-file closed docs without open (workspace type index)
|
||||||
4. Type hierarchy for multi-file closed docs without open (workspace type index)
|
4. User-facing `macro!` / `quote` syntax on top of graft/clone
|
||||||
|
|||||||
+255
@@ -518,6 +518,261 @@ module Ast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Macro / quote hygiene — graft sourceFile onto AST trees
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Stamp* = fill empty only (merge file paths; keep pre-set macro grafts)
|
||||||
|
// Graft* = force overwrite (call-site attribution after expansion)
|
||||||
|
// Clone* = deep copy for templates
|
||||||
|
// Quote* = clone + hygiene policy (def-site keep file | call-site graft)
|
||||||
|
|
||||||
|
/// Force-set sourceFile on an expression tree (overwrites existing).
|
||||||
|
func Ast_GraftExprFile(e: *Expr, path: String) {
|
||||||
|
if e == null as *Expr { return; }
|
||||||
|
if path == null as String || String_Eq(path, "") { return; }
|
||||||
|
e.sourceFile = path;
|
||||||
|
Ast_GraftExprFile(e.child1, path);
|
||||||
|
Ast_GraftExprFile(e.child2, path);
|
||||||
|
Ast_GraftExprFile(e.child3, path);
|
||||||
|
if e.refBlock != null as *Block {
|
||||||
|
Ast_GraftBlockFile(e.refBlock, path);
|
||||||
|
}
|
||||||
|
var args: *ExprList = e.callArgs;
|
||||||
|
while args != null as *ExprList {
|
||||||
|
Ast_GraftExprFile(args.expr, path);
|
||||||
|
args = args.next;
|
||||||
|
}
|
||||||
|
var arm: *MatchArm = e.matchArms;
|
||||||
|
while arm != null as *MatchArm {
|
||||||
|
Ast_GraftExprFile(arm.body, path);
|
||||||
|
if arm.pattern != null as *Pattern {
|
||||||
|
Ast_GraftPatternFile(arm.pattern, path);
|
||||||
|
}
|
||||||
|
arm = arm.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_GraftPatternFile(pat: *Pattern, path: String) {
|
||||||
|
if pat == null as *Pattern { return; }
|
||||||
|
if path == null as String || String_Eq(path, "") { return; }
|
||||||
|
if pat.patGuardExpr != null as *Expr {
|
||||||
|
Ast_GraftExprFile(pat.patGuardExpr, path);
|
||||||
|
}
|
||||||
|
Ast_GraftPatternFile(pat.patChild1, path);
|
||||||
|
Ast_GraftPatternFile(pat.patChild2, path);
|
||||||
|
Ast_GraftPatternFile(pat.patArgs, path);
|
||||||
|
Ast_GraftPatternFile(pat.patNext, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_GraftBlockFile(b: *Block, path: String) {
|
||||||
|
if b == null as *Block { return; }
|
||||||
|
if path == null as String || String_Eq(path, "") { return; }
|
||||||
|
b.sourceFile = path;
|
||||||
|
var s: *Stmt = b.firstStmt;
|
||||||
|
while s != null as *Stmt {
|
||||||
|
Ast_GraftStmtFile(s, path);
|
||||||
|
s = s.nextStmt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_GraftStmtFile(s: *Stmt, path: String) {
|
||||||
|
if s == null as *Stmt { return; }
|
||||||
|
if path == null as String || String_Eq(path, "") { return; }
|
||||||
|
s.sourceFile = path;
|
||||||
|
Ast_GraftExprFile(s.child1, path);
|
||||||
|
Ast_GraftExprFile(s.child2, path);
|
||||||
|
Ast_GraftExprFile(s.child3, path);
|
||||||
|
if s.refStmtBlock != null as *Block {
|
||||||
|
Ast_GraftBlockFile(s.refStmtBlock, path);
|
||||||
|
}
|
||||||
|
if s.refStmtElse != null as *Block {
|
||||||
|
Ast_GraftBlockFile(s.refStmtElse, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set line/column/sourceFile on a single node (no recurse).
|
||||||
|
func Ast_SetExprLoc(e: *Expr, line: uint32, col: uint32, path: String) {
|
||||||
|
if e == null as *Expr { return; }
|
||||||
|
e.line = line;
|
||||||
|
e.column = col;
|
||||||
|
if path != null as String && !String_Eq(path, "") {
|
||||||
|
e.sourceFile = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_SetStmtLoc(s: *Stmt, line: uint32, col: uint32, path: String) {
|
||||||
|
if s == null as *Stmt { return; }
|
||||||
|
s.line = line;
|
||||||
|
s.column = col;
|
||||||
|
if path != null as String && !String_Eq(path, "") {
|
||||||
|
s.sourceFile = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Effective source path for #line / diagnostics (empty if unknown).
|
||||||
|
func Ast_ExprSourceFile(e: *Expr) -> String {
|
||||||
|
if e == null as *Expr { return ""; }
|
||||||
|
if e.sourceFile == null as String { return ""; }
|
||||||
|
return e.sourceFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep clone for quote / macro expansion templates (shares TypeExpr/Decl pointers).
|
||||||
|
func Ast_ClonePattern(pat: *Pattern) -> *Pattern {
|
||||||
|
if pat == null as *Pattern { return null as *Pattern; }
|
||||||
|
let n: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
|
||||||
|
n.kind = pat.kind;
|
||||||
|
n.line = pat.line;
|
||||||
|
n.column = pat.column;
|
||||||
|
n.patIdent = pat.patIdent;
|
||||||
|
n.patLitKind = pat.patLitKind;
|
||||||
|
n.patLitText = pat.patLitText;
|
||||||
|
n.patRangeInclusive = pat.patRangeInclusive;
|
||||||
|
n.patEnumPath = pat.patEnumPath;
|
||||||
|
n.patStructName = pat.patStructName;
|
||||||
|
n.patFieldName = pat.patFieldName;
|
||||||
|
n.patChild1 = Ast_ClonePattern(pat.patChild1);
|
||||||
|
n.patChild2 = Ast_ClonePattern(pat.patChild2);
|
||||||
|
n.patArgs = Ast_ClonePattern(pat.patArgs);
|
||||||
|
n.patNext = Ast_ClonePattern(pat.patNext);
|
||||||
|
n.patGuardExpr = Ast_CloneExpr(pat.patGuardExpr);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_CloneExprList(list: *ExprList) -> *ExprList {
|
||||||
|
if list == null as *ExprList { return null as *ExprList; }
|
||||||
|
let n: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||||
|
n.expr = Ast_CloneExpr(list.expr);
|
||||||
|
n.next = Ast_CloneExprList(list.next);
|
||||||
|
n.argName = list.argName;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_CloneMatchArm(arm: *MatchArm) -> *MatchArm {
|
||||||
|
if arm == null as *MatchArm { return null as *MatchArm; }
|
||||||
|
let n: *MatchArm = bux_alloc(sizeof(MatchArm)) as *MatchArm;
|
||||||
|
n.line = arm.line;
|
||||||
|
n.column = arm.column;
|
||||||
|
n.pattern = Ast_ClonePattern(arm.pattern);
|
||||||
|
n.body = Ast_CloneExpr(arm.body);
|
||||||
|
n.next = Ast_CloneMatchArm(arm.next);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_CloneExpr(e: *Expr) -> *Expr {
|
||||||
|
if e == null as *Expr { return null as *Expr; }
|
||||||
|
let n: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
|
||||||
|
n.kind = e.kind;
|
||||||
|
n.line = e.line;
|
||||||
|
n.column = e.column;
|
||||||
|
n.sourceFile = e.sourceFile;
|
||||||
|
n.strValue = e.strValue;
|
||||||
|
n.intValue = e.intValue;
|
||||||
|
n.boolValue = e.boolValue;
|
||||||
|
n.tokKind = e.tokKind;
|
||||||
|
n.tokText = e.tokText;
|
||||||
|
n.child1 = Ast_CloneExpr(e.child1);
|
||||||
|
n.child2 = Ast_CloneExpr(e.child2);
|
||||||
|
n.child3 = Ast_CloneExpr(e.child3);
|
||||||
|
n.refType = e.refType; // share TypeExpr
|
||||||
|
n.refBlock = Ast_CloneBlock(e.refBlock);
|
||||||
|
n.genericCallee = e.genericCallee;
|
||||||
|
n.genericTypeArg0 = e.genericTypeArg0;
|
||||||
|
n.genericTypeArg1 = e.genericTypeArg1;
|
||||||
|
n.genericTypeArgCount = e.genericTypeArgCount;
|
||||||
|
n.structName = e.structName;
|
||||||
|
n.structFieldCount = e.structFieldCount;
|
||||||
|
n.closureParams = e.closureParams; // share param decl
|
||||||
|
n.captureCount = e.captureCount;
|
||||||
|
n.captureName0 = e.captureName0;
|
||||||
|
n.captureName1 = e.captureName1;
|
||||||
|
n.captureName2 = e.captureName2;
|
||||||
|
n.captureName3 = e.captureName3;
|
||||||
|
n.captureName4 = e.captureName4;
|
||||||
|
n.captureName5 = e.captureName5;
|
||||||
|
n.captureName6 = e.captureName6;
|
||||||
|
n.captureName7 = e.captureName7;
|
||||||
|
n.captureType0 = e.captureType0;
|
||||||
|
n.captureType1 = e.captureType1;
|
||||||
|
n.captureType2 = e.captureType2;
|
||||||
|
n.captureType3 = e.captureType3;
|
||||||
|
n.captureType4 = e.captureType4;
|
||||||
|
n.captureType5 = e.captureType5;
|
||||||
|
n.captureType6 = e.captureType6;
|
||||||
|
n.captureType7 = e.captureType7;
|
||||||
|
n.callArgs = Ast_CloneExprList(e.callArgs);
|
||||||
|
n.callArgCount = e.callArgCount;
|
||||||
|
n.matchArms = Ast_CloneMatchArm(e.matchArms);
|
||||||
|
n.matchArmCount = e.matchArmCount;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_CloneStmt(s: *Stmt) -> *Stmt {
|
||||||
|
if s == null as *Stmt { return null as *Stmt; }
|
||||||
|
let n: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
||||||
|
n.kind = s.kind;
|
||||||
|
n.line = s.line;
|
||||||
|
n.column = s.column;
|
||||||
|
n.sourceFile = s.sourceFile;
|
||||||
|
n.strValue = s.strValue;
|
||||||
|
n.boolValue = s.boolValue;
|
||||||
|
n.child1 = Ast_CloneExpr(s.child1);
|
||||||
|
n.child2 = Ast_CloneExpr(s.child2);
|
||||||
|
n.child3 = Ast_CloneExpr(s.child3);
|
||||||
|
n.refStmtType = s.refStmtType;
|
||||||
|
n.refStmtPattern = Ast_ClonePattern(s.refStmtPattern);
|
||||||
|
n.refStmtDecl = s.refStmtDecl;
|
||||||
|
n.refStmtBlock = Ast_CloneBlock(s.refStmtBlock);
|
||||||
|
n.refStmtElse = Ast_CloneBlock(s.refStmtElse);
|
||||||
|
n.elseIfCount = s.elseIfCount;
|
||||||
|
n.nextStmt = Ast_CloneStmt(s.nextStmt);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ast_CloneBlock(b: *Block) -> *Block {
|
||||||
|
if b == null as *Block { return null as *Block; }
|
||||||
|
let n: *Block = bux_alloc(sizeof(Block)) as *Block;
|
||||||
|
n.line = b.line;
|
||||||
|
n.column = b.column;
|
||||||
|
n.sourceFile = b.sourceFile;
|
||||||
|
n.stmtCount = b.stmtCount;
|
||||||
|
n.firstStmt = Ast_CloneStmt(b.firstStmt);
|
||||||
|
// Rebuild lastStmt
|
||||||
|
var cur: *Stmt = n.firstStmt;
|
||||||
|
var last: *Stmt = null as *Stmt;
|
||||||
|
while cur != null as *Stmt {
|
||||||
|
last = cur;
|
||||||
|
cur = cur.nextStmt;
|
||||||
|
}
|
||||||
|
n.lastStmt = last;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Definition-site quote: clone template, keep sourceFile (macro body locations).
|
||||||
|
func Ast_QuoteDefSite(e: *Expr) -> *Expr {
|
||||||
|
return Ast_CloneExpr(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call-site quote: clone template, graft call-site file and span for #line / diags.
|
||||||
|
func Ast_QuoteCallSite(e: *Expr, siteFile: String, siteLine: uint32, siteCol: uint32) -> *Expr {
|
||||||
|
let n: *Expr = Ast_CloneExpr(e);
|
||||||
|
if n == null as *Expr { return null as *Expr; }
|
||||||
|
Ast_GraftExprFile(n, siteFile);
|
||||||
|
// Root span is the invocation; children keep structure but share site file
|
||||||
|
n.line = siteLine;
|
||||||
|
n.column = siteCol;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply call-site graft to a whole statement tree (for stmt-producing macros).
|
||||||
|
func Ast_QuoteStmtCallSite(s: *Stmt, siteFile: String, siteLine: uint32, siteCol: uint32) -> *Stmt {
|
||||||
|
let n: *Stmt = Ast_CloneStmt(s);
|
||||||
|
if n == null as *Stmt { return null as *Stmt; }
|
||||||
|
Ast_GraftStmtFile(n, siteFile);
|
||||||
|
n.line = siteLine;
|
||||||
|
n.column = siteCol;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
|
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
|
||||||
return Decl { kind: kind, line: line, column: col, isPublic: false,
|
return Decl { kind: kind, line: line, column: col, isPublic: false,
|
||||||
sourceFile: "",
|
sourceFile: "",
|
||||||
|
|||||||
@@ -81,6 +81,26 @@ module HirLower {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Force-set sourceFile on HIR tree (definition-site hygiene for mono / macro body).
|
||||||
|
func Lcx_GraftSourceFile(node: *HirNode, file: String) {
|
||||||
|
if node == null as *HirNode { return; }
|
||||||
|
if file == null as String || String_Eq(file, "") { return; }
|
||||||
|
node.sourceFile = file;
|
||||||
|
Lcx_GraftSourceFile(node.child1, file);
|
||||||
|
Lcx_GraftSourceFile(node.child2, file);
|
||||||
|
Lcx_GraftSourceFile(node.child3, file);
|
||||||
|
if (node.kind == hCall || node.kind == hCallIndirect) && node.extraData != null as *void {
|
||||||
|
var cur: *HirArgList = node.extraData as *HirArgList;
|
||||||
|
while cur != null as *HirArgList {
|
||||||
|
Lcx_GraftSourceFile(cur.node, file);
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.kind == hIf && node.extraData != null as *void {
|
||||||
|
Lcx_GraftSourceFile(node.extraData as *HirNode, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Prefer Expr/Stmt.sourceFile (macro / cross-file graft); else enclosing Decl path.
|
/// Prefer Expr/Stmt.sourceFile (macro / cross-file graft); else enclosing Decl path.
|
||||||
func Lcx_SourceFileFor(ctx: *LowerCtx, nodeFile: String) -> String {
|
func Lcx_SourceFileFor(ctx: *LowerCtx, nodeFile: String) -> String {
|
||||||
if nodeFile != null as String && !String_Eq(nodeFile, "") {
|
if nodeFile != null as String && !String_Eq(nodeFile, "") {
|
||||||
@@ -526,6 +546,12 @@ module HirLower {
|
|||||||
// Lower the generic function with substitution active
|
// Lower the generic function with substitution active
|
||||||
let f: *HirFunc = Lcx_LowerFunc(ctx, genDecl);
|
let f: *HirFunc = Lcx_LowerFunc(ctx, genDecl);
|
||||||
f.name = mangled;
|
f.name = mangled;
|
||||||
|
// Definition-site hygiene: mono body always maps to the generic's source
|
||||||
|
// file (not the call-site module), even if synthetic nodes lacked a path.
|
||||||
|
if f.body != null as *HirNode && !String_Eq(genDecl.sourceFile, "") {
|
||||||
|
f.sourceFile = genDecl.sourceFile;
|
||||||
|
Lcx_GraftSourceFile(f.body, genDecl.sourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
// Add to module
|
// Add to module
|
||||||
ctx.funcs[ctx.funcCount] = *f;
|
ctx.funcs[ctx.funcCount] = *f;
|
||||||
|
|||||||
Executable
+97
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Smoke: macro/quote sourceFile hygiene foundation
|
||||||
|
# - Mono instances from lib keep definition-site #line (Array.bux / etc.)
|
||||||
|
# - User Main keeps Main.bux (no leak of lib paths into Main body)
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
BUXC2="$ROOT/build/selfhost/build/buxc2"
|
||||||
|
export BUX_STDLIB="$ROOT/lib"
|
||||||
|
unset BUX_DEBUG_FILE || true
|
||||||
|
unset BUX_NO_LINE || true
|
||||||
|
|
||||||
|
if [[ ! -x "$BUXC2" ]]; then
|
||||||
|
(cd "$ROOT" && make selfhost >/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
mkdir -p "$TMP/src"
|
||||||
|
cp -a "$ROOT/rt" "$TMP/"
|
||||||
|
|
||||||
|
cat > "$TMP/bux.toml" <<'EOF'
|
||||||
|
[Package]
|
||||||
|
Name = "graft_hygiene"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Main uses generic Array from stdlib — mono body must stay lib/*.bux (def site)
|
||||||
|
cat > "$TMP/src/Main.bux" <<'EOF'
|
||||||
|
import Std::Io::{PrintLine};
|
||||||
|
import Std::String::{String_FromInt};
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let a: Array<int> = Array_New<int>(4);
|
||||||
|
Array_Push<int>(&a, 1);
|
||||||
|
Array_Push<int>(&a, 2);
|
||||||
|
let n: int = Array_Len<int>(&a) as int;
|
||||||
|
PrintLine(String_FromInt(n as int64));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
(cd "$TMP" && "$BUXC2" project .)
|
||||||
|
out=$("$TMP/build/graft_hygiene")
|
||||||
|
echo "$out" | tee "$TMP/run.out"
|
||||||
|
grep -q '2' "$TMP/run.out"
|
||||||
|
|
||||||
|
MAIN_C="$TMP/build/main.c"
|
||||||
|
test -f "$MAIN_C"
|
||||||
|
|
||||||
|
# Array mono helpers must map to lib (definition site)
|
||||||
|
if ! grep -qE '#line [0-9]+ ".*lib/.*Array\.bux"|#line [0-9]+ ".*lib/Array\.bux"' "$MAIN_C"; then
|
||||||
|
# Also accept any lib path with Array mono function nearby
|
||||||
|
if ! grep -qE '#line [0-9]+ ".*lib/.*\.bux"' "$MAIN_C"; then
|
||||||
|
echo "error: no #line for stdlib (definition-site mono hygiene)" >&2
|
||||||
|
grep -E '^#line ' "$MAIN_C" | head -20
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo " def-site mono: PASS (lib #line present)"
|
||||||
|
|
||||||
|
# Main function body must not claim Array.bux
|
||||||
|
main_body=$(awk '
|
||||||
|
/#line 1 ".*Main\.bux"/ { grab=1 }
|
||||||
|
grab { print }
|
||||||
|
grab && /^}/ { exit }
|
||||||
|
' "$MAIN_C")
|
||||||
|
if echo "$main_body" | grep -qE '#line [0-9]+ ".*Array\.bux"'; then
|
||||||
|
echo "error: Main body has Array.bux #line (call-site leak)" >&2
|
||||||
|
echo "$main_body" | grep '#line '
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! echo "$main_body" | grep -vE '#line 1 "' | grep -qE '#line [0-9]+ ".*Main\.bux"'; then
|
||||||
|
echo "error: Main body missing statement #line Main.bux" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " call-site Main: PASS (no Array.bux leak)"
|
||||||
|
|
||||||
|
# Array_Push_int region: first #line after its definition should not be Main.bux
|
||||||
|
if grep -q 'Array_Push_int' "$MAIN_C"; then
|
||||||
|
# Grab ~30 lines after the Array_Push_int function start
|
||||||
|
push_region=$(grep -n 'Array_Push_int(' "$MAIN_C" | head -1 | cut -d: -f1)
|
||||||
|
if [[ -n "$push_region" ]]; then
|
||||||
|
region=$(sed -n "${push_region},$((push_region + 35))p" "$MAIN_C")
|
||||||
|
if echo "$region" | grep -vE '#line 1 ' | grep -qE '#line [0-9]+ ".*Main\.bux"'; then
|
||||||
|
echo "error: Array_Push_int region maps statements to Main.bux" >&2
|
||||||
|
echo "$region" | grep '#line ' || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo " Array_Push_int hygiene: PASS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PASS: graft/quote hygiene (def-site mono + call-site Main isolation)"
|
||||||
Reference in New Issue
Block a user