feat: restructure repo, borrow checker, expanded stdlib

- Reorganize repository to Rust-style layout:
  compiler/bootstrap/  compiler/selfhost/  compiler/tests/
  library/std/  library/runtime/  tests/  tools/
- Add buxs/ Windows-compatible project root
- Add borrow checker tests and implement:
  - Alias analysis (double mutable borrow detection)
  - Use-after-move detection for own T
- Expand standard library:
  - Std::Os: Args, Env, Cwd, Chdir
  - Std::Time: NowMs, NowUs, SleepMs
  - Std::Process: Run, Output
  - Std::Io: PrintInt64 (fixes 32-bit truncation bug)
- Add examples: os_time.bux, process.bux
- Fix PrintInt to use int64_t in C runtime
This commit is contained in:
2026-06-05 20:08:17 +03:00
parent a45a2ecd49
commit 9c6b516453
75 changed files with 581 additions and 79 deletions
+8 -6
View File
@@ -1,5 +1,5 @@
NIM := nim NIM := nim
SRC := src/main.nim SRC := compiler/bootstrap/main.nim
OUT := buxc OUT := buxc
BUILD_DIR := build BUILD_DIR := build
@@ -17,13 +17,15 @@ dev:
test: build test-examples test: build test-examples
@echo "Running lexer tests..." @echo "Running lexer tests..."
$(NIM) c -r tests/lexer_test.nim $(NIM) c -r compiler/tests/lexer_test.nim
@echo "Running parser tests..." @echo "Running parser tests..."
$(NIM) c -r tests/parser_test.nim $(NIM) c -r compiler/tests/parser_test.nim
@echo "Running sema tests..." @echo "Running sema tests..."
$(NIM) c -r tests/sema_test.nim $(NIM) c -r compiler/tests/sema_test.nim
@echo "Running HIR tests..." @echo "Running HIR tests..."
$(NIM) c -r tests/hir_test.nim $(NIM) c -r compiler/tests/hir_test.nim
@echo "Running borrow checker tests..."
$(NIM) c -r compiler/tests/borrow_test.nim
@echo "Running integration tests..." @echo "Running integration tests..."
rm -rf _test_tmp_pkg rm -rf _test_tmp_pkg
./$(OUT) new _test_tmp_pkg ./$(OUT) new _test_tmp_pkg
@@ -58,7 +60,7 @@ selfhost: build
@echo "=== Phase 7.9: Building self-hosted compiler ===" @echo "=== Phase 7.9: Building self-hosted compiler ==="
@rm -rf _selfhost/src @rm -rf _selfhost/src
@mkdir -p _selfhost/src @mkdir -p _selfhost/src
@cp src_bux/*.bux _selfhost/src/ @cp compiler/selfhost/*.bux _selfhost/src/
@mv _selfhost/src/main.bux _selfhost/src/Main.bux 2>/dev/null || true @mv _selfhost/src/main.bux _selfhost/src/Main.bux 2>/dev/null || true
@cd _selfhost && ../$(OUT) build @cd _selfhost && ../$(OUT) build
@echo "=== Self-hosted compiler built successfully ===" @echo "=== Self-hosted compiler built successfully ==="
+4 -4
View File
@@ -290,7 +290,7 @@ Phase 7.5 — Driver (depends on all):
**Goal:** Bux compiler compiles itself. This is the **main milestone**. **Goal:** Bux compiler compiles itself. This is the **main milestone**.
**All 14 modules ported** in `src_bux/` (4094 LOC total). Self-hosted project structure in `_selfhost/`. **All 14 modules ported** in `compiler/selfhost/` (4094 LOC total). Self-hosted project structure in `_selfhost/`.
| Task | Status | Details | LOC | | Task | Status | Details | LOC |
|------|--------|---------|-----| |------|--------|---------|-----|
@@ -355,8 +355,8 @@ Pipeline modules:
**Bootstrap loop goal:** **Bootstrap loop goal:**
``` ```
buxc (Nim) → compile src_bux/*.bux → buxc2 (Bux binary) buxc (Nim) → compile compiler/selfhost/*.bux → buxc2 (Bux binary)
buxc2 (Bux) → compile src_bux/*.bux → buxc3 (Bux binary) buxc2 (Bux) → compile compiler/selfhost/*.bux → buxc3 (Bux binary)
compare buxc2 == buxc3 → SELF-HOSTED ✅ compare buxc2 == buxc3 → SELF-HOSTED ✅
``` ```
@@ -556,7 +556,7 @@ bux/
│ ├── Linker.bux # Custom linker / build driver │ ├── Linker.bux # Custom linker / build driver
│ ├── Manifest.bux # bux.toml parser │ ├── Manifest.bux # bux.toml parser
│ └── Package.bux # Package resolution │ └── Package.bux # Package resolution
├── stdlib/ ├── library/
│ ├── Std/ │ ├── Std/
│ │ ├── Io.bux │ │ ├── Io.bux
│ │ ├── Memory.bux │ │ ├── Memory.bux
+17 -11
View File
@@ -149,7 +149,7 @@ func Main() -> int {
| **Methods** | `extend` blocks for struct methods | | **Methods** | `extend` blocks for struct methods |
| **Interfaces** | `interface` + `extend` for trait-like behavior | | **Interfaces** | `interface` + `extend` for trait-like behavior |
| **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator | | **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator |
| **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel` | | **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel`, `Os`, `Time`, `Process` |
| **Backend** | C transpiler (self-hosting + bootstrap) | | **Backend** | C transpiler (self-hosting + bootstrap) |
| **Strings** | Raw multi-line backtick strings (`...`), C-string interop | | **Strings** | Raw multi-line backtick strings (`...`), C-string interop |
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking | | **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
@@ -166,18 +166,22 @@ func Main() -> int {
``` ```
bux/ bux/
├── src/ # Bootstrap compiler (Nim) ├── compiler/ # Compiler source code
├── src_bux/ # Self-hosting compiler source (Bux) │ ├── bootstrap/ # Bootstrap compiler (Nim)
├── _selfhost/ # Self-hosting build artifacts ├── selfhost/ # Self-hosting compiler source (Bux)
├── stdlib/ # Standard library (.bux + .c runtime) │ └── tests/ # Compiler unit tests (Nim)
│ └── Std/ ├── library/ # Standard library
├── Io.bux, String.bux, Array.bux, Map.bux ├── std/ # Standard library modules (.bux)
├── Fs.bux, Mem.bux, Set.bux ├── Io.bux, String.bux, Array.bux, Map.bux
├── Path.bux, Math.bux ├── Fs.bux, Mem.bux, Set.bux
── Task.bux, Channel.bux ── Path.bux, Math.bux
│ │ └── Task.bux, Channel.bux
│ └── runtime/ # C runtime files (runtime.c, io.c)
├── tests/ # Language integration tests
├── examples/ # Example programs ├── examples/ # Example programs
├── tests/ # Unit tests (Nim) ├── tools/ # Additional tooling
├── docs/ # Documentation ├── docs/ # Documentation
├── buxs/ # Windows-compatible project root
├── README.md ├── README.md
├── PLAN.md # Roadmap to self-hosting ├── PLAN.md # Roadmap to self-hosting
└── Makefile └── Makefile
@@ -204,6 +208,8 @@ make test-examples
make clean make clean
``` ```
> **Windows users:** Use the `buxs/` directory as your project root to avoid path conflicts.
--- ---
## Documentation ## Documentation
+11 -11
View File
@@ -31,7 +31,7 @@ func DirExists(path: String) -> bool {
} }
// Import the compiler pipeline // Import the compiler pipeline
// In self-hosting mode, these are compiled together from src_bux/ // In self-hosting mode, these are compiled together from compiler/selfhost/
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Compile a single .bux source file // Compile a single .bux source file
@@ -140,19 +140,19 @@ func Cli_Build(srcPath: String, outPath: String) -> int {
PrintLine(cFile); PrintLine(cFile);
// Find runtime.c and io.c for linking // Find runtime.c and io.c for linking
var rtPath: String = "stdlib/runtime.c"; var rtPath: String = "library/runtime/runtime.c";
var ioPath: String = "stdlib/io.c"; var ioPath: String = "library/runtime/io.c";
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = "../stdlib/runtime.c"; rtPath = "../library/runtime/runtime.c";
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = "../stdlib/io.c"; ioPath = "../library/runtime/io.c";
} }
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = "/home/ziko/z-git/bux/bux/stdlib/runtime.c"; rtPath = "/home/ziko/z-git/bux/bux/library/runtime/runtime.c";
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = "/home/ziko/z-git/bux/bux/stdlib/io.c"; ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c";
} }
// Compile with cc // Compile with cc
@@ -633,16 +633,16 @@ func Cli_BuildProject(projectDir: String) -> int {
var rtPath: String = bux_path_join(stdlibDir, "runtime.c"); var rtPath: String = bux_path_join(stdlibDir, "runtime.c");
var ioPath: String = bux_path_join(stdlibDir, "io.c"); var ioPath: String = bux_path_join(stdlibDir, "io.c");
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "stdlib/runtime.c"); rtPath = bux_path_join(projectDir, "library/runtime/runtime.c");
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "stdlib/io.c"); ioPath = bux_path_join(projectDir, "library/runtime/io.c");
} }
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../stdlib/runtime.c"); rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c");
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../stdlib/io.c"); ioPath = bux_path_join(projectDir, "../library/runtime/io.c");
} }
// Compile with cc // Compile with cc
+1 -1
View File
@@ -9,7 +9,7 @@ const skConst: int = 3;
const skModule: int = 4; const skModule: int = 4;
// Maximum symbols per scope // Maximum symbols per scope
const maxSymbols: int = 1024; const maxSymbols: int = 8192;
struct Symbol { struct Symbol {
kind: int; kind: int;
+19
View File
@@ -438,6 +438,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
func Sema_CollectGlobals(sema: *Sema) { func Sema_CollectGlobals(sema: *Sema) {
var decl: *Decl = sema.module.firstItem; var decl: *Decl = sema.module.firstItem;
var funcCount: int = 0;
while decl != null as *Decl { while decl != null as *Decl {
let dk: int = decl.kind; let dk: int = decl.kind;
@@ -503,6 +504,24 @@ func Sema_CollectGlobals(sema: *Sema) {
} }
} }
// Const
if dk == dkConst {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skConst;
sym.name = decl.strValue;
if decl.constType != null as *TypeExpr {
sym.typeKind = Sema_ResolveType(sema, decl.constType);
sym.typeName = decl.constType.typeName;
} else {
sym.typeKind = tyInt;
}
sym.isMutable = false;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Use (import) // Use (import)
if dk == dkUse { if dk == dkUse {
if decl.useKind == 2 { if decl.useKind == 2 {
+20
View File
@@ -0,0 +1,20 @@
# BUXS — Windows-Compatible Project Root
This directory serves as an alternative project root for Windows environments
where the name `bux` may conflict with system paths or tooling.
## Usage
From this directory, you can build and run the Bux compiler using the
provided wrapper scripts.
## Structure
The actual project source lives in the parent directory (`../`):
- `../compiler/bootstrap/` — Nim bootstrap compiler
- `../compiler/selfhost/` — Self-hosting compiler (Bux)
- `../library/std/` — Standard library modules
- `../library/runtime/` — C runtime files
- `../tests/` — Integration tests
- `../docs/` — Documentation
+4
View File
@@ -0,0 +1,4 @@
@echo off
REM Windows wrapper to build Bux from buxs/ root
cd ..
make build
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Wrapper script to build Bux from buxs/ root
cd ..
make build
+7
View File
@@ -0,0 +1,7 @@
[Package]
Name = "buxc"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
+12 -12
View File
@@ -306,10 +306,10 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
# Phase 2: Merge with stdlib and check # Phase 2: Merge with stdlib and check
var stdlibDir = "" var stdlibDir = ""
let stdlibSearchPaths = @[ let stdlibSearchPaths = @[
getAppDir() / ".." / "stdlib", getAppDir() / ".." / "library",
getAppDir() / "stdlib", getAppDir() / "library",
getCurrentDir() / "stdlib", getCurrentDir() / "library",
"/home/ziko/z-git/bux/bux/stdlib", "/home/ziko/z-git/bux/bux/library",
] ]
for path in stdlibSearchPaths: for path in stdlibSearchPaths:
if dirExists(path): if dirExists(path):
@@ -434,10 +434,10 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
# Find stdlib directory # Find stdlib directory
var stdlibDir = "" var stdlibDir = ""
let stdlibSearchPaths = @[ let stdlibSearchPaths = @[
getAppDir() / ".." / "stdlib", getAppDir() / ".." / "library",
getAppDir() / "stdlib", getAppDir() / "library",
root / "stdlib", root / "library",
"/home/ziko/z-git/bux/bux/stdlib", "/home/ziko/z-git/bux/bux/library",
] ]
for path in stdlibSearchPaths: for path in stdlibSearchPaths:
if dirExists(path): if dirExists(path):
@@ -518,19 +518,19 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
return 1 return 1
# Copy runtime.c # Copy runtime.c
let runtimeSrc = stdlibDir / "runtime.c" let runtimeSrc = stdlibDir / "runtime" / "runtime.c"
if fileExists(runtimeSrc): if fileExists(runtimeSrc):
copyFile(runtimeSrc, runtimeDst) copyFile(runtimeSrc, runtimeDst)
else: else:
printError("runtime.c not found in stdlib", useColor) printError("runtime.c not found in library/runtime/", useColor)
return 1 return 1
# Copy io.c # Copy io.c
let ioSrc = stdlibDir / "io.c" let ioSrc = stdlibDir / "runtime" / "io.c"
if fileExists(ioSrc): if fileExists(ioSrc):
copyFile(ioSrc, ioDst) copyFile(ioSrc, ioDst)
else: else:
printError("io.c not found in stdlib", useColor) printError("io.c not found in library/runtime/", useColor)
return 1 return 1
# Compile with cc # Compile with cc
+41 -10
View File
@@ -43,6 +43,7 @@ type
# Borrow checker state # Borrow checker state
checkedFunc*: bool ## true inside @[Checked] function checkedFunc*: bool ## true inside @[Checked] function
currentFuncIsAsync*: bool ## true inside async func currentFuncIsAsync*: bool ## true inside async func
movedVars*: seq[string] ## variables moved in current checked function
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
@@ -727,6 +728,9 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
of tkNull: return makePointer(makeUnknown()) of tkNull: return makePointer(makeUnknown())
else: return makeUnknown() else: return makeUnknown()
of ekIdent: of ekIdent:
if sema.checkedFunc and expr.exprIdent in sema.movedVars:
sema.emitError(expr.loc, &"use of moved value '{expr.exprIdent}'")
return makeUnknown()
let sym = scope.lookup(expr.exprIdent) let sym = scope.lookup(expr.exprIdent)
if sym == nil: if sym == nil:
sema.emitError(expr.loc, &"undeclared identifier '{expr.exprIdent}'") sema.emitError(expr.loc, &"undeclared identifier '{expr.exprIdent}'")
@@ -931,16 +935,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
# Regular function call # Regular function call
let calleeType = sema.checkExpr(expr.exprCallCallee, scope) let calleeType = sema.checkExpr(expr.exprCallCallee, scope)
var argTypes = sema.checkExprList(expr.exprCallArgs, scope) var argTypes = sema.checkExprList(expr.exprCallArgs, scope)
if calleeType.kind == tkFunc: # Look up callee declaration early (needed for borrow checking)
let expectedParams = calleeType.inner[0..^2]
if argTypes.len != expectedParams.len:
sema.emitError(expr.loc, &"expected {expectedParams.len} arguments, got {argTypes.len}")
else:
for i in 0 ..< argTypes.len:
if not argTypes[i].isAssignableTo(expectedParams[i]) and not (argTypes[i].kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[i].toString}, got {argTypes[i].toString}")
# Check for inferred generic function call (no explicit type args)
var calleeDecl: Decl = nil var calleeDecl: Decl = nil
case expr.exprCallCallee.kind case expr.exprCallCallee.kind
of ekIdent: of ekIdent:
@@ -951,6 +946,40 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
let sym = scope.lookup(fullName) let sym = scope.lookup(fullName)
if sym != nil: calleeDecl = sym.decl if sym != nil: calleeDecl = sym.decl
else: discard else: discard
if calleeDecl != nil and calleeDecl.kind == dkFunc and calleeDecl.declFuncTypeParams.len > 0:
discard # will be handled later
if calleeType.kind == tkFunc:
let expectedParams = calleeType.inner[0..^2]
if argTypes.len != expectedParams.len:
sema.emitError(expr.loc, &"expected {expectedParams.len} arguments, got {argTypes.len}")
else:
for i in 0 ..< argTypes.len:
if not argTypes[i].isAssignableTo(expectedParams[i]) and not (argTypes[i].kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[i].toString}, got {argTypes[i].toString}")
# Borrow check: reject double mutable borrow (alias analysis)
if sema.checkedFunc:
var mutRefArgs: seq[tuple[idx: int, name: string]] = @[]
for i in 0 ..< argTypes.len:
if expectedParams[i].isMutRef and i < expr.exprCallArgs.len:
let arg = expr.exprCallArgs[i]
if arg.kind == ekUnary and arg.exprUnaryOp == tkAmp and arg.exprUnaryOperand.kind == ekIdent:
mutRefArgs.add((idx: i, name: arg.exprUnaryOperand.exprIdent))
for i in 0 ..< mutRefArgs.len:
for j in i+1 ..< mutRefArgs.len:
if mutRefArgs[i].name == mutRefArgs[j].name:
sema.emitError(expr.loc, &"mutable borrow conflict: arguments {mutRefArgs[i].idx+1} and {mutRefArgs[j].idx+1} both borrow '&mut {mutRefArgs[i].name}'")
# Borrow check: track moved variables (own T)
if calleeDecl != nil and calleeDecl.kind == dkFunc:
for i in 0 ..< argTypes.len:
if i < calleeDecl.declFuncParams.len and i < expr.exprCallArgs.len:
if calleeDecl.declFuncParams[i].ptype.kind == tekOwn:
let arg = expr.exprCallArgs[i]
if arg.kind == ekIdent:
sema.movedVars.add(arg.exprIdent)
# Check for inferred generic function call (no explicit type args)
if calleeDecl != nil and calleeDecl.kind == dkFunc and if calleeDecl != nil and calleeDecl.kind == dkFunc and
calleeDecl.declFuncTypeParams.len > 0 and calleeDecl.declFuncTypeParams.len > 0 and
@@ -1281,6 +1310,8 @@ proc checkFunc(sema: var Sema, decl: Decl) =
let wasAsync = sema.currentFuncIsAsync let wasAsync = sema.currentFuncIsAsync
sema.checkedFunc = "Checked" in decl.declAttrs sema.checkedFunc = "Checked" in decl.declAttrs
sema.currentFuncIsAsync = decl.declFuncIsAsync sema.currentFuncIsAsync = decl.declFuncIsAsync
if sema.checkedFunc:
sema.movedVars = @[]
var funcScope = newScope(sema.globalScope) var funcScope = newScope(sema.globalScope)
# Add type parameters to type table for resolution # Add type parameters to type table for resolution
var addedTypeParams: seq[string] = @[] var addedTypeParams: seq[string] = @[]
+11 -11
View File
@@ -31,7 +31,7 @@ func DirExists(path: String) -> bool {
} }
// Import the compiler pipeline // Import the compiler pipeline
// In self-hosting mode, these are compiled together from src_bux/ // In self-hosting mode, these are compiled together from compiler/selfhost/
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Compile a single .bux source file // Compile a single .bux source file
@@ -140,19 +140,19 @@ func Cli_Build(srcPath: String, outPath: String) -> int {
PrintLine(cFile); PrintLine(cFile);
// Find runtime.c and io.c for linking // Find runtime.c and io.c for linking
var rtPath: String = "stdlib/runtime.c"; var rtPath: String = "library/runtime/runtime.c";
var ioPath: String = "stdlib/io.c"; var ioPath: String = "library/runtime/io.c";
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = "../stdlib/runtime.c"; rtPath = "../library/runtime/runtime.c";
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = "../stdlib/io.c"; ioPath = "../library/runtime/io.c";
} }
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = "/home/ziko/z-git/bux/bux/stdlib/runtime.c"; rtPath = "/home/ziko/z-git/bux/bux/library/runtime/runtime.c";
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = "/home/ziko/z-git/bux/bux/stdlib/io.c"; ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c";
} }
// Compile with cc // Compile with cc
@@ -633,16 +633,16 @@ func Cli_BuildProject(projectDir: String) -> int {
var rtPath: String = bux_path_join(stdlibDir, "runtime.c"); var rtPath: String = bux_path_join(stdlibDir, "runtime.c");
var ioPath: String = bux_path_join(stdlibDir, "io.c"); var ioPath: String = bux_path_join(stdlibDir, "io.c");
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "stdlib/runtime.c"); rtPath = bux_path_join(projectDir, "library/runtime/runtime.c");
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "stdlib/io.c"); ioPath = bux_path_join(projectDir, "library/runtime/io.c");
} }
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../stdlib/runtime.c"); rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c");
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../stdlib/io.c"); ioPath = bux_path_join(projectDir, "../library/runtime/io.c");
} }
// Compile with cc // Compile with cc
@@ -9,7 +9,7 @@ const skConst: int = 3;
const skModule: int = 4; const skModule: int = 4;
// Maximum symbols per scope // Maximum symbols per scope
const maxSymbols: int = 1024; const maxSymbols: int = 8192;
struct Symbol { struct Symbol {
kind: int; kind: int;
@@ -438,6 +438,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
func Sema_CollectGlobals(sema: *Sema) { func Sema_CollectGlobals(sema: *Sema) {
var decl: *Decl = sema.module.firstItem; var decl: *Decl = sema.module.firstItem;
var funcCount: int = 0;
while decl != null as *Decl { while decl != null as *Decl {
let dk: int = decl.kind; let dk: int = decl.kind;
@@ -503,6 +504,24 @@ func Sema_CollectGlobals(sema: *Sema) {
} }
} }
// Const
if dk == dkConst {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skConst;
sym.name = decl.strValue;
if decl.constType != null as *TypeExpr {
sym.typeKind = Sema_ResolveType(sema, decl.constType);
sym.typeName = decl.constType.typeName;
} else {
sym.typeKind = tyInt;
}
sym.isMutable = false;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Use (import) // Use (import)
if dk == dkUse { if dk == dkUse {
if decl.useKind == 2 { if decl.useKind == 2 {
BIN
View File
Binary file not shown.
+117
View File
@@ -0,0 +1,117 @@
import std/[unittest, strutils]
import ../bootstrap/[lexer, parser, sema, types]
proc checkSource(source: string): SemaResult =
let lexRes = tokenize(source, "<test>")
check not lexRes.hasErrors
let parseRes = parse(lexRes.tokens, "<test>")
check parseRes.diagnostics.len == 0
result = analyze(parseRes.module)
suite "Borrow Checker":
test "@[Checked] function with &mut allows mutation":
let res = checkSource("""
func Mutate(val: &mut int) {
*val = 42;
}
func Main() -> int {
var x: int = 10;
Mutate(&x);
return 0;
}
""")
check(not res.hasErrors)
test "@[Checked] function rejects write through &T":
let res = checkSource("""
@[Checked]
func BadWrite(val: &int) {
*val = 42;
}
func Main() -> int {
var x: int = 10;
BadWrite(&x);
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("cannot assign through shared reference"))
test "unchecked function allows write through raw pointer":
let res = checkSource("""
func RawWrite(val: *int) {
*val = 42;
}
func Main() -> int {
var x: int = 10;
RawWrite(&x);
return 0;
}
""")
check(not res.hasErrors)
test "&T allows reading":
let res = checkSource("""
@[Checked]
func Read(val: &int) -> int {
return *val;
}
func Main() -> int {
var x: int = 10;
return Read(&x);
}
""")
check(not res.hasErrors)
test "own T type parses and resolves":
let res = checkSource("""
struct Box {
value: int;
}
func TakeOwn(b: own Box) -> int {
return b.value;
}
func Main() -> int {
var b: Box = Box { value: 42 };
return TakeOwn(b);
}
""")
check(not res.hasErrors)
test "@[Checked] rejects double mutable borrow in call":
let res = checkSource("""
@[Checked]
func Swap(a: &mut int, b: &mut int) {
let tmp = *a;
*a = *b;
*b = tmp;
}
@[Checked]
func Main() -> int {
var x: int = 10;
Swap(&x, &x);
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("mutable borrow"))
test "@[Checked] rejects use after move":
let res = checkSource("""
struct Box {
value: int;
}
@[Checked]
func Consume(b: own Box) -> int {
return b.value;
}
@[Checked]
func Main() -> int {
var b: Box = Box { value: 42 };
let x: int = Consume(b);
return b.value;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("moved"))
BIN
View File
Binary file not shown.
@@ -1,5 +1,5 @@
import std/[unittest, strformat] import std/[unittest, strformat]
import ../src/[lexer, parser, sema, hir, hir_lower, types, scope] import ../bootstrap/[lexer, parser, sema, hir, hir_lower, types, scope]
proc lowerSource(source: string): HirModule = proc lowerSource(source: string): HirModule =
let lexRes = tokenize(source, "<test>") let lexRes = tokenize(source, "<test>")
BIN
View File
Binary file not shown.
@@ -1,5 +1,5 @@
import std/[unittest, os, strutils] import std/[unittest, os, strutils]
import ../src/[lexer, token, source_location] import ../bootstrap/[lexer, token, source_location]
proc tokenKinds(source: string): seq[TokenKind] = proc tokenKinds(source: string): seq[TokenKind] =
let res = tokenize(source, "<test>") let res = tokenize(source, "<test>")
BIN
View File
Binary file not shown.
@@ -1,5 +1,5 @@
import std/[unittest, os, strutils] import std/[unittest, os, strutils]
import ../src/[lexer, parser, ast, token] import ../bootstrap/[lexer, parser, ast, token]
proc parseSource(source: string): ParseResult = proc parseSource(source: string): ParseResult =
let lexRes = tokenize(source, "<test>") let lexRes = tokenize(source, "<test>")
BIN
View File
Binary file not shown.
@@ -1,5 +1,5 @@
import std/[unittest, strutils] import std/[unittest, strutils]
import ../src/[lexer, parser, sema, types] import ../bootstrap/[lexer, parser, sema, types]
proc checkSource(source: string): SemaResult = proc checkSource(source: string): SemaResult =
let lexRes = tokenize(source, "<test>") let lexRes = tokenize(source, "<test>")
@@ -1,4 +1,4 @@
import "../src/lexer", "../src/parser", "../src/ast" import "../bootstrap/lexer", "../bootstrap/parser", "../bootstrap/ast"
let source = readFile("../_selfhost/src/ast.bux") let source = readFile("../_selfhost/src/ast.bux")
let lexRes = tokenize(source, "ast.bux") let lexRes = tokenize(source, "ast.bux")
@@ -1,4 +1,4 @@
import "../src/lexer", "../src/parser", "../src/ast", "../src/token" import "../bootstrap/lexer", "../bootstrap/parser", "../bootstrap/ast", "../bootstrap/token"
import std/os import std/os
let source = readFile("../_selfhost/src/ast.bux") let source = readFile("../_selfhost/src/ast.bux")
+3 -3
View File
@@ -34,7 +34,7 @@ make dev
The output is a single binary: `buxc` (bootstrap compiler in Nim). The output is a single binary: `buxc` (bootstrap compiler in Nim).
The self-hosted compiler `buxc2` is built from `src_bux/*.bux` sources via: The self-hosted compiler `buxc2` is built from `compiler/selfhost/*.bux` sources via:
```bash ```bash
make selfhost make selfhost
``` ```
@@ -140,7 +140,7 @@ bux/
│ ├── hir_lower.nim # AST → HIR lowering │ ├── hir_lower.nim # AST → HIR lowering
│ ├── c_backend.nim # HIR → C code generation │ ├── c_backend.nim # HIR → C code generation
│ └── ... │ └── ...
├── stdlib/ # Standard library ├── library/ # Standard library
│ ├── Std/ │ ├── Std/
│ │ ├── Io.bux │ │ ├── Io.bux
│ │ ├── Array.bux │ │ ├── Array.bux
@@ -186,7 +186,7 @@ cat build/main.c
| Error | Cause | Fix | | Error | Cause | Fix |
|-------|-------|-----| |-------|-------|-----|
| `stdlib directory not found` | `buxc` can't find `stdlib/` | Run from project root or set correct path | | `stdlib directory not found` | `buxc` can't find `library/` | Run from project root or set correct path |
| `duplicate symbol 'bux_alloc'` | Multiple stdlib modules declare same extern | Only declare in one module | | `duplicate symbol 'bux_alloc'` | Multiple stdlib modules declare same extern | Only declare in one module |
| `C compilation failed` | Generated C has errors | Check `build/main.c` for issues | | `C compilation failed` | Generated C has errors | Check `build/main.c` for issues |
+53 -2
View File
@@ -454,8 +454,59 @@ func Main() -> int {
- `Std::Mem` — Memory wrappers ✅ - `Std::Mem` — Memory wrappers ✅
- `Std::Set` — Hash set ✅ - `Std::Set` — Hash set ✅
- `Std::Path` — Path manipulation ✅ - `Std::Path` — Path manipulation ✅
- `Std::Os``Args`, `Env`, `Exit`, `Cwd` - `Std::Os``Args`, `Env`, `Cwd`, `Chdir`
- `Std::Process` — Spawn subprocess ⏳ - `Std::Time``NowMs`, `NowUs`, `SleepMs`
- `Std::Process``Run`, `Output`
- `Std::Fmt` — String formatting with interpolation ⏳ - `Std::Fmt` — String formatting with interpolation ⏳
- `Std::Iter` — Iterator trait and combinators ⏳ - `Std::Iter` — Iterator trait and combinators ⏳
- `Std::Task` / `Std::Channel` — Lightweight concurrency (pthread-based threads) ✅ - `Std::Task` / `Std::Channel` — Lightweight concurrency (pthread-based threads) ✅
---
## Std::Os
Operating system interface.
```bux
import Std::Os::{Os_ArgsCount, Os_Args, Os_GetEnv, Os_SetEnv, Os_GetCwd, Os_Chdir};
```
| Function | Signature | Description |
|----------|-----------|-------------|
| `Os_ArgsCount` | `func Os_ArgsCount() -> int` | Number of command-line arguments |
| `Os_Args` | `func Os_Args(index: int) -> String` | Get argument at index |
| `Os_GetEnv` | `func Os_GetEnv(name: String) -> String` | Get environment variable |
| `Os_SetEnv` | `func Os_SetEnv(name: String, value: String) -> bool` | Set environment variable |
| `Os_GetCwd` | `func Os_GetCwd() -> String` | Get current working directory |
| `Os_Chdir` | `func Os_Chdir(path: String) -> bool` | Change directory |
---
## Std::Time
Time utilities.
```bux
import Std::Time::{Time_NowMs, Time_NowUs, Time_SleepMs};
```
| Function | Signature | Description |
|----------|-----------|-------------|
| `Time_NowMs` | `func Time_NowMs() -> int64` | Current time in milliseconds |
| `Time_NowUs` | `func Time_NowUs() -> int64` | Current time in microseconds |
| `Time_SleepMs` | `func Time_SleepMs(ms: int64)` | Sleep for N milliseconds |
---
## Std::Process
Process spawning.
```bux
import Std::Process::{Process_Run, Process_Output};
```
| Function | Signature | Description |
|----------|-----------|-------------|
| `Process_Run` | `func Process_Run(cmd: String) -> int` | Run command, return exit code |
| `Process_Output` | `func Process_Output(cmd: String) -> String` | Run command, capture stdout |
+37
View File
@@ -0,0 +1,37 @@
// Os & Time - System and time utilities
import Std::Io::{PrintLine, PrintInt, PrintInt64};
import Std::Os::{Os_ArgsCount, Os_Args, Os_GetEnv, Os_GetCwd};
import Std::Time::{Time_NowMs, Time_SleepMs};
func Main() -> int {
PrintLine("=== OS Demo ===");
Print("Current directory: ");
PrintLine(Os_GetCwd());
Print("Command-line args: ");
PrintInt(Os_ArgsCount());
PrintLine("");
Print("HOME env: ");
PrintLine(Os_GetEnv("HOME"));
PrintLine("\n=== Time Demo ===");
let start: int64 = Time_NowMs();
Print("Start: ");
PrintInt64(start);
PrintLine(" ms");
PrintLine("Sleeping 150ms...");
Time_SleepMs(150);
let end: int64 = Time_NowMs();
Print("End: ");
PrintInt64(end);
PrintLine(" ms");
Print("Elapsed: ");
PrintInt64(end - start);
PrintLine(" ms");
return 0;
}
+18
View File
@@ -0,0 +1,18 @@
// Process - Run external commands and capture output
import Std::Io::{PrintLine, PrintInt};
import Std::Process::{Process_Run, Process_Output};
func Main() -> int {
PrintLine("Process demo:");
let output: String = Process_Output("echo Hello from subprocess");
Print("Captured: ");
PrintLine(output);
let rc: int = Process_Run("true");
Print("Exit code: ");
PrintInt(rc);
PrintLine("");
return 0;
}
+5
View File
@@ -25,6 +25,11 @@ void PrintInt(int n) {
printf("%d", n); printf("%d", n);
} }
/* PrintInt64 - print 64-bit integer */
void PrintInt64(int64_t n) {
printf("%lld", (long long)n);
}
/* PrintFloat - print float */ /* PrintFloat - print float */
void PrintFloat(double f) { void PrintFloat(double f) {
printf("%g", f); printf("%g", f);
@@ -1061,3 +1061,97 @@ void* bux_async_result(void* handle) {
bux_async_task_t* task = (bux_async_task_t*)handle; bux_async_task_t* task = (bux_async_task_t*)handle;
return task->result; return task->result;
} }
/* ============================================================================
* OS primitives
* ============================================================================ */
#include <unistd.h>
const char* bux_getenv(const char* name) {
if (!name) return "";
const char* val = getenv(name);
return val ? val : "";
}
int bux_setenv(const char* name, const char* value) {
if (!name || !value) return -1;
return setenv(name, value, 1);
}
const char* bux_getcwd(void) {
static char buf[4096];
if (getcwd(buf, sizeof(buf))) {
return buf;
}
return "";
}
int bux_chdir(const char* path) {
if (!path) return -1;
return chdir(path);
}
/* ============================================================================
* Time primitives
* ============================================================================ */
int64_t bux_time_ms(void) {
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
return 0;
}
int64_t bux_time_us(void) {
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
return 0;
}
void bux_sleep_ms(int64_t ms) {
if (ms <= 0) return;
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
}
/* ============================================================================
* Process primitives
* ============================================================================ */
#include <stdio.h>
int bux_process_run(const char* cmd) {
if (!cmd) return -1;
return system(cmd);
}
char* bux_process_output(const char* cmd) {
if (!cmd) return NULL;
FILE* pipe = popen(cmd, "r");
if (!pipe) return NULL;
size_t cap = 1024;
size_t len = 0;
char* buf = (char*)malloc(cap);
if (!buf) { pclose(pipe); return NULL; }
int c;
while ((c = fgetc(pipe)) != EOF) {
if (len + 1 >= cap) {
cap *= 2;
char* new_buf = (char*)realloc(buf, cap);
if (!new_buf) { free(buf); pclose(pipe); return NULL; }
buf = new_buf;
}
buf[len++] = (char)c;
}
buf[len] = '\0';
pclose(pipe);
return buf;
}
+1
View File
@@ -3,6 +3,7 @@ module Std::Io {
extern func PrintLine(s: String); extern func PrintLine(s: String);
extern func Print(s: String); extern func Print(s: String);
extern func PrintInt(n: int); extern func PrintInt(n: int);
extern func PrintInt64(n: int64);
extern func PrintFloat(f: float64); extern func PrintFloat(f: float64);
extern func PrintBool(b: bool); extern func PrintBool(b: bool);
extern func ReadLine() -> String; extern func ReadLine() -> String;
+34
View File
@@ -0,0 +1,34 @@
module Std::Os {
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_getenv(name: String) -> String;
extern func bux_setenv(name: String, value: String) -> int;
extern func bux_getcwd() -> String;
extern func bux_chdir(path: String) -> int;
func Os_ArgsCount() -> int {
return bux_argc();
}
func Os_Args(index: int) -> String {
return bux_argv(index);
}
func Os_GetEnv(name: String) -> String {
return bux_getenv(name);
}
func Os_SetEnv(name: String, value: String) -> bool {
return bux_setenv(name, value) == 0;
}
func Os_GetCwd() -> String {
return bux_getcwd();
}
func Os_Chdir(path: String) -> bool {
return bux_chdir(path) == 0;
}
}
+14
View File
@@ -0,0 +1,14 @@
module Std::Process {
extern func bux_process_run(cmd: String) -> int;
extern func bux_process_output(cmd: String) -> String;
func Process_Run(cmd: String) -> int {
return bux_process_run(cmd);
}
func Process_Output(cmd: String) -> String {
return bux_process_output(cmd);
}
}
+19
View File
@@ -0,0 +1,19 @@
module Std::Time {
extern func bux_time_ms() -> int64;
extern func bux_time_us() -> int64;
extern func bux_sleep_ms(ms: int64);
func Time_NowMs() -> int64 {
return bux_time_ms();
}
func Time_NowUs() -> int64 {
return bux_time_us();
}
func Time_SleepMs(ms: int64) {
bux_sleep_ms(ms);
}
}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.