feat: field-move Drop (partial/nested/ptr), stmt/pat macros, Windows runtime
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
Sessions 70–74: partialMovedPaths + ptrAliases in bootstrap/selfhost CBE, remaining drops after field moves, macro stmt/pat fragments, runtime_win.c and MinGW hello CI, examples + drop-move smoke coverage, QUALITY_PLAN update.
This commit is contained in:
@@ -310,14 +310,12 @@ jobs:
|
||||
make test-unit
|
||||
make test-examples-smoke
|
||||
|
||||
# ── Windows smoke (bootstrap + pure Nim unit tests) ─────────────────────
|
||||
# Full bux→C examples need POSIX runtime (ucontext / pthread sockets in
|
||||
# rt/runtime.c) — not ported yet. This job still catches Nim/bootstrap
|
||||
# regressions on Windows (prebuilt Nim zip = fast install).
|
||||
# ── Windows smoke (bootstrap + unit + MinGW hello) ──────────────────────
|
||||
# Uses rt/runtime_win.c (no pthread/OpenSSL). Full POSIX runtime is Unix-only.
|
||||
windows:
|
||||
name: windows smoke
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 35
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -345,6 +343,26 @@ jobs:
|
||||
echo "$PWD/${{ env.NIM_INSTALL_DIR }}/bin" >> "$GITHUB_PATH"
|
||||
echo "$HOME/.nimble/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install MinGW (gcc)
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
update: false
|
||||
install: mingw-w64-x86_64-gcc
|
||||
path-type: inherit
|
||||
|
||||
- name: Add MinGW64 to PATH
|
||||
run: |
|
||||
# setup-msys2 installs under C:/msys64 by default
|
||||
for d in /c/msys64/mingw64/bin /mingw64/bin "$HOME/msys64/mingw64/bin"; do
|
||||
if [[ -x "$d/gcc.exe" || -x "$d/gcc" ]]; then
|
||||
echo "$d" >> "$GITHUB_PATH"
|
||||
export PATH="$d:$PATH"
|
||||
break
|
||||
fi
|
||||
done
|
||||
gcc --version | head -1
|
||||
|
||||
- name: Cache nimcache (bootstrap + unit)
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -357,6 +375,8 @@ jobs:
|
||||
run: |
|
||||
set -e
|
||||
nim -v
|
||||
# MinGW gcc on PATH (from setup-msys2)
|
||||
gcc --version | head -1
|
||||
# Windows produces buxc.exe; keep name predictable for the smoke steps
|
||||
nim c --nimcache:nimcache -o:buxc.exe -d:release --opt:size bootstrap/main.nim
|
||||
./buxc.exe --version
|
||||
@@ -380,7 +400,17 @@ jobs:
|
||||
rm -rf _test_tmp_pkg
|
||||
./buxc.exe new _test_tmp_pkg
|
||||
./buxc.exe --version
|
||||
echo "windows smoke: PASS (bootstrap + unit + CLI)"
|
||||
echo "unit+CLI: PASS"
|
||||
|
||||
- name: hello smoke (MinGW + runtime_win)
|
||||
run: |
|
||||
set -e
|
||||
unset BUX_DEBUG_FILE || true
|
||||
export BUX_STDLIB="$PWD/lib"
|
||||
# bootstrap on Windows always picks runtime_win.c
|
||||
chmod +x tools/smoke_windows_hello.sh
|
||||
tools/smoke_windows_hello.sh
|
||||
echo "windows smoke: PASS (bootstrap + unit + CLI + hello)"
|
||||
|
||||
# ── Single required status for branch protection ────────────────────────
|
||||
ci-gate:
|
||||
|
||||
@@ -5,10 +5,10 @@ BUILD_DIR := build
|
||||
# Project-local nimcache so CI can cache compiles (default is ~/.cache/nim).
|
||||
NIMFLAGS ?= --nimcache:nimcache
|
||||
|
||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ownership_checked ownership_release drop_early_return lifetime_elision ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic generic_infer_hof struct_tuple_pat match_block nested_patterns match_guards pattern_shadow move_field move_field_partial c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic
|
||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ownership_checked ownership_release drop_early_return lifetime_elision ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic generic_infer_hof struct_tuple_pat match_block nested_patterns match_guards pattern_shadow move_field move_field_partial move_field_remaining move_field_nested move_field_ptr c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat
|
||||
|
||||
# Platform smoke (macOS CI): full EXAMPLES still runs on Linux.
|
||||
EXAMPLES_SMOKE := hello ownership ownership_release strings map move_field move_field_partial c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic
|
||||
EXAMPLES_SMOKE := hello ownership ownership_release strings map move_field move_field_partial move_field_remaining move_field_nested move_field_ptr c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat
|
||||
|
||||
.PHONY: all build dev debug test clean clean-all test-examples test-examples-smoke selfhost test-golden test-errors test-stdlib selfhost-loop lsp fmt-check docs bench test-apps test-dwarf test-selfhost-smoke test-unit ensure-buxc
|
||||
|
||||
|
||||
@@ -134,6 +134,8 @@ type
|
||||
ekStringInterp
|
||||
ekClosure
|
||||
ekMacroCall ## name!(args) — expanded before sema
|
||||
ekMacroStmt ## `$s:stmt` arg wrapper (expand only)
|
||||
ekMacroPat ## `$p:pat` arg wrapper (expand only)
|
||||
|
||||
MatchArm* = object
|
||||
loc*: SourceLocation
|
||||
@@ -243,6 +245,12 @@ type
|
||||
## Group lengths for multi-rep: `m!(1,2; 3,4)` → @[2, 2].
|
||||
## Empty means a single group of all args.
|
||||
exprMacroGroupLens*: seq[int]
|
||||
of ekMacroStmt:
|
||||
## Statement fragment argument (`$s:stmt`) — only during expand
|
||||
exprMacroStmt*: Stmt
|
||||
of ekMacroPat:
|
||||
## Pattern fragment argument (`$p:pat`) — only during expand
|
||||
exprMacroPat*: Pattern
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Statements
|
||||
@@ -375,6 +383,8 @@ type
|
||||
mfkTt ## token-tree (MVP: same as expr)
|
||||
mfkLiteral ## int/float/string/char/bool literal only
|
||||
mfkBlock ## block expression `{ … }`
|
||||
mfkStmt ## one statement (let/if/… or expression-stmt)
|
||||
mfkPat ## match/let pattern
|
||||
|
||||
MacroFragment* = object
|
||||
name*: string ## primary / first name (compat)
|
||||
|
||||
+34
-7
@@ -805,11 +805,16 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
return 1
|
||||
|
||||
let baseDir = stdlibDir.parentDir()
|
||||
let runtimeSrc = baseDir / "rt" / "runtime.c"
|
||||
# Windows / BUX_RUNTIME=win → minimal runtime (no pthread/OpenSSL).
|
||||
# Full POSIX runtime is rt/runtime.c.
|
||||
let forceWinRt = getEnv("BUX_RUNTIME") == "win" or getEnv("BUX_RUNTIME") == "windows"
|
||||
let useWinRt = forceWinRt or (when defined(windows): true else: false)
|
||||
let runtimeName = if useWinRt: "runtime_win.c" else: "runtime.c"
|
||||
let runtimeSrc = baseDir / "rt" / runtimeName
|
||||
if fileExists(runtimeSrc):
|
||||
copyFile(runtimeSrc, runtimeDst)
|
||||
else:
|
||||
printError("runtime.c not found in rt/", useColor)
|
||||
printError(&"{runtimeName} not found in rt/", useColor)
|
||||
return 1
|
||||
|
||||
let ioSrc = baseDir / "rt" / "io.c"
|
||||
@@ -821,13 +826,31 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
|
||||
# Compile with cc — debug default (-O0 -g) or --release (-O2)
|
||||
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
|
||||
let outputFile = buildDir / outputName
|
||||
let exeSuffix = when defined(windows): ".exe" else: ""
|
||||
let outputFile = buildDir / (outputName & exeSuffix)
|
||||
let optFlags = if opts.release: "-O2 -DNDEBUG" else: "-O0 -g"
|
||||
let extraCflags = getEnv("BUX_CFLAGS")
|
||||
let cflags = if extraCflags.len > 0: optFlags & " " & extraCflags else: optFlags
|
||||
# --build-id is GNU ld only (breaks Apple ld). Reproducible selfhost-loop uses Linux CI.
|
||||
let ldStable = when defined(linux): " -Wl,--build-id=none" else: ""
|
||||
let ccCmd = &"cc {cflags} -pthread{ldStable} -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
|
||||
# Host C toolchain + link flags
|
||||
let envCc = getEnv("BUX_CC")
|
||||
let ccBin =
|
||||
if envCc.len > 0: envCc
|
||||
else:
|
||||
when defined(windows): "gcc"
|
||||
else: "cc"
|
||||
let ldStable =
|
||||
when defined(linux):
|
||||
if useWinRt: "" else: " -Wl,--build-id=none"
|
||||
else:
|
||||
""
|
||||
# Note: -l libs must come *after* .c/.o inputs (GNU ld left-to-right).
|
||||
let (hostCflags, hostLibs) =
|
||||
if useWinRt:
|
||||
# gc-sections drops mono stdlib that is never called (crypto/tasks, …)
|
||||
(" -ffunction-sections -fdata-sections", " -Wl,--gc-sections -lm")
|
||||
else:
|
||||
(" -pthread" & ldStable, " -lm -lcrypto")
|
||||
let ccCmd = &"{ccBin} {cflags}{hostCflags} -o {outputFile} {cFile} {runtimeDst} {ioDst}{hostLibs} 2>&1"
|
||||
if opts.verbose:
|
||||
printInfo(&"running: {ccCmd}", useColor)
|
||||
let (output, exitCode) = execCmdEx(ccCmd)
|
||||
@@ -848,7 +871,11 @@ proc cmdRun*(args: seq[string], opts: GlobalOptions): int =
|
||||
return buildRes
|
||||
let man = loadManifest(root / "bux.toml")
|
||||
let outputName = if man.name != "": man.name else: "bux_out"
|
||||
let outputFile = root / "build" / outputName
|
||||
let exeSuffix = when defined(windows): ".exe" else: ""
|
||||
var outputFile = root / "build" / (outputName & exeSuffix)
|
||||
if not fileExists(outputFile):
|
||||
# Fallback without suffix (cross-env / older builds)
|
||||
outputFile = root / "build" / outputName
|
||||
if not fileExists(outputFile):
|
||||
printError("executable not found after build", useColor)
|
||||
return 1
|
||||
|
||||
+223
-16
@@ -38,6 +38,14 @@ type
|
||||
## Locals whose value was moved into another owner (struct field, let, return).
|
||||
## Auto-Drop is skipped for these (session 37 — field-move ownership).
|
||||
movedOutLocals*: HashSet[string]
|
||||
## Partial field moves: local → dotted paths moved out by value
|
||||
## (e.g. "items", "inner.items" for nested `a.b.c` — session 70/73).
|
||||
## When parent Type_Drop is skipped, remaining droppable fields still Drop.
|
||||
## Whole-local moves leave this empty → full skip, no field drops.
|
||||
partialMovedFields*: Table[string, HashSet[string]]
|
||||
## Pointer aliases: local pointer name → pointee local (`p = &bag` → p→bag).
|
||||
## Used so `p.items` / `(*p).items` mark the owner local (session 74).
|
||||
ptrAliases*: Table[string, string]
|
||||
|
||||
proc freshName(ctx: var LowerCtx): string =
|
||||
inc ctx.varCounter
|
||||
@@ -85,23 +93,80 @@ proc markMovedOutLocal(ctx: var LowerCtx, name: string) =
|
||||
if name.len > 0 and ctx.hasPendingDrop(name):
|
||||
ctx.movedOutLocals.incl(name)
|
||||
|
||||
# Forward decls (used by markMovedOutFromAst before their full definitions)
|
||||
# Forward decls (used by markMovedOutFromAst / remainingFieldDrops before defs)
|
||||
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type
|
||||
proc autoDropFuncName(ctx: var LowerCtx, ty: Type): string
|
||||
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
|
||||
proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type]): Type
|
||||
|
||||
proc resolvePtrAlias(ctx: LowerCtx, name: string): string =
|
||||
## Follow `p → bag` aliases (depth-limited).
|
||||
result = name
|
||||
var guard = 0
|
||||
while result.len > 0 and ctx.ptrAliases.hasKey(result) and guard < 8:
|
||||
result = ctx.ptrAliases[result]
|
||||
inc guard
|
||||
|
||||
proc fieldPathFromAst(ctx: LowerCtx, expr: Expr): tuple[base: string, path: seq[string]] =
|
||||
## Walk `a.b.c` / `(*p).b.c` / `p.b` (auto-deref) → owner local + path.
|
||||
## Resolves pointer aliases (`p = &bag` → owner is `bag`).
|
||||
result = ("", @[])
|
||||
if expr == nil: return
|
||||
var path: seq[string] = @[]
|
||||
var e = expr
|
||||
while e != nil and e.kind == ekField:
|
||||
path.insert(e.exprFieldName, 0)
|
||||
e = e.exprFieldObj
|
||||
# Peel explicit derefs: (*p).x or (**pp).x
|
||||
while e != nil and e.kind == ekUnary and e.exprUnaryOp == tkStar:
|
||||
e = e.exprUnaryOperand
|
||||
if e != nil and e.kind == ekIdent and e.exprIdent.len > 0 and path.len > 0:
|
||||
let owner = ctx.resolvePtrAlias(e.exprIdent)
|
||||
result = (owner, path)
|
||||
|
||||
proc pathKey(path: seq[string]): string =
|
||||
path.join(".")
|
||||
|
||||
proc recordPtrAliasFromAst(ctx: var LowerCtx, ptrName: string, init: Expr) =
|
||||
## If `init` is `&local` (possibly with paren/cast noise), record ptr→local.
|
||||
if ptrName.len == 0 or init == nil: return
|
||||
var e = init
|
||||
# Skip simple casts
|
||||
while e != nil and e.kind == ekCast:
|
||||
e = e.exprCastOperand
|
||||
if e != nil and e.kind == ekUnary and e.exprUnaryOp == tkAmp:
|
||||
var op = e.exprUnaryOperand
|
||||
while op != nil and op.kind == ekCast:
|
||||
op = op.exprCastOperand
|
||||
if op != nil and op.kind == ekIdent and op.exprIdent.len > 0:
|
||||
ctx.ptrAliases[ptrName] = op.exprIdent
|
||||
|
||||
proc markMovedOutFromAst(ctx: var LowerCtx, expr: Expr) =
|
||||
## Mark droppable locals used by-value in ownership-taking contexts.
|
||||
## Partial field moves: `return bag.items` / `let x = bag.items` mark `bag`
|
||||
## so auto-Drop of the parent is skipped — **only when the field type itself
|
||||
## is droppable** (not `return bag.tag` for an int field).
|
||||
## Partial field moves: `return bag.items` / `return outer.inner.items` /
|
||||
## `return p.items` (p = &bag) mark the **owner** local so auto-Drop of the
|
||||
## parent is skipped. Records dotted path so remaining fields still Drop
|
||||
## (sessions 70/73/74).
|
||||
if expr == nil: return
|
||||
case expr.kind
|
||||
of ekIdent:
|
||||
ctx.markMovedOutLocal(expr.exprIdent)
|
||||
ctx.markMovedOutLocal(ctx.resolvePtrAlias(expr.exprIdent))
|
||||
of ekField:
|
||||
let fieldTy = ctx.resolveExprType(expr)
|
||||
if ctx.autoDropFuncName(fieldTy).len > 0:
|
||||
ctx.markMovedOutFromAst(expr.exprFieldObj)
|
||||
let (base, path) = ctx.fieldPathFromAst(expr)
|
||||
if base.len > 0 and path.len > 0:
|
||||
if not ctx.partialMovedFields.hasKey(base):
|
||||
ctx.partialMovedFields[base] = initHashSet[string]()
|
||||
ctx.partialMovedFields[base].incl(pathKey(path))
|
||||
ctx.markMovedOutLocal(base)
|
||||
# Nested path recorded as a whole — do not recurse (would mis-mark intermediates)
|
||||
of ekUnary:
|
||||
# Moving `*p` by value (whole pointee) — mark owner local if known
|
||||
if expr.exprUnaryOp == tkStar and expr.exprUnaryOperand != nil and
|
||||
expr.exprUnaryOperand.kind == ekIdent:
|
||||
let owner = ctx.resolvePtrAlias(expr.exprUnaryOperand.exprIdent)
|
||||
ctx.markMovedOutLocal(owner)
|
||||
of ekStructInit:
|
||||
for f in expr.exprStructInitFields:
|
||||
ctx.markMovedOutFromAst(f.value)
|
||||
@@ -119,6 +184,136 @@ proc shouldSkipDrop(ctx: LowerCtx, dropNode: HirNode, skipName: string): bool =
|
||||
if target in ctx.movedOutLocals: return true
|
||||
false
|
||||
|
||||
proc structFieldsOf(ctx: var LowerCtx, te: TypeExpr, typeName: string): seq[tuple[name: string, typ: Type]] =
|
||||
## Resolve struct fields for a named / monomorphized type.
|
||||
result = @[]
|
||||
var declName = if te != nil: te.typeName else: ""
|
||||
if declName.len == 0: declName = typeName
|
||||
let sym = ctx.globalScope.lookup(declName)
|
||||
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct:
|
||||
for f in sym.decl.declStructFields:
|
||||
if f.ftype == nil: continue
|
||||
var fieldTy: Type
|
||||
if te != nil and te.typeArgs.len > 0 and ctx.genericStructs.hasKey(declName):
|
||||
var subst = initTable[string, Type]()
|
||||
let gdecl = ctx.genericStructs[declName]
|
||||
for j, tp in gdecl.declStructTypeParams:
|
||||
if j < te.typeArgs.len:
|
||||
subst[tp.name] = ctx.resolveTypeExpr(te.typeArgs[j])
|
||||
fieldTy = substituteType(ctx, f.ftype, subst)
|
||||
else:
|
||||
fieldTy = ctx.resolveTypeExpr(f.ftype)
|
||||
result.add((f.name, fieldTy))
|
||||
return
|
||||
if ctx.structInstMap.hasKey(typeName):
|
||||
for es in ctx.extraStructs:
|
||||
if es.name == typeName:
|
||||
for f in es.fields:
|
||||
result.add((f.name, f.typ))
|
||||
return
|
||||
# Also try mangled typeName as decl name
|
||||
let sym2 = ctx.globalScope.lookup(typeName)
|
||||
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkStruct:
|
||||
for f in sym2.decl.declStructFields:
|
||||
if f.ftype == nil: continue
|
||||
result.add((f.name, ctx.resolveTypeExpr(f.ftype)))
|
||||
|
||||
proc makeFieldPtrAt(ctx: var LowerCtx, base: HirNode, rootTe: TypeExpr,
|
||||
rootTypeName: string, path: seq[string], fieldTy: Type,
|
||||
loc: SourceLocation): HirNode =
|
||||
## `&(base.a.b)` with typed intermediate field accesses (needed by LIR/C).
|
||||
if path.len == 0:
|
||||
return hirUnary(tkAmp, base, makePointer(fieldTy), loc)
|
||||
if path.len == 1:
|
||||
return HirNode(kind: hFieldPtr, fieldPtrBase: base, fieldName: path[0],
|
||||
typ: makePointer(fieldTy), loc: loc)
|
||||
# Build typed prefix: base.a.b for path [a,b,c] → access a, then b; ptr on c
|
||||
var cur = base
|
||||
var curTe = rootTe
|
||||
var curTypeName = rootTypeName
|
||||
for i in 0 ..< path.len - 1:
|
||||
let fields = ctx.structFieldsOf(curTe, curTypeName)
|
||||
var nextTy: Type = makeUnknown()
|
||||
for f in fields:
|
||||
if f.name == path[i]:
|
||||
nextTy = f.typ
|
||||
break
|
||||
cur = HirNode(kind: hFieldAccess, fieldAccessBase: cur,
|
||||
fieldAccessName: path[i], typ: nextTy, loc: loc)
|
||||
if nextTy != nil and nextTy.kind == tkNamed:
|
||||
curTypeName = nextTy.name
|
||||
curTe = TypeExpr(kind: tekNamed, typeName: nextTy.name)
|
||||
else:
|
||||
curTe = nil
|
||||
curTypeName = ""
|
||||
return HirNode(kind: hFieldPtr, fieldPtrBase: cur, fieldName: path[^1],
|
||||
typ: makePointer(fieldTy), loc: loc)
|
||||
|
||||
proc remainingDropsAt(ctx: var LowerCtx, baseHir: HirNode, typeName: string,
|
||||
te: TypeExpr, prefix: seq[string],
|
||||
moved: HashSet[string], loc: SourceLocation,
|
||||
rootTe: TypeExpr, rootTypeName: string): seq[HirNode] =
|
||||
## Emit Drops for fields of `typeName` under `baseHir`+`prefix`, respecting
|
||||
## dotted moved paths (exact = fully moved; prefix = recurse nested).
|
||||
## `rootTe`/`rootTypeName` are the original local's type (for path typing).
|
||||
result = @[]
|
||||
let fields = ctx.structFieldsOf(te, typeName)
|
||||
for f in fields:
|
||||
var fpath = prefix
|
||||
fpath.add(f.name)
|
||||
let key = pathKey(fpath)
|
||||
# Fully moved this field
|
||||
if key in moved:
|
||||
continue
|
||||
# Nested partial: some path starts with key + "."
|
||||
var nestedMoved = false
|
||||
for m in moved:
|
||||
if m.startsWith(key & "."):
|
||||
nestedMoved = true
|
||||
break
|
||||
if nestedMoved:
|
||||
let fty = f.typ
|
||||
if fty == nil or fty.kind != tkNamed: continue
|
||||
var fte = TypeExpr(kind: tekNamed, typeName: fty.name)
|
||||
result.add(ctx.remainingDropsAt(baseHir, fty.name, fte, fpath, moved, loc,
|
||||
rootTe, rootTypeName))
|
||||
continue
|
||||
# Unrelated field — full Drop if droppable
|
||||
let dropFn = ctx.autoDropFuncName(f.typ)
|
||||
if dropFn.len == 0: continue
|
||||
let fieldPtr = ctx.makeFieldPtrAt(baseHir, rootTe, rootTypeName, fpath, f.typ, loc)
|
||||
result.add(hirCall(dropFn, @[fieldPtr], makeVoid(), loc))
|
||||
|
||||
proc remainingFieldDrops(ctx: var LowerCtx, localName: string, loc: SourceLocation): seq[HirNode] =
|
||||
## After a partial field move out of `localName`, Drop every *other* droppable
|
||||
## field (including nested remaining after `a.b.c` moves).
|
||||
result = @[]
|
||||
if localName.len == 0 or not ctx.partialMovedFields.hasKey(localName):
|
||||
return
|
||||
let moved = ctx.partialMovedFields[localName]
|
||||
if not ctx.varTypeExprs.hasKey(localName):
|
||||
return
|
||||
let te = ctx.varTypeExprs[localName]
|
||||
if te == nil or te.kind != tekNamed:
|
||||
return
|
||||
let localTy = ctx.resolveTypeExpr(te)
|
||||
if localTy == nil or localTy.kind != tkNamed:
|
||||
return
|
||||
let base = hirVar(localName, localTy, loc)
|
||||
result = ctx.remainingDropsAt(base, localTy.name, te, @[], moved, loc, te, localTy.name)
|
||||
|
||||
proc emitDropOrPartial(ctx: var LowerCtx, stmts: var seq[HirNode], dropNode: HirNode,
|
||||
skipName: string) =
|
||||
## Emit Type_Drop, or remaining field Drops after a partial move.
|
||||
if not ctx.shouldSkipDrop(dropNode, skipName):
|
||||
stmts.add(dropNode)
|
||||
return
|
||||
let target = dropTargetName(dropNode)
|
||||
if target.len > 0 and target in ctx.partialMovedFields:
|
||||
let loc = if dropNode != nil: dropNode.loc else: SourceLocation()
|
||||
for d in ctx.remainingFieldDrops(target, loc):
|
||||
stmts.add(d)
|
||||
|
||||
proc autoDropFuncName(ctx: var LowerCtx, ty: Type): string =
|
||||
## Return `Type_Drop` if this type should be auto-dropped, else "".
|
||||
## Also monomorphizes generic Drop/Free helpers for stdlib collections.
|
||||
@@ -195,8 +390,6 @@ proc patternLiteralNode(pat: Pattern, loc: SourceLocation): HirNode =
|
||||
return nil
|
||||
return hirLit(pat.patLit, litTokenType(pat.patLit), loc)
|
||||
|
||||
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
|
||||
|
||||
proc matchPatternCond(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
||||
subjectEnumName: string, subjectHasData: bool,
|
||||
loc: SourceLocation): HirNode =
|
||||
@@ -527,6 +720,8 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
||||
result.patternBoundNames = initHashSet[string]()
|
||||
result.patternRenames = initTable[string, string]()
|
||||
result.movedOutLocals = initHashSet[string]()
|
||||
result.partialMovedFields = initTable[string, HashSet[string]]()
|
||||
result.ptrAliases = initTable[string, string]()
|
||||
|
||||
proc sanitizeFatPart(s: string): string =
|
||||
result = s.replace("const char*", "cstr").replace("unsigned int", "uint")
|
||||
@@ -1466,6 +1661,9 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
return HirNode(kind: hAssign, assignOp: tkAssign,
|
||||
assignTarget: loadTarget, assignValue: value,
|
||||
typ: makeVoid(), loc: loc)
|
||||
# Pointer alias update: `p = &bag`
|
||||
if expr.exprAssignTarget.kind == ekIdent and expr.exprAssignValue != nil:
|
||||
ctx.recordPtrAliasFromAst(expr.exprAssignTarget.exprIdent, expr.exprAssignValue)
|
||||
let target = ctx.lowerExpr(expr.exprAssignTarget)
|
||||
let value = ctx.lowerExpr(expr.exprAssignValue)
|
||||
return HirNode(kind: hAssign, assignOp: expr.exprAssignOp,
|
||||
@@ -1842,6 +2040,9 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
if initHir != nil:
|
||||
let store = hirStore(varNode, initHir, loc)
|
||||
stmts.add(store)
|
||||
# Pointer alias: `let p = &bag` so later `p.items` marks bag (session 74)
|
||||
if stmt.stmtLetInit != nil:
|
||||
ctx.recordPtrAliasFromAst(stmt.stmtLetName, stmt.stmtLetInit)
|
||||
# Move: `let a = b` takes ownership of droppable local `b`
|
||||
if stmt.stmtLetInit != nil:
|
||||
ctx.markMovedOutFromAst(stmt.stmtLetInit)
|
||||
@@ -1879,8 +2080,7 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
retVal = hirVar(tmp, retTy, loc)
|
||||
# Add defers in reverse order (LIFO); snapshot full stack for every return path
|
||||
for i in countdown(ctx.deferStmts.len - 1, 0):
|
||||
if not ctx.shouldSkipDrop(ctx.deferStmts[i], skipDrop):
|
||||
stmts.add(ctx.deferStmts[i])
|
||||
ctx.emitDropOrPartial(stmts, ctx.deferStmts[i], skipDrop)
|
||||
stmts.add(hirReturn(retVal, loc))
|
||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||
|
||||
@@ -2201,8 +2401,7 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode =
|
||||
let lastAlwaysReturns = stmts.len > 0 and blockAlwaysReturns(stmts[^1])
|
||||
if ctx.deferStmts.len > deferBase and not lastAlwaysReturns:
|
||||
for i in countdown(ctx.deferStmts.len - 1, deferBase):
|
||||
if not ctx.shouldSkipDrop(ctx.deferStmts[i], skipDrop):
|
||||
stmts.add(ctx.deferStmts[i])
|
||||
ctx.emitDropOrPartial(stmts, ctx.deferStmts[i], skipDrop)
|
||||
ctx.deferStmts.setLen(deferBase)
|
||||
elif ctx.deferStmts.len > deferBase and lastAlwaysReturns:
|
||||
# Return path already owns these drops; pop so outer scopes don't re-run them
|
||||
@@ -2258,8 +2457,12 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||
ctx.patternRenames = initTable[string, string]()
|
||||
let oldDefers = ctx.deferStmts
|
||||
let oldMovedOut = ctx.movedOutLocals
|
||||
let oldPartialMoved = ctx.partialMovedFields
|
||||
let oldPtrAliases = ctx.ptrAliases
|
||||
ctx.deferStmts = @[]
|
||||
ctx.movedOutLocals = initHashSet[string]()
|
||||
ctx.partialMovedFields = initTable[string, HashSet[string]]()
|
||||
ctx.ptrAliases = initTable[string, string]()
|
||||
# Add parameters to varTypeExprs after clearing so they are visible in the body.
|
||||
for p in funcParams:
|
||||
if p.ptype != nil:
|
||||
@@ -2279,10 +2482,14 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||
hasReturn = true
|
||||
if not hasReturn:
|
||||
for i in countdown(ctx.deferStmts.len - 1, 0):
|
||||
if not ctx.shouldSkipDrop(ctx.deferStmts[i], ""):
|
||||
body.blockStmts.add(ctx.deferStmts[i])
|
||||
ctx.deferStmts = oldDefers
|
||||
ctx.movedOutLocals = oldMovedOut
|
||||
ctx.emitDropOrPartial(body.blockStmts, ctx.deferStmts[i], "")
|
||||
# Always restore — mono of generics (generateMethodInstance → lowerFunc) nests
|
||||
# inside an outer function. Restoring only when deferStmts.len > 0 wiped the
|
||||
# caller's Drop stack (PeekTagAndTake lost Array_Drop after Array_Len mono).
|
||||
ctx.deferStmts = oldDefers
|
||||
ctx.movedOutLocals = oldMovedOut
|
||||
ctx.partialMovedFields = oldPartialMoved
|
||||
ctx.ptrAliases = oldPtrAliases
|
||||
|
||||
ctx.currentFuncDecl = oldFuncDecl
|
||||
ctx.currentFuncRetType = oldFuncRetType
|
||||
|
||||
+153
-16
@@ -3,7 +3,7 @@
|
||||
## Hygiene: substitute clones args at call-site, graft call-site SourceLocation
|
||||
## onto expanded template nodes (Ast_QuoteCallSite policy from QUALITY_PLAN).
|
||||
|
||||
import std/[tables, sequtils, sets]
|
||||
import std/[tables, sequtils, sets, strutils]
|
||||
import ast, token, source_location
|
||||
|
||||
type
|
||||
@@ -193,9 +193,16 @@ proc cloneExpr*(e: Expr): Expr =
|
||||
captureCount: 0, captureNames: @[], captureTypeKinds: @[])
|
||||
of ekMacroCall:
|
||||
result = Expr(kind: ekMacroCall, loc: e.loc,
|
||||
exprMacroName: e.exprMacroName, exprMacroArgs: @[])
|
||||
exprMacroName: e.exprMacroName, exprMacroArgs: @[],
|
||||
exprMacroGroupLens: e.exprMacroGroupLens)
|
||||
for a in e.exprMacroArgs:
|
||||
result.exprMacroArgs.add(cloneExpr(a))
|
||||
of ekMacroStmt:
|
||||
result = Expr(kind: ekMacroStmt, loc: e.loc,
|
||||
exprMacroStmt: cloneStmt(e.exprMacroStmt))
|
||||
of ekMacroPat:
|
||||
result = Expr(kind: ekMacroPat, loc: e.loc,
|
||||
exprMacroPat: clonePattern(e.exprMacroPat))
|
||||
|
||||
proc cloneStmt*(s: Stmt): Stmt =
|
||||
if s == nil: return nil
|
||||
@@ -329,6 +336,10 @@ proc graftExprLoc(e: Expr, loc: SourceLocation) =
|
||||
of ekClosure: graftBlockLoc(e.exprClosureBody, loc)
|
||||
of ekMacroCall:
|
||||
for a in e.exprMacroArgs: graftExprLoc(a, loc)
|
||||
of ekMacroStmt:
|
||||
graftStmtLoc(e.exprMacroStmt, loc)
|
||||
of ekMacroPat:
|
||||
discard
|
||||
else: discard
|
||||
|
||||
proc graftStmtLoc(s: Stmt, loc: SourceLocation) =
|
||||
@@ -530,6 +541,46 @@ proc substExpr(e: Expr, env: MacroEnv, callLoc: SourceLocation): Expr
|
||||
proc substStmt(s: Stmt, env: MacroEnv, callLoc: SourceLocation): Stmt
|
||||
proc substBlock(b: Block, env: MacroEnv, callLoc: SourceLocation): Block
|
||||
proc substStmtsFlat(stmts: seq[Stmt], env: MacroEnv, callLoc: SourceLocation): seq[Stmt]
|
||||
proc substPattern(p: Pattern, env: MacroEnv, callLoc: SourceLocation): Pattern
|
||||
|
||||
proc substPattern(p: Pattern, env: MacroEnv, callLoc: SourceLocation): Pattern =
|
||||
## Substitute `$p:pat` (pkIdent `$name`) with the bound pattern.
|
||||
if p == nil: return nil
|
||||
if p.kind == pkIdent and env.singles.hasKey(p.patIdent):
|
||||
let bound = env.singles[p.patIdent]
|
||||
if bound != nil and bound.kind == ekMacroPat:
|
||||
result = clonePattern(bound.exprMacroPat)
|
||||
if result != nil: result.loc = callLoc
|
||||
return
|
||||
case p.kind
|
||||
of pkRange:
|
||||
result = Pattern(kind: pkRange, loc: callLoc,
|
||||
patRangeLo: substPattern(p.patRangeLo, env, callLoc),
|
||||
patRangeHi: substPattern(p.patRangeHi, env, callLoc),
|
||||
patRangeInclusive: p.patRangeInclusive)
|
||||
of pkEnum:
|
||||
result = Pattern(kind: pkEnum, loc: callLoc, patEnumPath: p.patEnumPath,
|
||||
patEnumArgs: @[], patEnumNamed: @[])
|
||||
for a in p.patEnumArgs:
|
||||
result.patEnumArgs.add(substPattern(a, env, callLoc))
|
||||
for nf in p.patEnumNamed:
|
||||
result.patEnumNamed.add((nf.name, substPattern(nf.pattern, env, callLoc)))
|
||||
of pkStruct:
|
||||
result = Pattern(kind: pkStruct, loc: callLoc, patStructName: p.patStructName,
|
||||
patStructFields: @[])
|
||||
for f in p.patStructFields:
|
||||
result.patStructFields.add((f.name, substPattern(f.pattern, env, callLoc)))
|
||||
of pkTuple:
|
||||
result = Pattern(kind: pkTuple, loc: callLoc, patTupleElements: @[])
|
||||
for el in p.patTupleElements:
|
||||
result.patTupleElements.add(substPattern(el, env, callLoc))
|
||||
of pkGuarded:
|
||||
result = Pattern(kind: pkGuarded, loc: callLoc,
|
||||
patGuardedInner: substPattern(p.patGuardedInner, env, callLoc),
|
||||
patGuardedExpr: substExpr(p.patGuardedExpr, env, callLoc))
|
||||
else:
|
||||
result = clonePattern(p)
|
||||
if result != nil: result.loc = callLoc
|
||||
|
||||
proc substBlock(b: Block, env: MacroEnv, callLoc: SourceLocation): Block =
|
||||
if b == nil: return nil
|
||||
@@ -611,6 +662,14 @@ proc substStmtsFlat(stmts: seq[Stmt], env: MacroEnv, callLoc: SourceLocation): s
|
||||
if body != nil:
|
||||
for st in body.stmts:
|
||||
result.add(st)
|
||||
elif s.kind == skExpr and s.stmtExpr != nil and s.stmtExpr.kind == ekIdent and
|
||||
env.singles.hasKey(s.stmtExpr.exprIdent):
|
||||
let bound = env.singles[s.stmtExpr.exprIdent]
|
||||
if bound != nil and bound.kind == ekMacroStmt:
|
||||
# Splice `$s:stmt` as a real statement (not an expression)
|
||||
result.add(substStmt(bound.exprMacroStmt, env, callLoc))
|
||||
else:
|
||||
result.add(substStmt(s, env, callLoc))
|
||||
else:
|
||||
result.add(substStmt(s, env, callLoc))
|
||||
|
||||
@@ -659,7 +718,7 @@ proc substStmt(s: Stmt, env: MacroEnv, callLoc: SourceLocation): Stmt =
|
||||
c.stmtMatchSubject = substExpr(c.stmtMatchSubject, env, callLoc)
|
||||
var arms: seq[MatchArm] = @[]
|
||||
for arm in c.stmtMatchArms:
|
||||
arms.add(MatchArm(loc: callLoc, pattern: arm.pattern,
|
||||
arms.add(MatchArm(loc: callLoc, pattern: substPattern(arm.pattern, env, callLoc),
|
||||
body: substExpr(arm.body, env, callLoc)))
|
||||
c.stmtMatchArms = arms
|
||||
of skReturn:
|
||||
@@ -775,7 +834,7 @@ proc substExpr(e: Expr, env: MacroEnv, callLoc: SourceLocation): Expr =
|
||||
c.exprMatchSubject = substExpr(c.exprMatchSubject, env, callLoc)
|
||||
var arms: seq[MatchArm] = @[]
|
||||
for arm in c.exprMatchArms:
|
||||
arms.add(MatchArm(loc: callLoc, pattern: arm.pattern,
|
||||
arms.add(MatchArm(loc: callLoc, pattern: substPattern(arm.pattern, env, callLoc),
|
||||
body: substExpr(arm.body, env, callLoc)))
|
||||
c.exprMatchArms = arms
|
||||
of ekStringInterp:
|
||||
@@ -871,14 +930,89 @@ proc expandOneCall(call: Expr, macros: Table[string, Decl],
|
||||
if f.kinds.len > 0: return f.kinds
|
||||
@[f.kind]
|
||||
|
||||
proc exprToPattern(arg: Expr): Pattern =
|
||||
## Convert a call-site expression into a pattern for `$p:pat`.
|
||||
if arg == nil: return nil
|
||||
if arg.kind == ekMacroPat: return clonePattern(arg.exprMacroPat)
|
||||
case arg.kind
|
||||
of ekIdent:
|
||||
if arg.exprIdent == "_":
|
||||
return Pattern(kind: pkWildcard, loc: arg.loc)
|
||||
return Pattern(kind: pkIdent, loc: arg.loc, patIdent: arg.exprIdent)
|
||||
of ekLiteral:
|
||||
return Pattern(kind: pkLiteral, loc: arg.loc, patLit: arg.exprLit)
|
||||
of ekPath:
|
||||
return Pattern(kind: pkEnum, loc: arg.loc, patEnumPath: arg.exprPath,
|
||||
patEnumArgs: @[], patEnumNamed: @[])
|
||||
of ekCall:
|
||||
# Enum::Variant(args) or Variant(args)
|
||||
var path: seq[string] = @[]
|
||||
if arg.exprCallCallee == nil: return nil
|
||||
case arg.exprCallCallee.kind
|
||||
of ekIdent: path = @[arg.exprCallCallee.exprIdent]
|
||||
of ekPath: path = arg.exprCallCallee.exprPath
|
||||
else: return nil
|
||||
var pargs: seq[Pattern] = @[]
|
||||
for a in arg.exprCallArgs:
|
||||
let ap = exprToPattern(a)
|
||||
if ap == nil: return nil
|
||||
pargs.add(ap)
|
||||
return Pattern(kind: pkEnum, loc: arg.loc, patEnumPath: path,
|
||||
patEnumArgs: pargs, patEnumNamed: @[])
|
||||
of ekTuple:
|
||||
var elems: seq[Pattern] = @[]
|
||||
for el in arg.exprTupleElements:
|
||||
let ep = exprToPattern(el)
|
||||
if ep == nil: return nil
|
||||
elems.add(ep)
|
||||
return Pattern(kind: pkTuple, loc: arg.loc, patTupleElements: elems)
|
||||
of ekStructInit:
|
||||
var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
|
||||
for f in arg.exprStructInitFields:
|
||||
let fp = exprToPattern(f.value)
|
||||
if fp == nil: return nil
|
||||
fields.add((f.name, fp))
|
||||
return Pattern(kind: pkStruct, loc: arg.loc,
|
||||
patStructName: arg.exprStructInitName, patStructFields: fields)
|
||||
of ekRange:
|
||||
let lo = exprToPattern(arg.exprRangeLo)
|
||||
let hi = exprToPattern(arg.exprRangeHi)
|
||||
if lo == nil or hi == nil: return nil
|
||||
return Pattern(kind: pkRange, loc: arg.loc, patRangeLo: lo, patRangeHi: hi,
|
||||
patRangeInclusive: arg.exprRangeInclusive)
|
||||
else:
|
||||
return nil
|
||||
|
||||
proc coerceArg(k: MacroFragKind, arg: Expr): Expr =
|
||||
## Normalize arg for storage (pat → ekMacroPat). Returns nil if kind fails.
|
||||
if arg == nil: return nil
|
||||
case k
|
||||
of mfkIdent:
|
||||
if arg.kind != ekIdent: return nil
|
||||
return arg
|
||||
of mfkLiteral:
|
||||
if arg.kind != ekLiteral: return nil
|
||||
return arg
|
||||
of mfkBlock:
|
||||
if arg.kind != ekBlock: return nil
|
||||
return arg
|
||||
of mfkStmt:
|
||||
if arg.kind == ekMacroStmt: return arg
|
||||
# Expression as expression-statement
|
||||
if arg.kind in {ekMacroPat}: return nil
|
||||
return Expr(kind: ekMacroStmt, loc: arg.loc,
|
||||
exprMacroStmt: Stmt(kind: skExpr, loc: arg.loc, stmtExpr: arg))
|
||||
of mfkPat:
|
||||
let pat = exprToPattern(arg)
|
||||
if pat == nil: return nil
|
||||
return Expr(kind: ekMacroPat, loc: arg.loc, exprMacroPat: pat)
|
||||
of mfkExpr, mfkTt:
|
||||
if arg.kind in {ekMacroStmt, ekMacroPat}: return nil
|
||||
return arg
|
||||
|
||||
proc fragMatches(k: MacroFragKind, arg: Expr): bool =
|
||||
## Kind constraint at match time (after arg expand).
|
||||
if arg == nil: return false
|
||||
case k
|
||||
of mfkIdent: arg.kind == ekIdent
|
||||
of mfkLiteral: arg.kind == ekLiteral
|
||||
of mfkBlock: arg.kind == ekBlock
|
||||
of mfkExpr, mfkTt: true
|
||||
coerceArg(k, arg) != nil
|
||||
|
||||
var matched: MacroRule
|
||||
var env: MacroEnv
|
||||
@@ -916,10 +1050,11 @@ proc expandOneCall(call: Expr, macros: Table[string, Decl],
|
||||
for c in 0 ..< chunk:
|
||||
let arg = g[i + c]
|
||||
let k = if c < ks.len: ks[c] else: mfkExpr
|
||||
if not fragMatches(k, arg):
|
||||
let coerced = coerceArg(k, arg)
|
||||
if coerced == nil:
|
||||
failed = true
|
||||
break
|
||||
e.lists[ns[c]].add(arg)
|
||||
e.lists[ns[c]].add(coerced)
|
||||
if failed: break
|
||||
i += chunk
|
||||
else:
|
||||
@@ -930,10 +1065,11 @@ proc expandOneCall(call: Expr, macros: Table[string, Decl],
|
||||
for c in 0 ..< chunk:
|
||||
let arg = flat[ai]
|
||||
let k = if c < ks.len: ks[c] else: mfkExpr
|
||||
if not fragMatches(k, arg):
|
||||
let coerced = coerceArg(k, arg)
|
||||
if coerced == nil:
|
||||
failed = true
|
||||
break
|
||||
e.lists[ns[c]].add(arg)
|
||||
e.lists[ns[c]].add(coerced)
|
||||
inc ai
|
||||
if failed: break
|
||||
else:
|
||||
@@ -954,11 +1090,12 @@ proc expandOneCall(call: Expr, macros: Table[string, Decl],
|
||||
arg = flat[ai]
|
||||
inc ai
|
||||
let k = if ks.len > 0: ks[0] else: frag.kind
|
||||
if not fragMatches(k, arg):
|
||||
let coerced = coerceArg(k, arg)
|
||||
if coerced == nil:
|
||||
failed = true
|
||||
break
|
||||
let n = if ns.len > 0: ns[0] else: frag.name
|
||||
e.singles[n] = arg
|
||||
e.singles[n] = coerced
|
||||
|
||||
if not failed:
|
||||
if useGroups:
|
||||
|
||||
+25
-3
@@ -429,6 +429,26 @@ proc parseAssign(p: var Parser): Expr
|
||||
proc parseExpr(p: var Parser): Expr =
|
||||
p.parseAssign()
|
||||
|
||||
proc isMacroStmtStart(p: Parser): bool =
|
||||
## Keywords that begin a statement (for `$s:stmt` call-site args).
|
||||
p.peek() in {tkLet, tkVar, tkIf, tkWhile, tkFor, tkLoop, tkMatch, tkReturn,
|
||||
tkBreak, tkContinue, tkDefer, tkSwitch, tkDo}
|
||||
|
||||
proc parseMacroArg(p: var Parser): Expr =
|
||||
## Macro call argument:
|
||||
## - statement keywords → ekMacroStmt
|
||||
## - `_` / pattern-only starts → ekMacroPat (also `$p:pat` from expr via coerce)
|
||||
## - else expression
|
||||
let loc = p.currentLoc
|
||||
if p.isMacroStmtStart():
|
||||
let st = p.parseStmt()
|
||||
return Expr(kind: ekMacroStmt, loc: loc, exprMacroStmt: st)
|
||||
# Wildcard is not a valid expression; parse as pattern for `$p:pat`
|
||||
if p.check(tkUnderscore):
|
||||
let pat = p.parsePattern()
|
||||
return Expr(kind: ekMacroPat, loc: loc, exprMacroPat: pat)
|
||||
p.parseExpr()
|
||||
|
||||
proc parseStringInterpolation(p: var Parser, tok: Token): Expr =
|
||||
## Parse a string literal that contains {expr} interpolations.
|
||||
let text = tok.text
|
||||
@@ -749,7 +769,7 @@ proc parsePostfix(p: var Parser): Expr =
|
||||
curGroup = 0
|
||||
p.skipNewlines()
|
||||
continue
|
||||
margs.add(p.parseExpr())
|
||||
margs.add(p.parseMacroArg())
|
||||
inc curGroup
|
||||
p.skipNewlines()
|
||||
if p.check(tkComma):
|
||||
@@ -1628,10 +1648,12 @@ proc parseMacroFragKind(p: var Parser, kindTok: Token): MacroFragKind =
|
||||
of "tt": mfkTt
|
||||
of "literal", "lit": mfkLiteral
|
||||
of "block": mfkBlock
|
||||
of "stmt": mfkStmt
|
||||
of "pat", "pattern": mfkPat
|
||||
else:
|
||||
p.emitError(kindTok.loc,
|
||||
"unsupported macro fragment kind '" & kindTok.text &
|
||||
"' (expr|ident|tt|literal|block)")
|
||||
"' (expr|ident|tt|literal|block|stmt|pat)")
|
||||
mfkExpr
|
||||
|
||||
proc parseMacroFragment(p: var Parser): MacroFragment =
|
||||
@@ -1640,7 +1662,7 @@ proc parseMacroFragment(p: var Parser): MacroFragment =
|
||||
if not fragTok.text.startsWith("$"):
|
||||
p.emitError(fragTok.loc, "macro fragment must start with '$' (e.g. $x:expr)")
|
||||
discard p.expect(tkColon, "expected ':' after macro fragment name")
|
||||
let kindTok = p.expect(tkIdent, "expected fragment kind (expr|ident|tt|literal|block)")
|
||||
let kindTok = p.expect(tkIdent, "expected fragment kind (expr|ident|tt|literal|block|stmt|pat)")
|
||||
let k = p.parseMacroFragKind(kindTok)
|
||||
result = MacroFragment(
|
||||
name: fragTok.text,
|
||||
|
||||
@@ -1902,6 +1902,10 @@ proc checkExpr*(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
# Should have been expanded before analyze; leftover is a compiler bug
|
||||
sema.emitError(expr.loc, "unexpanded macro call '" & expr.exprMacroName & "!'")
|
||||
return makeUnknown()
|
||||
of ekMacroStmt, ekMacroPat:
|
||||
# Expand-only wrappers; must not reach type-checking
|
||||
sema.emitError(expr.loc, "internal: unexpanded macro stmt/pat fragment")
|
||||
return makeUnknown()
|
||||
of ekClosure:
|
||||
let savedRetType = sema.currentRetType
|
||||
let savedClosureDepth = sema.closureDepth
|
||||
|
||||
@@ -209,7 +209,7 @@ make test # full sequential suite (local)
|
||||
| `apps` | ubuntu | `test-apps` |
|
||||
| `selfhost` | ubuntu | `test-selfhost-smoke` |
|
||||
| `macos` | macos-14 | rebuild + `test-unit` + `test-examples-smoke` (subset) |
|
||||
| `windows` | windows-latest | rebuild `buxc.exe` + pure Nim unit tests + CLI smoke |
|
||||
| `windows` | windows-latest | `buxc.exe` + Nim unit tests + CLI + **MinGW `hello`** |
|
||||
| `ci-gate` | ubuntu | fails if any required job failed (branch protection) |
|
||||
|
||||
**CI speed helpers:**
|
||||
@@ -217,8 +217,9 @@ make test # full sequential suite (local)
|
||||
Windows uses a prebuilt Nim zip)
|
||||
- Project-local `nimcache/` via `NIMFLAGS=--nimcache:nimcache`, cached per job by source hash
|
||||
- macOS skips full EXAMPLES (Linux already runs them) and skips `fmt-check` (Linux unit job)
|
||||
- **Windows** does **not** run `bux run` examples yet: `rt/runtime.c` needs POSIX
|
||||
(`ucontext`, `pthread`, BSD sockets). Smoke still validates bootstrap + unit tests on Win.
|
||||
- **Windows** runs `tools/smoke_windows_hello.sh` with **MinGW gcc** + `rt/runtime_win.c`
|
||||
(no pthread/OpenSSL/ucontext). Full POSIX runtime (`rt/runtime.c`) remains Unix-only.
|
||||
Locally on Linux/macOS: `BUX_RUNTIME=win ./tools/smoke_windows_hello.sh`.
|
||||
|
||||
Parallel Linux jobs set `BUX_SKIP_BUILD=1` after downloading the `buxc` artifact.
|
||||
Locally, `make test` still runs the full suite sequentially and builds once.
|
||||
@@ -330,7 +331,8 @@ bux/
|
||||
│ ├── Task.bux
|
||||
│ └── Channel.bux
|
||||
├── rt/ # C runtime
|
||||
│ ├── runtime.c
|
||||
│ ├── runtime.c # full POSIX + OpenSSL (Unix)
|
||||
│ ├── runtime_win.c # MinGW minimal (Windows / BUX_RUNTIME=win)
|
||||
│ └── io.c
|
||||
├── examples/ # Example programs
|
||||
├── tests/ # Unit tests (Nim)
|
||||
|
||||
+78
-6
@@ -827,10 +827,59 @@ Rules:
|
||||
etc.). Reading `bag.tag` (`int`) does **not** mark `bag` moved.
|
||||
- After `let moved = bag.items`, `Bag_Drop(&bag)` is skipped; `moved` owns the
|
||||
array and is auto-dropped at scope end.
|
||||
- Avoid using other droppable fields of the parent after a partial move (they
|
||||
may be left in a moved-from state without per-field Drop).
|
||||
- **Remaining fields:** if the parent has other droppable fields that were *not*
|
||||
moved out, those still run their `Type_Drop` / collection Drop (session 70).
|
||||
Example: move `pair.left` → skip `PairBag_Drop`, still `Tracked_Drop(&pair.right)`.
|
||||
|
||||
Golden smoke: `make test-drop-move` / `examples/move_field_partial.bux`.
|
||||
```bux
|
||||
@[Drop]
|
||||
struct PairBag {
|
||||
left: Array<int>,
|
||||
right: Tracked, // also @[Drop]
|
||||
}
|
||||
func TakeLeft() -> Array<int> {
|
||||
let pair: PairBag = …;
|
||||
return pair.left; // Tracked_Drop(&pair.right) still runs
|
||||
}
|
||||
```
|
||||
|
||||
#### Nested path moves (`a.b.c`)
|
||||
|
||||
Moving a **deep** droppable field also works. The full dotted path is recorded
|
||||
so remaining fields at every level still Drop:
|
||||
|
||||
```bux
|
||||
@[Drop]
|
||||
struct Outer {
|
||||
inner: Inner, // Inner has items: Array + note: Tracked
|
||||
tag: Tracked,
|
||||
}
|
||||
func TakeNested() -> Array<int> {
|
||||
let outer: Outer = …;
|
||||
return outer.inner.items;
|
||||
// skips Outer_Drop
|
||||
// still: Tracked_Drop(&outer.inner.note) + Tracked_Drop(&outer.tag)
|
||||
}
|
||||
```
|
||||
|
||||
#### Field moves through pointers
|
||||
|
||||
When a local pointer aliases a local owner (`let p = &bag`), moving a field
|
||||
through the pointer marks the **owner**, not the pointer:
|
||||
|
||||
```bux
|
||||
let bag: Bag = …;
|
||||
let p: *Bag = &bag;
|
||||
return p.items; // same as (*p).items
|
||||
// skips Bag_Drop; still Tracked_Drop(&bag.tag)
|
||||
```
|
||||
|
||||
Nested paths work the same: `p.inner.items` resolves `p → outer` then path
|
||||
`inner.items`.
|
||||
|
||||
Golden smoke: `make test-drop-move` / `examples/move_field_partial.bux` /
|
||||
`examples/move_field_remaining.bux` / `examples/move_field_nested.bux` /
|
||||
`examples/move_field_ptr.bux`.
|
||||
|
||||
#### Manual Drop and non-Drop types
|
||||
|
||||
@@ -841,9 +890,11 @@ Golden smoke: `make test-drop-move` / `examples/move_field_partial.bux`.
|
||||
|
||||
#### Limits (honest)
|
||||
|
||||
- Partial field moves mark the **whole parent local** as moved for Drop purposes
|
||||
(not per-field Drop of remaining fields).
|
||||
- Nested `a.b.c` path moves and moving through pointers are limited.
|
||||
- Partial field moves skip the **parent** `Type_Drop` and drop **remaining**
|
||||
droppable fields individually, including nested paths `a.b.c` and pointer
|
||||
aliases `p = &owner` (sessions 70/73/74).
|
||||
- Pointer aliases are tracked for **local** `p = &local` only (not parameters
|
||||
that point at caller-owned data across function boundaries).
|
||||
- Interface Drop uses a static `TypeName_Drop` symbol (zero cost), not dynamic
|
||||
dispatch through a vtable.
|
||||
- Double-free bugs in **unchecked** code that manually free *and* auto-drop are
|
||||
@@ -1174,6 +1225,25 @@ macro! wrap_block {
|
||||
( $b:block ) => { $b }
|
||||
}
|
||||
|
||||
// stmt — one statement (let/if/… or expression-statement)
|
||||
macro! with_setup {
|
||||
( $s:stmt, $body:expr ) => {
|
||||
{
|
||||
$s
|
||||
$body
|
||||
}
|
||||
}
|
||||
}
|
||||
// call: with_setup!(let x: int = 10, x + 1)
|
||||
|
||||
// pat — match/let pattern (literals, `_`, enum variants, …)
|
||||
macro! matches {
|
||||
( $p:pat, $e:expr ) => {
|
||||
match $e { $p => 1, _ => 0 }
|
||||
}
|
||||
}
|
||||
// call: matches!(1, 1) · matches!(_, 99) · matches!(Opt::Some(v), opt)
|
||||
|
||||
// gensym: template locals renamed per expansion
|
||||
macro! with_acc {
|
||||
( $start:literal ) => {
|
||||
@@ -1195,6 +1265,8 @@ macro! with_acc {
|
||||
| `tt` | token-tree (MVP: same as `expr`) |
|
||||
| `literal` / `lit` | int/float/string/char/bool literal only |
|
||||
| `block` | block expression `{ … }` |
|
||||
| `stmt` | one statement (`let`/`if`/… or expression-stmt) |
|
||||
| `pat` / `pattern` | match pattern (`_`, literals, `Enum::Var(…)`, …) |
|
||||
|
||||
- Fragment names start with `$` (lexer `$ident`).
|
||||
- **Repetition:** `$( $x:expr ),*` / `$( $x:expr )*` — one or more rep fragments per pattern.
|
||||
|
||||
+96
-6
@@ -1,7 +1,7 @@
|
||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||
|
||||
> **Дата:** 2026-07-20
|
||||
> **Текущо:** v0.5.x — macros (unhygienic binders + multi-rep), partial field-move, lean CI
|
||||
> **Дата:** 2026-07-21
|
||||
> **Текущо:** v0.5.x — macros, field-move via pointers, Windows hello
|
||||
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||
|
||||
---
|
||||
@@ -14,7 +14,7 @@
|
||||
| Sema / generics | Monomorphization, trait bounds basic | ★★★★☆ |
|
||||
| HIR → C | Tuples + fat `func` ABI в bootstrap **и** selfhost | ★★★★☆ |
|
||||
| Selfhost (`src/`) | ~12k LOC, binary-identical loop, closures+tuples | ★★★★★ |
|
||||
| Gradual ownership | `@[Checked]`, move, Drop, elision, **field-move skip Drop** | ★★★★★ |
|
||||
| Gradual ownership | `@[Checked]`, move, Drop, elision, **field-move + remaining-field Drop** | ★★★★★ |
|
||||
| Concurrency | M:N tasks + channels + async | ★★★★☆ |
|
||||
| Stdlib | Array/Map/Set/String/Iter HOF разширени | ★★★★☆ |
|
||||
| Tooling | LSP 0.5 hover/def/outline/**refs/rename** + fmt/test/doc | ★★★★★ |
|
||||
@@ -1117,8 +1117,98 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
||||
|
||||
---
|
||||
|
||||
## Сесия 70 (per-field Drop + mono defer restore)
|
||||
|
||||
1. **Bug (critical):** `lowerFunc` restored `deferStmts` / `movedOutLocals` only
|
||||
when the *inner* mono function still had pending defers. Nested
|
||||
`generateMethodInstance` → `lowerFunc` (e.g. `Array_Len` inside
|
||||
`PeekTagAndTake`) wiped the caller's Drop stack → leaked moved Arrays.
|
||||
2. **Fix bootstrap:** always restore `deferStmts` / `movedOutLocals` /
|
||||
`partialMovedFields` after lowering a function body.
|
||||
3. **Per-field Drop after partial move:**
|
||||
- Track `partialMovedFields: local → {field names}`
|
||||
- Skip parent `Type_Drop`; emit Drop for **remaining** droppable fields
|
||||
- `emitDropOrPartial` at return / block exit / function tail
|
||||
4. **Selfhost CBE** (`c_backend.bux`):
|
||||
- partial (var, field) slots + local type registry on `hAlloca`
|
||||
- `CBE_EmitRemainingFieldDrops` when skipping moved parent Drop
|
||||
5. **Example + smoke:**
|
||||
- `examples/move_field_remaining.bux` (PairBag left move → Tracked_Drop right)
|
||||
- `tools/smoke_drop_move.sh` checks PeekTag Array_Drop + remaining Tracked_Drop
|
||||
6. **LanguageRef:** remaining-field rule; limits updated
|
||||
7. Verified: bootstrap + **buxc2** remaining/partial/move_field; smoke; EXAMPLES
|
||||
|
||||
---
|
||||
|
||||
## Сесия 71 (Windows MinGW + `hello` smoke)
|
||||
|
||||
1. **`rt/runtime_win.c`** — minimal runtime without pthread / ucontext / sockets /
|
||||
OpenSSL. Real alloc, strings, files, time, env; stubs for tasks/crypto/net.
|
||||
2. **Bootstrap CLI** (`bootstrap/cli.nim`):
|
||||
- Windows (or `BUX_RUNTIME=win`) copies `runtime_win.c` instead of `runtime.c`
|
||||
- Link: `-ffunction-sections -Wl,--gc-sections -lm` (no `-pthread` / `-lcrypto`)
|
||||
- Host `gcc` on Windows; `.exe` suffix on build/run
|
||||
- Fixed: `-l` libs **after** `.c` inputs (GNU ld order)
|
||||
3. **CI** (`.github/workflows/ci.yml` windows job):
|
||||
- MinGW via `msys2/setup-msys2` (`mingw-w64-x86_64-gcc`)
|
||||
- `tools/smoke_windows_hello.sh` after unit/CLI smoke
|
||||
4. **Docs:** BuildAndTest CI table + `rt/` tree
|
||||
5. Verified locally: normal `hello` + `BUX_RUNTIME=win` smoke PASS
|
||||
|
||||
---
|
||||
|
||||
## Сесия 72 (macro `stmt` / `pat` fragments)
|
||||
|
||||
1. **Kinds:** `mfkStmt` / `mfkPat` (+ aliases `pattern`, `lit` already)
|
||||
2. **AST wrappers:** `ekMacroStmt` / `ekMacroPat` (expand-only)
|
||||
3. **Call-site parse:**
|
||||
- stmt keywords → `parseStmt` → MacroStmt
|
||||
- `_` → `parsePattern` → MacroPat
|
||||
- else expr; `pat` coerces via `exprToPattern` (ident/lit/path/call/tuple/struct/range)
|
||||
4. **Expand:**
|
||||
- `coerceArg` at match; store normalized MacroStmt/MacroPat
|
||||
- `$s` as skExpr splices MacroStmt into the statement list
|
||||
- `$p` as pkIdent pattern substitutes bound MacroPat
|
||||
5. **Selfhost:** same kinds, coerce, splice, pattern subst
|
||||
6. **Example:** `examples/macro_stmt_pat.bux` — setup/do_twice/matches/if_let_like
|
||||
7. LanguageRef kind table + docs
|
||||
8. Verified: bootstrap + **buxc2** `macro_stmt_pat` PASS
|
||||
|
||||
---
|
||||
|
||||
## Сесия 73 (nested `a.b.c` field-move Drop)
|
||||
|
||||
1. **Bootstrap** (`hir_lower.nim`):
|
||||
- `fieldPathFromAst` → base local + path `@["inner","items"]`
|
||||
- `partialMovedFields` stores **dotted paths** (`"inner.items"`)
|
||||
- `remainingDropsAt` recursive: exact path = skip; prefix = recurse;
|
||||
other droppable fields → `Type_Drop(&(base.a.b))`
|
||||
- Typed intermediate `hFieldAccess` so LIR/C keep `Inner` not `int`
|
||||
2. **Selfhost CBE:** full dotted path on mark; recursive `CBE_EmitRemainingAt`
|
||||
3. **Example:** `examples/move_field_nested.bux` — `outer.inner.items` → 2 Tracked drops
|
||||
4. Smoke + EXAMPLES; LanguageRef nested path section
|
||||
5. Selfhost: recursive remaining drops + skip Drop when `HasPartialMoved`
|
||||
(struct emit multi-pass topo for Outer{Inner})
|
||||
6. Verified: bootstrap + **buxc2** `nested_drops=4` PASS; full `test-drop-move`
|
||||
|
||||
---
|
||||
|
||||
## Сесия 74 (field moves through pointers)
|
||||
|
||||
1. **Pointer aliases:** `let p = &bag` / `p = &bag` → `ptrAliases[p] = bag`
|
||||
2. **fieldPathFromAst:** peel `(*p)` (ekUnary tkStar); resolve alias to owner
|
||||
3. **`p.field`** (auto-deref) and **`(*p).field`** mark owner + path
|
||||
4. Nested via ptr: `p.inner.items` → owner + `"inner.items"`
|
||||
5. Selfhost CBE: alias slots + resolve in `CBE_BaseVarName`; record on store/assign
|
||||
6. **Example:** `examples/move_field_ptr.bux` → `ptr_drops=5`
|
||||
7. Smoke + LanguageRef; limits: local aliases only (not cross-function params)
|
||||
8. Selfhost: unary C parens fix `(*p).field`; alias slots + BaseVar resolve
|
||||
9. Verified: bootstrap + **buxc2** `ptr_drops=5` PASS; full `test-drop-move`
|
||||
|
||||
---
|
||||
|
||||
## Следващи стъпки
|
||||
|
||||
1. Windows: MinGW + runtime stubs for `hello` smoke (stretch)
|
||||
2. Per-field Drop after partial move (stretch)
|
||||
3. Macro: true `stmt`/`pat` token-tree frags (stretch)
|
||||
1. Windows: more examples (strings/ownership) on MinGW; optional Win OpenSSL
|
||||
2. Macro: true token-tree `tt` / nested pattern rewrite depth
|
||||
3. Cross-function pointer ownership transfer (callee `*Bag` param)
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// Session 72 — macro fragment kinds `stmt` and `pat`
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::String::{String_FromInt};
|
||||
import Std::Test::{Test_AssertTrue, Test_Pass};
|
||||
|
||||
// stmt: splice a full statement (let / assign) into the template
|
||||
macro! with_setup {
|
||||
( $s:stmt, $body:expr ) => {
|
||||
{
|
||||
$s
|
||||
$body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stmt from an expression-statement (assign)
|
||||
macro! do_twice {
|
||||
( $s:stmt ) => {
|
||||
{
|
||||
$s
|
||||
$s
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pat: match arm pattern from call-site
|
||||
macro! matches {
|
||||
( $p:pat, $e:expr ) => {
|
||||
match $e {
|
||||
$p => 1,
|
||||
_ => 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combined: extract payload when pattern matches
|
||||
macro! if_let_like {
|
||||
( $p:pat, $e:expr, $then:expr ) => {
|
||||
match $e {
|
||||
$p => $then,
|
||||
_ => -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Opt {
|
||||
Some(int),
|
||||
None
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
// with_setup: inject `let x = 10` then use x
|
||||
let a: int = with_setup!(let x: int = 10, x + 1);
|
||||
Test_AssertTrue(a == 11);
|
||||
|
||||
// do_twice: run assign twice
|
||||
var n: int = 0;
|
||||
discard do_twice!(n = n + 1);
|
||||
Test_AssertTrue(n == 2);
|
||||
|
||||
// matches: literal / wildcard / enum (bind results — match-as-arg is fragile on buxc2)
|
||||
let m1: int = matches!(1, 1);
|
||||
let m2: int = matches!(2, 1);
|
||||
let m3: int = matches!(_, 99);
|
||||
Test_AssertTrue(m1 == 1);
|
||||
Test_AssertTrue(m2 == 0);
|
||||
Test_AssertTrue(m3 == 1);
|
||||
|
||||
var o1: Opt = Opt { tag: Opt_Some };
|
||||
o1.data.Some_0 = 7;
|
||||
let o2: Opt = Opt { tag: Opt_None };
|
||||
let e1: int = if_let_like!(Opt::Some(v), o1, v);
|
||||
let e2: int = if_let_like!(Opt::Some(v), o2, v);
|
||||
let e3: int = matches!(Opt::None, o2);
|
||||
Test_AssertTrue(e1 == 7);
|
||||
Test_AssertTrue(e2 == -1);
|
||||
Test_AssertTrue(e3 == 1);
|
||||
|
||||
PrintLine(String_FromInt(a));
|
||||
PrintLine(String_FromInt(n));
|
||||
Test_Pass("macro_stmt_pat");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Session 73 — nested field path moves (`outer.inner.items`)
|
||||
// Moving a deep droppable field skips root Drop and still drops remaining
|
||||
// fields at every level of the path.
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::Array::{Array, Array_New, Array_Push, Array_Len, Array_Get};
|
||||
import Std::String::{String_FromInt, String_Concat};
|
||||
import Std::Test::{Test_AssertTrue, Test_Pass};
|
||||
|
||||
@[Drop]
|
||||
struct Tracked {
|
||||
id: int,
|
||||
counter: *int
|
||||
}
|
||||
|
||||
func Tracked_Drop(self: *Tracked) {
|
||||
if self.counter != null as *int {
|
||||
*self.counter = *self.counter + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@[Drop]
|
||||
struct Inner {
|
||||
items: Array<int>,
|
||||
note: Tracked
|
||||
}
|
||||
|
||||
func Inner_Drop(self: *Inner) {
|
||||
Array_Drop<int>(&self.items);
|
||||
Tracked_Drop(&self.note);
|
||||
}
|
||||
|
||||
@[Drop]
|
||||
struct Outer {
|
||||
inner: Inner,
|
||||
tag: Tracked
|
||||
}
|
||||
|
||||
func Outer_Drop(self: *Outer) {
|
||||
Inner_Drop(&self.inner);
|
||||
Tracked_Drop(&self.tag);
|
||||
}
|
||||
|
||||
// Move outer.inner.items — must Drop: outer.inner.note + outer.tag
|
||||
// (not Outer_Drop whole, not Array double-free)
|
||||
func TakeNestedItems(counter: *int) -> Array<int> {
|
||||
var items: Array<int> = Array_New<int>(2);
|
||||
Array_Push<int>(&items, 10);
|
||||
Array_Push<int>(&items, 20);
|
||||
let outer: Outer = Outer {
|
||||
inner: Inner {
|
||||
items: items,
|
||||
note: Tracked { id: 1, counter: counter }
|
||||
},
|
||||
tag: Tracked { id: 2, counter: counter }
|
||||
};
|
||||
return outer.inner.items;
|
||||
}
|
||||
|
||||
// let-move nested path
|
||||
func PeekAfterNestedTake(counter: *int) -> int {
|
||||
var items: Array<int> = Array_New<int>(1);
|
||||
Array_Push<int>(&items, 7);
|
||||
let outer: Outer = Outer {
|
||||
inner: Inner {
|
||||
items: items,
|
||||
note: Tracked { id: 3, counter: counter }
|
||||
},
|
||||
tag: Tracked { id: 4, counter: counter }
|
||||
};
|
||||
let moved: Array<int> = outer.inner.items;
|
||||
return Array_Len<int>(&moved) as int;
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
var drops: int = 0;
|
||||
|
||||
let taken: Array<int> = TakeNestedItems(&drops);
|
||||
Test_AssertTrue(Array_Len<int>(&taken) == 2);
|
||||
Test_AssertTrue(Array_Get<int>(&taken, 0) == 10);
|
||||
// TakeNestedItems: note(id1) + tag(id2) → 2 Tracked drops
|
||||
Test_AssertTrue(drops == 2);
|
||||
|
||||
let n: int = PeekAfterNestedTake(&drops);
|
||||
Test_AssertTrue(n == 1);
|
||||
// +2 more Tracked drops
|
||||
Test_AssertTrue(drops == 4);
|
||||
|
||||
PrintLine(String_Concat("nested_drops=", String_FromInt(drops as int64)));
|
||||
Test_Pass("move_field_nested");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Session 74 — field moves through pointers (`p.field` / `(*p).field`)
|
||||
// When `p = &bag`, moving `p.items` marks the owner `bag` (not the pointer).
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::Array::{Array, Array_New, Array_Push, Array_Len, Array_Get};
|
||||
import Std::String::{String_FromInt, String_Concat};
|
||||
import Std::Test::{Test_AssertTrue, Test_Pass};
|
||||
|
||||
@[Drop]
|
||||
struct Tracked {
|
||||
id: int,
|
||||
counter: *int
|
||||
}
|
||||
|
||||
func Tracked_Drop(self: *Tracked) {
|
||||
if self.counter != null as *int {
|
||||
*self.counter = *self.counter + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@[Drop]
|
||||
struct Bag {
|
||||
items: Array<int>,
|
||||
tag: Tracked
|
||||
}
|
||||
|
||||
func Bag_Drop(self: *Bag) {
|
||||
Array_Drop<int>(&self.items);
|
||||
Tracked_Drop(&self.tag);
|
||||
}
|
||||
|
||||
// p.items via auto-deref of *Bag
|
||||
func TakeViaPtr(counter: *int) -> Array<int> {
|
||||
var items: Array<int> = Array_New<int>(2);
|
||||
Array_Push<int>(&items, 10);
|
||||
Array_Push<int>(&items, 20);
|
||||
let bag: Bag = Bag {
|
||||
items: items,
|
||||
tag: Tracked { id: 1, counter: counter }
|
||||
};
|
||||
let p: *Bag = &bag;
|
||||
return p.items;
|
||||
}
|
||||
|
||||
// Explicit (*p).items
|
||||
func TakeViaDeref(counter: *int) -> Array<int> {
|
||||
var items: Array<int> = Array_New<int>(1);
|
||||
Array_Push<int>(&items, 7);
|
||||
let bag: Bag = Bag {
|
||||
items: items,
|
||||
tag: Tracked { id: 2, counter: counter }
|
||||
};
|
||||
let p: *Bag = &bag;
|
||||
return (*p).items;
|
||||
}
|
||||
|
||||
// let-move through pointer; remaining tag still Drops
|
||||
func PeekAfterPtrTake(counter: *int) -> int {
|
||||
var items: Array<int> = Array_New<int>(1);
|
||||
Array_Push<int>(&items, 3);
|
||||
let bag: Bag = Bag {
|
||||
items: items,
|
||||
tag: Tracked { id: 3, counter: counter }
|
||||
};
|
||||
var p: *Bag = &bag;
|
||||
let moved: Array<int> = p.items;
|
||||
return Array_Len<int>(&moved) as int;
|
||||
}
|
||||
|
||||
// Nested path through pointer: p.inner.items
|
||||
@[Drop]
|
||||
struct Inner {
|
||||
items: Array<int>,
|
||||
note: Tracked
|
||||
}
|
||||
|
||||
func Inner_Drop(self: *Inner) {
|
||||
Array_Drop<int>(&self.items);
|
||||
Tracked_Drop(&self.note);
|
||||
}
|
||||
|
||||
@[Drop]
|
||||
struct Outer {
|
||||
inner: Inner,
|
||||
tag: Tracked
|
||||
}
|
||||
|
||||
func Outer_Drop(self: *Outer) {
|
||||
Inner_Drop(&self.inner);
|
||||
Tracked_Drop(&self.tag);
|
||||
}
|
||||
|
||||
func TakeNestedViaPtr(counter: *int) -> Array<int> {
|
||||
var items: Array<int> = Array_New<int>(1);
|
||||
Array_Push<int>(&items, 99);
|
||||
let outer: Outer = Outer {
|
||||
inner: Inner {
|
||||
items: items,
|
||||
note: Tracked { id: 4, counter: counter }
|
||||
},
|
||||
tag: Tracked { id: 5, counter: counter }
|
||||
};
|
||||
let p: *Outer = &outer;
|
||||
return p.inner.items;
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
var drops: int = 0;
|
||||
|
||||
let a: Array<int> = TakeViaPtr(&drops);
|
||||
Test_AssertTrue(Array_Len<int>(&a) == 2);
|
||||
Test_AssertTrue(Array_Get<int>(&a, 0) == 10);
|
||||
// tag Drop once
|
||||
Test_AssertTrue(drops == 1);
|
||||
|
||||
let b: Array<int> = TakeViaDeref(&drops);
|
||||
Test_AssertTrue(Array_Len<int>(&b) == 1);
|
||||
Test_AssertTrue(drops == 2);
|
||||
|
||||
let n: int = PeekAfterPtrTake(&drops);
|
||||
Test_AssertTrue(n == 1);
|
||||
Test_AssertTrue(drops == 3);
|
||||
|
||||
let c: Array<int> = TakeNestedViaPtr(&drops);
|
||||
Test_AssertTrue(Array_Get<int>(&c, 0) == 99);
|
||||
// note + tag → +2
|
||||
Test_AssertTrue(drops == 5);
|
||||
|
||||
PrintLine(String_Concat("ptr_drops=", String_FromInt(drops as int64)));
|
||||
Test_Pass("move_field_ptr");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Session 70 — per-field Drop after partial move
|
||||
// Moving one droppable field out of a @[Drop] parent skips Type_Drop of the
|
||||
// parent but still drops the *remaining* droppable fields.
|
||||
import Std::Io::{PrintLine};
|
||||
import Std::Array::{Array, Array_New, Array_Push, Array_Len, Array_Get};
|
||||
import Std::String::{String_FromInt, String_Concat};
|
||||
import Std::Test::{Test_AssertTrue, Test_Pass};
|
||||
|
||||
@[Drop]
|
||||
struct Tracked {
|
||||
n: int,
|
||||
counter: *int
|
||||
}
|
||||
|
||||
func Tracked_Drop(self: *Tracked) {
|
||||
if self.counter != null as *int {
|
||||
*self.counter = *self.counter + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@[Drop]
|
||||
struct PairBag {
|
||||
left: Array<int>,
|
||||
right: Tracked
|
||||
}
|
||||
|
||||
func PairBag_Drop(self: *PairBag) {
|
||||
Array_Drop<int>(&self.left);
|
||||
Tracked_Drop(&self.right);
|
||||
}
|
||||
|
||||
// Move left out via return — right must still Drop (Tracked_Drop)
|
||||
func TakeLeft(counter: *int) -> Array<int> {
|
||||
var left: Array<int> = Array_New<int>(2);
|
||||
Array_Push<int>(&left, 10);
|
||||
Array_Push<int>(&left, 20);
|
||||
let pair: PairBag = PairBag {
|
||||
left: left,
|
||||
right: Tracked { n: 2, counter: counter }
|
||||
};
|
||||
return pair.left;
|
||||
}
|
||||
|
||||
// Move left via let; right still drops at scope end
|
||||
func PeekAfterTake(counter: *int) -> int {
|
||||
var left: Array<int> = Array_New<int>(1);
|
||||
Array_Push<int>(&left, 7);
|
||||
let pair: PairBag = PairBag {
|
||||
left: left,
|
||||
right: Tracked { n: 2, counter: counter }
|
||||
};
|
||||
let moved: Array<int> = pair.left;
|
||||
return Array_Len<int>(&moved) as int;
|
||||
}
|
||||
|
||||
// Whole-struct return: no PairBag_Drop here (caller owns it)
|
||||
func MakePair() -> PairBag {
|
||||
var left: Array<int> = Array_New<int>(1);
|
||||
Array_Push<int>(&left, 1);
|
||||
let pair: PairBag = PairBag {
|
||||
left: left,
|
||||
right: Tracked { n: 9, counter: null as *int }
|
||||
};
|
||||
return pair;
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
var drops: int = 0;
|
||||
|
||||
let taken: Array<int> = TakeLeft(&drops);
|
||||
Test_AssertTrue(Array_Len<int>(&taken) == 2);
|
||||
Test_AssertTrue(Array_Get<int>(&taken, 0) == 10);
|
||||
// TakeLeft must have Dropped remaining right field once
|
||||
Test_AssertTrue(drops == 1);
|
||||
|
||||
let n: int = PeekAfterTake(&drops);
|
||||
Test_AssertTrue(n == 1);
|
||||
Test_AssertTrue(drops == 2);
|
||||
|
||||
let p: PairBag = MakePair();
|
||||
Test_AssertTrue(Array_Len<int>(&p.left) == 1);
|
||||
Test_AssertTrue(p.right.n == 9);
|
||||
|
||||
PrintLine(String_Concat("right_drops=", String_FromInt(drops as int64)));
|
||||
Test_Pass("move_field_remaining");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,733 @@
|
||||
/* Bux Runtime — Windows / MinGW minimal build (session 71)
|
||||
*
|
||||
* No pthread, ucontext, BSD sockets, or OpenSSL. Enough for hello and
|
||||
* basic single-threaded programs. Advanced features return failure / no-op.
|
||||
*
|
||||
* Linked with -ffunction-sections -fdata-sections -Wl,--gc-sections so
|
||||
* monomorphized stdlib in main.c that is never called is discarded.
|
||||
*
|
||||
* Unix full runtime remains rt/runtime.c (POSIX + OpenSSL).
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
# include <windows.h>
|
||||
# include <direct.h>
|
||||
# include <io.h>
|
||||
# define BUX_IS_WIN 1
|
||||
# define bux_mkdir_one(p) _mkdir(p)
|
||||
#else
|
||||
# include <unistd.h>
|
||||
# include <sys/stat.h>
|
||||
# define BUX_IS_WIN 0
|
||||
# define bux_mkdir_one(p) mkdir((p), 0755)
|
||||
#endif
|
||||
|
||||
/* ── CLI args ─────────────────────────────────────────────────────────── */
|
||||
int g_argc = 0;
|
||||
char** g_argv = NULL;
|
||||
|
||||
int bux_argc(void) { return g_argc; }
|
||||
char* bux_argv(int index) {
|
||||
if (index < 0 || index >= g_argc) return "";
|
||||
return g_argv[index];
|
||||
}
|
||||
|
||||
/* ── Memory ───────────────────────────────────────────────────────────── */
|
||||
void* bux_alloc(size_t size) {
|
||||
void* ptr = calloc(1, size);
|
||||
if (ptr == NULL && size > 0) {
|
||||
fprintf(stderr, "bux runtime: out of memory (alloc %zu)\n", size);
|
||||
abort();
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
void* bux_realloc(void* ptr, size_t size) {
|
||||
void* p = realloc(ptr, size);
|
||||
if (p == NULL && size > 0) {
|
||||
fprintf(stderr, "bux runtime: out of memory (realloc %zu)\n", size);
|
||||
abort();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
void bux_free(void* ptr) { free(ptr); }
|
||||
|
||||
/* ── Basic I/O / panic ────────────────────────────────────────────────── */
|
||||
void bux_print(const char* s) { if (s) fputs(s, stdout); fflush(stdout); }
|
||||
void bux_println(const char* s) { if (s) puts(s); else puts(""); fflush(stdout); }
|
||||
void bux_print_int(int64_t n) { printf("%lld", (long long)n); }
|
||||
void bux_print_float(double f) { printf("%g", f); }
|
||||
void bux_print_bool(bool b) { fputs(b ? "true" : "false", stdout); }
|
||||
void bux_print_char(char c) { fputc(c, stdout); }
|
||||
void bux_panic(const char* msg) {
|
||||
fprintf(stderr, "PANIC: %s\n", msg ? msg : "");
|
||||
abort();
|
||||
}
|
||||
void bux_exit(int code) { exit(code); }
|
||||
void bux_assert(int cond, const char* file, int line, const char* expr) {
|
||||
if (!cond) {
|
||||
fprintf(stderr, "ASSERT FAILED: %s at %s:%d\n",
|
||||
expr ? expr : "?", file ? file : "?", line);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Checked arithmetic (same semantics as full runtime) ──────────────── */
|
||||
int64_t bux_div_i64(int64_t a, int64_t b) {
|
||||
if (b == 0) bux_panic("division by zero");
|
||||
return a / b;
|
||||
}
|
||||
int64_t bux_mod_i64(int64_t a, int64_t b) {
|
||||
if (b == 0) bux_panic("modulo by zero");
|
||||
return a % b;
|
||||
}
|
||||
int64_t bux_add_i64_checked(int64_t a, int64_t b) { return a + b; }
|
||||
int64_t bux_sub_i64_checked(int64_t a, int64_t b) { return a - b; }
|
||||
int64_t bux_mul_i64_checked(int64_t a, int64_t b) { return a * b; }
|
||||
int64_t bux_neg_i64_checked(int64_t a) { return -a; }
|
||||
|
||||
/* ── Strings ──────────────────────────────────────────────────────────── */
|
||||
unsigned int bux_strlen(const char* s) { return s ? (unsigned int)strlen(s) : 0; }
|
||||
int bux_strlen_c(const char* s) { return s ? (int)strlen(s) : 0; }
|
||||
int bux_strcmp(const char* a, const char* b) {
|
||||
if (!a) a = ""; if (!b) b = "";
|
||||
return strcmp(a, b);
|
||||
}
|
||||
int bux_strncmp(const char* a, const char* b, unsigned int n) {
|
||||
if (!a) a = ""; if (!b) b = "";
|
||||
return strncmp(a, b, (size_t)n);
|
||||
}
|
||||
char* bux_strcpy(char* dest, const char* src) {
|
||||
if (!dest) return NULL;
|
||||
if (!src) { dest[0] = 0; return dest; }
|
||||
return strcpy(dest, src);
|
||||
}
|
||||
char* bux_strcat(char* dest, const char* src) {
|
||||
if (!dest) return NULL;
|
||||
if (!src) return dest;
|
||||
return strcat(dest, src);
|
||||
}
|
||||
char* bux_strncpy(char* dest, const char* src, unsigned int n) {
|
||||
if (!dest) return NULL;
|
||||
if (!src) { if (n) dest[0] = 0; return dest; }
|
||||
return strncpy(dest, src, (size_t)n);
|
||||
}
|
||||
double bux_str_to_float(const char* s) { return s ? atof(s) : 0.0; }
|
||||
int64_t bux_str_to_int(const char* s) { return s ? (int64_t)atoll(s) : 0; }
|
||||
const char* bux_strstr(const char* haystack, const char* needle) {
|
||||
if (!haystack || !needle) return NULL;
|
||||
return strstr(haystack, needle);
|
||||
}
|
||||
unsigned int bux_str_offset(const char* pos, const char* base) {
|
||||
if (!pos || !base) return 0;
|
||||
return (unsigned int)(pos - base);
|
||||
}
|
||||
int bux_str_contains(const char* haystack, const char* needle) {
|
||||
if (!haystack || !needle) return 0;
|
||||
return strstr(haystack, needle) != NULL;
|
||||
}
|
||||
int bux_str_is_null(const char* s) { return s == NULL; }
|
||||
|
||||
char* bux_str_slice(const char* s, unsigned int start, unsigned int len) {
|
||||
if (!s) s = "";
|
||||
unsigned int sl = (unsigned int)strlen(s);
|
||||
if (start > sl) start = sl;
|
||||
if (start + len > sl) len = sl - start;
|
||||
char* out = (char*)bux_alloc(len + 1);
|
||||
memcpy(out, s + start, len);
|
||||
out[len] = 0;
|
||||
return out;
|
||||
}
|
||||
static int is_ws(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
||||
}
|
||||
char* bux_str_trim_left(const char* s) {
|
||||
if (!s) s = "";
|
||||
while (*s && is_ws(*s)) s++;
|
||||
unsigned int n = (unsigned int)strlen(s);
|
||||
char* out = (char*)bux_alloc(n + 1);
|
||||
memcpy(out, s, n + 1);
|
||||
return out;
|
||||
}
|
||||
char* bux_str_trim_right(const char* s) {
|
||||
if (!s) s = "";
|
||||
unsigned int n = (unsigned int)strlen(s);
|
||||
while (n > 0 && is_ws(s[n - 1])) n--;
|
||||
char* out = (char*)bux_alloc(n + 1);
|
||||
memcpy(out, s, n);
|
||||
out[n] = 0;
|
||||
return out;
|
||||
}
|
||||
char* bux_str_trim(const char* s) {
|
||||
char* a = bux_str_trim_left(s);
|
||||
char* b = bux_str_trim_right(a);
|
||||
bux_free(a);
|
||||
return b;
|
||||
}
|
||||
char* bux_int_to_str(int64_t n) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%lld", (long long)n);
|
||||
unsigned int len = (unsigned int)strlen(buf);
|
||||
char* out = (char*)bux_alloc(len + 1);
|
||||
memcpy(out, buf, len + 1);
|
||||
return out;
|
||||
}
|
||||
char* bux_float_to_string(double f) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%g", f);
|
||||
unsigned int len = (unsigned int)strlen(buf);
|
||||
char* out = (char*)bux_alloc(len + 1);
|
||||
memcpy(out, buf, len + 1);
|
||||
return out;
|
||||
}
|
||||
unsigned int bux_str_split_count(const char* s, const char* delim) {
|
||||
if (!s || !delim || !delim[0]) return 0;
|
||||
unsigned int count = 1;
|
||||
const char* p = s;
|
||||
size_t dlen = strlen(delim);
|
||||
while ((p = strstr(p, delim)) != NULL) {
|
||||
count++;
|
||||
p += dlen;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
char* bux_str_split_part(const char* s, const char* delim, unsigned int index) {
|
||||
if (!s || !delim) return (char*)bux_alloc(1);
|
||||
size_t dlen = strlen(delim);
|
||||
const char* start = s;
|
||||
unsigned int i = 0;
|
||||
while (i < index) {
|
||||
const char* p = strstr(start, delim);
|
||||
if (!p) return (char*)bux_alloc(1);
|
||||
start = p + dlen;
|
||||
i++;
|
||||
}
|
||||
const char* end = strstr(start, delim);
|
||||
size_t len = end ? (size_t)(end - start) : strlen(start);
|
||||
char* out = (char*)bux_alloc(len + 1);
|
||||
memcpy(out, start, len);
|
||||
out[len] = 0;
|
||||
return out;
|
||||
}
|
||||
char* bux_str_join2(const char* a, const char* b, const char* sep) {
|
||||
if (!a) a = ""; if (!b) b = ""; if (!sep) sep = "";
|
||||
size_t la = strlen(a), lb = strlen(b), ls = strlen(sep);
|
||||
char* out = (char*)bux_alloc(la + ls + lb + 1);
|
||||
memcpy(out, a, la);
|
||||
memcpy(out + la, sep, ls);
|
||||
memcpy(out + la + ls, b, lb + 1);
|
||||
return out;
|
||||
}
|
||||
char* bux_str_format(const char* fmt, const char* a0, const char* a1, const char* a2, const char* a3) {
|
||||
/* Minimal: return copy of fmt (full formatter is Unix runtime only) */
|
||||
(void)a0; (void)a1; (void)a2; (void)a3;
|
||||
if (!fmt) fmt = "";
|
||||
size_t n = strlen(fmt);
|
||||
char* out = (char*)bux_alloc(n + 1);
|
||||
memcpy(out, fmt, n + 1);
|
||||
return out;
|
||||
}
|
||||
char* bux_escape_c_string(const char* s, int len) {
|
||||
if (!s || len <= 0) {
|
||||
char* e = (char*)bux_alloc(1);
|
||||
e[0] = 0;
|
||||
return e;
|
||||
}
|
||||
char* buf = (char*)bux_alloc((size_t)len * 2 + 1);
|
||||
int j = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = s[i];
|
||||
switch (c) {
|
||||
case '\n': buf[j++] = '\\'; buf[j++] = 'n'; break;
|
||||
case '\r': buf[j++] = '\\'; buf[j++] = 'r'; break;
|
||||
case '\t': buf[j++] = '\\'; buf[j++] = 't'; break;
|
||||
case '\\': buf[j++] = '\\'; buf[j++] = '\\'; break;
|
||||
case '"': buf[j++] = '\\'; buf[j++] = '"'; break;
|
||||
default: buf[j++] = c; break;
|
||||
}
|
||||
}
|
||||
buf[j] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ── String builder ───────────────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
char* data;
|
||||
unsigned int len;
|
||||
unsigned int cap;
|
||||
} BuxStringBuilder;
|
||||
|
||||
BuxStringBuilder* bux_sb_new(unsigned int initial_cap) {
|
||||
if (initial_cap < 16) initial_cap = 16;
|
||||
BuxStringBuilder* sb = (BuxStringBuilder*)bux_alloc(sizeof(BuxStringBuilder));
|
||||
sb->data = (char*)bux_alloc(initial_cap);
|
||||
sb->data[0] = 0;
|
||||
sb->len = 0;
|
||||
sb->cap = initial_cap;
|
||||
return sb;
|
||||
}
|
||||
static void sb_ensure(BuxStringBuilder* sb, unsigned int need) {
|
||||
if (sb->len + need + 1 <= sb->cap) return;
|
||||
unsigned int ncap = sb->cap * 2;
|
||||
while (ncap < sb->len + need + 1) ncap *= 2;
|
||||
sb->data = (char*)bux_realloc(sb->data, ncap);
|
||||
sb->cap = ncap;
|
||||
}
|
||||
void bux_sb_append(BuxStringBuilder* sb, const char* s) {
|
||||
if (!sb || !s) return;
|
||||
unsigned int n = (unsigned int)strlen(s);
|
||||
sb_ensure(sb, n);
|
||||
memcpy(sb->data + sb->len, s, n + 1);
|
||||
sb->len += n;
|
||||
}
|
||||
void bux_sb_append_int(BuxStringBuilder* sb, int64_t n) {
|
||||
char* t = bux_int_to_str(n);
|
||||
bux_sb_append(sb, t);
|
||||
bux_free(t);
|
||||
}
|
||||
void bux_sb_append_float(BuxStringBuilder* sb, double f) {
|
||||
char* t = bux_float_to_string(f);
|
||||
bux_sb_append(sb, t);
|
||||
bux_free(t);
|
||||
}
|
||||
void bux_sb_append_char(BuxStringBuilder* sb, char c) {
|
||||
if (!sb) return;
|
||||
sb_ensure(sb, 1);
|
||||
sb->data[sb->len++] = c;
|
||||
sb->data[sb->len] = 0;
|
||||
}
|
||||
const char* bux_sb_build(BuxStringBuilder* sb) { return sb ? sb->data : ""; }
|
||||
void bux_sb_free(BuxStringBuilder* sb) {
|
||||
if (!sb) return;
|
||||
bux_free(sb->data);
|
||||
bux_free(sb);
|
||||
}
|
||||
|
||||
/* ── Files / paths ────────────────────────────────────────────────────── */
|
||||
char* bux_read_file(const char* path) {
|
||||
if (!path) return NULL;
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long sz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (sz < 0) { fclose(f); return NULL; }
|
||||
char* buf = (char*)bux_alloc((size_t)sz + 1);
|
||||
size_t n = fread(buf, 1, (size_t)sz, f);
|
||||
buf[n] = 0;
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
int bux_write_file(const char* path, const char* content) {
|
||||
if (!path) return 0;
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) return 0;
|
||||
if (content) fputs(content, f);
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
int bux_file_exists(const char* path) {
|
||||
if (!path) return 0;
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
char* bux_path_join(const char* a, const char* b) {
|
||||
if (!a && !b) { char* e = (char*)bux_alloc(1); e[0]=0; return e; }
|
||||
if (!a) {
|
||||
size_t n = strlen(b);
|
||||
char* r = (char*)bux_alloc(n + 1);
|
||||
memcpy(r, b, n + 1);
|
||||
return r;
|
||||
}
|
||||
if (!b) {
|
||||
size_t n = strlen(a);
|
||||
char* r = (char*)bux_alloc(n + 1);
|
||||
memcpy(r, a, n + 1);
|
||||
return r;
|
||||
}
|
||||
size_t la = strlen(a), lb = strlen(b);
|
||||
int need = (la > 0 && a[la-1] != '/' && a[la-1] != '\\') ? 1 : 0;
|
||||
char* r = (char*)bux_alloc(la + need + lb + 1);
|
||||
memcpy(r, a, la);
|
||||
if (need) r[la] = '/';
|
||||
memcpy(r + la + need, b, lb + 1);
|
||||
return r;
|
||||
}
|
||||
char* bux_path_parent(const char* path) {
|
||||
if (!path) { char* e = (char*)bux_alloc(1); e[0]=0; return e; }
|
||||
int len = (int)strlen(path);
|
||||
while (len > 0 && (path[len-1] == '/' || path[len-1] == '\\')) len--;
|
||||
while (len > 0 && path[len-1] != '/' && path[len-1] != '\\') len--;
|
||||
while (len > 0 && (path[len-1] == '/' || path[len-1] == '\\')) len--;
|
||||
if (len == 0) {
|
||||
char* d = (char*)bux_alloc(2);
|
||||
d[0] = '.'; d[1] = 0;
|
||||
return d;
|
||||
}
|
||||
char* r = (char*)bux_alloc((size_t)len + 1);
|
||||
memcpy(r, path, (size_t)len);
|
||||
r[len] = 0;
|
||||
return r;
|
||||
}
|
||||
char* bux_path_ext(const char* path) {
|
||||
if (!path) { char* e = (char*)bux_alloc(1); e[0]=0; return e; }
|
||||
const char* dot = strrchr(path, '.');
|
||||
if (!dot) { char* e = (char*)bux_alloc(1); e[0]=0; return e; }
|
||||
const char* slash = strrchr(path, '/');
|
||||
const char* bslash = strrchr(path, '\\');
|
||||
if (slash && slash > dot) { char* e = (char*)bux_alloc(1); e[0]=0; return e; }
|
||||
if (bslash && bslash > dot) { char* e = (char*)bux_alloc(1); e[0]=0; return e; }
|
||||
size_t n = strlen(dot);
|
||||
char* r = (char*)bux_alloc(n + 1);
|
||||
memcpy(r, dot, n + 1);
|
||||
return r;
|
||||
}
|
||||
int bux_mkdir_if_needed(const char* path) {
|
||||
if (!path) return -1;
|
||||
return bux_mkdir_one(path);
|
||||
}
|
||||
int bux_dir_exists(const char* path) {
|
||||
if (!path) return 0;
|
||||
#if BUX_IS_WIN
|
||||
DWORD attr = GetFileAttributesA(path);
|
||||
return (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY);
|
||||
#else
|
||||
struct stat st;
|
||||
return (stat(path, &st) == 0 && S_ISDIR(st.st_mode));
|
||||
#endif
|
||||
}
|
||||
char** bux_list_dir(const char* dir, const char* ext, int* out_count) {
|
||||
(void)dir; (void)ext;
|
||||
if (out_count) *out_count = 0;
|
||||
return NULL; /* stub: recursive listing not ported */
|
||||
}
|
||||
|
||||
/* ── Math / hash ──────────────────────────────────────────────────────── */
|
||||
double bux_sqrt(double x) { return sqrt(x); }
|
||||
double bux_pow(double x, double y) { return pow(x, y); }
|
||||
int64_t bux_abs_i64(int64_t x) { return x < 0 ? -x : x; }
|
||||
double bux_abs_f64(double x) { return x < 0 ? -x : x; }
|
||||
int64_t bux_min_i64(int64_t a, int64_t b) { return a < b ? a : b; }
|
||||
int64_t bux_max_i64(int64_t a, int64_t b) { return a > b ? a : b; }
|
||||
double bux_min_f64(double a, double b) { return a < b ? a : b; }
|
||||
double bux_max_f64(double a, double b) { return a > b ? a : b; }
|
||||
unsigned int bux_hash_bytes(const void* ptr, size_t size) {
|
||||
if (!ptr) return 0;
|
||||
unsigned int hash = 5381;
|
||||
const unsigned char* b = (const unsigned char*)ptr;
|
||||
for (size_t i = 0; i < size; i++) hash = ((hash << 5) + hash) + b[i];
|
||||
return hash;
|
||||
}
|
||||
int bux_mem_eq(const void* a, const void* b, size_t size) {
|
||||
if (a == b) return 1;
|
||||
if (!a || !b) return 0;
|
||||
return memcmp(a, b, size) == 0;
|
||||
}
|
||||
unsigned int bux_hash_string(const char* s) {
|
||||
return bux_hash_bytes(s, s ? strlen(s) : 0);
|
||||
}
|
||||
|
||||
/* ── OS env / cwd ─────────────────────────────────────────────────────── */
|
||||
const char* bux_getenv(const char* name) {
|
||||
if (!name) return "";
|
||||
const char* v = getenv(name);
|
||||
return v ? v : "";
|
||||
}
|
||||
const char* bux_cc_ld_stable(void) { return ""; }
|
||||
int bux_setenv(const char* name, const char* value) {
|
||||
if (!name || !value) return -1;
|
||||
#if BUX_IS_WIN
|
||||
return _putenv_s(name, value) == 0 ? 0 : -1;
|
||||
#else
|
||||
return setenv(name, value, 1);
|
||||
#endif
|
||||
}
|
||||
const char* bux_getcwd(void) {
|
||||
static char buf[4096];
|
||||
#if BUX_IS_WIN
|
||||
if (_getcwd(buf, (int)sizeof(buf))) return buf;
|
||||
#else
|
||||
if (getcwd(buf, sizeof(buf))) return buf;
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
int bux_chdir(const char* path) {
|
||||
if (!path) return -1;
|
||||
#if BUX_IS_WIN
|
||||
return _chdir(path);
|
||||
#else
|
||||
return chdir(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ── Time ─────────────────────────────────────────────────────────────── */
|
||||
int64_t bux_time_ms(void) {
|
||||
#if BUX_IS_WIN
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
ULARGE_INTEGER u;
|
||||
u.LowPart = ft.dwLowDateTime;
|
||||
u.HighPart = ft.dwHighDateTime;
|
||||
/* 100-ns intervals since 1601 → ms since Unix epoch */
|
||||
return (int64_t)((u.QuadPart / 10000ULL) - 11644473600000ULL);
|
||||
#else
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) == 0)
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
return (int64_t)time(NULL) * 1000;
|
||||
#endif
|
||||
}
|
||||
int64_t bux_time_us(void) { return bux_time_ms() * 1000; }
|
||||
void bux_sleep_ms(int64_t ms) {
|
||||
if (ms <= 0) return;
|
||||
#if BUX_IS_WIN
|
||||
Sleep((DWORD)ms);
|
||||
#else
|
||||
struct timespec ts;
|
||||
ts.tv_sec = (time_t)(ms / 1000);
|
||||
ts.tv_nsec = (long)((ms % 1000) * 1000000);
|
||||
nanosleep(&ts, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ── Process ──────────────────────────────────────────────────────────── */
|
||||
int bux_system(const char* cmd) { return cmd ? system(cmd) : -1; }
|
||||
int bux_run_nim(const char* nim_file, const char* out_bin) {
|
||||
char cmd[4096];
|
||||
snprintf(cmd, sizeof(cmd), "nim c -o:%s -d:release --gc:orc %s 2>&1",
|
||||
out_bin ? out_bin : "a.out", nim_file ? nim_file : "");
|
||||
return system(cmd);
|
||||
}
|
||||
int bux_process_run(const char* cmd) { return bux_system(cmd); }
|
||||
char* bux_process_output(const char* cmd) {
|
||||
(void)cmd;
|
||||
return NULL; /* popen portability varies; stub on minimal runtime */
|
||||
}
|
||||
|
||||
/* ── Tasks / channels / mutex / async — stubs ─────────────────────────── */
|
||||
void bux_task_init(int num_workers) { (void)num_workers; }
|
||||
void bux_task_shutdown(void) {}
|
||||
void* bux_task_spawn(void* (*func)(void*), void* arg) {
|
||||
(void)func; (void)arg;
|
||||
return NULL;
|
||||
}
|
||||
void bux_task_join(void* handle) { (void)handle; }
|
||||
void bux_task_sleep(int64_t ms) { bux_sleep_ms(ms); }
|
||||
void bux_task_yield(void) {}
|
||||
int bux_task_current_id(void) { return 0; }
|
||||
|
||||
void* bux_channel_new(int64_t capacity, int64_t elem_size) {
|
||||
(void)capacity; (void)elem_size;
|
||||
return NULL;
|
||||
}
|
||||
void bux_channel_send(void* handle, void* elem) { (void)handle; (void)elem; }
|
||||
int bux_channel_recv(void* handle, void* out) { (void)handle; (void)out; return 0; }
|
||||
void bux_channel_close(void* handle) { (void)handle; }
|
||||
void bux_channel_free(void* handle) { (void)handle; }
|
||||
|
||||
void* bux_mutex_new(void) { return bux_alloc(1); }
|
||||
void bux_mutex_lock(void* handle) { (void)handle; }
|
||||
void bux_mutex_unlock(void* handle) { (void)handle; }
|
||||
void bux_mutex_free(void* handle) { bux_free(handle); }
|
||||
void* bux_rwlock_new(void) { return bux_alloc(1); }
|
||||
void bux_rwlock_rdlock(void* handle) { (void)handle; }
|
||||
void bux_rwlock_wrlock(void* handle) { (void)handle; }
|
||||
void bux_rwlock_unlock(void* handle) { (void)handle; }
|
||||
void bux_rwlock_free(void* handle) { bux_free(handle); }
|
||||
|
||||
void* bux_async_spawn(void (*func)(void)) { (void)func; return NULL; }
|
||||
void bux_async_yield(void) {}
|
||||
void bux_async_run(void) {}
|
||||
void* bux_async_await(void* handle) { (void)handle; return NULL; }
|
||||
void bux_async_sleep(int64_t ms) { bux_sleep_ms(ms); }
|
||||
void bux_async_return(void* value, size_t size) { (void)value; (void)size; }
|
||||
void* bux_async_result(void* handle) { (void)handle; return NULL; }
|
||||
|
||||
/* ── Sockets — stubs ──────────────────────────────────────────────────── */
|
||||
int bux_socket_create(void) { return -1; }
|
||||
int bux_socket_reuse(int fd) { (void)fd; return -1; }
|
||||
int bux_socket_bind(int fd, const char* addr, int port) {
|
||||
(void)fd; (void)addr; (void)port; return -1;
|
||||
}
|
||||
int bux_socket_listen(int fd, int backlog) { (void)fd; (void)backlog; return -1; }
|
||||
int bux_socket_accept(int fd) { (void)fd; return -1; }
|
||||
int bux_socket_connect(int fd, const char* addr, int port) {
|
||||
(void)fd; (void)addr; (void)port; return -1;
|
||||
}
|
||||
int bux_socket_send(int fd, const char* data, int len) {
|
||||
(void)fd; (void)data; (void)len; return -1;
|
||||
}
|
||||
/* BuxString used by full runtime; provide a simple struct-compatible layout */
|
||||
typedef struct { char* data; int len; } BuxString;
|
||||
BuxString bux_socket_recv(int fd, int max_len) {
|
||||
(void)fd; (void)max_len;
|
||||
BuxString s; s.data = NULL; s.len = 0; return s;
|
||||
}
|
||||
int bux_socket_close(int fd) { (void)fd; return -1; }
|
||||
const char* bux_socket_error(void) { return "sockets not available on this platform"; }
|
||||
|
||||
/* ── Crypto — stubs (no OpenSSL) ──────────────────────────────────────── */
|
||||
static void zero_out(unsigned char* out, int n) {
|
||||
if (out && n > 0) memset(out, 0, (size_t)n);
|
||||
}
|
||||
void bux_sha1(const char* data, int len, unsigned char* out) {
|
||||
(void)data; (void)len; zero_out(out, 20);
|
||||
}
|
||||
void bux_sha256(const char* data, int len, unsigned char* out) {
|
||||
(void)data; (void)len; zero_out(out, 32);
|
||||
}
|
||||
void bux_sha384(const char* data, int len, unsigned char* out) {
|
||||
(void)data; (void)len; zero_out(out, 48);
|
||||
}
|
||||
void bux_sha512(const char* data, int len, unsigned char* out) {
|
||||
(void)data; (void)len; zero_out(out, 64);
|
||||
}
|
||||
void bux_hmac_sha256(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) {
|
||||
(void)key; (void)keylen; (void)msg; (void)msglen; zero_out(out, 32);
|
||||
}
|
||||
void bux_hmac_sha384(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) {
|
||||
(void)key; (void)keylen; (void)msg; (void)msglen; zero_out(out, 48);
|
||||
}
|
||||
void bux_hmac_sha512(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) {
|
||||
(void)key; (void)keylen; (void)msg; (void)msglen; zero_out(out, 64);
|
||||
}
|
||||
int bux_random_bytes(unsigned char* buf, int len) {
|
||||
if (!buf || len <= 0) return 0;
|
||||
#if BUX_IS_WIN
|
||||
/* Best-effort: not cryptographically strong */
|
||||
for (int i = 0; i < len; i++) buf[i] = (unsigned char)(rand() & 0xFF);
|
||||
return 1;
|
||||
#else
|
||||
for (int i = 0; i < len; i++) buf[i] = (unsigned char)(rand() & 0xFF);
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
char* bux_base64_encode(const unsigned char* in, int inlen) {
|
||||
(void)in; (void)inlen;
|
||||
char* o = (char*)bux_alloc(1); o[0] = 0; return o;
|
||||
}
|
||||
char* bux_base64_decode(const char* in, int inlen, int* outlen) {
|
||||
(void)in; (void)inlen;
|
||||
if (outlen) *outlen = 0;
|
||||
return (char*)bux_alloc(1);
|
||||
}
|
||||
char* bux_base64url_encode(const unsigned char* in, int inlen) {
|
||||
return bux_base64_encode(in, inlen);
|
||||
}
|
||||
char* bux_base64url_decode(const char* in, int inlen, int* outlen) {
|
||||
return bux_base64_decode(in, inlen, outlen);
|
||||
}
|
||||
char* bux_bytes_to_hex(const unsigned char* data, int len) {
|
||||
if (!data || len <= 0) { char* e = (char*)bux_alloc(1); e[0]=0; return e; }
|
||||
char* out = (char*)bux_alloc((size_t)len * 2 + 1);
|
||||
static const char* hex = "0123456789abcdef";
|
||||
for (int i = 0; i < len; i++) {
|
||||
out[i*2] = hex[(data[i] >> 4) & 0xF];
|
||||
out[i*2+1] = hex[data[i] & 0xF];
|
||||
}
|
||||
out[len*2] = 0;
|
||||
return out;
|
||||
}
|
||||
int bux_aes_256_cbc_encrypt(const unsigned char* key, const unsigned char* iv,
|
||||
const char* in, int inlen, unsigned char* out, int* outlen) {
|
||||
(void)key; (void)iv; (void)in; (void)inlen; (void)out;
|
||||
if (outlen) *outlen = 0;
|
||||
return 0;
|
||||
}
|
||||
int bux_aes_256_cbc_decrypt(const unsigned char* key, const unsigned char* iv,
|
||||
const char* in, int inlen, unsigned char* out, int* outlen) {
|
||||
(void)key; (void)iv; (void)in; (void)inlen; (void)out;
|
||||
if (outlen) *outlen = 0;
|
||||
return 0;
|
||||
}
|
||||
int bux_aes_256_gcm_encrypt(const unsigned char* key, const unsigned char* iv, int ivlen,
|
||||
const char* in, int inlen, unsigned char* out, int* outlen,
|
||||
unsigned char* tag) {
|
||||
(void)key; (void)iv; (void)ivlen; (void)in; (void)inlen; (void)out; (void)tag;
|
||||
if (outlen) *outlen = 0;
|
||||
return 0;
|
||||
}
|
||||
int bux_aes_256_gcm_decrypt(const unsigned char* key, const unsigned char* iv, int ivlen,
|
||||
const char* in, int inlen, const unsigned char* tag,
|
||||
unsigned char* out, int* outlen) {
|
||||
(void)key; (void)iv; (void)ivlen; (void)in; (void)inlen; (void)tag; (void)out;
|
||||
if (outlen) *outlen = 0;
|
||||
return 0;
|
||||
}
|
||||
char* bux_rsa_sign_sha256(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen;
|
||||
if (siglen) *siglen = 0; return NULL;
|
||||
}
|
||||
char* bux_rsa_sign_sha384(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen;
|
||||
if (siglen) *siglen = 0; return NULL;
|
||||
}
|
||||
char* bux_rsa_sign_sha512(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen;
|
||||
if (siglen) *siglen = 0; return NULL;
|
||||
}
|
||||
int bux_rsa_verify_sha256(const char* pem, int keylen, const char* data, int datalen,
|
||||
const char* sig, int siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen; (void)sig; (void)siglen;
|
||||
return 0;
|
||||
}
|
||||
int bux_rsa_verify_sha384(const char* pem, int keylen, const char* data, int datalen,
|
||||
const char* sig, int siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen; (void)sig; (void)siglen;
|
||||
return 0;
|
||||
}
|
||||
int bux_rsa_verify_sha512(const char* pem, int keylen, const char* data, int datalen,
|
||||
const char* sig, int siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen; (void)sig; (void)siglen;
|
||||
return 0;
|
||||
}
|
||||
char* bux_ecdsa_sign_p256(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen;
|
||||
if (siglen) *siglen = 0; return NULL;
|
||||
}
|
||||
char* bux_ecdsa_sign_p384(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen;
|
||||
if (siglen) *siglen = 0; return NULL;
|
||||
}
|
||||
int bux_ecdsa_verify_p256(const char* pem, int keylen, const char* data, int datalen,
|
||||
const char* sig, int siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen; (void)sig; (void)siglen;
|
||||
return 0;
|
||||
}
|
||||
int bux_ecdsa_verify_p384(const char* pem, int keylen, const char* data, int datalen,
|
||||
const char* sig, int siglen) {
|
||||
(void)pem; (void)keylen; (void)data; (void)datalen; (void)sig; (void)siglen;
|
||||
return 0;
|
||||
}
|
||||
int bux_ed25519_keypair(unsigned char* pub, unsigned char* priv) {
|
||||
zero_out(pub, 32); zero_out(priv, 32); return 0;
|
||||
}
|
||||
int bux_ed25519_sign(const char* priv, const char* data, int datalen, unsigned char* sig) {
|
||||
(void)priv; (void)data; (void)datalen; zero_out(sig, 64); return 0;
|
||||
}
|
||||
int bux_ed25519_verify(const char* pub, const char* sig, const char* data, int datalen) {
|
||||
(void)pub; (void)sig; (void)data; (void)datalen; return 0;
|
||||
}
|
||||
|
||||
/* Legacy string helpers used by some mono paths */
|
||||
typedef struct { char* data; int len; } BuxStringLegacy;
|
||||
BuxStringLegacy bux_string_from_cstr(const char* s) {
|
||||
BuxStringLegacy r;
|
||||
r.data = (char*)(s ? s : "");
|
||||
r.len = s ? (int)strlen(s) : 0;
|
||||
return r;
|
||||
}
|
||||
BuxStringLegacy bux_string_concat(BuxStringLegacy a, BuxStringLegacy b) {
|
||||
(void)a; (void)b;
|
||||
BuxStringLegacy r; r.data = ""; r.len = 0; return r;
|
||||
}
|
||||
+9
-1
@@ -128,6 +128,8 @@ module Ast {
|
||||
const ekStringInterp: int = 26;
|
||||
const ekClosure: int = 27;
|
||||
const ekMacroCall: int = 28; // name!(args) — expanded before sema
|
||||
const ekMacroStmt: int = 29; // `$s:stmt` arg wrapper (expand only)
|
||||
const ekMacroPat: int = 30; // `$p:pat` arg wrapper (expand only)
|
||||
|
||||
struct ExprList {
|
||||
expr: *Expr,
|
||||
@@ -188,6 +190,9 @@ module Ast {
|
||||
// Match arms (for ekMatch)
|
||||
matchArms: *MatchArm,
|
||||
matchArmCount: int,
|
||||
// Macro fragment wrappers (session 72)
|
||||
macroStmt: *Stmt, // ekMacroStmt
|
||||
macroPat: *Pattern, // ekMacroPat
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -387,7 +392,8 @@ module Ast {
|
||||
refType: null as *TypeExpr, refBlock: null as *Block,
|
||||
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
|
||||
structName: "", structFieldCount: 0,
|
||||
callArgs: null as *ExprList, callArgCount: 0 };
|
||||
callArgs: null as *ExprList, callArgCount: 0,
|
||||
macroStmt: null as *Stmt, macroPat: null as *Pattern };
|
||||
}
|
||||
|
||||
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
|
||||
@@ -706,6 +712,8 @@ module Ast {
|
||||
n.callArgCount = e.callArgCount;
|
||||
n.matchArms = Ast_CloneMatchArm(e.matchArms);
|
||||
n.matchArmCount = e.matchArmCount;
|
||||
n.macroStmt = Ast_CloneStmt(e.macroStmt);
|
||||
n.macroPat = Ast_ClonePattern(e.macroPat);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
+495
-35
@@ -76,6 +76,60 @@ module CBackend {
|
||||
movedName5: String,
|
||||
movedName6: String,
|
||||
movedName7: String,
|
||||
// Partial field moves: (var, field) pairs (session 70)
|
||||
partialCount: int,
|
||||
partialVar0: String,
|
||||
partialField0: String,
|
||||
partialVar1: String,
|
||||
partialField1: String,
|
||||
partialVar2: String,
|
||||
partialField2: String,
|
||||
partialVar3: String,
|
||||
partialField3: String,
|
||||
partialVar4: String,
|
||||
partialField4: String,
|
||||
partialVar5: String,
|
||||
partialField5: String,
|
||||
partialVar6: String,
|
||||
partialField6: String,
|
||||
partialVar7: String,
|
||||
partialField7: String,
|
||||
// Local name → typeName for remaining-field Drop lookup
|
||||
localCount: int,
|
||||
localName0: String,
|
||||
localType0: String,
|
||||
localName1: String,
|
||||
localType1: String,
|
||||
localName2: String,
|
||||
localType2: String,
|
||||
localName3: String,
|
||||
localType3: String,
|
||||
localName4: String,
|
||||
localType4: String,
|
||||
localName5: String,
|
||||
localType5: String,
|
||||
localName6: String,
|
||||
localType6: String,
|
||||
localName7: String,
|
||||
localType7: String,
|
||||
// Pointer aliases: p → bag when `p = &bag` (session 74)
|
||||
aliasCount: int,
|
||||
aliasPtr0: String,
|
||||
aliasOwner0: String,
|
||||
aliasPtr1: String,
|
||||
aliasOwner1: String,
|
||||
aliasPtr2: String,
|
||||
aliasOwner2: String,
|
||||
aliasPtr3: String,
|
||||
aliasOwner3: String,
|
||||
aliasPtr4: String,
|
||||
aliasOwner4: String,
|
||||
aliasPtr5: String,
|
||||
aliasOwner5: String,
|
||||
aliasPtr6: String,
|
||||
aliasOwner6: String,
|
||||
aliasPtr7: String,
|
||||
aliasOwner7: String,
|
||||
tmpCounter: int,
|
||||
currentRetType: String,
|
||||
// #line debug maps (E.4 selfhost parity)
|
||||
@@ -130,6 +184,329 @@ module CBackend {
|
||||
return true;
|
||||
}
|
||||
|
||||
func CBE_AddPtrAlias(cbe: *CEmitter, ptrName: String, ownerName: String) {
|
||||
if ptrName == null as String || String_Eq(ptrName, "") { return; }
|
||||
if ownerName == null as String || String_Eq(ownerName, "") { return; }
|
||||
// Update existing alias for same pointer
|
||||
var i: int = 0;
|
||||
while i < cbe.aliasCount {
|
||||
var pn: String = "";
|
||||
if i == 0 { pn = cbe.aliasPtr0; }
|
||||
else if i == 1 { pn = cbe.aliasPtr1; }
|
||||
else if i == 2 { pn = cbe.aliasPtr2; }
|
||||
else if i == 3 { pn = cbe.aliasPtr3; }
|
||||
else if i == 4 { pn = cbe.aliasPtr4; }
|
||||
else if i == 5 { pn = cbe.aliasPtr5; }
|
||||
else if i == 6 { pn = cbe.aliasPtr6; }
|
||||
else if i == 7 { pn = cbe.aliasPtr7; }
|
||||
if String_Eq(pn, ptrName) {
|
||||
if i == 0 { cbe.aliasOwner0 = ownerName; }
|
||||
else if i == 1 { cbe.aliasOwner1 = ownerName; }
|
||||
else if i == 2 { cbe.aliasOwner2 = ownerName; }
|
||||
else if i == 3 { cbe.aliasOwner3 = ownerName; }
|
||||
else if i == 4 { cbe.aliasOwner4 = ownerName; }
|
||||
else if i == 5 { cbe.aliasOwner5 = ownerName; }
|
||||
else if i == 6 { cbe.aliasOwner6 = ownerName; }
|
||||
else if i == 7 { cbe.aliasOwner7 = ownerName; }
|
||||
return;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
if cbe.aliasCount >= 8 { return; }
|
||||
if cbe.aliasCount == 0 { cbe.aliasPtr0 = ptrName; cbe.aliasOwner0 = ownerName; }
|
||||
else if cbe.aliasCount == 1 { cbe.aliasPtr1 = ptrName; cbe.aliasOwner1 = ownerName; }
|
||||
else if cbe.aliasCount == 2 { cbe.aliasPtr2 = ptrName; cbe.aliasOwner2 = ownerName; }
|
||||
else if cbe.aliasCount == 3 { cbe.aliasPtr3 = ptrName; cbe.aliasOwner3 = ownerName; }
|
||||
else if cbe.aliasCount == 4 { cbe.aliasPtr4 = ptrName; cbe.aliasOwner4 = ownerName; }
|
||||
else if cbe.aliasCount == 5 { cbe.aliasPtr5 = ptrName; cbe.aliasOwner5 = ownerName; }
|
||||
else if cbe.aliasCount == 6 { cbe.aliasPtr6 = ptrName; cbe.aliasOwner6 = ownerName; }
|
||||
else if cbe.aliasCount == 7 { cbe.aliasPtr7 = ptrName; cbe.aliasOwner7 = ownerName; }
|
||||
cbe.aliasCount = cbe.aliasCount + 1;
|
||||
}
|
||||
|
||||
func CBE_ResolvePtrAlias(cbe: *CEmitter, name: String) -> String {
|
||||
var cur: String = name;
|
||||
var guard: int = 0;
|
||||
while guard < 8 {
|
||||
var found: bool = false;
|
||||
var owner: String = "";
|
||||
var i: int = 0;
|
||||
while i < cbe.aliasCount {
|
||||
var pn: String = "";
|
||||
var on: String = "";
|
||||
if i == 0 { pn = cbe.aliasPtr0; on = cbe.aliasOwner0; }
|
||||
else if i == 1 { pn = cbe.aliasPtr1; on = cbe.aliasOwner1; }
|
||||
else if i == 2 { pn = cbe.aliasPtr2; on = cbe.aliasOwner2; }
|
||||
else if i == 3 { pn = cbe.aliasPtr3; on = cbe.aliasOwner3; }
|
||||
else if i == 4 { pn = cbe.aliasPtr4; on = cbe.aliasOwner4; }
|
||||
else if i == 5 { pn = cbe.aliasPtr5; on = cbe.aliasOwner5; }
|
||||
else if i == 6 { pn = cbe.aliasPtr6; on = cbe.aliasOwner6; }
|
||||
else if i == 7 { pn = cbe.aliasPtr7; on = cbe.aliasOwner7; }
|
||||
if String_Eq(pn, cur) {
|
||||
owner = on;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
if !found { break; }
|
||||
cur = owner;
|
||||
guard = guard + 1;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
/// Record alias if init is `&local` (hUnary Amp of hVar).
|
||||
func CBE_TryRecordPtrAlias(cbe: *CEmitter, ptrName: String, init: *HirNode) {
|
||||
if init == null as *HirNode { return; }
|
||||
var n: *HirNode = init;
|
||||
if n.kind == hLoad { n = n.child1; }
|
||||
if n == null as *HirNode { return; }
|
||||
if n.kind == hUnary && n.intValue == tkAmp {
|
||||
let op: *HirNode = n.child1;
|
||||
if op != null as *HirNode && op.kind == hVar {
|
||||
CBE_AddPtrAlias(cbe, ptrName, op.strValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Base local name under load/field/deref chains (for partial field moves).
|
||||
/// Resolves pointer aliases: p → bag when `p = &bag`.
|
||||
func CBE_BaseVarName(cbe: *CEmitter, node: *HirNode) -> String {
|
||||
if node == null as *HirNode { return ""; }
|
||||
if node.kind == hVar {
|
||||
return CBE_ResolvePtrAlias(cbe, node.strValue);
|
||||
}
|
||||
// Deref: *p → follow p's alias
|
||||
if node.kind == hUnary && node.intValue == tkStar {
|
||||
return CBE_BaseVarName(cbe, node.child1);
|
||||
}
|
||||
if node.kind == hLoad || node.kind == hFieldAccess || node.kind == hFieldPtr {
|
||||
return CBE_BaseVarName(cbe, node.child1);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// Back-compat: BaseVarName without emitter (no alias resolve).
|
||||
func CBE_BaseVarNameRaw(node: *HirNode) -> String {
|
||||
if node == null as *HirNode { return ""; }
|
||||
if node.kind == hVar { return node.strValue; }
|
||||
if node.kind == hUnary && node.intValue == tkStar {
|
||||
return CBE_BaseVarNameRaw(node.child1);
|
||||
}
|
||||
if node.kind == hLoad || node.kind == hFieldAccess || node.kind == hFieldPtr {
|
||||
return CBE_BaseVarNameRaw(node.child1);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// Dotted field path from nested hFieldAccess/hFieldPtr chain: "inner.items".
|
||||
/// Peels explicit derefs (`*p`) so path is field-only.
|
||||
func CBE_FieldPathFromNode(node: *HirNode) -> String {
|
||||
if node == null as *HirNode { return ""; }
|
||||
if node.kind == hLoad {
|
||||
return CBE_FieldPathFromNode(node.child1);
|
||||
}
|
||||
if node.kind == hUnary && node.intValue == tkStar {
|
||||
return CBE_FieldPathFromNode(node.child1);
|
||||
}
|
||||
if node.kind == hFieldAccess || node.kind == hFieldPtr {
|
||||
let rest: String = CBE_FieldPathFromNode(node.child1);
|
||||
let seg: String = node.strValue;
|
||||
if String_Eq(seg, "") { return rest; }
|
||||
if String_Eq(rest, "") { return seg; }
|
||||
return String_Concat(rest, String_Concat(".", seg));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// True if dotted path `key` is exactly moved, or is a prefix of a moved path.
|
||||
func CBE_PathIsFullyMoved(cbe: *CEmitter, varName: String, key: String) -> bool {
|
||||
return CBE_IsPartialMovedField(cbe, varName, key);
|
||||
}
|
||||
|
||||
func CBE_PathHasNestedMove(cbe: *CEmitter, varName: String, key: String) -> bool {
|
||||
let prefix: String = String_Concat(key, ".");
|
||||
var i: int = 0;
|
||||
while i < cbe.partialCount {
|
||||
var pvar: String = "";
|
||||
var pfield: String = "";
|
||||
if i == 0 { pvar = cbe.partialVar0; pfield = cbe.partialField0; }
|
||||
else if i == 1 { pvar = cbe.partialVar1; pfield = cbe.partialField1; }
|
||||
else if i == 2 { pvar = cbe.partialVar2; pfield = cbe.partialField2; }
|
||||
else if i == 3 { pvar = cbe.partialVar3; pfield = cbe.partialField3; }
|
||||
else if i == 4 { pvar = cbe.partialVar4; pfield = cbe.partialField4; }
|
||||
else if i == 5 { pvar = cbe.partialVar5; pfield = cbe.partialField5; }
|
||||
else if i == 6 { pvar = cbe.partialVar6; pfield = cbe.partialField6; }
|
||||
else if i == 7 { pvar = cbe.partialVar7; pfield = cbe.partialField7; }
|
||||
if String_Eq(pvar, varName) && String_StartsWith(pfield, prefix) {
|
||||
return true;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func CBE_RegisterLocalType(cbe: *CEmitter, name: String, typeName: String) {
|
||||
if name == null as String || String_Eq(name, "") { return; }
|
||||
if typeName == null as String || String_Eq(typeName, "") { return; }
|
||||
if cbe.localCount >= 8 { return; }
|
||||
let tn: String = CBE_NormalizeTypeName(typeName);
|
||||
if cbe.localCount == 0 { cbe.localName0 = name; cbe.localType0 = tn; }
|
||||
else if cbe.localCount == 1 { cbe.localName1 = name; cbe.localType1 = tn; }
|
||||
else if cbe.localCount == 2 { cbe.localName2 = name; cbe.localType2 = tn; }
|
||||
else if cbe.localCount == 3 { cbe.localName3 = name; cbe.localType3 = tn; }
|
||||
else if cbe.localCount == 4 { cbe.localName4 = name; cbe.localType4 = tn; }
|
||||
else if cbe.localCount == 5 { cbe.localName5 = name; cbe.localType5 = tn; }
|
||||
else if cbe.localCount == 6 { cbe.localName6 = name; cbe.localType6 = tn; }
|
||||
else if cbe.localCount == 7 { cbe.localName7 = name; cbe.localType7 = tn; }
|
||||
cbe.localCount = cbe.localCount + 1;
|
||||
}
|
||||
|
||||
func CBE_LookupLocalType(cbe: *CEmitter, name: String) -> String {
|
||||
if cbe.localCount > 0 && String_Eq(cbe.localName0, name) { return cbe.localType0; }
|
||||
if cbe.localCount > 1 && String_Eq(cbe.localName1, name) { return cbe.localType1; }
|
||||
if cbe.localCount > 2 && String_Eq(cbe.localName2, name) { return cbe.localType2; }
|
||||
if cbe.localCount > 3 && String_Eq(cbe.localName3, name) { return cbe.localType3; }
|
||||
if cbe.localCount > 4 && String_Eq(cbe.localName4, name) { return cbe.localType4; }
|
||||
if cbe.localCount > 5 && String_Eq(cbe.localName5, name) { return cbe.localType5; }
|
||||
if cbe.localCount > 6 && String_Eq(cbe.localName6, name) { return cbe.localType6; }
|
||||
if cbe.localCount > 7 && String_Eq(cbe.localName7, name) { return cbe.localType7; }
|
||||
return "";
|
||||
}
|
||||
|
||||
func CBE_AddPartialMoved(cbe: *CEmitter, varName: String, fieldName: String) {
|
||||
if varName == null as String || String_Eq(varName, "") { return; }
|
||||
if fieldName == null as String || String_Eq(fieldName, "") { return; }
|
||||
if cbe.partialCount >= 8 { return; }
|
||||
if cbe.partialCount == 0 { cbe.partialVar0 = varName; cbe.partialField0 = fieldName; }
|
||||
else if cbe.partialCount == 1 { cbe.partialVar1 = varName; cbe.partialField1 = fieldName; }
|
||||
else if cbe.partialCount == 2 { cbe.partialVar2 = varName; cbe.partialField2 = fieldName; }
|
||||
else if cbe.partialCount == 3 { cbe.partialVar3 = varName; cbe.partialField3 = fieldName; }
|
||||
else if cbe.partialCount == 4 { cbe.partialVar4 = varName; cbe.partialField4 = fieldName; }
|
||||
else if cbe.partialCount == 5 { cbe.partialVar5 = varName; cbe.partialField5 = fieldName; }
|
||||
else if cbe.partialCount == 6 { cbe.partialVar6 = varName; cbe.partialField6 = fieldName; }
|
||||
else if cbe.partialCount == 7 { cbe.partialVar7 = varName; cbe.partialField7 = fieldName; }
|
||||
cbe.partialCount = cbe.partialCount + 1;
|
||||
}
|
||||
|
||||
func CBE_IsPartialMovedField(cbe: *CEmitter, varName: String, fieldName: String) -> bool {
|
||||
if cbe.partialCount > 0 && String_Eq(cbe.partialVar0, varName) && String_Eq(cbe.partialField0, fieldName) { return true; }
|
||||
if cbe.partialCount > 1 && String_Eq(cbe.partialVar1, varName) && String_Eq(cbe.partialField1, fieldName) { return true; }
|
||||
if cbe.partialCount > 2 && String_Eq(cbe.partialVar2, varName) && String_Eq(cbe.partialField2, fieldName) { return true; }
|
||||
if cbe.partialCount > 3 && String_Eq(cbe.partialVar3, varName) && String_Eq(cbe.partialField3, fieldName) { return true; }
|
||||
if cbe.partialCount > 4 && String_Eq(cbe.partialVar4, varName) && String_Eq(cbe.partialField4, fieldName) { return true; }
|
||||
if cbe.partialCount > 5 && String_Eq(cbe.partialVar5, varName) && String_Eq(cbe.partialField5, fieldName) { return true; }
|
||||
if cbe.partialCount > 6 && String_Eq(cbe.partialVar6, varName) && String_Eq(cbe.partialField6, fieldName) { return true; }
|
||||
if cbe.partialCount > 7 && String_Eq(cbe.partialVar7, varName) && String_Eq(cbe.partialField7, fieldName) { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
func CBE_HasPartialMoved(cbe: *CEmitter, varName: String) -> bool {
|
||||
if cbe.partialCount > 0 && String_Eq(cbe.partialVar0, varName) { return true; }
|
||||
if cbe.partialCount > 1 && String_Eq(cbe.partialVar1, varName) { return true; }
|
||||
if cbe.partialCount > 2 && String_Eq(cbe.partialVar2, varName) { return true; }
|
||||
if cbe.partialCount > 3 && String_Eq(cbe.partialVar3, varName) { return true; }
|
||||
if cbe.partialCount > 4 && String_Eq(cbe.partialVar4, varName) { return true; }
|
||||
if cbe.partialCount > 5 && String_Eq(cbe.partialVar5, varName) { return true; }
|
||||
if cbe.partialCount > 6 && String_Eq(cbe.partialVar6, varName) { return true; }
|
||||
if cbe.partialCount > 7 && String_Eq(cbe.partialVar7, varName) { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Drop function name for a C type (`Array_int` → `Array_Drop_int`, `Bag` → `Bag_Drop`).
|
||||
func CBE_DropFuncName(tn: String) -> String {
|
||||
if tn == null as String || String_Eq(tn, "") { return ""; }
|
||||
if !CBE_IsDroppableTypeName(tn) { return ""; }
|
||||
if String_StartsWith(tn, "Array_") {
|
||||
let n: uint = String_Len(tn);
|
||||
let elem: String = String_Slice(tn, 6, n - 6);
|
||||
return String_Concat("Array_Drop_", elem);
|
||||
}
|
||||
if String_StartsWith(tn, "Map_") {
|
||||
let n: uint = String_Len(tn);
|
||||
let rest: String = String_Slice(tn, 4, n - 4);
|
||||
return String_Concat("Map_Drop_", rest);
|
||||
}
|
||||
if String_StartsWith(tn, "Set_") {
|
||||
let n: uint = String_Len(tn);
|
||||
let elem: String = String_Slice(tn, 4, n - 4);
|
||||
return String_Concat("Set_Drop_", elem);
|
||||
}
|
||||
if String_StartsWith(tn, "Channel_") {
|
||||
let n: uint = String_Len(tn);
|
||||
let elem: String = String_Slice(tn, 8, n - 8);
|
||||
return String_Concat("Channel_Drop_", elem);
|
||||
}
|
||||
return String_Concat(tn, "_Drop");
|
||||
}
|
||||
|
||||
/// Lookup struct index by name; -1 if missing.
|
||||
func CBE_FindStruct(cbe: *CEmitter, typeName: String) -> int {
|
||||
if cbe.mod == null as *HirModule { return -1; }
|
||||
var si: int = 0;
|
||||
while si < cbe.mod.structCount {
|
||||
if String_Eq(cbe.mod.structs[si].name, typeName) { return si; }
|
||||
si = si + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Emit one Drop call: `Type_Drop(&(accessPath));`
|
||||
func CBE_EmitOneFieldDrop(cbe: *CEmitter, dropFn: String, accessPath: String) {
|
||||
if String_Eq(dropFn, "") { return; }
|
||||
StringBuilder_Append(&cbe.sb, "\n");
|
||||
var sp: int = 0;
|
||||
while sp < cbe.indent {
|
||||
StringBuilder_Append(&cbe.sb, " ");
|
||||
sp = sp + 1;
|
||||
}
|
||||
StringBuilder_Append(&cbe.sb, dropFn);
|
||||
StringBuilder_Append(&cbe.sb, "(&(");
|
||||
StringBuilder_Append(&cbe.sb, accessPath);
|
||||
StringBuilder_Append(&cbe.sb, "));");
|
||||
}
|
||||
|
||||
/// Recursive remaining-field drops for nested paths (session 73).
|
||||
/// `prefix` is dotted path under varName ("" at root); `typeName` is type at that prefix.
|
||||
func CBE_EmitRemainingAt(cbe: *CEmitter, varName: String, typeName: String, prefix: String) {
|
||||
let si: int = CBE_FindStruct(cbe, typeName);
|
||||
if si < 0 { return; }
|
||||
var fi: int = 0;
|
||||
while fi < cbe.mod.structs[si].fieldCount {
|
||||
let fname: String = cbe.mod.structs[si].fields[fi].name;
|
||||
let fty: String = CBE_NormalizeTypeName(cbe.mod.structs[si].fields[fi].typeName);
|
||||
var key: String = fname;
|
||||
if !String_Eq(prefix, "") {
|
||||
key = String_Concat(prefix, String_Concat(".", fname));
|
||||
}
|
||||
var access: String = String_Concat(varName, String_Concat(".", key));
|
||||
// path may use dots in key already: var.inner.note
|
||||
if String_Eq(prefix, "") {
|
||||
access = String_Concat(varName, String_Concat(".", fname));
|
||||
} else {
|
||||
access = String_Concat(varName, String_Concat(".", String_Concat(prefix, String_Concat(".", fname))));
|
||||
}
|
||||
if CBE_PathIsFullyMoved(cbe, varName, key) {
|
||||
// fully moved — skip
|
||||
} else if CBE_PathHasNestedMove(cbe, varName, key) {
|
||||
// recurse into nested type
|
||||
CBE_EmitRemainingAt(cbe, varName, fty, key);
|
||||
} else {
|
||||
let dropFn: String = CBE_DropFuncName(fty);
|
||||
CBE_EmitOneFieldDrop(cbe, dropFn, access);
|
||||
}
|
||||
fi = fi + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// After partial field move, Drop remaining droppable fields (incl. nested).
|
||||
func CBE_EmitRemainingFieldDrops(cbe: *CEmitter, varName: String) {
|
||||
let typeName: String = CBE_LookupLocalType(cbe, varName);
|
||||
if String_Eq(typeName, "") { return; }
|
||||
CBE_EmitRemainingAt(cbe, varName, typeName, "");
|
||||
}
|
||||
|
||||
/// Mark droppable locals moved by-value (struct fields / nested / partial field).
|
||||
/// `valueTypeHint`: when non-empty (e.g. function return type), used to decide
|
||||
/// whether a field access is an ownership move (`return bag.items` vs `return bag.tag`).
|
||||
@@ -145,14 +522,43 @@ module CBackend {
|
||||
}
|
||||
// Partial field move: only when the *value* type is droppable
|
||||
if node.kind == hFieldPtr || node.kind == hFieldAccess {
|
||||
// Prefer return-type hint, then resolved field type, then node.typeName
|
||||
// (selfhost stores *base* struct on field HIR, not the field type).
|
||||
var vty: String = valueTypeHint;
|
||||
if String_Eq(vty, "") && cbe.mod != null as *HirModule {
|
||||
vty = CBE_GetExprTypeName(cbe.mod, node);
|
||||
}
|
||||
if String_Eq(vty, "") {
|
||||
vty = node.typeName;
|
||||
}
|
||||
// Field HIR often stores the *base* struct typeName — prefer hint
|
||||
if CBE_IsDroppableTypeName(vty) {
|
||||
// Walk to base local (hVar / load / nested)
|
||||
CBE_MarkMovedFromNodeHint(cbe, node.child1, "");
|
||||
vty = CBE_NormalizeTypeName(vty);
|
||||
let baseName: String = CBE_BaseVarName(cbe, node);
|
||||
var path: String = CBE_FieldPathFromNode(node);
|
||||
// Fallback path: just the leaf field name if chain walk failed
|
||||
if String_Eq(path, "") && !String_Eq(node.strValue, "") {
|
||||
path = node.strValue;
|
||||
}
|
||||
// Single-level: mark when value type is droppable.
|
||||
// Nested path (`a.b.c`): always mark — leaf is ownership transfer.
|
||||
if !String_Eq(baseName, "") && !String_Eq(path, "") {
|
||||
var shouldMark: bool = CBE_IsDroppableTypeName(vty);
|
||||
if !shouldMark && cbe.mod != null as *HirModule {
|
||||
let ft: String = CBE_NormalizeTypeName(CBE_GetExprTypeName(cbe.mod, node));
|
||||
shouldMark = CBE_IsDroppableTypeName(ft);
|
||||
}
|
||||
var pi: int = 0;
|
||||
let plen: int = bux_strlen(path) as int;
|
||||
while pi < plen {
|
||||
if path[pi] == 46 as char8 { // '.'
|
||||
shouldMark = true;
|
||||
break;
|
||||
}
|
||||
pi = pi + 1;
|
||||
}
|
||||
if shouldMark {
|
||||
CBE_AddPartialMoved(cbe, baseName, path);
|
||||
CBE_AddMoved(cbe, baseName);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -292,10 +698,16 @@ module CBackend {
|
||||
if i == 5 { dn = cbe.defer5; }
|
||||
if i == 6 { dn = cbe.defer6; }
|
||||
if i == 7 { dn = cbe.defer7; }
|
||||
// Skip auto-drop for moved variables
|
||||
// Skip auto-drop for moved variables; after partial field move still
|
||||
// Drop remaining droppable fields (session 70/73).
|
||||
let deferVarName: String = CBE_GetAutoDropVarName(dn);
|
||||
if !String_Eq(deferVarName, "") && CBE_IsMoved(cbe, deferVarName) {
|
||||
return;
|
||||
if !String_Eq(deferVarName, "") {
|
||||
if CBE_IsMoved(cbe, deferVarName) || CBE_HasPartialMoved(cbe, deferVarName) {
|
||||
if CBE_HasPartialMoved(cbe, deferVarName) {
|
||||
CBE_EmitRemainingFieldDrops(cbe, deferVarName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
StringBuilder_Append(&cbe.sb, "\n");
|
||||
var sp: int = 0;
|
||||
@@ -427,12 +839,14 @@ module CBackend {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unary — always parenthesize operand so `!(a && b)` is not `!a && b`
|
||||
// Unary — wrap whole op so `(*p).field` is not parsed as `*(p.field)`
|
||||
// and `!(a && b)` is not `!a && b`.
|
||||
if kind == hUnary {
|
||||
StringBuilder_Append(&cbe.sb, "(");
|
||||
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
|
||||
StringBuilder_Append(&cbe.sb, "(");
|
||||
CBE_EmitExpr(cbe, node.child1);
|
||||
StringBuilder_Append(&cbe.sb, ")");
|
||||
StringBuilder_Append(&cbe.sb, "))");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -596,6 +1010,7 @@ module CBackend {
|
||||
if !String_Eq(node.typeName, "") {
|
||||
ct = node.typeName;
|
||||
}
|
||||
CBE_RegisterLocalType(cbe, node.strValue, ct);
|
||||
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(ct, node.strValue));
|
||||
return;
|
||||
}
|
||||
@@ -610,6 +1025,15 @@ module CBackend {
|
||||
CBE_MarkMovedFromNode(cbe, node.child2);
|
||||
}
|
||||
}
|
||||
// Pointer alias: `p = &bag` / `let p = &bag`
|
||||
if node.child1 != null as *HirNode && node.child2 != null as *HirNode {
|
||||
var ptrN: String = "";
|
||||
if node.child1.kind == hAlloca { ptrN = node.child1.strValue; }
|
||||
if node.child1.kind == hVar { ptrN = node.child1.strValue; }
|
||||
if !String_Eq(ptrN, "") {
|
||||
CBE_TryRecordPtrAlias(cbe, ptrN, node.child2);
|
||||
}
|
||||
}
|
||||
// Reinitialization removes moved status
|
||||
if node.child1 != null as *HirNode && node.child1.kind == hVar {
|
||||
CBE_RemoveMoved(cbe, node.child1.strValue);
|
||||
@@ -620,6 +1044,8 @@ module CBackend {
|
||||
if !String_Eq(node.child1.typeName, "") {
|
||||
ct = node.child1.typeName;
|
||||
}
|
||||
// Register for remaining-field Drop after partial move (session 70)
|
||||
CBE_RegisterLocalType(cbe, node.child1.strValue, ct);
|
||||
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(ct, node.child1.strValue));
|
||||
if node.child2 != null as *HirNode {
|
||||
StringBuilder_Append(&cbe.sb, " = ");
|
||||
@@ -880,6 +1306,10 @@ module CBackend {
|
||||
|
||||
// Assign: target = value
|
||||
if kind == hAssign {
|
||||
// Pointer alias: `p = &bag`
|
||||
if node.child1 != null as *HirNode && node.child1.kind == hVar && node.child2 != null as *HirNode {
|
||||
CBE_TryRecordPtrAlias(cbe, node.child1.strValue, node.child2);
|
||||
}
|
||||
CBE_EmitExpr(cbe, node.child1);
|
||||
StringBuilder_Append(&cbe.sb, " = ");
|
||||
CBE_EmitExpr(cbe, node.child2);
|
||||
@@ -1478,6 +1908,9 @@ module CBackend {
|
||||
cbe.mod = mod;
|
||||
cbe.deferCount = 0;
|
||||
cbe.movedCount = 0;
|
||||
cbe.partialCount = 0;
|
||||
cbe.aliasCount = 0;
|
||||
cbe.localCount = 0;
|
||||
cbe.tmpCounter = 0;
|
||||
cbe.lastDebugLine = 0;
|
||||
cbe.lastDebugFile = "";
|
||||
@@ -1542,34 +1975,59 @@ module CBackend {
|
||||
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int_int {\n int _0;\n int _1;\n int _2;\n} Tuple_int_int_int;\n");
|
||||
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_Empty {\n char _pad;\n} Tuple_Empty;\n\n");
|
||||
|
||||
// Struct definitions before enums (enums may embed structs by value, e.g. Shape::Dot(Point))
|
||||
// Pass 1: emit structs with no value-typed struct fields (leaf structs)
|
||||
si = 0;
|
||||
while si < mod.structCount {
|
||||
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
|
||||
// Struct definitions before enums (enums may embed structs by value).
|
||||
// Multi-pass topo: emit only when every value-typed field type is already
|
||||
// emitted (handles Outer { inner: Inner } nesting — session 73).
|
||||
var emittedNames: String = ";";
|
||||
var progress: bool = true;
|
||||
var pass: int = 0;
|
||||
while progress && pass < 64 {
|
||||
progress = false;
|
||||
pass = pass + 1;
|
||||
si = 0;
|
||||
while si < mod.structCount {
|
||||
let sn: String = mod.structs[si].name;
|
||||
if String_Eq(sn, "") || CBE_StructHasGeneric(&mod.structs[si]) {
|
||||
si = si + 1;
|
||||
continue;
|
||||
}
|
||||
let mark: String = String_Concat(";", String_Concat(sn, ";"));
|
||||
if String_Contains(emittedNames, mark) {
|
||||
si = si + 1;
|
||||
continue;
|
||||
}
|
||||
var ready: bool = true;
|
||||
var fi2: int = 0;
|
||||
while fi2 < mod.structs[si].fieldCount {
|
||||
let ft: String = CBE_NormalizeTypeName(mod.structs[si].fields[fi2].typeName);
|
||||
if !CBE_IsPrimitiveTypeName(ft) && !String_EndsWith(ft, "*") {
|
||||
let fmark: String = String_Concat(";", String_Concat(ft, ";"));
|
||||
if !String_Contains(emittedNames, fmark) {
|
||||
// Only block if ft is a pending user struct we define
|
||||
var isOur: bool = false;
|
||||
var oj: int = 0;
|
||||
while oj < mod.structCount {
|
||||
if String_Eq(mod.structs[oj].name, ft) {
|
||||
isOur = true;
|
||||
break;
|
||||
}
|
||||
oj = oj + 1;
|
||||
}
|
||||
if isOur {
|
||||
ready = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fi2 = fi2 + 1;
|
||||
}
|
||||
if ready {
|
||||
CBE_EmitStructDef(cbe, &mod.structs[si]);
|
||||
emittedNames = String_Concat(emittedNames, String_Concat(sn, ";"));
|
||||
progress = true;
|
||||
}
|
||||
si = si + 1;
|
||||
continue;
|
||||
}
|
||||
if CBE_StructHasValueStructField(&mod.structs[si]) {
|
||||
si = si + 1;
|
||||
continue;
|
||||
}
|
||||
CBE_EmitStructDef(cbe, &mod.structs[si]);
|
||||
si = si + 1;
|
||||
}
|
||||
// Pass 2: emit structs that contain value-typed struct fields
|
||||
si = 0;
|
||||
while si < mod.structCount {
|
||||
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
|
||||
si = si + 1;
|
||||
continue;
|
||||
}
|
||||
if !CBE_StructHasValueStructField(&mod.structs[si]) {
|
||||
si = si + 1;
|
||||
continue;
|
||||
}
|
||||
CBE_EmitStructDef(cbe, &mod.structs[si]);
|
||||
si = si + 1;
|
||||
}
|
||||
|
||||
// Enum definitions
|
||||
@@ -1810,6 +2268,8 @@ module CBackend {
|
||||
cbe.checkedFunc = mod.funcs[i].checkedFunc;
|
||||
cbe.deferCount = 0;
|
||||
cbe.movedCount = 0;
|
||||
cbe.partialCount = 0;
|
||||
cbe.localCount = 0;
|
||||
cbe.tmpCounter = 0;
|
||||
cbe.currentRetType = mod.funcs[i].retTypeName;
|
||||
var hasReturn: bool = false;
|
||||
|
||||
+179
-10
@@ -329,20 +329,134 @@ module MacroExpand {
|
||||
}
|
||||
}
|
||||
|
||||
// Fragment kind check: "ident" | "literal" | "block" | expr|tt (any)
|
||||
func Macro_FragMatches(kindStr: String, aexp: *Expr) -> bool {
|
||||
if aexp == null as *Expr { return false; }
|
||||
// Flatten ekIdent / ekField(::) chain to "A::B::C" (selfhost path style)
|
||||
func Macro_PathFromExpr(e: *Expr) -> String {
|
||||
if e == null as *Expr { return ""; }
|
||||
if e.kind == ekIdent { return e.strValue; }
|
||||
if e.kind == ekPath { return e.strValue; }
|
||||
if e.kind == ekField {
|
||||
let base: String = Macro_PathFromExpr(e.child1);
|
||||
if String_Eq(base, "") { return e.strValue; }
|
||||
return String_Concat(base, String_Concat("::", e.strValue));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Convert call-site expr → pattern for `$p:pat` (ident/lit/path/call/field)
|
||||
func Macro_ExprToPattern(aexp: *Expr) -> *Pattern {
|
||||
if aexp == null as *Expr { return null as *Pattern; }
|
||||
if aexp.kind == ekMacroPat { return Ast_ClonePattern(aexp.macroPat); }
|
||||
if aexp.kind == ekIdent {
|
||||
if String_Eq(aexp.strValue, "_") {
|
||||
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
|
||||
p.kind = pkWildcard;
|
||||
p.line = aexp.line;
|
||||
p.column = aexp.column;
|
||||
return p;
|
||||
}
|
||||
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
|
||||
p.kind = pkIdent;
|
||||
p.line = aexp.line;
|
||||
p.column = aexp.column;
|
||||
p.patIdent = aexp.strValue;
|
||||
return p;
|
||||
}
|
||||
if aexp.kind == ekLiteral {
|
||||
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
|
||||
p.kind = pkLiteral;
|
||||
p.line = aexp.line;
|
||||
p.column = aexp.column;
|
||||
p.patLitKind = aexp.tokKind;
|
||||
p.patLitText = aexp.tokText;
|
||||
return p;
|
||||
}
|
||||
if aexp.kind == ekPath || aexp.kind == ekField {
|
||||
let path: String = Macro_PathFromExpr(aexp);
|
||||
if String_Eq(path, "") { return null as *Pattern; }
|
||||
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
|
||||
p.kind = pkEnum;
|
||||
p.line = aexp.line;
|
||||
p.column = aexp.column;
|
||||
p.patEnumPath = path;
|
||||
return p;
|
||||
}
|
||||
if aexp.kind == ekCall {
|
||||
// Opt::Some(v) — callee is ekField chain
|
||||
var path: String = Macro_PathFromExpr(aexp.child1);
|
||||
if String_Eq(path, "") { return null as *Pattern; }
|
||||
let p: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
|
||||
p.kind = pkEnum;
|
||||
p.line = aexp.line;
|
||||
p.column = aexp.column;
|
||||
p.patEnumPath = path;
|
||||
var last: *Pattern = null as *Pattern;
|
||||
var arg: *ExprList = aexp.callArgs;
|
||||
while arg != null as *ExprList {
|
||||
let ap: *Pattern = Macro_ExprToPattern(arg.expr);
|
||||
if ap == null as *Pattern { return null as *Pattern; }
|
||||
if last == null as *Pattern {
|
||||
p.patArgs = ap;
|
||||
last = ap;
|
||||
} else {
|
||||
last.patNext = ap;
|
||||
last = ap;
|
||||
}
|
||||
arg = arg.next;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
return null as *Pattern;
|
||||
}
|
||||
|
||||
// Coerce arg for kind; returns normalized expr or null on mismatch
|
||||
func Macro_CoerceArg(kindStr: String, aexp: *Expr) -> *Expr {
|
||||
if aexp == null as *Expr { return null as *Expr; }
|
||||
if String_Eq(kindStr, "ident") {
|
||||
return aexp.kind == ekIdent;
|
||||
if aexp.kind != ekIdent { return null as *Expr; }
|
||||
return aexp;
|
||||
}
|
||||
if String_Eq(kindStr, "literal") {
|
||||
return aexp.kind == ekLiteral;
|
||||
if aexp.kind != ekLiteral { return null as *Expr; }
|
||||
return aexp;
|
||||
}
|
||||
if String_Eq(kindStr, "block") {
|
||||
return aexp.kind == ekBlock;
|
||||
if aexp.kind != ekBlock { return null as *Expr; }
|
||||
return aexp;
|
||||
}
|
||||
// expr / tt / unknown → accept
|
||||
return true;
|
||||
if String_Eq(kindStr, "stmt") {
|
||||
if aexp.kind == ekMacroStmt { return aexp; }
|
||||
if aexp.kind == ekMacroPat { return null as *Expr; }
|
||||
// Wrap expression as expression-statement
|
||||
let st: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
||||
st.kind = skExpr;
|
||||
st.line = aexp.line;
|
||||
st.column = aexp.column;
|
||||
st.child1 = aexp;
|
||||
let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
|
||||
e.kind = ekMacroStmt;
|
||||
e.line = aexp.line;
|
||||
e.column = aexp.column;
|
||||
e.macroStmt = st;
|
||||
return e;
|
||||
}
|
||||
if String_Eq(kindStr, "pat") {
|
||||
let pat: *Pattern = Macro_ExprToPattern(aexp);
|
||||
if pat == null as *Pattern { return null as *Expr; }
|
||||
let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
|
||||
e.kind = ekMacroPat;
|
||||
e.line = aexp.line;
|
||||
e.column = aexp.column;
|
||||
e.macroPat = pat;
|
||||
return e;
|
||||
}
|
||||
// expr / tt
|
||||
if aexp.kind == ekMacroStmt || aexp.kind == ekMacroPat { return null as *Expr; }
|
||||
return aexp;
|
||||
}
|
||||
|
||||
// Fragment kind check: "ident" | "literal" | "block" | "stmt" | "pat" | expr|tt
|
||||
func Macro_FragMatches(kindStr: String, aexp: *Expr) -> bool {
|
||||
return Macro_CoerceArg(kindStr, aexp) != null as *Expr;
|
||||
}
|
||||
|
||||
// kinds encoded as "expr;ident;rep:expr," — return fi-th segment
|
||||
@@ -479,6 +593,36 @@ module MacroExpand {
|
||||
li = li + 1;
|
||||
}
|
||||
}
|
||||
} else if s.kind == skExpr && s.child1 != null as *Expr && s.child1.kind == ekIdent {
|
||||
// Splice `$s:stmt` bound to ekMacroStmt as a real statement
|
||||
let bound: *Expr = Env_Lookup(env, s.child1.strValue);
|
||||
if bound != null as *Expr && bound.kind == ekMacroStmt && bound.macroStmt != null as *Stmt {
|
||||
let one: *Stmt = Subst_Stmt(bound.macroStmt, env, file, line, col);
|
||||
if one != null as *Stmt {
|
||||
one.nextStmt = null as *Stmt;
|
||||
if n.firstStmt == null as *Stmt {
|
||||
n.firstStmt = one;
|
||||
n.lastStmt = one;
|
||||
} else {
|
||||
n.lastStmt.nextStmt = one;
|
||||
n.lastStmt = one;
|
||||
}
|
||||
n.stmtCount = n.stmtCount + 1;
|
||||
}
|
||||
} else {
|
||||
let one2: *Stmt = Subst_Stmt(s, env, file, line, col);
|
||||
if one2 != null as *Stmt {
|
||||
one2.nextStmt = null as *Stmt;
|
||||
if n.firstStmt == null as *Stmt {
|
||||
n.firstStmt = one2;
|
||||
n.lastStmt = one2;
|
||||
} else {
|
||||
n.lastStmt.nextStmt = one2;
|
||||
n.lastStmt = one2;
|
||||
}
|
||||
n.stmtCount = n.stmtCount + 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let one: *Stmt = Subst_Stmt(s, env, file, line, col);
|
||||
if one != null as *Stmt {
|
||||
@@ -647,6 +791,7 @@ module MacroExpand {
|
||||
}
|
||||
var arm: *MatchArm = c.matchArms;
|
||||
while arm != null as *MatchArm {
|
||||
arm.pattern = Subst_Pattern(arm.pattern, env, file, line, col);
|
||||
arm.body = Subst_Expr(arm.body, env, file, line, col);
|
||||
arm = arm.next;
|
||||
}
|
||||
@@ -658,6 +803,29 @@ module MacroExpand {
|
||||
return c;
|
||||
}
|
||||
|
||||
func Subst_Pattern(p: *Pattern, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Pattern {
|
||||
if p == null as *Pattern { return null as *Pattern; }
|
||||
// `$p:pat` as whole pattern (pkIdent name `$p`)
|
||||
if p.kind == pkIdent && !String_Eq(p.patIdent, "") {
|
||||
let bound: *Expr = Env_Lookup(env, p.patIdent);
|
||||
if bound != null as *Expr && bound.kind == ekMacroPat && bound.macroPat != null as *Pattern {
|
||||
let np: *Pattern = Ast_ClonePattern(bound.macroPat);
|
||||
if np != null as *Pattern {
|
||||
np.line = line;
|
||||
np.column = col;
|
||||
}
|
||||
return np;
|
||||
}
|
||||
}
|
||||
// MVP: other patterns kept as cloned (no nested `$p` rewrite)
|
||||
let c: *Pattern = Ast_ClonePattern(p);
|
||||
if c != null as *Pattern {
|
||||
c.line = line;
|
||||
c.column = col;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
func Subst_Stmt(s: *Stmt, env: *MacroEnv, file: String, line: uint32, col: uint32) -> *Stmt {
|
||||
if s == null as *Stmt { return null as *Stmt; }
|
||||
let c: *Stmt = Ast_CloneStmt(s);
|
||||
@@ -944,8 +1112,9 @@ module MacroExpand {
|
||||
if argList == null as *ExprList { ok = false; break; }
|
||||
}
|
||||
let aexp: *Expr = argList.expr;
|
||||
if !Macro_FragMatches(kindStr, aexp) { ok = false; break; }
|
||||
Env_Set(&env, paramIdx, Rule_FragName(rule, paramIdx), aexp);
|
||||
let coerced: *Expr = Macro_CoerceArg(kindStr, aexp);
|
||||
if coerced == null as *Expr { ok = false; break; }
|
||||
Env_Set(&env, paramIdx, Rule_FragName(rule, paramIdx), coerced);
|
||||
argList = argList.next;
|
||||
flatLeft = flatLeft - 1;
|
||||
paramIdx = paramIdx + 1;
|
||||
|
||||
+36
-3
@@ -395,9 +395,38 @@ module Parser {
|
||||
e.callArgCount = 0;
|
||||
e.matchArms = null as *MatchArm;
|
||||
e.matchArmCount = 0;
|
||||
e.macroStmt = null as *Stmt;
|
||||
e.macroPat = null as *Pattern;
|
||||
return e;
|
||||
}
|
||||
|
||||
func parserIsMacroStmtStart(p: *Parser) -> bool {
|
||||
let k: int = parserPeek(p, 0);
|
||||
if k == tkLet || k == tkVar { return true; }
|
||||
if k == tkIf || k == tkWhile || k == tkFor || k == tkLoop { return true; }
|
||||
if k == tkMatch || k == tkReturn || k == tkBreak || k == tkContinue { return true; }
|
||||
if k == tkDefer || k == tkSwitch || k == tkDo { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
func parserParseMacroArg(p: *Parser) -> *Expr {
|
||||
let line: uint32 = parserCurToken(p).line;
|
||||
let col: uint32 = parserCurToken(p).column;
|
||||
if parserIsMacroStmtStart(p) {
|
||||
let st: *Stmt = parserParseStmt(p);
|
||||
let e: *Expr = parserMakeExpr(ekMacroStmt, line, col);
|
||||
e.macroStmt = st;
|
||||
return e;
|
||||
}
|
||||
if parserCheck(p, tkUnderscore) {
|
||||
let pat: *Pattern = parserParsePattern(p);
|
||||
let e: *Expr = parserMakeExpr(ekMacroPat, line, col);
|
||||
e.macroPat = pat;
|
||||
return e;
|
||||
}
|
||||
return parserParseExpr(p);
|
||||
}
|
||||
|
||||
|
||||
func parserMakeStringLitExpr(text: String, line: uint32, col: uint32) -> *Expr {
|
||||
let quoted: String = String_Concat(String_Concat("\"", text), "\"");
|
||||
@@ -1284,7 +1313,7 @@ module Parser {
|
||||
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
||||
continue;
|
||||
}
|
||||
let argExpr: *Expr = parserParseExpr(p);
|
||||
let argExpr: *Expr = parserParseMacroArg(p);
|
||||
let argNode: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
|
||||
argNode.expr = argExpr;
|
||||
argNode.next = null as *ExprList;
|
||||
@@ -2387,8 +2416,10 @@ module Parser {
|
||||
let kindTok: LexToken = parserExpect(p, tkIdent, "expected fragment kind");
|
||||
var kname: String = kindTok.text;
|
||||
if String_Eq(kname, "lit") { kname = "literal"; }
|
||||
if String_Eq(kname, "pattern") { kname = "pat"; }
|
||||
if !(String_Eq(kname, "expr") || String_Eq(kname, "ident") || String_Eq(kname, "tt")
|
||||
|| String_Eq(kname, "literal") || String_Eq(kname, "block")) {
|
||||
|| String_Eq(kname, "literal") || String_Eq(kname, "block")
|
||||
|| String_Eq(kname, "stmt") || String_Eq(kname, "pat")) {
|
||||
kname = "expr";
|
||||
}
|
||||
if nIn == 0 {
|
||||
@@ -2432,8 +2463,10 @@ module Parser {
|
||||
let kindTok: LexToken = parserExpect(p, tkIdent, "expected fragment kind");
|
||||
var kname: String = kindTok.text;
|
||||
if String_Eq(kname, "lit") { kname = "literal"; }
|
||||
if String_Eq(kname, "pattern") { kname = "pat"; }
|
||||
if !(String_Eq(kname, "expr") || String_Eq(kname, "ident") || String_Eq(kname, "tt")
|
||||
|| String_Eq(kname, "literal") || String_Eq(kname, "block")) {
|
||||
|| String_Eq(kname, "literal") || String_Eq(kname, "block")
|
||||
|| String_Eq(kname, "stmt") || String_Eq(kname, "pat")) {
|
||||
kname = "expr";
|
||||
}
|
||||
if rule.paramCount < 9 {
|
||||
|
||||
@@ -58,7 +58,97 @@ if sed -n '/^Array_int TakeItems/,/^}/p' "$TMP/mp/build/main.c" | grep -q 'Bag_D
|
||||
sed -n '/^Array_int TakeItems/,/^}/p' "$TMP/mp/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
echo " move_field_partial: PASS (run + TakeItems has no Bag_Drop)"
|
||||
# PeekTagAndTake must Drop the moved-out Array after Array_Len mono (defer restore)
|
||||
if ! sed -n '/^int PeekTagAndTake/,/^}/p' "$TMP/mp/build/main.c" | grep -q 'Array_Drop'; then
|
||||
echo "error: PeekTagAndTake missing Array_Drop for moved local (defer restore?)" >&2
|
||||
sed -n '/^int PeekTagAndTake/,/^}/p' "$TMP/mp/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
echo " move_field_partial: PASS (run + TakeItems has no Bag_Drop + PeekTag drops moved)"
|
||||
|
||||
# --- remaining field Drop after partial move ---
|
||||
echo "=== smoke: move_field_remaining ==="
|
||||
mkdir -p "$TMP/mr/src"
|
||||
cp -a "$ROOT/rt" "$TMP/mr/"
|
||||
cat > "$TMP/mr/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "move_field_remaining"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/move_field_remaining.bux" "$TMP/mr/src/Main.bux"
|
||||
(cd "$TMP/mr" && "$BUXC" run .)
|
||||
# TakeLeft must not PairBag_Drop, but must Tracked_Drop remaining right field
|
||||
if sed -n '/^Array_int TakeLeft/,/^}/p' "$TMP/mr/build/main.c" | grep -q 'PairBag_Drop'; then
|
||||
echo "error: TakeLeft still PairBag_Drops after partial field move" >&2
|
||||
sed -n '/^Array_int TakeLeft/,/^}/p' "$TMP/mr/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
if ! sed -n '/^Array_int TakeLeft/,/^}/p' "$TMP/mr/build/main.c" | grep -q 'Tracked_Drop'; then
|
||||
echo "error: TakeLeft missing Tracked_Drop for remaining field" >&2
|
||||
sed -n '/^Array_int TakeLeft/,/^}/p' "$TMP/mr/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
echo " move_field_remaining: PASS (run + no PairBag_Drop + Tracked_Drop on right)"
|
||||
|
||||
# --- nested path move outer.inner.items ---
|
||||
echo "=== smoke: move_field_nested ==="
|
||||
mkdir -p "$TMP/mn/src"
|
||||
cp -a "$ROOT/rt" "$TMP/mn/"
|
||||
cat > "$TMP/mn/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "move_field_nested"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/move_field_nested.bux" "$TMP/mn/src/Main.bux"
|
||||
(cd "$TMP/mn" && "$BUXC" run .)
|
||||
# TakeNestedItems: no Outer_Drop; must Tracked_Drop remaining note + tag
|
||||
if sed -n '/^Array_int TakeNestedItems/,/^}/p' "$TMP/mn/build/main.c" | grep -q 'Outer_Drop'; then
|
||||
echo "error: TakeNestedItems still Outer_Drops after nested path move" >&2
|
||||
sed -n '/^Array_int TakeNestedItems/,/^}/p' "$TMP/mn/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
tdrops=$(sed -n '/^Array_int TakeNestedItems/,/^}/p' "$TMP/mn/build/main.c" | grep -c 'Tracked_Drop' || true)
|
||||
if [[ "${tdrops:-0}" -lt 2 ]]; then
|
||||
echo "error: TakeNestedItems expected ≥2 Tracked_Drop (inner.note + tag), got $tdrops" >&2
|
||||
sed -n '/^Array_int TakeNestedItems/,/^}/p' "$TMP/mn/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
echo " move_field_nested: PASS (run + no Outer_Drop + Tracked_Drop remaining)"
|
||||
|
||||
# --- pointer field move p.items / (*p).items ---
|
||||
echo "=== smoke: move_field_ptr ==="
|
||||
mkdir -p "$TMP/mptr/src"
|
||||
cp -a "$ROOT/rt" "$TMP/mptr/"
|
||||
cat > "$TMP/mptr/bux.toml" <<'EOF'
|
||||
[Package]
|
||||
Name = "move_field_ptr"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/move_field_ptr.bux" "$TMP/mptr/src/Main.bux"
|
||||
(cd "$TMP/mptr" && "$BUXC" run .)
|
||||
if sed -n '/^Array_int TakeViaPtr/,/^}/p' "$TMP/mptr/build/main.c" | grep -q 'Bag_Drop'; then
|
||||
echo "error: TakeViaPtr still Bag_Drops after p.items move" >&2
|
||||
sed -n '/^Array_int TakeViaPtr/,/^}/p' "$TMP/mptr/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
if ! sed -n '/^Array_int TakeViaPtr/,/^}/p' "$TMP/mptr/build/main.c" | grep -q 'Tracked_Drop'; then
|
||||
echo "error: TakeViaPtr missing Tracked_Drop for remaining tag" >&2
|
||||
sed -n '/^Array_int TakeViaPtr/,/^}/p' "$TMP/mptr/build/main.c"
|
||||
exit 1
|
||||
fi
|
||||
echo " move_field_ptr: PASS (run + no Bag_Drop + Tracked_Drop remaining)"
|
||||
|
||||
# --- early return Drop counts ---
|
||||
echo "=== smoke: drop_early_return ==="
|
||||
@@ -78,4 +168,4 @@ out=$(cd "$TMP/de" && "$BUXC" run .)
|
||||
echo "$out" | grep -q 'PASS'
|
||||
echo " drop_early_return: PASS"
|
||||
|
||||
echo "PASS: smoke_drop_move (field-move + partial + early-return)"
|
||||
echo "PASS: smoke_drop_move (field-move + partial + remaining + nested + ptr + early-return)"
|
||||
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Session 71 — Windows / MinGW hello smoke (also testable on Unix via BUX_RUNTIME=win).
|
||||
# Builds examples/hello.bux with the minimal runtime and checks stdout.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
export BUX_STDLIB="${BUX_STDLIB:-$ROOT/lib}"
|
||||
unset BUX_DEBUG_FILE || true
|
||||
|
||||
# Prefer buxc.exe on Windows, else buxc
|
||||
if [[ -x "$ROOT/buxc.exe" ]]; then
|
||||
BUXC="$ROOT/buxc.exe"
|
||||
elif [[ -x "$ROOT/buxc" ]]; then
|
||||
BUXC="$ROOT/buxc"
|
||||
else
|
||||
(cd "$ROOT" && make build)
|
||||
if [[ -x "$ROOT/buxc.exe" ]]; then BUXC="$ROOT/buxc.exe"
|
||||
else BUXC="$ROOT/buxc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Force minimal runtime when not already on Windows (Linux/macOS local check)
|
||||
case "$(uname -s 2>/dev/null || echo unknown)" in
|
||||
MINGW*|MSYS*|CYGWIN*|Windows_NT) ;;
|
||||
*)
|
||||
export BUX_RUNTIME="${BUX_RUNTIME:-win}"
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! command -v gcc >/dev/null 2>&1 && ! command -v cc >/dev/null 2>&1; then
|
||||
echo "error: need gcc/cc on PATH (MinGW on Windows)" >&2
|
||||
exit 1
|
||||
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 = "hello"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
EOF
|
||||
cp "$ROOT/examples/hello.bux" "$TMP/src/Main.bux"
|
||||
|
||||
echo "=== smoke_windows_hello: build+run ==="
|
||||
out=$("$BUXC" run "$TMP" 2>&1) || {
|
||||
echo "$out" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "$out"
|
||||
echo "$out" | grep -q 'Hello, Bux!'
|
||||
|
||||
# Confirm minimal runtime was used when forced / on Windows
|
||||
if [[ -f "$TMP/build/runtime.c" ]]; then
|
||||
if ! grep -q 'Windows / MinGW minimal' "$TMP/build/runtime.c"; then
|
||||
# On native Windows the CLI always copies runtime_win.c content into runtime.c
|
||||
if grep -q 'pthread\|openssl/evp' "$TMP/build/runtime.c"; then
|
||||
echo "error: expected runtime_win.c content, found full POSIX runtime" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "PASS: smoke_windows_hello"
|
||||
Reference in New Issue
Block a user