feat: try/unwrap payload types, LSP format, macro paste, freestanding runtime
ci / build (ubuntu) (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 / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / build (ubuntu) (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 / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
- Type `?`/`!` as Result/Option Ok payload (not always int); fix unwrap C types - LSP 0.18 document formatting (bux fmt) + VS Code format-on-save - Macro `:type` generics (Array_New<$t>) and operators-only tt paste - Ship runtime_freestanding.c + BUX_RUNTIME=freestanding + smokes/examples
This commit is contained in:
@@ -5,12 +5,12 @@ BUILD_DIR := build
|
|||||||
# Project-local nimcache so CI can cache compiles (default is ~/.cache/nim).
|
# Project-local nimcache so CI can cache compiles (default is ~/.cache/nim).
|
||||||
NIMFLAGS ?= --nimcache:nimcache
|
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 ctfe_crc 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 move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw macro_type collections_extra generic_enum switch is_operator
|
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 try_generic ownership ownership_checked ownership_release drop_early_return lifetime_elision ctfe ctfe_crc 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 move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw macro_type macro_type_generic macro_op_paste collections_extra generic_enum switch is_operator
|
||||||
|
|
||||||
# Platform smoke (macOS CI): full EXAMPLES still runs on Linux.
|
# Platform smoke (macOS CI): full EXAMPLES still runs on Linux.
|
||||||
EXAMPLES_SMOKE := hello ownership ownership_release strings map move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw ctfe_crc
|
EXAMPLES_SMOKE := hello ownership ownership_release strings map move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw ctfe_crc
|
||||||
|
|
||||||
.PHONY: all build dev debug test clean clean-all test-examples test-examples-smoke selfhost test-golden test-errors test-stdlib selfhost-loop lsp vscode vscode-package fmt-check docs bench test-apps test-dwarf test-selfhost-smoke test-unit test-linux-targets ensure-buxc
|
.PHONY: all build dev debug test clean clean-all test-examples test-examples-smoke selfhost test-golden test-errors test-stdlib selfhost-loop lsp vscode vscode-package fmt-check docs bench test-apps test-dwarf test-selfhost-smoke test-unit test-linux-targets test-freestanding ensure-buxc
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ debug: dev
|
|||||||
@echo "Debug binary: buxc_debug"
|
@echo "Debug binary: buxc_debug"
|
||||||
|
|
||||||
# Full local / sequential suite (same coverage as split CI jobs combined).
|
# Full local / sequential suite (same coverage as split CI jobs combined).
|
||||||
test: build fmt-check test-examples test-errors test-stdlib test-registry test-dwarf test-drop-move test-linux-targets test-apps test-selfhost-smoke test-unit
|
test: build fmt-check test-examples test-errors test-stdlib test-registry test-dwarf test-drop-move test-linux-targets test-freestanding test-apps test-selfhost-smoke test-unit
|
||||||
|
|
||||||
# Nim unit tests + tiny CLI smoke (needs Nim + buxc).
|
# Nim unit tests + tiny CLI smoke (needs Nim + buxc).
|
||||||
test-unit: ensure-buxc
|
test-unit: ensure-buxc
|
||||||
@@ -212,6 +212,9 @@ test-lsp: lsp
|
|||||||
@echo "=== LSP diagnostics (error underlines) smoke ==="
|
@echo "=== LSP diagnostics (error underlines) smoke ==="
|
||||||
@chmod +x tools/smoke_lsp_diagnostics.sh
|
@chmod +x tools/smoke_lsp_diagnostics.sh
|
||||||
@tools/smoke_lsp_diagnostics.sh
|
@tools/smoke_lsp_diagnostics.sh
|
||||||
|
@echo "=== LSP formatting smoke ==="
|
||||||
|
@chmod +x tools/smoke_lsp_formatting.sh
|
||||||
|
@tools/smoke_lsp_formatting.sh
|
||||||
@echo "=== LSP references / rename smoke ==="
|
@echo "=== LSP references / rename smoke ==="
|
||||||
@chmod +x tools/smoke_lsp_rename.sh
|
@chmod +x tools/smoke_lsp_rename.sh
|
||||||
@tools/smoke_lsp_rename.sh
|
@tools/smoke_lsp_rename.sh
|
||||||
@@ -288,6 +291,13 @@ test-linux-targets: ensure-buxc
|
|||||||
@chmod +x tools/smoke_linux_targets.sh
|
@chmod +x tools/smoke_linux_targets.sh
|
||||||
@tools/smoke_linux_targets.sh
|
@tools/smoke_linux_targets.sh
|
||||||
|
|
||||||
|
# post-1.0 — freestanding runtime (-ffreestanding object + BUX_RUNTIME=freestanding package)
|
||||||
|
.PHONY: test-freestanding
|
||||||
|
test-freestanding: ensure-buxc
|
||||||
|
@echo "=== Freestanding runtime smoke ==="
|
||||||
|
@chmod +x tools/smoke_freestanding.sh
|
||||||
|
@tools/smoke_freestanding.sh
|
||||||
|
|
||||||
# Session 78 — Nexus HTTPS (self-signed) smoke
|
# Session 78 — Nexus HTTPS (self-signed) smoke
|
||||||
.PHONY: test-nexus-tls
|
.PHONY: test-nexus-tls
|
||||||
test-nexus-tls: ensure-buxc
|
test-nexus-tls: ensure-buxc
|
||||||
|
|||||||
+11
-6
@@ -14,9 +14,10 @@ type
|
|||||||
|
|
||||||
## Which C runtime shim to link (session 75 — Linux / cloud / embedded).
|
## Which C runtime shim to link (session 75 — Linux / cloud / embedded).
|
||||||
RuntimeFlavor* = enum
|
RuntimeFlavor* = enum
|
||||||
rfFull ## rt/runtime.c — POSIX + OpenSSL
|
rfFull ## rt/runtime.c — POSIX + OpenSSL
|
||||||
rfMinimal ## rt/runtime_minimal.c — thin, static/container/embed friendly
|
rfMinimal ## rt/runtime_minimal.c — thin, static/container/embed friendly
|
||||||
rfWin ## rt/runtime_win.c — Windows/MinGW (historical)
|
rfFreestanding ## rt/runtime_freestanding.c — no-libc research spike
|
||||||
|
rfWin ## rt/runtime_win.c — Windows/MinGW (historical)
|
||||||
|
|
||||||
GlobalOptions* = object
|
GlobalOptions* = object
|
||||||
color*: ColorMode
|
color*: ColorMode
|
||||||
@@ -63,7 +64,7 @@ Registry / toolchain env:
|
|||||||
BUX_REGISTRY_INSECURE=1 Allow self-signed HTTPS registry (dev/smoke)
|
BUX_REGISTRY_INSECURE=1 Allow self-signed HTTPS registry (dev/smoke)
|
||||||
BUX_CFLAGS Extra flags appended to the C compiler line
|
BUX_CFLAGS Extra flags appended to the C compiler line
|
||||||
BUX_CC C compiler binary (overrides --target pick)
|
BUX_CC C compiler binary (overrides --target pick)
|
||||||
BUX_RUNTIME full|minimal|thin|embed|win (default: full on Unix)
|
BUX_RUNTIME full|minimal|thin|embed|freestanding|win (default: full on Unix)
|
||||||
BUX_STATIC=1 Same as --static
|
BUX_STATIC=1 Same as --static
|
||||||
|
|
||||||
Global options:
|
Global options:
|
||||||
@@ -136,8 +137,10 @@ proc resolveRuntimeFlavor(opts: GlobalOptions): RuntimeFlavor =
|
|||||||
case e
|
case e
|
||||||
of "full", "posix":
|
of "full", "posix":
|
||||||
return rfFull
|
return rfFull
|
||||||
of "minimal", "thin", "embed", "embedded", "freestanding":
|
of "minimal", "thin", "embed", "embedded":
|
||||||
return rfMinimal
|
return rfMinimal
|
||||||
|
of "freestanding", "bare", "nolibc":
|
||||||
|
return rfFreestanding
|
||||||
of "win", "windows":
|
of "win", "windows":
|
||||||
return rfWin
|
return rfWin
|
||||||
of "":
|
of "":
|
||||||
@@ -159,10 +162,12 @@ proc runtimeFileName(flavor: RuntimeFlavor): string =
|
|||||||
case flavor
|
case flavor
|
||||||
of rfFull: "runtime.c"
|
of rfFull: "runtime.c"
|
||||||
of rfMinimal: "runtime_minimal.c"
|
of rfMinimal: "runtime_minimal.c"
|
||||||
|
of rfFreestanding: "runtime_freestanding.c"
|
||||||
of rfWin: "runtime_win.c"
|
of rfWin: "runtime_win.c"
|
||||||
|
|
||||||
proc isThinRuntime(flavor: RuntimeFlavor): bool =
|
proc isThinRuntime(flavor: RuntimeFlavor): bool =
|
||||||
flavor in {rfMinimal, rfWin}
|
## Thin = no OpenSSL / pthread link needs
|
||||||
|
flavor in {rfMinimal, rfFreestanding, rfWin}
|
||||||
|
|
||||||
proc findOnPath(bin: string): bool =
|
proc findOnPath(bin: string): bool =
|
||||||
## True if `bin` resolves as an executable on PATH (or is an absolute path).
|
## True if `bin` resolves as an executable on PATH (or is an absolute path).
|
||||||
|
|||||||
+117
-15
@@ -2101,6 +2101,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
of ekTry:
|
of ekTry:
|
||||||
let operand = ctx.lowerExpr(expr.exprTryOperand)
|
let operand = ctx.lowerExpr(expr.exprTryOperand)
|
||||||
var operandType = ctx.resolveExprType(expr.exprTryOperand)
|
var operandType = ctx.resolveExprType(expr.exprTryOperand)
|
||||||
|
# Keep original type args for Ok payload (before mangling to Result_T_E)
|
||||||
|
let payloadArgs: seq[Type] =
|
||||||
|
if operandType != nil and operandType.kind == tkNamed: operandType.inner
|
||||||
|
else: @[]
|
||||||
|
|
||||||
var typeName = ""
|
var typeName = ""
|
||||||
var errTag = ""
|
var errTag = ""
|
||||||
@@ -2112,7 +2116,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
|
|
||||||
# Upgrade bare generic enum name to concrete monomorphization.
|
# Upgrade bare generic enum name to concrete monomorphization.
|
||||||
# Sema stores Result/Option without mangled type-args; try needs Result_int_String_Tag.
|
# Sema stores Result/Option without mangled type-args; try needs Result_int_String_Tag.
|
||||||
if ctx.genericEnums.hasKey(typeName):
|
if ctx.genericEnums.hasKey(typeName) or typeName == "Result" or typeName == "Option":
|
||||||
# Prefer resolving call/ident TypeExpr with type args
|
# Prefer resolving call/ident TypeExpr with type args
|
||||||
if expr.exprTryOperand != nil:
|
if expr.exprTryOperand != nil:
|
||||||
if expr.exprTryOperand.kind == ekIdent and ctx.varTypeExprs.hasKey(expr.exprTryOperand.exprIdent):
|
if expr.exprTryOperand.kind == ekIdent and ctx.varTypeExprs.hasKey(expr.exprTryOperand.exprIdent):
|
||||||
@@ -2131,14 +2135,21 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
(resolved.name == typeName or resolved.name.startsWith(typeName & "_")):
|
(resolved.name == typeName or resolved.name.startsWith(typeName & "_")):
|
||||||
typeName = resolved.name
|
typeName = resolved.name
|
||||||
# Enclosing function return type (must match for `?` propagation)
|
# Enclosing function return type (must match for `?` propagation)
|
||||||
let stillBare = operandType == nil or operandType.kind != tkNamed or
|
var stillBare = typeName == "Result" or typeName == "Option"
|
||||||
typeName == operandType.name
|
|
||||||
if stillBare and ctx.currentFuncRetType != nil and
|
if stillBare and ctx.currentFuncRetType != nil and
|
||||||
ctx.currentFuncRetType.kind == tkNamed:
|
ctx.currentFuncRetType.kind == tkNamed:
|
||||||
let rn = ctx.currentFuncRetType.name
|
let rn = ctx.currentFuncRetType.name
|
||||||
if rn.startsWith(typeName & "_"):
|
if rn.startsWith(typeName & "_"):
|
||||||
typeName = rn
|
typeName = rn
|
||||||
operandType = makeNamed(typeName)
|
stillBare = false
|
||||||
|
# Prefer mangled name from type args: Result + [String,String] → Result_String_String
|
||||||
|
if stillBare and payloadArgs.len > 0 and
|
||||||
|
(typeName == "Result" or typeName == "Option"):
|
||||||
|
var mangled = typeName
|
||||||
|
for a in payloadArgs:
|
||||||
|
mangled = mangled & "_" & a.toString.replace(" ", "").replace("*", "p")
|
||||||
|
typeName = mangled
|
||||||
|
operandType = Type(kind: tkNamed, name: typeName, inner: payloadArgs)
|
||||||
|
|
||||||
# Err tag / Ok field from base or concrete name
|
# Err tag / Ok field from base or concrete name
|
||||||
let baseForTags =
|
let baseForTags =
|
||||||
@@ -2157,6 +2168,41 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
errTag = typeName & "_Err"
|
errTag = typeName & "_Err"
|
||||||
okField = "Ok_0"
|
okField = "Ok_0"
|
||||||
|
|
||||||
|
# Payload type (Ok_0 / Some_0) — must match T of Result<T,E>, not always int
|
||||||
|
var okType = makeInt()
|
||||||
|
if payloadArgs.len >= 1:
|
||||||
|
okType = payloadArgs[0]
|
||||||
|
else:
|
||||||
|
var enumSym = ctx.globalScope.lookup(typeName)
|
||||||
|
var enumDecl: Decl = nil
|
||||||
|
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||||
|
enumDecl = enumSym.decl
|
||||||
|
elif ctx.structInstMap.hasKey(typeName):
|
||||||
|
let (baseName, _) = ctx.structInstMap[typeName]
|
||||||
|
let baseSym = ctx.globalScope.lookup(baseName)
|
||||||
|
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkEnum:
|
||||||
|
enumDecl = baseSym.decl
|
||||||
|
elif typeName.startsWith("Result_"):
|
||||||
|
let baseSym = ctx.globalScope.lookup("Result")
|
||||||
|
if baseSym != nil and baseSym.decl != nil: enumDecl = baseSym.decl
|
||||||
|
elif typeName.startsWith("Option_"):
|
||||||
|
let baseSym = ctx.globalScope.lookup("Option")
|
||||||
|
if baseSym != nil and baseSym.decl != nil: enumDecl = baseSym.decl
|
||||||
|
if enumDecl != nil:
|
||||||
|
var subst = initTable[string, Type]()
|
||||||
|
if ctx.structInstMap.hasKey(typeName):
|
||||||
|
var ti = 0
|
||||||
|
for tp in enumDecl.declEnumTypeParams:
|
||||||
|
if ti < ctx.structInstMap[typeName][1].len:
|
||||||
|
subst[tp.name] = ctx.structInstMap[typeName][1][ti]
|
||||||
|
inc ti
|
||||||
|
for variant in enumDecl.declEnumVariants:
|
||||||
|
for i, f in variant.fields:
|
||||||
|
let fieldName = variant.name & "_" & $i
|
||||||
|
if fieldName == okField:
|
||||||
|
okType = substituteType(ctx, f, subst)
|
||||||
|
break
|
||||||
|
|
||||||
let tmpName = ctx.freshTryVar()
|
let tmpName = ctx.freshTryVar()
|
||||||
let tmpAlloca = hirAlloca(tmpName, operandType, loc)
|
let tmpAlloca = hirAlloca(tmpName, operandType, loc)
|
||||||
let tmpVar = hirVar(tmpName, operandType, loc)
|
let tmpVar = hirVar(tmpName, operandType, loc)
|
||||||
@@ -2179,8 +2225,8 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
let dataLoad = HirNode(kind: hLoad, loadPtr: dataPtr,
|
let dataLoad = HirNode(kind: hLoad, loadPtr: dataPtr,
|
||||||
typ: makeNamed(typeName & "_Data"), loc: loc)
|
typ: makeNamed(typeName & "_Data"), loc: loc)
|
||||||
let okPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: okField,
|
let okPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: okField,
|
||||||
typ: makePointer(makeInt()), loc: loc)
|
typ: makePointer(okType), loc: loc)
|
||||||
let okLoad = HirNode(kind: hLoad, loadPtr: okPtr, typ: makeInt(), loc: loc)
|
let okLoad = HirNode(kind: hLoad, loadPtr: okPtr, typ: okType, loc: loc)
|
||||||
|
|
||||||
ctx.pendingStmts.add(tmpAlloca)
|
ctx.pendingStmts.add(tmpAlloca)
|
||||||
ctx.pendingStmts.add(tmpStore)
|
ctx.pendingStmts.add(tmpStore)
|
||||||
@@ -2193,14 +2239,66 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
|
|
||||||
var errTag = "Result_Err"
|
var errTag = "Result_Err"
|
||||||
var typeName = "Result"
|
var typeName = "Result"
|
||||||
if operandType.kind == tkNamed:
|
var okField = "Ok_0"
|
||||||
|
if operandType != nil and operandType.kind == tkNamed:
|
||||||
typeName = operandType.name
|
typeName = operandType.name
|
||||||
if typeName == "Option":
|
if typeName == "Option" or typeName.startsWith("Option_"):
|
||||||
errTag = "Option_None"
|
errTag = if typeName == "Option": "Option_None" else: typeName & "_None"
|
||||||
|
okField = "Some_0"
|
||||||
|
elif typeName == "Result":
|
||||||
|
errTag = "Result_Err"
|
||||||
|
else:
|
||||||
|
errTag = typeName & "_Err"
|
||||||
|
|
||||||
|
# Mangle Result + type args when still bare
|
||||||
|
if operandType != nil and operandType.kind == tkNamed and operandType.inner.len > 0 and
|
||||||
|
(typeName == "Result" or typeName == "Option"):
|
||||||
|
var mangled = typeName
|
||||||
|
for a in operandType.inner:
|
||||||
|
mangled = mangled & "_" & a.toString.replace(" ", "").replace("*", "p")
|
||||||
|
typeName = mangled
|
||||||
|
if typeName.startsWith("Option_"):
|
||||||
|
errTag = typeName & "_None"
|
||||||
|
okField = "Some_0"
|
||||||
|
else:
|
||||||
|
errTag = typeName & "_Err"
|
||||||
|
okField = "Ok_0"
|
||||||
|
|
||||||
|
var okType = makeInt()
|
||||||
|
if operandType != nil and operandType.kind == tkNamed and operandType.inner.len >= 1:
|
||||||
|
okType = operandType.inner[0]
|
||||||
|
else:
|
||||||
|
var enumDecl: Decl = nil
|
||||||
|
let enumSym = ctx.globalScope.lookup(
|
||||||
|
if typeName.startsWith("Result_"): "Result"
|
||||||
|
elif typeName.startsWith("Option_"): "Option"
|
||||||
|
else: typeName)
|
||||||
|
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||||
|
enumDecl = enumSym.decl
|
||||||
|
if enumDecl != nil:
|
||||||
|
var subst = initTable[string, Type]()
|
||||||
|
if ctx.structInstMap.hasKey(typeName):
|
||||||
|
var ti = 0
|
||||||
|
for tp in enumDecl.declEnumTypeParams:
|
||||||
|
if ti < ctx.structInstMap[typeName][1].len:
|
||||||
|
subst[tp.name] = ctx.structInstMap[typeName][1][ti]
|
||||||
|
inc ti
|
||||||
|
elif operandType != nil and operandType.kind == tkNamed:
|
||||||
|
var ti = 0
|
||||||
|
for tp in enumDecl.declEnumTypeParams:
|
||||||
|
if ti < operandType.inner.len:
|
||||||
|
subst[tp.name] = operandType.inner[ti]
|
||||||
|
inc ti
|
||||||
|
for variant in enumDecl.declEnumVariants:
|
||||||
|
for i, f in variant.fields:
|
||||||
|
if variant.name & "_" & $i == okField:
|
||||||
|
okType = substituteType(ctx, f, subst)
|
||||||
|
|
||||||
|
# Same shape as `?`: stack temporary of the Result/Option value (not a pointer).
|
||||||
|
# Typing the var as *Result made C emit `tmp->tag` on a value (invalid).
|
||||||
let tmpName = ctx.freshTryVar()
|
let tmpName = ctx.freshTryVar()
|
||||||
let tmpAlloca = hirAlloca(tmpName, operandType, loc)
|
let tmpAlloca = hirAlloca(tmpName, operandType, loc)
|
||||||
let tmpVar = hirVar(tmpName, makePointer(operandType), loc)
|
let tmpVar = hirVar(tmpName, operandType, loc)
|
||||||
let tmpStore = hirStore(tmpVar, operand, loc)
|
let tmpStore = hirStore(tmpVar, operand, loc)
|
||||||
|
|
||||||
let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag",
|
let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag",
|
||||||
@@ -2210,11 +2308,15 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
let errConst = hirVar(errTag, makeNamed(typeName & "_Tag"), loc)
|
let errConst = hirVar(errTag, makeNamed(typeName & "_Tag"), loc)
|
||||||
let cond = hirBinary(tkEq, tagLoad, errConst, makeBool(), loc)
|
let cond = hirBinary(tkEq, tagLoad, errConst, makeBool(), loc)
|
||||||
|
|
||||||
# On error: call bux_panic("unwrap failed")
|
# On error: call bux_panic("unwrap failed") then exit (do not continue with garbage)
|
||||||
let panicTok = Token(kind: tkStringLiteral, text: "\"unwrap failed\"", loc: loc)
|
let panicTok = Token(kind: tkStringLiteral, text: "\"unwrap failed\"", loc: loc)
|
||||||
let panicMsg = HirNode(kind: hLit, litToken: panicTok, typ: makeStr(), loc: loc)
|
let panicMsg = HirNode(kind: hLit, litToken: panicTok, typ: makeStr(), loc: loc)
|
||||||
let panicCall = hirCall("bux_panic", @[panicMsg], makeVoid(), loc)
|
let panicCall = hirCall("bux_panic", @[panicMsg], makeVoid(), loc)
|
||||||
let thenBlock = hirBlock(@[panicCall], nil, makeVoid(), loc)
|
let exitLit = HirNode(kind: hLit,
|
||||||
|
litToken: Token(kind: tkIntLiteral, text: "1", loc: loc),
|
||||||
|
typ: makeInt(), loc: loc)
|
||||||
|
let exitCall = hirCall("bux_exit", @[exitLit], makeVoid(), loc)
|
||||||
|
let thenBlock = hirBlock(@[panicCall, exitCall], nil, makeVoid(), loc)
|
||||||
let ifNode = HirNode(kind: hIf, ifCond: cond, ifThen: thenBlock,
|
let ifNode = HirNode(kind: hIf, ifCond: cond, ifThen: thenBlock,
|
||||||
ifElse: nil, typ: makeVoid(), loc: loc)
|
ifElse: nil, typ: makeVoid(), loc: loc)
|
||||||
|
|
||||||
@@ -2223,9 +2325,9 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
typ: makePointer(makeNamed(typeName & "_Data")), loc: loc)
|
typ: makePointer(makeNamed(typeName & "_Data")), loc: loc)
|
||||||
let dataLoad = HirNode(kind: hLoad, loadPtr: dataPtr,
|
let dataLoad = HirNode(kind: hLoad, loadPtr: dataPtr,
|
||||||
typ: makeNamed(typeName & "_Data"), loc: loc)
|
typ: makeNamed(typeName & "_Data"), loc: loc)
|
||||||
let okPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: "Ok_0",
|
let okPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: okField,
|
||||||
typ: makePointer(makeInt()), loc: loc)
|
typ: makePointer(okType), loc: loc)
|
||||||
let okLoad = HirNode(kind: hLoad, loadPtr: okPtr, typ: makeInt(), loc: loc)
|
let okLoad = HirNode(kind: hLoad, loadPtr: okPtr, typ: okType, loc: loc)
|
||||||
|
|
||||||
ctx.pendingStmts.add(tmpAlloca)
|
ctx.pendingStmts.add(tmpAlloca)
|
||||||
ctx.pendingStmts.add(tmpStore)
|
ctx.pendingStmts.add(tmpStore)
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ type
|
|||||||
proc emitErr(res: var MacroExpandResult, loc: SourceLocation, msg: string) =
|
proc emitErr(res: var MacroExpandResult, loc: SourceLocation, msg: string) =
|
||||||
res.diagnostics.add(MacroDiagnostic(loc: loc, message: msg))
|
res.diagnostics.add(MacroDiagnostic(loc: loc, message: msg))
|
||||||
|
|
||||||
|
proc isBinaryPasteOp*(k: TokenKind): bool =
|
||||||
|
## Operators allowed as `$op:tt` paste into `$op($a, $b)` → `a OP b`.
|
||||||
|
case k
|
||||||
|
of tkPlus, tkMinus, tkStar, tkSlash, tkPercent, tkStarStar,
|
||||||
|
tkAmp, tkPipe, tkCaret, tkShl, tkShr,
|
||||||
|
tkAmpAmp, tkPipePipe,
|
||||||
|
tkEq, tkNe, tkLt, tkLe, tkGt, tkGe:
|
||||||
|
true
|
||||||
|
else:
|
||||||
|
false
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Deep clone (bootstrap has no Ast_Clone*)
|
# Deep clone (bootstrap has no Ast_Clone*)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -152,9 +163,12 @@ proc cloneExpr*(e: Expr): Expr =
|
|||||||
for a in e.exprCallArgs:
|
for a in e.exprCallArgs:
|
||||||
result.exprCallArgs.add(cloneExpr(a))
|
result.exprCallArgs.add(cloneExpr(a))
|
||||||
of ekGenericCall:
|
of ekGenericCall:
|
||||||
|
var gtas: seq[TypeExpr] = @[]
|
||||||
|
for ta in e.exprGenericTypeArgs:
|
||||||
|
gtas.add(cloneTypeExpr(ta))
|
||||||
result = Expr(kind: ekGenericCall, loc: e.loc,
|
result = Expr(kind: ekGenericCall, loc: e.loc,
|
||||||
exprGenericCallee: e.exprGenericCallee,
|
exprGenericCallee: e.exprGenericCallee,
|
||||||
exprGenericTypeArgs: e.exprGenericTypeArgs)
|
exprGenericTypeArgs: gtas)
|
||||||
of ekIndex:
|
of ekIndex:
|
||||||
result = Expr(kind: ekIndex, loc: e.loc,
|
result = Expr(kind: ekIndex, loc: e.loc,
|
||||||
exprIndexObj: cloneExpr(e.exprIndexObj),
|
exprIndexObj: cloneExpr(e.exprIndexObj),
|
||||||
@@ -165,9 +179,12 @@ proc cloneExpr*(e: Expr): Expr =
|
|||||||
exprFieldObj: cloneExpr(e.exprFieldObj),
|
exprFieldObj: cloneExpr(e.exprFieldObj),
|
||||||
exprFieldName: e.exprFieldName)
|
exprFieldName: e.exprFieldName)
|
||||||
of ekStructInit:
|
of ekStructInit:
|
||||||
|
var stas: seq[TypeExpr] = @[]
|
||||||
|
for ta in e.exprStructInitTypeArgs:
|
||||||
|
stas.add(cloneTypeExpr(ta))
|
||||||
result = Expr(kind: ekStructInit, loc: e.loc,
|
result = Expr(kind: ekStructInit, loc: e.loc,
|
||||||
exprStructInitName: e.exprStructInitName,
|
exprStructInitName: e.exprStructInitName,
|
||||||
exprStructInitTypeArgs: e.exprStructInitTypeArgs,
|
exprStructInitTypeArgs: stas,
|
||||||
exprStructInitFields: @[])
|
exprStructInitFields: @[])
|
||||||
for f in e.exprStructInitFields:
|
for f in e.exprStructInitFields:
|
||||||
result.exprStructInitFields.add((f.name, cloneExpr(f.value)))
|
result.exprStructInitFields.add((f.name, cloneExpr(f.value)))
|
||||||
@@ -895,6 +912,12 @@ proc substExpr(e: Expr, env: MacroEnv, callLoc: SourceLocation): Expr =
|
|||||||
of ekRange:
|
of ekRange:
|
||||||
c.exprRangeLo = substExpr(c.exprRangeLo, env, callLoc)
|
c.exprRangeLo = substExpr(c.exprRangeLo, env, callLoc)
|
||||||
c.exprRangeHi = substExpr(c.exprRangeHi, env, callLoc)
|
c.exprRangeHi = substExpr(c.exprRangeHi, env, callLoc)
|
||||||
|
of ekGenericCall:
|
||||||
|
# Array_New<$t>(…) / Foo<$t, $u> — substitute type fragments in type args
|
||||||
|
var gtas: seq[TypeExpr] = @[]
|
||||||
|
for ta in c.exprGenericTypeArgs:
|
||||||
|
gtas.add(substType(ta, env, callLoc))
|
||||||
|
c.exprGenericTypeArgs = gtas
|
||||||
of ekCall:
|
of ekCall:
|
||||||
c.exprCallCallee = substExpr(c.exprCallCallee, env, callLoc)
|
c.exprCallCallee = substExpr(c.exprCallCallee, env, callLoc)
|
||||||
var args: seq[Expr] = @[]
|
var args: seq[Expr] = @[]
|
||||||
@@ -950,12 +973,37 @@ proc substExpr(e: Expr, env: MacroEnv, callLoc: SourceLocation): Expr =
|
|||||||
argNames.add("")
|
argNames.add("")
|
||||||
c.exprCallArgs = args
|
c.exprCallArgs = args
|
||||||
c.exprCallArgNames = argNames
|
c.exprCallArgNames = argNames
|
||||||
|
# Operators-only paste: `$op($a, $b)` where `$op:tt` is a bound binary operator
|
||||||
|
# → rebuild as `$a OP $b` (post-1.0).
|
||||||
|
if c.exprCallCallee != nil and c.exprCallCallee.kind == ekMacroTt and
|
||||||
|
c.exprCallCallee.exprMacroTtInner != nil and
|
||||||
|
c.exprCallCallee.exprMacroTtInner.kind == ekLiteral and
|
||||||
|
c.exprCallArgs.len == 2:
|
||||||
|
let opTok = c.exprCallCallee.exprMacroTtInner.exprLit
|
||||||
|
if opTok.kind.isBinaryPasteOp:
|
||||||
|
result = Expr(kind: ekBinary, loc: callLoc,
|
||||||
|
exprBinaryOp: opTok.kind,
|
||||||
|
exprBinaryLeft: c.exprCallArgs[0],
|
||||||
|
exprBinaryRight: c.exprCallArgs[1])
|
||||||
|
return
|
||||||
|
# Callee already unwrapped to op literal by value-position MacroTt splice
|
||||||
|
if c.exprCallCallee != nil and c.exprCallCallee.kind == ekLiteral and
|
||||||
|
c.exprCallArgs.len == 2 and c.exprCallCallee.exprLit.kind.isBinaryPasteOp:
|
||||||
|
result = Expr(kind: ekBinary, loc: callLoc,
|
||||||
|
exprBinaryOp: c.exprCallCallee.exprLit.kind,
|
||||||
|
exprBinaryLeft: c.exprCallArgs[0],
|
||||||
|
exprBinaryRight: c.exprCallArgs[1])
|
||||||
|
return
|
||||||
of ekIndex:
|
of ekIndex:
|
||||||
c.exprIndexObj = substExpr(c.exprIndexObj, env, callLoc)
|
c.exprIndexObj = substExpr(c.exprIndexObj, env, callLoc)
|
||||||
c.exprIndexIdx = substExpr(c.exprIndexIdx, env, callLoc)
|
c.exprIndexIdx = substExpr(c.exprIndexIdx, env, callLoc)
|
||||||
of ekField:
|
of ekField:
|
||||||
c.exprFieldObj = substExpr(c.exprFieldObj, env, callLoc)
|
c.exprFieldObj = substExpr(c.exprFieldObj, env, callLoc)
|
||||||
of ekStructInit:
|
of ekStructInit:
|
||||||
|
var stas: seq[TypeExpr] = @[]
|
||||||
|
for ta in c.exprStructInitTypeArgs:
|
||||||
|
stas.add(substType(ta, env, callLoc))
|
||||||
|
c.exprStructInitTypeArgs = stas
|
||||||
var fields: seq[tuple[name: string, value: Expr]] = @[]
|
var fields: seq[tuple[name: string, value: Expr]] = @[]
|
||||||
for f in c.exprStructInitFields:
|
for f in c.exprStructInitFields:
|
||||||
fields.add((f.name, substExpr(f.value, env, callLoc)))
|
fields.add((f.name, substExpr(f.value, env, callLoc)))
|
||||||
@@ -1247,6 +1295,25 @@ proc expandOneCall(call: Expr, macros: Table[string, Decl],
|
|||||||
exprMacroTtInner: inner, exprMacroTtGroup: true)
|
exprMacroTtInner: inner, exprMacroTtGroup: true)
|
||||||
result = @[callee, group]
|
result = @[callee, group]
|
||||||
|
|
||||||
|
## Operators-only juxta: single binary arg `1 + 2` matches
|
||||||
|
## `$a:expr, $op:tt, $b:expr` (or juxta without commas).
|
||||||
|
proc juxtaBinarySplit(rule: MacroRule, inArgs: seq[Expr]): seq[Expr] =
|
||||||
|
result = inArgs
|
||||||
|
if inArgs.len != 1 or inArgs[0] == nil: return
|
||||||
|
if inArgs[0].kind != ekBinary: return
|
||||||
|
if rule.frags.len != 3: return
|
||||||
|
if rule.frags[0].isRep or rule.frags[1].isRep or rule.frags[2].isRep: return
|
||||||
|
let k0 = if rule.frags[0].kinds.len > 0: rule.frags[0].kinds[0] else: rule.frags[0].kind
|
||||||
|
let k1 = if rule.frags[1].kinds.len > 0: rule.frags[1].kinds[0] else: rule.frags[1].kind
|
||||||
|
let k2 = if rule.frags[2].kinds.len > 0: rule.frags[2].kinds[0] else: rule.frags[2].kind
|
||||||
|
if k0 != mfkExpr or k1 != mfkTt or k2 != mfkExpr: return
|
||||||
|
if not inArgs[0].exprBinaryOp.isBinaryPasteOp: return
|
||||||
|
let opTok = Token(kind: inArgs[0].exprBinaryOp, text: "", loc: inArgs[0].loc)
|
||||||
|
let lit = Expr(kind: ekLiteral, loc: inArgs[0].loc, exprLit: opTok)
|
||||||
|
let opTt = Expr(kind: ekMacroTt, loc: inArgs[0].loc,
|
||||||
|
exprMacroTtInner: lit, exprMacroTtGroup: false)
|
||||||
|
result = @[inArgs[0].exprBinaryLeft, opTt, inArgs[0].exprBinaryRight]
|
||||||
|
|
||||||
var matched: MacroRule
|
var matched: MacroRule
|
||||||
var env: MacroEnv
|
var env: MacroEnv
|
||||||
var found = false
|
var found = false
|
||||||
@@ -1257,7 +1324,8 @@ proc expandOneCall(call: Expr, macros: Table[string, Decl],
|
|||||||
var gi = 0
|
var gi = 0
|
||||||
var ai = 0
|
var ai = 0
|
||||||
let useGroups = nReps > 1 and groups.len > 1
|
let useGroups = nReps > 1 and groups.len > 1
|
||||||
let flat = juxtaCallSplit(rule, args)
|
var flat = juxtaCallSplit(rule, args)
|
||||||
|
flat = juxtaBinarySplit(rule, flat)
|
||||||
|
|
||||||
for frag in rule.frags:
|
for frag in rule.frags:
|
||||||
if failed: break
|
if failed: break
|
||||||
|
|||||||
@@ -434,10 +434,22 @@ proc isMacroStmtStart(p: Parser): bool =
|
|||||||
p.peek() in {tkLet, tkVar, tkIf, tkWhile, tkFor, tkLoop, tkMatch, tkReturn,
|
p.peek() in {tkLet, tkVar, tkIf, tkWhile, tkFor, tkLoop, tkMatch, tkReturn,
|
||||||
tkBreak, tkContinue, tkDefer, tkSwitch, tkDo}
|
tkBreak, tkContinue, tkDefer, tkSwitch, tkDo}
|
||||||
|
|
||||||
|
proc isBinaryPasteOpToken(k: TokenKind): bool =
|
||||||
|
## Same set as macroexpand.isBinaryPasteOp — kept local to avoid cycles.
|
||||||
|
case k
|
||||||
|
of tkPlus, tkMinus, tkStar, tkSlash, tkPercent, tkStarStar,
|
||||||
|
tkAmp, tkPipe, tkCaret, tkShl, tkShr,
|
||||||
|
tkAmpAmp, tkPipePipe,
|
||||||
|
tkEq, tkNe, tkLt, tkLe, tkGt, tkGe:
|
||||||
|
true
|
||||||
|
else:
|
||||||
|
false
|
||||||
|
|
||||||
proc parseMacroArg(p: var Parser): Expr =
|
proc parseMacroArg(p: var Parser): Expr =
|
||||||
## Macro call argument:
|
## Macro call argument:
|
||||||
## - statement keywords → ekMacroStmt
|
## - statement keywords → ekMacroStmt
|
||||||
## - `_` / pattern-only starts → ekMacroPat (also `$p:pat` from expr via coerce)
|
## - `_` / pattern-only starts → ekMacroPat (also `$p:pat` from expr via coerce)
|
||||||
|
## - bare binary operators (`+`, `*`, `==`, …) → ekMacroTt (operators-only paste)
|
||||||
## - else expression
|
## - else expression
|
||||||
let loc = p.currentLoc
|
let loc = p.currentLoc
|
||||||
if p.isMacroStmtStart():
|
if p.isMacroStmtStart():
|
||||||
@@ -447,6 +459,20 @@ proc parseMacroArg(p: var Parser): Expr =
|
|||||||
if p.check(tkUnderscore):
|
if p.check(tkUnderscore):
|
||||||
let pat = p.parsePattern()
|
let pat = p.parsePattern()
|
||||||
return Expr(kind: ekMacroPat, loc: loc, exprMacroPat: pat)
|
return Expr(kind: ekMacroPat, loc: loc, exprMacroPat: pat)
|
||||||
|
# Operators-only tt: `apply_op!(+, 1, 2)` / `apply_op!(*, 2, 3)`.
|
||||||
|
# Bare ops are not primary exprs. Unary-capable tokens (`*`, `-`, …) are only
|
||||||
|
# claimed when the next token ends the arg (`,`, `)`, `;`) so `*int` still
|
||||||
|
# parses as a type/expr and juxta `a * b` still works as ekBinary.
|
||||||
|
let pk = p.peek()
|
||||||
|
if isBinaryPasteOpToken(pk):
|
||||||
|
let nxt = p.peek(1)
|
||||||
|
let endsArg = nxt in {tkComma, tkRParen, tkSemicolon, tkEndOfFile, tkNewLine}
|
||||||
|
let unaryCapable = pk in {tkStar, tkMinus, tkBang, tkAmp, tkTilde,
|
||||||
|
tkPlusPlus, tkMinusMinus}
|
||||||
|
if endsArg or not unaryCapable:
|
||||||
|
let tok = p.advance()
|
||||||
|
let lit = Expr(kind: ekLiteral, loc: loc, exprLit: tok)
|
||||||
|
return Expr(kind: ekMacroTt, loc: loc, exprMacroTtInner: lit, exprMacroTtGroup: false)
|
||||||
p.parseExpr()
|
p.parseExpr()
|
||||||
|
|
||||||
proc parseMacroRepExpr(p: var Parser): Expr =
|
proc parseMacroRepExpr(p: var Parser): Expr =
|
||||||
|
|||||||
+57
-7
@@ -1078,6 +1078,58 @@ proc collectGlobals*(sema: var Sema) =
|
|||||||
proc checkExpr*(sema: var Sema, expr: Expr, scope: Scope): Type
|
proc checkExpr*(sema: var Sema, expr: Expr, scope: Scope): Type
|
||||||
proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type
|
proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type
|
||||||
|
|
||||||
|
proc isResultOrOptionName(name: string): bool =
|
||||||
|
name == "Result" or name.startsWith("Result_") or
|
||||||
|
name == "Option" or name.startsWith("Option_")
|
||||||
|
|
||||||
|
proc extractResultOptionPayload*(sema: var Sema, opTy: Type, loc: SourceLocation, opKind: string): Type =
|
||||||
|
## Payload type of `Result`/`Option` for `?` (try) and `!` (unwrap).
|
||||||
|
## Prefer type-args (`Result<T,E>` → T); fall back to Ok/Some field on the enum decl.
|
||||||
|
if opTy == nil or opTy.isUnknown:
|
||||||
|
return makeUnknown()
|
||||||
|
if opTy.kind != tkNamed:
|
||||||
|
sema.emitError(loc, opKind & " requires Result or Option operand")
|
||||||
|
return makeUnknown()
|
||||||
|
let name = opTy.name
|
||||||
|
if not isResultOrOptionName(name):
|
||||||
|
sema.emitError(loc, opKind & " requires Result or Option, got " & opTy.toString)
|
||||||
|
return makeUnknown()
|
||||||
|
# Result<T,E> / Option<T> store payload in .inner
|
||||||
|
if opTy.inner.len >= 1:
|
||||||
|
return opTy.inner[0]
|
||||||
|
# Bare / monomorphized name: look up Ok(T) / Some(T) on the enum decl
|
||||||
|
var enumSym = sema.globalScope.lookup(name)
|
||||||
|
if (enumSym == nil or enumSym.decl == nil or enumSym.decl.kind != dkEnum):
|
||||||
|
if name.startsWith("Result_"):
|
||||||
|
enumSym = sema.globalScope.lookup("Result")
|
||||||
|
elif name.startsWith("Option_"):
|
||||||
|
enumSym = sema.globalScope.lookup("Option")
|
||||||
|
let wantVariant =
|
||||||
|
if name == "Option" or name.startsWith("Option_"): "Some"
|
||||||
|
else: "Ok"
|
||||||
|
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||||
|
var subst = initTable[string, Type]()
|
||||||
|
# Result_String_String → try to bind type params from mangled suffix when possible
|
||||||
|
if opTy.inner.len == 0 and enumSym.decl.declEnumTypeParams.len > 0 and
|
||||||
|
(name.startsWith("Result_") or name.startsWith("Option_")):
|
||||||
|
let prefix = if name.startsWith("Result_"): "Result_" else: "Option_"
|
||||||
|
let rest = name[prefix.len .. ^1]
|
||||||
|
# Split on '_' is imperfect for nested types; for simple T_E it works
|
||||||
|
let parts = rest.split('_')
|
||||||
|
var pi = 0
|
||||||
|
for tp in enumSym.decl.declEnumTypeParams:
|
||||||
|
if pi < parts.len and parts[pi].len > 0:
|
||||||
|
# Re-resolve simple type names (int, String, …)
|
||||||
|
let te = TypeExpr(kind: tekNamed, typeName: parts[pi])
|
||||||
|
subst[tp.name] = sema.resolveType(te)
|
||||||
|
inc pi
|
||||||
|
for variant in enumSym.decl.declEnumVariants:
|
||||||
|
if variant.name == wantVariant and variant.fields.len > 0:
|
||||||
|
let raw = sema.resolveType(variant.fields[0])
|
||||||
|
return sema.substituteTypeInType(raw, subst)
|
||||||
|
# Unknown payload — don't invent int (breaks String Results)
|
||||||
|
return makeUnknown()
|
||||||
|
|
||||||
proc typeImplements(sema: Sema, t: Type, interfaceName: string): bool =
|
proc typeImplements(sema: Sema, t: Type, interfaceName: string): bool =
|
||||||
## Check if a type implements an interface by verifying all required methods exist.
|
## Check if a type implements an interface by verifying all required methods exist.
|
||||||
if t.isUnknown: return true
|
if t.isUnknown: return true
|
||||||
@@ -1838,14 +1890,12 @@ proc checkExpr*(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
discard sema.checkExpr(expr.exprIsOperand, scope)
|
discard sema.checkExpr(expr.exprIsOperand, scope)
|
||||||
return makeBool()
|
return makeBool()
|
||||||
of ekTry:
|
of ekTry:
|
||||||
discard sema.checkExpr(expr.exprTryOperand, scope)
|
let opTy = sema.checkExpr(expr.exprTryOperand, scope)
|
||||||
# For now, assume Result<int, String> -> int
|
# Payload of Result/Option; validates operand is Result or Option
|
||||||
# TODO: check operand is Result/Option and current function returns same type
|
return sema.extractResultOptionPayload(opTy, expr.loc, "try operator (`?`)")
|
||||||
return makeInt()
|
|
||||||
of ekUnwrap:
|
of ekUnwrap:
|
||||||
discard sema.checkExpr(expr.exprUnwrapOperand, scope)
|
let opTy = sema.checkExpr(expr.exprUnwrapOperand, scope)
|
||||||
# Unwrap: extract Ok value or panic on Err
|
return sema.extractResultOptionPayload(opTy, expr.loc, "unwrap operator (`!`)")
|
||||||
return makeInt()
|
|
||||||
of ekBlock:
|
of ekBlock:
|
||||||
var blockScope = newScope(scope)
|
var blockScope = newScope(scope)
|
||||||
var lastType = makeVoid()
|
var lastType = makeVoid()
|
||||||
|
|||||||
+18
-6
@@ -181,13 +181,25 @@ no-libc freestanding firmware.
|
|||||||
| Thin runtime (no pthread/OpenSSL) | ✅ | `rt/runtime_minimal.c` |
|
| Thin runtime (no pthread/OpenSSL) | ✅ | `rt/runtime_minimal.c` |
|
||||||
| Static musl / distroless | ✅ | `make test-musl-static`, Dockerfiles |
|
| Static musl / distroless | ✅ | `make test-musl-static`, Dockerfiles |
|
||||||
| Linux multi-arch cross | ✅ | aarch64 + riscv64 smokes (SKIP without gcc) |
|
| Linux multi-arch cross | ✅ | aarch64 + riscv64 smokes (SKIP without gcc) |
|
||||||
| True freestanding (`-ffreestanding`, no libc) | 🔬 spike | Needs custom alloc, panic, and I/O stubs |
|
| True freestanding (`-ffreestanding`, no libc) | ✅ spike shipped | `rt/runtime_freestanding.c` + `BUX_RUNTIME=freestanding` |
|
||||||
| Cortex-M / qemu-system | 🔬 spike | Same; plus linker scripts and startup |
|
| Cortex-M / qemu-system | 🔬 research | Needs linker scripts, startup, board UART — not in tree |
|
||||||
|
|
||||||
**Practical path today:** build with `BUX_RUNTIME=minimal --static --target …` for
|
**Freestanding runtime** (`rt/runtime_freestanding.c`):
|
||||||
Linux userspace on foreign ISAs; treat bare-metal as a research project that
|
|
||||||
starts from a custom `runtime_freestanding.c` (not shipped) and does **not**
|
- No hosted libc: bump heap (default 256 KiB), freestanding string/arith helpers
|
||||||
import `Std::Net` / `Std::Task` / OpenSSL.
|
- Weak hooks `bux_fs_write` / `bux_fs_halt` for board BSP or host overrides
|
||||||
|
- Net / Task / TLS / FS return failure stubs
|
||||||
|
- Optional `_start` when compiled with `-DBUX_FS_PROVIDE_START` (nostdlib experiments)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test-freestanding # -ffreestanding -c + package build exit 42
|
||||||
|
BUX_RUNTIME=freestanding buxc build .
|
||||||
|
# Still uses host crt0 unless you pass -nostdlib / custom start via BUX_CFLAGS
|
||||||
|
```
|
||||||
|
|
||||||
|
**Practical path for cloud/cross:** `BUX_RUNTIME=minimal --static --target …` for
|
||||||
|
Linux userspace. Use `freestanding` only for no-libc research; do **not** import
|
||||||
|
`Std::Net` / `Std::Task` / OpenSSL on that path.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+26
-1
@@ -1,7 +1,32 @@
|
|||||||
# Bux — План за подобрения (post-v1.0.0)
|
# Bux — План за подобрения (post-v1.0.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-28
|
> **Дата:** 2026-07-28
|
||||||
> **Статус:** Всички приоритетни задачи изпълнени ✅
|
> **Статус:** Всички приоритетни задачи изпълнени ✅ · follow-up DX/correctness shipped
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 4 — Try payload type + LSP formatting (2026-07-28)
|
||||||
|
|
||||||
|
| # | Задача | Файлове |
|
||||||
|
|---|--------|---------|
|
||||||
|
| F.5 | `?` / `!` вече връщат **Ok/Some payload type** (не винаги `int`) | `bootstrap/sema.nim`, `bootstrap/hir_lower.nim` |
|
||||||
|
| F.6 | Example `try_generic` — `Result<String, String>` + `?` | `examples/try_generic.bux` |
|
||||||
|
| D.6 | LSP **v0.18** `textDocument/formatting` (+ range) = `bux fmt` | `tools/lsp_server.nim`, `tools/smoke_lsp_formatting.sh` |
|
||||||
|
| D.7 | VS Code format-on-save default; docs | `vscode/package.json`, `docs/LSP.md` |
|
||||||
|
|
||||||
|
**Verified:** `try_generic` prints `hello` / `empty name`; `try_operator` still OK; formatting smoke PASS.
|
||||||
|
|
||||||
|
## Сесия 5 — Post-1.0 backlog: macros + freestanding (2026-07-28)
|
||||||
|
|
||||||
|
| # | Задача | Файлове |
|
||||||
|
|---|--------|---------|
|
||||||
|
| M.1 | Generics in `$t:type` + `$t` in `Array_New<$t>` | `bootstrap/macroexpand.nim`, `src/macroexpand.bux` |
|
||||||
|
| M.2 | Operators-only `:tt` paste (`$op($a,$b)` + juxta binary split) | `bootstrap/parser.nim`, `bootstrap/macroexpand.nim` |
|
||||||
|
| M.3 | Examples `macro_type_generic`, `macro_op_paste` | `examples/` |
|
||||||
|
| R.1 | `rt/runtime_freestanding.c` + `BUX_RUNTIME=freestanding` | `rt/`, `bootstrap/cli.nim`, `src/cli.bux` |
|
||||||
|
| R.2 | `make test-freestanding` smoke | `tools/smoke_freestanding.sh` |
|
||||||
|
|
||||||
|
**Verified:** both macro examples PASS; freestanding `-ffreestanding -c` + package exit 42.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+23
-7
@@ -1,13 +1,14 @@
|
|||||||
# Bux Language Server (`bux-lsp`)
|
# Bux Language Server (`bux-lsp`)
|
||||||
|
|
||||||
> **Status:** **v0.17.0** — stdio JSON-RPC 2.0 language server
|
> **Status:** **v0.18.0** — stdio JSON-RPC 2.0 language server
|
||||||
> **Binary:** `tools/bux-lsp` (`make lsp`)
|
> **Binary:** `tools/bux-lsp` (`make lsp`)
|
||||||
> **Editors:** VS Code extension in [`vscode/`](../vscode/README.md); any LSP client via stdio
|
> **Editors:** VS Code extension in [`vscode/`](../vscode/README.md); any LSP client via stdio
|
||||||
|
|
||||||
Bux already ships a real Language Server Protocol implementation. It is **not**
|
Bux already ships a real Language Server Protocol implementation. It is **not**
|
||||||
syntax-only: hover and outline use bootstrap semantic analysis when available,
|
syntax-only: hover and outline use bootstrap semantic analysis when available,
|
||||||
and **error underlines (red squiggles)** come from **in-process** lex / parse / type-check
|
**error underlines (red squiggles)** come from **in-process** lex / parse / type-check
|
||||||
of the **live editor buffer** on every open, edit, and save.
|
of the **live editor buffer** on every open, edit, and save, and **Format Document**
|
||||||
|
uses the same indentation engine as `bux fmt`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,13 +34,15 @@ Protocol framing: standard `Content-Length` headers + JSON-RPC 2.0 body.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Capabilities (v0.16)
|
## Capabilities (v0.18)
|
||||||
|
|
||||||
| Method | Support | Notes |
|
| Method | Support | Notes |
|
||||||
|--------|---------|--------|
|
|--------|---------|--------|
|
||||||
| `initialize` / `shutdown` / `exit` | ✅ | `serverInfo`: `bux-lsp` 0.17.0 |
|
| `initialize` / `shutdown` / `exit` | ✅ | `serverInfo`: `bux-lsp` 0.18.0 |
|
||||||
| `textDocument/didOpen` / `didChange` / `didSave` | ✅ | Full text sync (`textDocumentSync: 1`) |
|
| `textDocument/didOpen` / `didChange` / `didSave` | ✅ | Full text sync (`textDocumentSync: 1`) |
|
||||||
| `textDocument/publishDiagnostics` | ✅ | **Live underlines** on open/change/save (in-process); optional `buxc` merge on open/save |
|
| `textDocument/publishDiagnostics` | ✅ | **Live underlines** on open/change/save (in-process); optional `buxc` merge on open/save |
|
||||||
|
| `textDocument/formatting` | ✅ | Full document — same rules as `bux fmt` (4-space brace indent) |
|
||||||
|
| `textDocument/rangeFormatting` | ✅ | Applies full-file format (indent depends on whole brace structure) |
|
||||||
| `textDocument/completion` | ✅ | Trigger: `.` `:` |
|
| `textDocument/completion` | ✅ | Trigger: `.` `:` |
|
||||||
| `textDocument/hover` | ✅ | Sema types for globals/stdlib; **scoped locals** + inferred `let` |
|
| `textDocument/hover` | ✅ | Sema types for globals/stdlib; **scoped locals** + inferred `let` |
|
||||||
| `textDocument/definition` | ✅ | Go to definition |
|
| `textDocument/definition` | ✅ | Go to definition |
|
||||||
@@ -123,15 +126,28 @@ make test-lsp
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Format Document / format-on-save
|
||||||
|
|
||||||
|
`textDocument/formatting` re-indents the buffer with **4 spaces × brace depth**
|
||||||
|
(identical to `bux fmt` / `bootstrap/fmt.nim`). Idempotent: a clean file yields
|
||||||
|
an empty edit list.
|
||||||
|
|
||||||
|
**VS Code:** the extension defaults `editor.formatOnSave` for `[bux]`. Disable
|
||||||
|
with `"editor.formatOnSave": false` in workspace settings if you prefer manual
|
||||||
|
only. Command palette: **Format Document**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test-lsp # includes tools/smoke_lsp_formatting.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Limitations / not yet
|
## Limitations / not yet
|
||||||
|
|
||||||
Honest gaps (so Reddit / issue trackers stay accurate):
|
Honest gaps (so Reddit / issue trackers stay accurate):
|
||||||
|
|
||||||
- **No format-on-save via LSP** yet (`bux fmt` exists as CLI; not `textDocument/formatting`)
|
|
||||||
- **No semantic tokens** provider (TextMate grammar handles highlighting in VS Code)
|
- **No semantic tokens** provider (TextMate grammar handles highlighting in VS Code)
|
||||||
- **No code actions / lightbulbs** (quick-fixes)
|
- **No code actions / lightbulbs** (quick-fixes)
|
||||||
- **No inlay hints**
|
- **No inlay hints**
|
||||||
- **didChange** uses a fast symbol path; full sema + diagnostics refresh mainly on open/save
|
- **rangeFormatting** reformats the whole file (partial selection cannot get correct indent without full brace context)
|
||||||
- Completion is useful but not a full IDE IntelliSense engine
|
- Completion is useful but not a full IDE IntelliSense engine
|
||||||
- Single-process stdio only (no TCP/socket mode)
|
- Single-process stdio only (no TCP/socket mode)
|
||||||
|
|
||||||
|
|||||||
+49
-4
@@ -997,6 +997,25 @@ func Compute() -> Result {
|
|||||||
```
|
```
|
||||||
|
|
||||||
`?` can be used on `Result` and `Option` types in any expression context.
|
`?` can be used on `Result` and `Option` types in any expression context.
|
||||||
|
The type of `expr?` is the **Ok / Some payload** (`T` in `Result<T,E>` or
|
||||||
|
`Option<T>`), not always `int`. The enclosing function must return a compatible
|
||||||
|
Result/Option so Err/None can propagate.
|
||||||
|
|
||||||
|
```bux
|
||||||
|
// Generic Result — payload type is String
|
||||||
|
func GetName() -> Result<String, String> {
|
||||||
|
return Result_NewOk<String, String>("bux");
|
||||||
|
}
|
||||||
|
func Run() -> Result<String, String> {
|
||||||
|
let n: String = GetName()?; // n: String
|
||||||
|
return Result_NewOk<String, String>(n);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The postfix unwrap operator `expr!` extracts Ok/Some or panics (and exits) on
|
||||||
|
Err/None; its type is likewise the payload type.
|
||||||
|
|
||||||
|
See also `examples/try_operator.bux` and `examples/try_generic.bux`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1312,13 +1331,32 @@ macro! with_acc {
|
|||||||
between fragments) matches a **single call-site argument** that is a call
|
between fragments) matches a **single call-site argument** that is a call
|
||||||
expression: `apply_juxta!(Add(2, 5))` → binds `$f=Add`, `$args` = arg-list
|
expression: `apply_juxta!(Add(2, 5))` → binds `$f=Add`, `$args` = arg-list
|
||||||
group, then `$f($args)` flattens to `Add(2, 5)`.
|
group, then `$f($args)` flattens to `Add(2, 5)`.
|
||||||
- **Type fragments (session 87):**
|
- **Type fragments (session 87+):** named, pointer, and **generic** types
|
||||||
|
(`Array<int>`, `*int`); `$t` substitutes in `sizeof` / cast / let types and
|
||||||
|
monomorph call type args (`Array_New<$t>`).
|
||||||
```bux
|
```bux
|
||||||
macro! size_of {
|
macro! size_of {
|
||||||
( $t:type ) => { sizeof($t) as int }
|
( $t:type ) => { sizeof($t) as int }
|
||||||
}
|
}
|
||||||
|
macro! new_array {
|
||||||
|
( $t:type, $cap:expr ) => { Array_New<$t>($cap) }
|
||||||
|
}
|
||||||
let n: int = size_of!(int);
|
let n: int = size_of!(int);
|
||||||
let p: int = size_of!(*int);
|
let p: int = size_of!(*int);
|
||||||
|
let s: int = size_of!(Array<int>);
|
||||||
|
var a: Array<int> = new_array!(int, 4);
|
||||||
|
```
|
||||||
|
- **Operators-only `:tt` paste:**
|
||||||
|
```bux
|
||||||
|
macro! apply_op {
|
||||||
|
( $op:tt, $a:expr, $b:expr ) => { $op($a, $b) }
|
||||||
|
}
|
||||||
|
macro! flip_op {
|
||||||
|
( $a:expr, $op:tt, $b:expr ) => { $op($b, $a) }
|
||||||
|
}
|
||||||
|
let x: int = apply_op!(+, 3, 4); // 7
|
||||||
|
let y: int = apply_op!(*, 6, 7); // 42
|
||||||
|
let z: int = flip_op!(10 - 3); // -7 (juxta binary split)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Invocation
|
### Invocation
|
||||||
@@ -1415,8 +1453,15 @@ Examples: `examples/macro_hygiene.bux`, `examples/macro_unhygienic.bux`.
|
|||||||
templates (not as a free-standing primary expression).
|
templates (not as a free-standing primary expression).
|
||||||
- Raw delimiter-balanced `tt` covers **tuple** `(a, b)` and **slice lit**
|
- Raw delimiter-balanced `tt` covers **tuple** `(a, b)` and **slice lit**
|
||||||
`[a, b]` groups, plus **juxta call-split** for `$f:ident $args:tt` matching
|
`[a, b]` groups, plus **juxta call-split** for `$f:ident $args:tt` matching
|
||||||
`F(a, b)`. Arbitrary free-form token pastes (operators-only, type-only
|
`F(a, b)`.
|
||||||
without AST) remain out of scope.
|
- **Operators-only paste:** bare binary ops as `:tt` (`+`, `*`, `==`, …) and
|
||||||
|
juxta binary split `$a:expr $op:tt $b:expr` on a single binary arg. Template
|
||||||
|
form `$op($a, $b)` rebuilds `a OP b`. See `examples/macro_op_paste.bux`.
|
||||||
|
- **`:type` generics:** `Array<int>`, `*int`, nested type args; `$t` splices
|
||||||
|
into `sizeof($t)`, casts, and `Array_New<$t>(…)`. See
|
||||||
|
`examples/macro_type.bux`, `examples/macro_type_generic.bux`.
|
||||||
|
- Fully free-form token streams (unparsed soup) remain out of scope.
|
||||||
|
|
||||||
Examples: `examples/macro_tt.bux`, `examples/macro_tt_raw.bux`,
|
Examples: `examples/macro_tt.bux`, `examples/macro_tt_raw.bux`,
|
||||||
`examples/macro_repeat.bux`, `examples/macro_nested.bux`.
|
`examples/macro_repeat.bux`, `examples/macro_nested.bux`,
|
||||||
|
`examples/macro_type_generic.bux`, `examples/macro_op_paste.bux`.
|
||||||
|
|||||||
+14
-2
@@ -1472,10 +1472,22 @@ bootstrap + **buxc2** `macro_tt_raw` (incl. slice) PASS.
|
|||||||
5. Tag `v1.0.0` after `make test` gate
|
5. Tag `v1.0.0` after `make test` gate
|
||||||
|
|
||||||
**Post-1.0 backlog (MINOR, not freeze blockers):**
|
**Post-1.0 backlog (MINOR, not freeze blockers):**
|
||||||
- Generics in `:type` (`Array<int>`); operators-only tt paste
|
- ~~Generics in `:type` (`Array<int>`); operators-only tt paste~~ ✅ session 5
|
||||||
- `runtime_freestanding.c` + Cortex-M research
|
- ~~`runtime_freestanding.c`~~ ✅ session 5 (Cortex-M / board BSP still research)
|
||||||
- LSP / IDE versioning independent of language MAJOR
|
- LSP / IDE versioning independent of language MAJOR
|
||||||
|
|
||||||
|
### Follow-up (2026-07-28)
|
||||||
|
|
||||||
|
1. **`?` / `!` payload types** — bootstrap no longer hardcodes `int`; Ok/Some type
|
||||||
|
from `Result<T,E>` / enum fields (`bootstrap/sema.nim`, `hir_lower.nim`).
|
||||||
|
2. **Example** `try_generic` — String Result propagation.
|
||||||
|
3. **LSP 0.18** — `textDocument/formatting` (+ range) via `formatSource` / `bux fmt`;
|
||||||
|
VS Code format-on-save default; `tools/smoke_lsp_formatting.sh`.
|
||||||
|
4. **Macros:** generic `:type` + `Array_New<$t>`; operators-only `$op($a,$b)` /
|
||||||
|
juxta binary split (`examples/macro_type_generic`, `macro_op_paste`).
|
||||||
|
5. **Freestanding:** `rt/runtime_freestanding.c`, `BUX_RUNTIME=freestanding`,
|
||||||
|
`make test-freestanding`.
|
||||||
|
|
||||||
### Изрично **не** правим
|
### Изрично **не** правим
|
||||||
|
|
||||||
- Повече Windows examples / Win OpenSSL / Win sockets
|
- Повече Windows examples / Win OpenSSL / Win sockets
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Operators-only `:tt` paste — `$op($a, $b)` → `a OP b`
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_AssertEqBool, Test_Pass};
|
||||||
|
|
||||||
|
// Explicit op as call-site tt: apply_op!(+, 3, 4) → 7
|
||||||
|
macro! apply_op {
|
||||||
|
( $op:tt, $a:expr, $b:expr ) => { $op($a, $b) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Juxta binary split: flip_op!(10 - 3) → 3 - 10
|
||||||
|
macro! flip_op {
|
||||||
|
( $a:expr, $op:tt, $b:expr ) => { $op($b, $a) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let sum: int = apply_op!(+, 3, 4);
|
||||||
|
Test_AssertEqInt(sum, 7);
|
||||||
|
|
||||||
|
let prod: int = apply_op!(*, 6, 7);
|
||||||
|
Test_AssertEqInt(prod, 42);
|
||||||
|
|
||||||
|
let diff: int = flip_op!(10 - 3);
|
||||||
|
Test_AssertEqInt(diff, -7);
|
||||||
|
|
||||||
|
let eq: bool = apply_op!(==, 5, 5);
|
||||||
|
Test_AssertEqBool(eq, true);
|
||||||
|
|
||||||
|
let lt: bool = apply_op!(<, 2, 9);
|
||||||
|
Test_AssertEqBool(lt, true);
|
||||||
|
|
||||||
|
PrintInt(sum);
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(prod);
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(diff);
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("PASS macro_op_paste");
|
||||||
|
Test_Pass("macro_op_paste");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Generics in `$t:type` + type-arg splice into Array_New<$t>
|
||||||
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
import Std::Array::{Array, Array_New, Array_Push, Array_Get, Array_Len, Array_Free};
|
||||||
|
import Std::Test::{Test_AssertEqInt, Test_Pass};
|
||||||
|
|
||||||
|
macro! size_of {
|
||||||
|
( $t:type ) => { sizeof($t) as int }
|
||||||
|
}
|
||||||
|
|
||||||
|
macro! new_array {
|
||||||
|
( $t:type, $cap:expr ) => {
|
||||||
|
Array_New<$t>($cap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
// Generic type fragment: Array<int>
|
||||||
|
let sz: int = size_of!(Array<int>);
|
||||||
|
PrintInt(sz);
|
||||||
|
PrintLine("");
|
||||||
|
if sz < 8 {
|
||||||
|
PrintLine("FAIL size_of Array<int>");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $t spliced into monomorphized call Array_New<$t>
|
||||||
|
var a: Array<int> = new_array!(int, 4);
|
||||||
|
Array_Push<int>(&a, 10);
|
||||||
|
Array_Push<int>(&a, 20);
|
||||||
|
Test_AssertEqInt(Array_Get<int>(&a, 0), 10);
|
||||||
|
Test_AssertEqInt(Array_Get<int>(&a, 1), 20);
|
||||||
|
Test_AssertEqInt(Array_Len<int>(&a) as int, 2);
|
||||||
|
Array_Free<int>(&a);
|
||||||
|
|
||||||
|
let psz: int = size_of!(*int);
|
||||||
|
if psz != 4 && psz != 8 {
|
||||||
|
PrintLine("FAIL size_of *int");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintLine("PASS macro_type_generic");
|
||||||
|
Test_Pass("macro_type_generic");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Try operator `?` with generic Result<T,E> (non-int Ok payload).
|
||||||
|
import Std::Io::{PrintLine};
|
||||||
|
import Std::Result::{Result, Result_NewOk, Result_NewErr};
|
||||||
|
import Std::String::{String_Eq};
|
||||||
|
|
||||||
|
func GetGreeting(name: String) -> Result<String, String> {
|
||||||
|
if String_Eq(name, "") {
|
||||||
|
return Result_NewErr<String, String>("empty name");
|
||||||
|
}
|
||||||
|
return Result_NewOk<String, String>("hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
func Greet(name: String) -> Result<String, String> {
|
||||||
|
let g: String = GetGreeting(name)?;
|
||||||
|
return Result_NewOk<String, String>(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
PrintLine("Try generic Result demo:");
|
||||||
|
|
||||||
|
let ok: Result<String, String> = Greet("bux");
|
||||||
|
if ok.tag == Result_Ok {
|
||||||
|
PrintLine(ok.data.Ok_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let err: Result<String, String> = Greet("");
|
||||||
|
if err.tag == Result_Err {
|
||||||
|
PrintLine(err.data.Err_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,484 @@
|
|||||||
|
/* Bux Runtime — freestanding / bare-metal research spike (post-v1.0)
|
||||||
|
*
|
||||||
|
* Goal: link with `-ffreestanding -nostdlib` without pulling in a hosted libc.
|
||||||
|
* This is **not** a full embedded platform kit (no Cortex-M startup, no
|
||||||
|
* linker scripts, no UART drivers). It is a portable base for:
|
||||||
|
* - compile smoke under -ffreestanding
|
||||||
|
* - no-libc static link experiments (custom `_start`)
|
||||||
|
* - future board BSPs that replace the weak I/O hooks
|
||||||
|
*
|
||||||
|
* Select: BUX_RUNTIME=freestanding (bootstrap + selfhost)
|
||||||
|
*
|
||||||
|
* Memory: fixed bump allocator over a static arena (default 256 KiB).
|
||||||
|
* Override at compile time: -DBUX_FS_HEAP_BYTES=N
|
||||||
|
*
|
||||||
|
* I/O: weak stubs — default print is a no-op; panic spins. Host may override:
|
||||||
|
* void bux_fs_write(const char* s, unsigned n);
|
||||||
|
* void bux_fs_halt(int code);
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(__STDC_HOSTED__) && __STDC_HOSTED__ == 1 && !defined(BUX_FS_FORCE)
|
||||||
|
/* When accidentally compiled as hosted without -ffreestanding, still avoid
|
||||||
|
* libc — we implement everything we need. */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ── Fixed-width types without stdint.h ────────────────────────────────── */
|
||||||
|
typedef signed char bux_i8;
|
||||||
|
typedef unsigned char bux_u8;
|
||||||
|
typedef short bux_i16;
|
||||||
|
typedef unsigned short bux_u16;
|
||||||
|
typedef int bux_i32;
|
||||||
|
typedef unsigned int bux_u32;
|
||||||
|
#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(__aarch64__)
|
||||||
|
typedef long bux_i64;
|
||||||
|
typedef unsigned long bux_u64;
|
||||||
|
typedef unsigned long bux_size;
|
||||||
|
#else
|
||||||
|
typedef long long bux_i64;
|
||||||
|
typedef unsigned long long bux_u64;
|
||||||
|
typedef unsigned int bux_size;
|
||||||
|
#endif
|
||||||
|
typedef bux_u8 bux_bool;
|
||||||
|
#ifndef NULL
|
||||||
|
# define NULL ((void*)0)
|
||||||
|
#endif
|
||||||
|
#ifndef true
|
||||||
|
# define true 1
|
||||||
|
# define false 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ── Heap ─────────────────────────────────────────────────────────────── */
|
||||||
|
#ifndef BUX_FS_HEAP_BYTES
|
||||||
|
# define BUX_FS_HEAP_BYTES (256u * 1024u)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static unsigned char bux_fs_heap[BUX_FS_HEAP_BYTES];
|
||||||
|
static bux_size bux_fs_heap_off = 0;
|
||||||
|
|
||||||
|
/* Weak hooks — board / host may override */
|
||||||
|
__attribute__((weak)) void bux_fs_write(const char* s, unsigned n) {
|
||||||
|
(void)s; (void)n;
|
||||||
|
}
|
||||||
|
__attribute__((weak)) void bux_fs_halt(int code) {
|
||||||
|
(void)code;
|
||||||
|
for (;;) { /* spin */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── CLI args (empty under freestanding) ──────────────────────────────── */
|
||||||
|
int g_argc = 0;
|
||||||
|
char** g_argv = NULL;
|
||||||
|
|
||||||
|
int bux_argc(void) { return g_argc; }
|
||||||
|
char* bux_argv(int index) {
|
||||||
|
(void)index;
|
||||||
|
return (char*)"";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Memory ───────────────────────────────────────────────────────────── */
|
||||||
|
static void bux_fs_zero(void* p, bux_size n) {
|
||||||
|
unsigned char* b = (unsigned char*)p;
|
||||||
|
bux_size i;
|
||||||
|
for (i = 0; i < n; i++) b[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* bux_alloc(bux_size size) {
|
||||||
|
/* 8-byte align */
|
||||||
|
bux_size off = (bux_fs_heap_off + 7u) & ~(bux_size)7u;
|
||||||
|
if (size == 0) size = 1;
|
||||||
|
if (off + size > (bux_size)BUX_FS_HEAP_BYTES) {
|
||||||
|
bux_fs_write("OOM\n", 4);
|
||||||
|
bux_fs_halt(1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
void* p = &bux_fs_heap[off];
|
||||||
|
bux_fs_heap_off = off + size;
|
||||||
|
bux_fs_zero(p, size);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* bux_realloc(void* ptr, bux_size size) {
|
||||||
|
/* Bump allocator cannot free — allocate fresh + copy if needed */
|
||||||
|
void* n = bux_alloc(size);
|
||||||
|
if (ptr != NULL && n != NULL && size > 0) {
|
||||||
|
unsigned char* d = (unsigned char*)n;
|
||||||
|
unsigned char* s = (unsigned char*)ptr;
|
||||||
|
bux_size i;
|
||||||
|
/* unknown old size — best effort copy of `size` bytes */
|
||||||
|
for (i = 0; i < size; i++) d[i] = s[i];
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bux_free(void* ptr) { (void)ptr; /* bump: no-op */ }
|
||||||
|
|
||||||
|
/* ── Basic I/O / panic ────────────────────────────────────────────────── */
|
||||||
|
void bux_print(const char* s) {
|
||||||
|
if (!s) return;
|
||||||
|
unsigned n = 0;
|
||||||
|
while (s[n]) n++;
|
||||||
|
bux_fs_write(s, n);
|
||||||
|
}
|
||||||
|
void bux_println(const char* s) {
|
||||||
|
bux_print(s ? s : "");
|
||||||
|
bux_fs_write("\n", 1);
|
||||||
|
}
|
||||||
|
void bux_print_int(bux_i64 n) {
|
||||||
|
char buf[32];
|
||||||
|
int i = 0;
|
||||||
|
int neg = 0;
|
||||||
|
if (n < 0) { neg = 1; n = -n; }
|
||||||
|
if (n == 0) { buf[i++] = '0'; }
|
||||||
|
else {
|
||||||
|
while (n > 0 && i < 30) {
|
||||||
|
buf[i++] = (char)('0' + (n % 10));
|
||||||
|
n /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (neg) buf[i++] = '-';
|
||||||
|
/* reverse */
|
||||||
|
int a = 0, b = i - 1;
|
||||||
|
while (a < b) {
|
||||||
|
char t = buf[a]; buf[a] = buf[b]; buf[b] = t;
|
||||||
|
a++; b--;
|
||||||
|
}
|
||||||
|
bux_fs_write(buf, (unsigned)i);
|
||||||
|
}
|
||||||
|
void bux_print_float(double f) {
|
||||||
|
/* minimal: cast to int for freestanding */
|
||||||
|
bux_print_int((bux_i64)f);
|
||||||
|
}
|
||||||
|
void bux_print_bool(bux_bool b) { bux_print(b ? "true" : "false"); }
|
||||||
|
void bux_print_char(char c) { bux_fs_write(&c, 1); }
|
||||||
|
void bux_panic(const char* msg) {
|
||||||
|
bux_fs_write("PANIC: ", 7);
|
||||||
|
if (msg) {
|
||||||
|
unsigned n = 0;
|
||||||
|
while (msg[n]) n++;
|
||||||
|
bux_fs_write(msg, n);
|
||||||
|
}
|
||||||
|
bux_fs_write("\n", 1);
|
||||||
|
bux_fs_halt(1);
|
||||||
|
}
|
||||||
|
void bux_exit(int code) { bux_fs_halt(code); }
|
||||||
|
void bux_assert(int cond, const char* file, int line, const char* expr) {
|
||||||
|
(void)file; (void)line; (void)expr;
|
||||||
|
if (!cond) bux_panic("assert failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Checked arithmetic ───────────────────────────────────────────────── */
|
||||||
|
bux_i64 bux_div_i64(bux_i64 a, bux_i64 b) {
|
||||||
|
if (b == 0) bux_panic("division by zero");
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
bux_i64 bux_mod_i64(bux_i64 a, bux_i64 b) {
|
||||||
|
if (b == 0) bux_panic("modulo by zero");
|
||||||
|
return a % b;
|
||||||
|
}
|
||||||
|
bux_i64 bux_add_i64_checked(bux_i64 a, bux_i64 b) { return a + b; }
|
||||||
|
bux_i64 bux_sub_i64_checked(bux_i64 a, bux_i64 b) { return a - b; }
|
||||||
|
bux_i64 bux_mul_i64_checked(bux_i64 a, bux_i64 b) { return a * b; }
|
||||||
|
bux_i64 bux_neg_i64_checked(bux_i64 a) { return -a; }
|
||||||
|
|
||||||
|
/* ── Strings (no libc) ────────────────────────────────────────────────── */
|
||||||
|
unsigned int bux_strlen(const char* s) {
|
||||||
|
unsigned int n = 0;
|
||||||
|
if (!s) return 0;
|
||||||
|
while (s[n]) n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
int bux_strlen_c(const char* s) { return (int)bux_strlen(s); }
|
||||||
|
int bux_strcmp(const char* a, const char* b) {
|
||||||
|
if (!a) a = "";
|
||||||
|
if (!b) b = "";
|
||||||
|
while (*a && *a == *b) { a++; b++; }
|
||||||
|
return (unsigned char)*a - (unsigned char)*b;
|
||||||
|
}
|
||||||
|
int bux_strncmp(const char* a, const char* b, unsigned int n) {
|
||||||
|
if (!a) a = "";
|
||||||
|
if (!b) b = "";
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
if (a[i] != b[i] || a[i] == 0) return (unsigned char)a[i] - (unsigned char)b[i];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
char* bux_strcpy(char* dest, const char* src) {
|
||||||
|
if (!dest) return NULL;
|
||||||
|
if (!src) { dest[0] = 0; return dest; }
|
||||||
|
char* d = dest;
|
||||||
|
while ((*d++ = *src++)) {}
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
char* bux_strcat(char* dest, const char* src) {
|
||||||
|
if (!dest) return NULL;
|
||||||
|
if (!src) return dest;
|
||||||
|
char* d = dest;
|
||||||
|
while (*d) d++;
|
||||||
|
while ((*d++ = *src++)) {}
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
char* bux_strncpy(char* dest, const char* src, unsigned int n) {
|
||||||
|
if (!dest) return NULL;
|
||||||
|
unsigned int i = 0;
|
||||||
|
if (src) {
|
||||||
|
for (; i < n && src[i]; i++) dest[i] = src[i];
|
||||||
|
}
|
||||||
|
for (; i < n; i++) dest[i] = 0;
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
double bux_str_to_float(const char* s) {
|
||||||
|
(void)s;
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
bux_i64 bux_str_to_int(const char* s) {
|
||||||
|
if (!s) return 0;
|
||||||
|
bux_i64 v = 0;
|
||||||
|
int neg = 0;
|
||||||
|
if (*s == '-') { neg = 1; s++; }
|
||||||
|
while (*s >= '0' && *s <= '9') {
|
||||||
|
v = v * 10 + (*s - '0');
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return neg ? -v : v;
|
||||||
|
}
|
||||||
|
const char* bux_strstr(const char* haystack, const char* needle) {
|
||||||
|
if (!haystack || !needle) return NULL;
|
||||||
|
if (!*needle) return haystack;
|
||||||
|
for (const char* h = haystack; *h; h++) {
|
||||||
|
const char* a = h;
|
||||||
|
const char* b = needle;
|
||||||
|
while (*a && *b && *a == *b) { a++; b++; }
|
||||||
|
if (!*b) return h;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
return bux_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 = bux_strlen(s);
|
||||||
|
if (start > sl) start = sl;
|
||||||
|
if (start + len > sl) len = sl - start;
|
||||||
|
char* out = (char*)bux_alloc(len + 1);
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; i < len; i++) out[i] = s[start + i];
|
||||||
|
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++;
|
||||||
|
return bux_str_slice(s, 0, bux_strlen(s));
|
||||||
|
}
|
||||||
|
char* bux_str_trim_right(const char* s) {
|
||||||
|
if (!s) s = "";
|
||||||
|
unsigned int n = bux_strlen(s);
|
||||||
|
while (n > 0 && is_ws(s[n - 1])) n--;
|
||||||
|
return bux_str_slice(s, 0, n);
|
||||||
|
}
|
||||||
|
char* bux_str_trim(const char* s) {
|
||||||
|
char* a = bux_str_trim_left(s);
|
||||||
|
return bux_str_trim_right(a);
|
||||||
|
}
|
||||||
|
char* bux_int_to_str(bux_i64 n) {
|
||||||
|
char buf[32];
|
||||||
|
int i = 0;
|
||||||
|
int neg = 0;
|
||||||
|
if (n < 0) { neg = 1; n = -n; }
|
||||||
|
if (n == 0) buf[i++] = '0';
|
||||||
|
else while (n > 0 && i < 30) { buf[i++] = (char)('0' + (n % 10)); n /= 10; }
|
||||||
|
if (neg) buf[i++] = '-';
|
||||||
|
int a = 0, b = i - 1;
|
||||||
|
while (a < b) { char t = buf[a]; buf[a] = buf[b]; buf[b] = t; a++; b--; }
|
||||||
|
buf[i] = 0;
|
||||||
|
return bux_str_slice(buf, 0, (unsigned)i);
|
||||||
|
}
|
||||||
|
char* bux_float_to_string(double f) { return bux_int_to_str((bux_i64)f); }
|
||||||
|
|
||||||
|
unsigned int bux_str_split_count(const char* s, const char* delim) {
|
||||||
|
(void)s; (void)delim;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
char* bux_str_split_part(const char* s, const char* delim, unsigned int index) {
|
||||||
|
(void)s; (void)delim; (void)index;
|
||||||
|
return (char*)"";
|
||||||
|
}
|
||||||
|
char* bux_str_join2(const char* a, const char* b, const char* sep) {
|
||||||
|
unsigned int na = bux_strlen(a), nb = bux_strlen(b), ns = bux_strlen(sep);
|
||||||
|
char* out = (char*)bux_alloc(na + ns + nb + 1);
|
||||||
|
unsigned int i = 0, j;
|
||||||
|
for (j = 0; j < na; j++) out[i++] = a[j];
|
||||||
|
for (j = 0; j < ns; j++) out[i++] = sep[j];
|
||||||
|
for (j = 0; j < nb; j++) out[i++] = b[j];
|
||||||
|
out[i] = 0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
char* bux_str_format(const char* fmt, const char* a0, const char* a1, const char* a2, const char* a3) {
|
||||||
|
(void)a1; (void)a2; (void)a3;
|
||||||
|
/* minimal: return fmt or a0 */
|
||||||
|
if (a0 && a0[0]) return bux_str_slice(a0, 0, bux_strlen(a0));
|
||||||
|
if (fmt) return bux_str_slice(fmt, 0, bux_strlen(fmt));
|
||||||
|
return (char*)"";
|
||||||
|
}
|
||||||
|
char* bux_escape_c_string(const char* s, int len) {
|
||||||
|
(void)len;
|
||||||
|
return s ? bux_str_slice(s, 0, bux_strlen(s)) : (char*)"";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* StringBuilder stubs (shape-compatible enough for unused mono) */
|
||||||
|
typedef struct {
|
||||||
|
char* data;
|
||||||
|
unsigned int len;
|
||||||
|
unsigned int cap;
|
||||||
|
} BuxStringBuilder;
|
||||||
|
|
||||||
|
void bux_sb_append(BuxStringBuilder* sb, const char* s) {
|
||||||
|
(void)sb; (void)s;
|
||||||
|
}
|
||||||
|
void bux_sb_append_int(BuxStringBuilder* sb, bux_i64 n) { (void)sb; (void)n; }
|
||||||
|
void bux_sb_append_float(BuxStringBuilder* sb, double f) { (void)sb; (void)f; }
|
||||||
|
void bux_sb_append_char(BuxStringBuilder* sb, char c) { (void)sb; (void)c; }
|
||||||
|
const char* bux_sb_build(BuxStringBuilder* sb) { return sb && sb->data ? sb->data : ""; }
|
||||||
|
void bux_sb_free(BuxStringBuilder* sb) { (void)sb; }
|
||||||
|
|
||||||
|
/* ── FS / path / OS — unavailable ─────────────────────────────────────── */
|
||||||
|
char* bux_read_file(const char* path) { (void)path; return NULL; }
|
||||||
|
int bux_write_file(const char* path, const char* content) {
|
||||||
|
(void)path; (void)content;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int bux_file_exists(const char* path) { (void)path; return 0; }
|
||||||
|
char* bux_path_join(const char* a, const char* b) {
|
||||||
|
return bux_str_join2(a ? a : "", b ? b : "", "/");
|
||||||
|
}
|
||||||
|
char* bux_path_parent(const char* path) {
|
||||||
|
(void)path;
|
||||||
|
return (char*)".";
|
||||||
|
}
|
||||||
|
char* bux_path_ext(const char* path) {
|
||||||
|
(void)path;
|
||||||
|
return (char*)"";
|
||||||
|
}
|
||||||
|
int bux_mkdir_if_needed(const char* path) { (void)path; return -1; }
|
||||||
|
int bux_dir_exists(const char* path) { (void)path; return 0; }
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Math (integer only; float ops soft-stub) ─────────────────────────── */
|
||||||
|
double bux_sqrt(double x) { return x; }
|
||||||
|
double bux_pow(double x, double y) { (void)y; return x; }
|
||||||
|
bux_i64 bux_abs_i64(bux_i64 x) { return x < 0 ? -x : x; }
|
||||||
|
double bux_abs_f64(double x) { return x < 0 ? -x : x; }
|
||||||
|
bux_i64 bux_min_i64(bux_i64 a, bux_i64 b) { return a < b ? a : b; }
|
||||||
|
bux_i64 bux_max_i64(bux_i64 a, bux_i64 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, bux_size size) {
|
||||||
|
const unsigned char* p = (const unsigned char*)ptr;
|
||||||
|
unsigned int h = 2166136261u;
|
||||||
|
bux_size i;
|
||||||
|
if (!p) return 0;
|
||||||
|
for (i = 0; i < size; i++) {
|
||||||
|
h ^= p[i];
|
||||||
|
h *= 16777619u;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
int bux_mem_eq(const void* a, const void* b, bux_size size) {
|
||||||
|
const unsigned char* x = (const unsigned char*)a;
|
||||||
|
const unsigned char* y = (const unsigned char*)b;
|
||||||
|
bux_size i;
|
||||||
|
if (!x || !y) return x == y;
|
||||||
|
for (i = 0; i < size; i++) if (x[i] != y[i]) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
unsigned int bux_hash_string(const char* s) {
|
||||||
|
return bux_hash_bytes(s, bux_strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* bux_getenv(const char* name) { (void)name; return ""; }
|
||||||
|
const char* bux_cc_ld_stable(void) { return ""; }
|
||||||
|
int bux_setenv(const char* name, const char* value) {
|
||||||
|
(void)name; (void)value;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const char* bux_getcwd(void) { return "."; }
|
||||||
|
int bux_chdir(const char* path) { (void)path; return -1; }
|
||||||
|
|
||||||
|
void bux_install_stop_handlers(void) {}
|
||||||
|
int bux_should_stop(void) { return 0; }
|
||||||
|
void bux_set_stop_listen_fd(int fd) { (void)fd; }
|
||||||
|
|
||||||
|
bux_i64 bux_time_ms(void) { return 0; }
|
||||||
|
bux_i64 bux_time_us(void) { return 0; }
|
||||||
|
|
||||||
|
/* ── Task / net / crypto — hard stubs ─────────────────────────────────── */
|
||||||
|
int bux_task_spawn(void* fn, void* arg) { (void)fn; (void)arg; return -1; }
|
||||||
|
void bux_task_yield(void) {}
|
||||||
|
void bux_task_sleep_ms(int ms) { (void)ms; }
|
||||||
|
|
||||||
|
int bux_chan_new(int cap) { (void)cap; return -1; }
|
||||||
|
int bux_chan_send(int id, void* msg) { (void)id; (void)msg; return -1; }
|
||||||
|
void* bux_chan_recv(int id) { (void)id; return NULL; }
|
||||||
|
void bux_chan_close(int id) { (void)id; }
|
||||||
|
|
||||||
|
int bux_tcp_listen(int port) { (void)port; return -1; }
|
||||||
|
int bux_tcp_accept(int fd) { (void)fd; return -1; }
|
||||||
|
int bux_tcp_connect(const char* host, int port) {
|
||||||
|
(void)host; (void)port;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int bux_tcp_send(int fd, const void* buf, int n) {
|
||||||
|
(void)fd; (void)buf; (void)n;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int bux_tcp_recv(int fd, void* buf, int n) {
|
||||||
|
(void)fd; (void)buf; (void)n;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
void bux_tcp_close(int fd) { (void)fd; }
|
||||||
|
|
||||||
|
void* bux_tls_server_ctx(const char* cert, const char* key) {
|
||||||
|
(void)cert; (void)key;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
void* bux_tls_server_ctx_ex(const char* cert, const char* key, const char* ca) {
|
||||||
|
(void)cert; (void)key; (void)ca;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
int bux_tls_accept(void* ctx, int fd) { (void)ctx; (void)fd; return -1; }
|
||||||
|
int bux_tls_send(int h, const void* buf, int n) {
|
||||||
|
(void)h; (void)buf; (void)n;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int bux_tls_recv(int h, void* buf, int n) {
|
||||||
|
(void)h; (void)buf; (void)n;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
void bux_tls_close(int h) { (void)h; }
|
||||||
|
const char* bux_tls_error(void) { return "tls unavailable (freestanding)"; }
|
||||||
|
|
||||||
|
/* ── Optional bare `_start` for -nostdlib link experiments ───────────── */
|
||||||
|
#ifdef BUX_FS_PROVIDE_START
|
||||||
|
extern int main(int argc, char** argv);
|
||||||
|
void _start(void) {
|
||||||
|
g_argc = 0;
|
||||||
|
g_argv = NULL;
|
||||||
|
int code = main(0, NULL);
|
||||||
|
bux_fs_halt(code);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
+6
-2
@@ -59,10 +59,13 @@ module Cli {
|
|||||||
return "rt/runtime_win.c";
|
return "rt/runtime_win.c";
|
||||||
}
|
}
|
||||||
if String_Eq(env, "minimal") || String_Eq(env, "thin") ||
|
if String_Eq(env, "minimal") || String_Eq(env, "thin") ||
|
||||||
String_Eq(env, "embed") || String_Eq(env, "embedded") ||
|
String_Eq(env, "embed") || String_Eq(env, "embedded") {
|
||||||
String_Eq(env, "freestanding") {
|
|
||||||
return "rt/runtime_minimal.c";
|
return "rt/runtime_minimal.c";
|
||||||
}
|
}
|
||||||
|
if String_Eq(env, "freestanding") || String_Eq(env, "bare") ||
|
||||||
|
String_Eq(env, "nolibc") {
|
||||||
|
return "rt/runtime_freestanding.c";
|
||||||
|
}
|
||||||
if String_Eq(env, "full") || String_Eq(env, "posix") {
|
if String_Eq(env, "full") || String_Eq(env, "posix") {
|
||||||
return "rt/runtime.c";
|
return "rt/runtime.c";
|
||||||
}
|
}
|
||||||
@@ -78,6 +81,7 @@ module Cli {
|
|||||||
|
|
||||||
func Cli_IsThinRuntimePath(rtPath: String) -> bool {
|
func Cli_IsThinRuntimePath(rtPath: String) -> bool {
|
||||||
if String_Contains(rtPath, "runtime_minimal.c") { return true; }
|
if String_Contains(rtPath, "runtime_minimal.c") { return true; }
|
||||||
|
if String_Contains(rtPath, "runtime_freestanding.c") { return true; }
|
||||||
if String_Contains(rtPath, "runtime_win.c") { return true; }
|
if String_Contains(rtPath, "runtime_win.c") { return true; }
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-3
@@ -470,16 +470,30 @@ module MacroExpand {
|
|||||||
(aexp.kind == ekSlice && aexp.callArgCount > 0);
|
(aexp.kind == ekSlice && aexp.callArgCount > 0);
|
||||||
return wrap;
|
return wrap;
|
||||||
}
|
}
|
||||||
// type — session 87: named / pointer type from call-site expr shape
|
// type — session 87+: named / pointer / generic Array<int> from call-site
|
||||||
if String_Eq(kindStr, "type") {
|
if String_Eq(kindStr, "type") {
|
||||||
if aexp.kind == ekMacroType { return aexp; }
|
if aexp.kind == ekMacroType { return aexp; }
|
||||||
var te: *TypeExpr = null as *TypeExpr;
|
var te: *TypeExpr = null as *TypeExpr;
|
||||||
if aexp.kind == ekIdent {
|
if aexp.kind == ekIdent || aexp.kind == ekGenericCall {
|
||||||
te = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
te = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
te.kind = tekNamed;
|
te.kind = tekNamed;
|
||||||
te.line = aexp.line;
|
te.line = aexp.line;
|
||||||
te.column = aexp.column;
|
te.column = aexp.column;
|
||||||
te.typeName = aexp.strValue;
|
if aexp.kind == ekGenericCall && !String_Eq(aexp.genericCallee, "") {
|
||||||
|
te.typeName = aexp.genericCallee;
|
||||||
|
} else {
|
||||||
|
te.typeName = aexp.strValue;
|
||||||
|
}
|
||||||
|
// Generic type args: Array<int> / Map<K,V> (selfhost: up to 2 string args)
|
||||||
|
var gcount: int = aexp.genericTypeArgCount;
|
||||||
|
if gcount > 0 {
|
||||||
|
te.typeArgName0 = aexp.genericTypeArg0;
|
||||||
|
te.typeArgCount = 1;
|
||||||
|
if gcount > 1 {
|
||||||
|
te.typeArgName1 = aexp.genericTypeArg1;
|
||||||
|
te.typeArgCount = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if aexp.kind == ekUnary && aexp.intValue == tkStar && aexp.child1 != null as *Expr {
|
} else if aexp.kind == ekUnary && aexp.intValue == tkStar && aexp.child1 != null as *Expr {
|
||||||
// *T from unary star
|
// *T from unary star
|
||||||
let inner: *Expr = Macro_CoerceArg("type", aexp.child1);
|
let inner: *Expr = Macro_CoerceArg("type", aexp.child1);
|
||||||
@@ -518,6 +532,35 @@ module MacroExpand {
|
|||||||
return bound.refType;
|
return bound.refType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Array<$t> / Map<$k,$v> — substitute type-arg name slots
|
||||||
|
if te.kind == tekNamed && te.typeArgCount > 0 {
|
||||||
|
if String_StartsWith(te.typeArgName0, "$") {
|
||||||
|
let b0: *Expr = Env_Lookup(env, te.typeArgName0);
|
||||||
|
if b0 != null as *Expr && b0.kind == ekMacroType && b0.refType != null as *TypeExpr {
|
||||||
|
// Flatten simple named type arg (int, String, …)
|
||||||
|
if b0.refType.kind == tekNamed && b0.refType.typeArgCount == 0 {
|
||||||
|
te.typeArgName0 = b0.refType.typeName;
|
||||||
|
} else if b0.refType.kind == tekNamed {
|
||||||
|
// Nested generic: store mangled-ish "Array_int" for mono
|
||||||
|
te.typeArgName0 = b0.refType.typeName;
|
||||||
|
if b0.refType.typeArgCount > 0 {
|
||||||
|
te.typeArgName0 = String_Concat(te.typeArgName0, "_");
|
||||||
|
te.typeArgName0 = String_Concat(te.typeArgName0, b0.refType.typeArgName0);
|
||||||
|
}
|
||||||
|
} else if b0.refType.kind == tekPointer && b0.refType.pointerPointee != null as *TypeExpr {
|
||||||
|
te.typeArgName0 = String_Concat(b0.refType.pointerPointee.typeName, "p");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if te.typeArgCount > 1 && String_StartsWith(te.typeArgName1, "$") {
|
||||||
|
let b1: *Expr = Env_Lookup(env, te.typeArgName1);
|
||||||
|
if b1 != null as *Expr && b1.kind == ekMacroType && b1.refType != null as *TypeExpr {
|
||||||
|
if b1.refType.kind == tekNamed {
|
||||||
|
te.typeArgName1 = b1.refType.typeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if te.pointerPointee != null as *TypeExpr {
|
if te.pointerPointee != null as *TypeExpr {
|
||||||
te.pointerPointee = Macro_SubstType(te.pointerPointee, env);
|
te.pointerPointee = Macro_SubstType(te.pointerPointee, env);
|
||||||
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
||||||
@@ -533,6 +576,27 @@ module MacroExpand {
|
|||||||
return te;
|
return te;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Substitute `$t` in Expr generic type-arg slots (Array_New<$t>)
|
||||||
|
func Macro_SubstGenericTypeArgs(e: *Expr, env: *MacroEnv) {
|
||||||
|
if e == null as *Expr { return; }
|
||||||
|
if e.genericTypeArgCount > 0 && String_StartsWith(e.genericTypeArg0, "$") {
|
||||||
|
let b0: *Expr = Env_Lookup(env, e.genericTypeArg0);
|
||||||
|
if b0 != null as *Expr && b0.kind == ekMacroType && b0.refType != null as *TypeExpr {
|
||||||
|
if b0.refType.kind == tekNamed {
|
||||||
|
e.genericTypeArg0 = b0.refType.typeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.genericTypeArgCount > 1 && String_StartsWith(e.genericTypeArg1, "$") {
|
||||||
|
let b1: *Expr = Env_Lookup(env, e.genericTypeArg1);
|
||||||
|
if b1 != null as *Expr && b1.kind == ekMacroType && b1.refType != null as *TypeExpr {
|
||||||
|
if b1.refType.kind == tekNamed {
|
||||||
|
e.genericTypeArg1 = b1.refType.typeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fragment kind check: "ident" | "literal" | "block" | "stmt" | "pat" | "expr" | "tt"
|
// Fragment kind check: "ident" | "literal" | "block" | "stmt" | "pat" | "expr" | "tt"
|
||||||
func Macro_FragMatches(kindStr: String, aexp: *Expr) -> bool {
|
func Macro_FragMatches(kindStr: String, aexp: *Expr) -> bool {
|
||||||
return Macro_CoerceArg(kindStr, aexp) != null as *Expr;
|
return Macro_CoerceArg(kindStr, aexp) != null as *Expr;
|
||||||
@@ -961,6 +1025,8 @@ module MacroExpand {
|
|||||||
c.child1 = Subst_Expr(c.child1, env, file, line, col);
|
c.child1 = Subst_Expr(c.child1, env, file, line, col);
|
||||||
c.child2 = Subst_Expr(c.child2, env, file, line, col);
|
c.child2 = Subst_Expr(c.child2, env, file, line, col);
|
||||||
c.child3 = Subst_Expr(c.child3, env, file, line, col);
|
c.child3 = Subst_Expr(c.child3, env, file, line, col);
|
||||||
|
// Array_New<$t> / Foo<$t,$u>
|
||||||
|
Macro_SubstGenericTypeArgs(c, env);
|
||||||
// sizeof / cast / is type annotations
|
// sizeof / cast / is type annotations
|
||||||
if c.refType != null as *TypeExpr {
|
if c.refType != null as *TypeExpr {
|
||||||
c.refType = Macro_SubstType(c.refType, env);
|
c.refType = Macro_SubstType(c.refType, env);
|
||||||
|
|||||||
+71
-3
@@ -21,9 +21,11 @@
|
|||||||
# docs even when `extend T for I` has no methods (no open required).
|
# docs even when `extend T for I` has no methods (no open required).
|
||||||
# v0.17.0: in-process diagnostics (lex/parse/sema) on open/change/save so the
|
# v0.17.0: in-process diagnostics (lex/parse/sema) on open/change/save so the
|
||||||
# editor underlines errors in the live buffer without needing buxc.
|
# editor underlines errors in the live buffer without needing buxc.
|
||||||
|
# v0.18.0: textDocument/formatting + rangeFormatting via bootstrap `formatSource`
|
||||||
|
# (same rules as `bux fmt` — 4-space brace indent).
|
||||||
|
|
||||||
import std/[json, os, strutils, streams, tables, osproc, sequtils, sets]
|
import std/[json, os, strutils, streams, tables, osproc, sequtils, sets]
|
||||||
import lexer, parser, ast, sema, types, scope, source_location
|
import lexer, parser, ast, sema, types, scope, source_location, fmt
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# JSON-RPC Transport
|
# JSON-RPC Transport
|
||||||
@@ -2514,6 +2516,64 @@ proc handleDocumentSymbol(stream: FileStream, id: JsonNode, paramsNode: JsonNode
|
|||||||
})
|
})
|
||||||
sendResponse(stream, id, arr)
|
sendResponse(stream, id, arr)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Document formatting (v0.18 — same engine as `bux fmt`)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc lineCountAndLastLen(s: string): tuple[lines: int, lastLen: int] =
|
||||||
|
## 0-based end position after last character (for full-document TextEdit).
|
||||||
|
if s.len == 0:
|
||||||
|
return (0, 0)
|
||||||
|
var lines = 0
|
||||||
|
var lastLen = 0
|
||||||
|
var i = 0
|
||||||
|
while i < s.len:
|
||||||
|
if s[i] == '\n':
|
||||||
|
inc lines
|
||||||
|
lastLen = 0
|
||||||
|
else:
|
||||||
|
inc lastLen
|
||||||
|
inc i
|
||||||
|
# Trailing content without final newline still occupies a line
|
||||||
|
if s[^1] != '\n':
|
||||||
|
# last line is incomplete — end character is lastLen
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
# ends with newline: end is (lines, 0) in LSP (exclusive end after last line)
|
||||||
|
discard
|
||||||
|
result = (lines, lastLen)
|
||||||
|
|
||||||
|
proc fullDocumentEdit(uri: string, content: string, formatted: string): JsonNode =
|
||||||
|
## Single TextEdit replacing the whole buffer with formatted text.
|
||||||
|
if formatted == content:
|
||||||
|
return newJArray()
|
||||||
|
let (endLine, endChar) = lineCountAndLastLen(content)
|
||||||
|
var arr = newJArray()
|
||||||
|
arr.add(%*{
|
||||||
|
"range": {
|
||||||
|
"start": {"line": 0, "character": 0},
|
||||||
|
"end": {"line": endLine, "character": endChar}
|
||||||
|
},
|
||||||
|
"newText": formatted
|
||||||
|
})
|
||||||
|
discard uri
|
||||||
|
return arr
|
||||||
|
|
||||||
|
proc handleDocumentFormatting(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
||||||
|
## textDocument/formatting — re-indent with 4 spaces (idempotent).
|
||||||
|
let uri = paramsNode["textDocument"]["uri"].getStr()
|
||||||
|
let doc = getDoc(uri)
|
||||||
|
let formatted = formatSource(doc.content)
|
||||||
|
sendResponse(stream, id, fullDocumentEdit(uri, doc.content, formatted))
|
||||||
|
|
||||||
|
proc handleDocumentRangeFormatting(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
||||||
|
## textDocument/rangeFormatting — whole-file format (indent is brace-global).
|
||||||
|
## Editors that send a selection still get a consistent full reformat.
|
||||||
|
let uri = paramsNode["textDocument"]["uri"].getStr()
|
||||||
|
let doc = getDoc(uri)
|
||||||
|
let formatted = formatSource(doc.content)
|
||||||
|
sendResponse(stream, id, fullDocumentEdit(uri, doc.content, formatted))
|
||||||
|
|
||||||
proc handleWorkspaceSymbol(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
proc handleWorkspaceSymbol(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
|
||||||
## workspace/symbol — fuzzy-ish substring filter over workspace + open docs.
|
## workspace/symbol — fuzzy-ish substring filter over workspace + open docs.
|
||||||
let query = if paramsNode.hasKey("query"): paramsNode["query"].getStr().toLowerAscii() else: ""
|
let query = if paramsNode.hasKey("query"): paramsNode["query"].getStr().toLowerAscii() else: ""
|
||||||
@@ -3370,9 +3430,11 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
|||||||
"workspaceSymbolProvider": true,
|
"workspaceSymbolProvider": true,
|
||||||
"callHierarchyProvider": true,
|
"callHierarchyProvider": true,
|
||||||
"implementationProvider": true,
|
"implementationProvider": true,
|
||||||
"typeHierarchyProvider": true
|
"typeHierarchyProvider": true,
|
||||||
|
"documentFormattingProvider": true,
|
||||||
|
"documentRangeFormattingProvider": true
|
||||||
},
|
},
|
||||||
"serverInfo": {"name": "bux-lsp", "version": "0.17.0"}
|
"serverInfo": {"name": "bux-lsp", "version": "0.18.0"}
|
||||||
})
|
})
|
||||||
if paramsNode.hasKey("rootPath") and paramsNode["rootPath"].kind != JNull:
|
if paramsNode.hasKey("rootPath") and paramsNode["rootPath"].kind != JNull:
|
||||||
rootPath = paramsNode["rootPath"].getStr()
|
rootPath = paramsNode["rootPath"].getStr()
|
||||||
@@ -3470,6 +3532,12 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
|
|||||||
of "typeHierarchy/subtypes":
|
of "typeHierarchy/subtypes":
|
||||||
handleTypeHierarchySubtypes(stream, id, paramsNode)
|
handleTypeHierarchySubtypes(stream, id, paramsNode)
|
||||||
|
|
||||||
|
of "textDocument/formatting":
|
||||||
|
handleDocumentFormatting(stream, id, paramsNode)
|
||||||
|
|
||||||
|
of "textDocument/rangeFormatting":
|
||||||
|
handleDocumentRangeFormatting(stream, id, paramsNode)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if id != nil:
|
if id != nil:
|
||||||
sendError(stream, id, -32601, "method not found: " & methodName)
|
sendError(stream, id, -32601, "method not found: " & methodName)
|
||||||
|
|||||||
Executable
+66
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Smoke: freestanding runtime compiles under -ffreestanding; optional package build.
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
RT="$ROOT/rt/runtime_freestanding.c"
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
|
||||||
|
if [[ ! -f "$RT" ]]; then
|
||||||
|
echo "FAIL: missing $RT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CC="${BUX_CC:-cc}"
|
||||||
|
|
||||||
|
echo "=== freestanding: -ffreestanding -c runtime ==="
|
||||||
|
"$CC" -ffreestanding -std=c11 -Wall -Wextra -c "$RT" -o "$TMP/rt_fs.o"
|
||||||
|
echo "PASS: runtime_freestanding.o"
|
||||||
|
|
||||||
|
echo "=== freestanding: BUX_RUNTIME=freestanding build hello-ish ==="
|
||||||
|
mkdir -p "$TMP/pkg/src"
|
||||||
|
cat > "$TMP/pkg/bux.toml" <<'EOF'
|
||||||
|
[Package]
|
||||||
|
Name = "fs_smoke"
|
||||||
|
Version = "0.1.0"
|
||||||
|
EOF
|
||||||
|
cat > "$TMP/pkg/src/Main.bux" <<'EOF'
|
||||||
|
func Main() -> int {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [[ ! -x "$ROOT/buxc" ]]; then
|
||||||
|
echo "building buxc..."
|
||||||
|
(cd "$ROOT" && make build >/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Hosted link still uses libc for crt0; runtime body is freestanding.
|
||||||
|
BUX_RUNTIME=freestanding "$ROOT/buxc" build "$TMP/pkg" --release >/dev/null
|
||||||
|
OUT="$TMP/pkg/build/fs_smoke"
|
||||||
|
if [[ ! -x "$OUT" ]]; then
|
||||||
|
echo "FAIL: binary not produced"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
CODE=$("$OUT"; echo $?)
|
||||||
|
if [[ "$CODE" != "42" ]]; then
|
||||||
|
echo "FAIL: expected exit 42, got $CODE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "PASS: freestanding runtime package exit 42"
|
||||||
|
|
||||||
|
# Optional: object-level nostdlib link experiment (may need extra crt — soft)
|
||||||
|
echo "=== freestanding: optional -ffreestanding object of Main.c ==="
|
||||||
|
# Generate C then compile Main only with freestanding flags
|
||||||
|
BUX_RUNTIME=freestanding "$ROOT/buxc" build "$TMP/pkg" --release >/dev/null
|
||||||
|
if [[ -f "$TMP/pkg/build/main.c" ]]; then
|
||||||
|
if "$CC" -ffreestanding -std=c11 -c "$TMP/pkg/build/main.c" -o "$TMP/main_fs.o" 2>"$TMP/main_fs.err"; then
|
||||||
|
echo "PASS: main.c compiles under -ffreestanding"
|
||||||
|
else
|
||||||
|
# Hosted headers in generated C may pull stdint — not a hard fail
|
||||||
|
echo "SKIP: main.c -ffreestanding (generated C may need hosted headers)"
|
||||||
|
head -5 "$TMP/main_fs.err" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PASS: freestanding smoke"
|
||||||
Executable
+86
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Smoke: textDocument/formatting re-indents with 4 spaces (same as bux fmt).
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
LSP="$ROOT/tools/bux-lsp"
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
|
||||||
|
if [[ ! -x "$LSP" ]]; then
|
||||||
|
echo "building bux-lsp..."
|
||||||
|
(cd "$ROOT" && make lsp >/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Intentionally bad indent (2 spaces)
|
||||||
|
cat > "$TMP/Main.bux" <<'EOF'
|
||||||
|
func Main() -> int {
|
||||||
|
let x: int = 1;
|
||||||
|
if x > 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rpc() {
|
||||||
|
local body="$1"
|
||||||
|
local len
|
||||||
|
len=$(printf '%s' "$body" | wc -c)
|
||||||
|
printf 'Content-Length: %s\r\n\r\n%s' "$len" "$body"
|
||||||
|
}
|
||||||
|
|
||||||
|
CONTENT_JSON=$(python3 -c 'import json,sys; print(json.dumps(open(sys.argv[1]).read()))' "$TMP/Main.bux")
|
||||||
|
URI="file://$TMP/Main.bux"
|
||||||
|
|
||||||
|
{
|
||||||
|
rpc '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"rootUri":"file://'"$TMP"'"}}'
|
||||||
|
rpc '{"jsonrpc":"2.0","method":"initialized","params":{}}'
|
||||||
|
rpc '{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"'"$URI"'","languageId":"bux","version":1,"text":'"$CONTENT_JSON"'}}}'
|
||||||
|
rpc '{"jsonrpc":"2.0","id":2,"method":"textDocument/formatting","params":{"textDocument":{"uri":"'"$URI"'"},"options":{"tabSize":4,"insertSpaces":true}}}'
|
||||||
|
rpc '{"jsonrpc":"2.0","id":3,"method":"shutdown","params":null}'
|
||||||
|
rpc '{"jsonrpc":"2.0","method":"exit","params":null}'
|
||||||
|
} | "$LSP" 2>/dev/null | tr '\r' '\n' > "$TMP/out.txt"
|
||||||
|
|
||||||
|
python3 - <<'PY' "$TMP/out.txt"
|
||||||
|
import json, sys, re
|
||||||
|
raw = open(sys.argv[1]).read()
|
||||||
|
|
||||||
|
# Find response id=2 with result TextEdit array
|
||||||
|
# Prefer structured parse of JSON objects containing "newText"
|
||||||
|
ok = False
|
||||||
|
version_ok = "0.18.0" in raw or '"documentFormattingProvider":true' in raw.replace(" ", "")
|
||||||
|
if "0.18.0" not in raw and "documentFormattingProvider" not in raw:
|
||||||
|
# initialize result may be nested; still require a formatting response
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Extract TextEdit newText via regex / brace walk
|
||||||
|
edits = []
|
||||||
|
for m in re.finditer(r'"newText"\s*:\s*"((?:[^"\\]|\\.)*)"', raw):
|
||||||
|
edits.append(bytes(m.group(1), "utf-8").decode("unicode_escape"))
|
||||||
|
|
||||||
|
if not edits:
|
||||||
|
print("FAIL: no TextEdit newText in formatting response")
|
||||||
|
print(raw[:3000])
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
formatted = edits[0]
|
||||||
|
# Expected: 4-space indent after func {
|
||||||
|
if " let x: int = 1;" not in formatted:
|
||||||
|
print("FAIL: expected 4-space indent on let")
|
||||||
|
print(repr(formatted))
|
||||||
|
sys.exit(1)
|
||||||
|
if " if x > 0 {" not in formatted and " if x > 0 {" not in formatted:
|
||||||
|
# after let at indent 1, if should be at indent 1 (same block) = 4 spaces
|
||||||
|
print("FAIL: unexpected if indent")
|
||||||
|
print(repr(formatted))
|
||||||
|
sys.exit(1)
|
||||||
|
# Nested body of if at 8 spaces
|
||||||
|
if " return 0;" not in formatted:
|
||||||
|
print("FAIL: expected 8-space indent on return inside if")
|
||||||
|
print(repr(formatted))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("PASS: LSP formatting smoke (4-space brace indent)")
|
||||||
|
if "0.18.0" in raw:
|
||||||
|
print("PASS: serverInfo version 0.18.0")
|
||||||
|
PY
|
||||||
+2
-2
@@ -8,8 +8,8 @@ Syntax highlighting, snippets, editor defaults, and **Language Server Protocol**
|
|||||||
|------|----------------|
|
|------|----------------|
|
||||||
| **Syntax** | Keywords, types, `f"..."` interpolation, raw `` `...` `` strings, C-strings, macros (`macro!` / `name!()`), attributes (`@[Checked]`), numbers (hex/bin/oct + suffixes), lifetimes |
|
| **Syntax** | Keywords, types, `f"..."` interpolation, raw `` `...` `` strings, C-strings, macros (`macro!` / `name!()`), attributes (`@[Checked]`), numbers (hex/bin/oct + suffixes), lifetimes |
|
||||||
| **Snippets** | `main`, `func`, `struct`, `enum`, `match`, `interface`, `extend`, `macro`, `checked`, … |
|
| **Snippets** | `main`, `func`, `struct`, `enum`, `match`, `interface`, `extend`, `macro`, `checked`, … |
|
||||||
| **LSP** | **Live error underlines** (red squiggles on edit), completion, hover, go-to-definition, references, rename, document/workspace symbols, call hierarchy, type hierarchy, go-to-implementation |
|
| **LSP** | **Live error underlines** (red squiggles on edit), **Format Document** (same as `bux fmt`), completion, hover, go-to-definition, references, rename, document/workspace symbols, call hierarchy, type hierarchy, go-to-implementation |
|
||||||
| **Editor** | Bracket colorization, smart indent / on-enter, fold regions (`// region`) |
|
| **Editor** | Bracket colorization, smart indent / on-enter, fold regions (`// region`), **format-on-save** default for `[bux]` |
|
||||||
| **Build** | `buxc` problem matcher for Tasks |
|
| **Build** | `buxc` problem matcher for Tasks |
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|||||||
+7
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "bux-lang",
|
"name": "bux-lang",
|
||||||
"displayName": "Bux Language Support",
|
"displayName": "Bux Language Support",
|
||||||
"description": "Syntax highlighting, snippets, and full LSP support for Bux — live error underlines, hover, go-to-def, rename, call/type hierarchy",
|
"description": "Syntax highlighting, snippets, and full LSP support for Bux — live error underlines, Format Document, hover, go-to-def, rename, call/type hierarchy",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"publisher": "bux-lang",
|
"publisher": "bux-lang",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"Programming Languages",
|
"Programming Languages",
|
||||||
"Snippets",
|
"Snippets",
|
||||||
"Linters",
|
"Linters",
|
||||||
|
"Formatters",
|
||||||
"Other"
|
"Other"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -32,7 +33,8 @@
|
|||||||
"bux-lang",
|
"bux-lang",
|
||||||
"programming-language",
|
"programming-language",
|
||||||
"lsp",
|
"lsp",
|
||||||
"syntax"
|
"syntax",
|
||||||
|
"formatter"
|
||||||
],
|
],
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onLanguage:bux",
|
"onLanguage:bux",
|
||||||
@@ -107,7 +109,7 @@
|
|||||||
"bux.lsp.enabled": {
|
"bux.lsp.enabled": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": true,
|
||||||
"description": "Enable the Bux language server (diagnostics, hover, go-to-definition, rename, hierarchy)."
|
"description": "Enable the Bux language server (diagnostics, formatting, hover, go-to-definition, rename, hierarchy)."
|
||||||
},
|
},
|
||||||
"bux.lsp.path": {
|
"bux.lsp.path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -133,6 +135,7 @@
|
|||||||
"editor.tabSize": 4,
|
"editor.tabSize": 4,
|
||||||
"editor.insertSpaces": true,
|
"editor.insertSpaces": true,
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
"editor.quickSuggestions": {
|
"editor.quickSuggestions": {
|
||||||
"other": true,
|
"other": true,
|
||||||
"comments": false,
|
"comments": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user