diff --git a/Makefile b/Makefile index 256c95e..7858096 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ NIM := nim -SRC := src/main.nim +SRC := compiler/bootstrap/main.nim OUT := buxc BUILD_DIR := build @@ -17,13 +17,15 @@ dev: test: build test-examples @echo "Running lexer tests..." - $(NIM) c -r tests/lexer_test.nim + $(NIM) c -r compiler/tests/lexer_test.nim @echo "Running parser tests..." - $(NIM) c -r tests/parser_test.nim + $(NIM) c -r compiler/tests/parser_test.nim @echo "Running sema tests..." - $(NIM) c -r tests/sema_test.nim + $(NIM) c -r compiler/tests/sema_test.nim @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..." rm -rf _test_tmp_pkg ./$(OUT) new _test_tmp_pkg @@ -58,7 +60,7 @@ selfhost: build @echo "=== Phase 7.9: Building self-hosted compiler ===" @rm -rf _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 @cd _selfhost && ../$(OUT) build @echo "=== Self-hosted compiler built successfully ===" diff --git a/PLAN.md b/PLAN.md index b3a38e4..becc8fc 100644 --- a/PLAN.md +++ b/PLAN.md @@ -290,7 +290,7 @@ Phase 7.5 — Driver (depends on all): **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 | |------|--------|---------|-----| @@ -355,8 +355,8 @@ Pipeline modules: **Bootstrap loop goal:** ``` -buxc (Nim) → compile src_bux/*.bux → buxc2 (Bux binary) -buxc2 (Bux) → compile src_bux/*.bux → buxc3 (Bux binary) +buxc (Nim) → compile compiler/selfhost/*.bux → buxc2 (Bux binary) +buxc2 (Bux) → compile compiler/selfhost/*.bux → buxc3 (Bux binary) compare buxc2 == buxc3 → SELF-HOSTED ✅ ``` @@ -556,7 +556,7 @@ bux/ │ ├── Linker.bux # Custom linker / build driver │ ├── Manifest.bux # bux.toml parser │ └── Package.bux # Package resolution -├── stdlib/ +├── library/ │ ├── Std/ │ │ ├── Io.bux │ │ ├── Memory.bux diff --git a/README.md b/README.md index 940a84a..20c4390 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ func Main() -> int { | **Methods** | `extend` blocks for struct methods | | **Interfaces** | `interface` + `extend` for trait-like behavior | | **Error Handling** | `Result`, `Option`, 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) | | **Strings** | Raw multi-line backtick strings (`...`), C-string interop | | **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking | @@ -166,18 +166,22 @@ func Main() -> int { ``` bux/ -├── src/ # Bootstrap compiler (Nim) -├── src_bux/ # Self-hosting compiler source (Bux) -├── _selfhost/ # Self-hosting build artifacts -├── stdlib/ # Standard library (.bux + .c runtime) -│ └── Std/ -│ ├── Io.bux, String.bux, Array.bux, Map.bux -│ ├── Fs.bux, Mem.bux, Set.bux -│ ├── Path.bux, Math.bux -│ └── Task.bux, Channel.bux +├── compiler/ # Compiler source code +│ ├── bootstrap/ # Bootstrap compiler (Nim) +│ ├── selfhost/ # Self-hosting compiler source (Bux) +│ └── tests/ # Compiler unit tests (Nim) +├── library/ # Standard library +│ ├── std/ # Standard library modules (.bux) +│ │ ├── Io.bux, String.bux, Array.bux, Map.bux +│ │ ├── Fs.bux, Mem.bux, Set.bux +│ │ ├── Path.bux, Math.bux +│ │ └── Task.bux, Channel.bux +│ └── runtime/ # C runtime files (runtime.c, io.c) +├── tests/ # Language integration tests ├── examples/ # Example programs -├── tests/ # Unit tests (Nim) +├── tools/ # Additional tooling ├── docs/ # Documentation +├── buxs/ # Windows-compatible project root ├── README.md ├── PLAN.md # Roadmap to self-hosting └── Makefile @@ -204,6 +208,8 @@ make test-examples make clean ``` +> **Windows users:** Use the `buxs/` directory as your project root to avoid path conflicts. + --- ## Documentation diff --git a/_selfhost/src/cli.bux b/_selfhost/src/cli.bux index b653a7f..f4ac134 100644 --- a/_selfhost/src/cli.bux +++ b/_selfhost/src/cli.bux @@ -31,7 +31,7 @@ func DirExists(path: String) -> bool { } // 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 @@ -140,19 +140,19 @@ func Cli_Build(srcPath: String, outPath: String) -> int { PrintLine(cFile); // Find runtime.c and io.c for linking - var rtPath: String = "stdlib/runtime.c"; - var ioPath: String = "stdlib/io.c"; + var rtPath: String = "library/runtime/runtime.c"; + var ioPath: String = "library/runtime/io.c"; if !FileExists(rtPath) { - rtPath = "../stdlib/runtime.c"; + rtPath = "../library/runtime/runtime.c"; } if !FileExists(ioPath) { - ioPath = "../stdlib/io.c"; + ioPath = "../library/runtime/io.c"; } 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) { - ioPath = "/home/ziko/z-git/bux/bux/stdlib/io.c"; + ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c"; } // Compile with cc @@ -633,16 +633,16 @@ func Cli_BuildProject(projectDir: String) -> int { var rtPath: String = bux_path_join(stdlibDir, "runtime.c"); var ioPath: String = bux_path_join(stdlibDir, "io.c"); if !FileExists(rtPath) { - rtPath = bux_path_join(projectDir, "stdlib/runtime.c"); + rtPath = bux_path_join(projectDir, "library/runtime/runtime.c"); } if !FileExists(ioPath) { - ioPath = bux_path_join(projectDir, "stdlib/io.c"); + ioPath = bux_path_join(projectDir, "library/runtime/io.c"); } if !FileExists(rtPath) { - rtPath = bux_path_join(projectDir, "../stdlib/runtime.c"); + rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c"); } if !FileExists(ioPath) { - ioPath = bux_path_join(projectDir, "../stdlib/io.c"); + ioPath = bux_path_join(projectDir, "../library/runtime/io.c"); } // Compile with cc diff --git a/_selfhost/src/scope.bux b/_selfhost/src/scope.bux index 00cee9c..d08d90d 100644 --- a/_selfhost/src/scope.bux +++ b/_selfhost/src/scope.bux @@ -9,7 +9,7 @@ const skConst: int = 3; const skModule: int = 4; // Maximum symbols per scope -const maxSymbols: int = 1024; +const maxSymbols: int = 8192; struct Symbol { kind: int; diff --git a/_selfhost/src/sema.bux b/_selfhost/src/sema.bux index ac07765..df37759 100644 --- a/_selfhost/src/sema.bux +++ b/_selfhost/src/sema.bux @@ -438,6 +438,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) { func Sema_CollectGlobals(sema: *Sema) { var decl: *Decl = sema.module.firstItem; + var funcCount: int = 0; while decl != null as *Decl { 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) if dk == dkUse { if decl.useKind == 2 { diff --git a/buxs/README.md b/buxs/README.md new file mode 100644 index 0000000..a466450 --- /dev/null +++ b/buxs/README.md @@ -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 diff --git a/buxs/build.bat b/buxs/build.bat new file mode 100644 index 0000000..32155d4 --- /dev/null +++ b/buxs/build.bat @@ -0,0 +1,4 @@ +@echo off +REM Windows wrapper to build Bux from buxs/ root +cd .. +make build diff --git a/buxs/build.sh b/buxs/build.sh new file mode 100755 index 0000000..c2ee67b --- /dev/null +++ b/buxs/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Wrapper script to build Bux from buxs/ root +cd .. +make build diff --git a/buxs/bux.toml b/buxs/bux.toml new file mode 100644 index 0000000..8eee9db --- /dev/null +++ b/buxs/bux.toml @@ -0,0 +1,7 @@ +[Package] +Name = "buxc" +Version = "0.1.0" +Type = "bin" + +[Build] +Output = "Bin" diff --git a/src/ast.nim b/compiler/bootstrap/ast.nim similarity index 100% rename from src/ast.nim rename to compiler/bootstrap/ast.nim diff --git a/src/c_backend.nim b/compiler/bootstrap/c_backend.nim similarity index 100% rename from src/c_backend.nim rename to compiler/bootstrap/c_backend.nim diff --git a/src/cli.nim b/compiler/bootstrap/cli.nim similarity index 97% rename from src/cli.nim rename to compiler/bootstrap/cli.nim index 8d736a9..dfaaefb 100644 --- a/src/cli.nim +++ b/compiler/bootstrap/cli.nim @@ -306,10 +306,10 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int = # Phase 2: Merge with stdlib and check var stdlibDir = "" let stdlibSearchPaths = @[ - getAppDir() / ".." / "stdlib", - getAppDir() / "stdlib", - getCurrentDir() / "stdlib", - "/home/ziko/z-git/bux/bux/stdlib", + getAppDir() / ".." / "library", + getAppDir() / "library", + getCurrentDir() / "library", + "/home/ziko/z-git/bux/bux/library", ] for path in stdlibSearchPaths: if dirExists(path): @@ -434,10 +434,10 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = # Find stdlib directory var stdlibDir = "" let stdlibSearchPaths = @[ - getAppDir() / ".." / "stdlib", - getAppDir() / "stdlib", - root / "stdlib", - "/home/ziko/z-git/bux/bux/stdlib", + getAppDir() / ".." / "library", + getAppDir() / "library", + root / "library", + "/home/ziko/z-git/bux/bux/library", ] for path in stdlibSearchPaths: if dirExists(path): @@ -518,19 +518,19 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = return 1 # Copy runtime.c - let runtimeSrc = stdlibDir / "runtime.c" + let runtimeSrc = stdlibDir / "runtime" / "runtime.c" if fileExists(runtimeSrc): copyFile(runtimeSrc, runtimeDst) else: - printError("runtime.c not found in stdlib", useColor) + printError("runtime.c not found in library/runtime/", useColor) return 1 # Copy io.c - let ioSrc = stdlibDir / "io.c" + let ioSrc = stdlibDir / "runtime" / "io.c" if fileExists(ioSrc): copyFile(ioSrc, ioDst) else: - printError("io.c not found in stdlib", useColor) + printError("io.c not found in library/runtime/", useColor) return 1 # Compile with cc diff --git a/src/hir.nim b/compiler/bootstrap/hir.nim similarity index 100% rename from src/hir.nim rename to compiler/bootstrap/hir.nim diff --git a/src/hir_lower.nim b/compiler/bootstrap/hir_lower.nim similarity index 100% rename from src/hir_lower.nim rename to compiler/bootstrap/hir_lower.nim diff --git a/src/lexer.nim b/compiler/bootstrap/lexer.nim similarity index 100% rename from src/lexer.nim rename to compiler/bootstrap/lexer.nim diff --git a/src/main.nim b/compiler/bootstrap/main.nim similarity index 100% rename from src/main.nim rename to compiler/bootstrap/main.nim diff --git a/src/manifest.nim b/compiler/bootstrap/manifest.nim similarity index 100% rename from src/manifest.nim rename to compiler/bootstrap/manifest.nim diff --git a/src/parser.nim b/compiler/bootstrap/parser.nim similarity index 100% rename from src/parser.nim rename to compiler/bootstrap/parser.nim diff --git a/src/scope.nim b/compiler/bootstrap/scope.nim similarity index 100% rename from src/scope.nim rename to compiler/bootstrap/scope.nim diff --git a/src/sema.nim b/compiler/bootstrap/sema.nim similarity index 96% rename from src/sema.nim rename to compiler/bootstrap/sema.nim index 6ac77bb..61edd7c 100644 --- a/src/sema.nim +++ b/compiler/bootstrap/sema.nim @@ -43,6 +43,7 @@ type # Borrow checker state checkedFunc*: bool ## true inside @[Checked] function currentFuncIsAsync*: bool ## true inside async func + movedVars*: seq[string] ## variables moved in current checked function # --------------------------------------------------------------------------- # Helpers @@ -727,6 +728,9 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = of tkNull: return makePointer(makeUnknown()) else: return makeUnknown() 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) if sym == nil: sema.emitError(expr.loc, &"undeclared identifier '{expr.exprIdent}'") @@ -931,6 +935,19 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = # Regular function call let calleeType = sema.checkExpr(expr.exprCallCallee, scope) var argTypes = sema.checkExprList(expr.exprCallArgs, scope) + # Look up callee declaration early (needed for borrow checking) + var calleeDecl: Decl = nil + case expr.exprCallCallee.kind + of ekIdent: + let sym = scope.lookup(expr.exprCallCallee.exprIdent) + if sym != nil: calleeDecl = sym.decl + of ekPath: + let fullName = expr.exprCallCallee.exprPath.join("::") + let sym = scope.lookup(fullName) + if sym != nil: calleeDecl = sym.decl + 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: @@ -940,17 +957,29 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = 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) - var calleeDecl: Decl = nil - case expr.exprCallCallee.kind - of ekIdent: - let sym = scope.lookup(expr.exprCallCallee.exprIdent) - if sym != nil: calleeDecl = sym.decl - of ekPath: - let fullName = expr.exprCallCallee.exprPath.join("::") - let sym = scope.lookup(fullName) - if sym != nil: calleeDecl = sym.decl - else: discard if calleeDecl != nil and calleeDecl.kind == dkFunc and calleeDecl.declFuncTypeParams.len > 0 and @@ -1281,6 +1310,8 @@ proc checkFunc(sema: var Sema, decl: Decl) = let wasAsync = sema.currentFuncIsAsync sema.checkedFunc = "Checked" in decl.declAttrs sema.currentFuncIsAsync = decl.declFuncIsAsync + if sema.checkedFunc: + sema.movedVars = @[] var funcScope = newScope(sema.globalScope) # Add type parameters to type table for resolution var addedTypeParams: seq[string] = @[] diff --git a/src/source_location.nim b/compiler/bootstrap/source_location.nim similarity index 100% rename from src/source_location.nim rename to compiler/bootstrap/source_location.nim diff --git a/src/token.nim b/compiler/bootstrap/token.nim similarity index 100% rename from src/token.nim rename to compiler/bootstrap/token.nim diff --git a/src/types.nim b/compiler/bootstrap/types.nim similarity index 100% rename from src/types.nim rename to compiler/bootstrap/types.nim diff --git a/src_bux/ast.bux b/compiler/selfhost/ast.bux similarity index 100% rename from src_bux/ast.bux rename to compiler/selfhost/ast.bux diff --git a/src_bux/c_backend.bux b/compiler/selfhost/c_backend.bux similarity index 100% rename from src_bux/c_backend.bux rename to compiler/selfhost/c_backend.bux diff --git a/src_bux/cli.bux b/compiler/selfhost/cli.bux similarity index 97% rename from src_bux/cli.bux rename to compiler/selfhost/cli.bux index b653a7f..f4ac134 100644 --- a/src_bux/cli.bux +++ b/compiler/selfhost/cli.bux @@ -31,7 +31,7 @@ func DirExists(path: String) -> bool { } // 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 @@ -140,19 +140,19 @@ func Cli_Build(srcPath: String, outPath: String) -> int { PrintLine(cFile); // Find runtime.c and io.c for linking - var rtPath: String = "stdlib/runtime.c"; - var ioPath: String = "stdlib/io.c"; + var rtPath: String = "library/runtime/runtime.c"; + var ioPath: String = "library/runtime/io.c"; if !FileExists(rtPath) { - rtPath = "../stdlib/runtime.c"; + rtPath = "../library/runtime/runtime.c"; } if !FileExists(ioPath) { - ioPath = "../stdlib/io.c"; + ioPath = "../library/runtime/io.c"; } 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) { - ioPath = "/home/ziko/z-git/bux/bux/stdlib/io.c"; + ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c"; } // Compile with cc @@ -633,16 +633,16 @@ func Cli_BuildProject(projectDir: String) -> int { var rtPath: String = bux_path_join(stdlibDir, "runtime.c"); var ioPath: String = bux_path_join(stdlibDir, "io.c"); if !FileExists(rtPath) { - rtPath = bux_path_join(projectDir, "stdlib/runtime.c"); + rtPath = bux_path_join(projectDir, "library/runtime/runtime.c"); } if !FileExists(ioPath) { - ioPath = bux_path_join(projectDir, "stdlib/io.c"); + ioPath = bux_path_join(projectDir, "library/runtime/io.c"); } if !FileExists(rtPath) { - rtPath = bux_path_join(projectDir, "../stdlib/runtime.c"); + rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c"); } if !FileExists(ioPath) { - ioPath = bux_path_join(projectDir, "../stdlib/io.c"); + ioPath = bux_path_join(projectDir, "../library/runtime/io.c"); } // Compile with cc diff --git a/src_bux/hir.bux b/compiler/selfhost/hir.bux similarity index 100% rename from src_bux/hir.bux rename to compiler/selfhost/hir.bux diff --git a/src_bux/hir_lower.bux b/compiler/selfhost/hir_lower.bux similarity index 100% rename from src_bux/hir_lower.bux rename to compiler/selfhost/hir_lower.bux diff --git a/src_bux/lexer.bux b/compiler/selfhost/lexer.bux similarity index 100% rename from src_bux/lexer.bux rename to compiler/selfhost/lexer.bux diff --git a/src_bux/main.bux b/compiler/selfhost/main.bux similarity index 100% rename from src_bux/main.bux rename to compiler/selfhost/main.bux diff --git a/src_bux/manifest.bux b/compiler/selfhost/manifest.bux similarity index 100% rename from src_bux/manifest.bux rename to compiler/selfhost/manifest.bux diff --git a/src_bux/nim_backend.bux b/compiler/selfhost/nim_backend.bux similarity index 100% rename from src_bux/nim_backend.bux rename to compiler/selfhost/nim_backend.bux diff --git a/src_bux/parser.bux b/compiler/selfhost/parser.bux similarity index 100% rename from src_bux/parser.bux rename to compiler/selfhost/parser.bux diff --git a/src_bux/qbe_backend.bux b/compiler/selfhost/qbe_backend.bux similarity index 100% rename from src_bux/qbe_backend.bux rename to compiler/selfhost/qbe_backend.bux diff --git a/src_bux/scope.bux b/compiler/selfhost/scope.bux similarity index 98% rename from src_bux/scope.bux rename to compiler/selfhost/scope.bux index 00cee9c..d08d90d 100644 --- a/src_bux/scope.bux +++ b/compiler/selfhost/scope.bux @@ -9,7 +9,7 @@ const skConst: int = 3; const skModule: int = 4; // Maximum symbols per scope -const maxSymbols: int = 1024; +const maxSymbols: int = 8192; struct Symbol { kind: int; diff --git a/src_bux/sema.bux b/compiler/selfhost/sema.bux similarity index 97% rename from src_bux/sema.bux rename to compiler/selfhost/sema.bux index ac07765..df37759 100644 --- a/src_bux/sema.bux +++ b/compiler/selfhost/sema.bux @@ -438,6 +438,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) { func Sema_CollectGlobals(sema: *Sema) { var decl: *Decl = sema.module.firstItem; + var funcCount: int = 0; while decl != null as *Decl { 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) if dk == dkUse { if decl.useKind == 2 { diff --git a/src_bux/source_location.bux b/compiler/selfhost/source_location.bux similarity index 100% rename from src_bux/source_location.bux rename to compiler/selfhost/source_location.bux diff --git a/src_bux/token.bux b/compiler/selfhost/token.bux similarity index 100% rename from src_bux/token.bux rename to compiler/selfhost/token.bux diff --git a/src_bux/types.bux b/compiler/selfhost/types.bux similarity index 100% rename from src_bux/types.bux rename to compiler/selfhost/types.bux diff --git a/compiler/tests/borrow_test b/compiler/tests/borrow_test new file mode 100755 index 0000000..db85517 Binary files /dev/null and b/compiler/tests/borrow_test differ diff --git a/compiler/tests/borrow_test.nim b/compiler/tests/borrow_test.nim new file mode 100644 index 0000000..4773063 --- /dev/null +++ b/compiler/tests/borrow_test.nim @@ -0,0 +1,117 @@ +import std/[unittest, strutils] +import ../bootstrap/[lexer, parser, sema, types] + +proc checkSource(source: string): SemaResult = + let lexRes = tokenize(source, "") + check not lexRes.hasErrors + let parseRes = parse(lexRes.tokens, "") + 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")) diff --git a/compiler/tests/hir_test b/compiler/tests/hir_test new file mode 100755 index 0000000..e4326dd Binary files /dev/null and b/compiler/tests/hir_test differ diff --git a/tests/hir_test.nim b/compiler/tests/hir_test.nim similarity index 97% rename from tests/hir_test.nim rename to compiler/tests/hir_test.nim index 39e6b9c..6eb91f8 100644 --- a/tests/hir_test.nim +++ b/compiler/tests/hir_test.nim @@ -1,5 +1,5 @@ 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 = let lexRes = tokenize(source, "") diff --git a/compiler/tests/lexer_test b/compiler/tests/lexer_test new file mode 100755 index 0000000..c15b203 Binary files /dev/null and b/compiler/tests/lexer_test differ diff --git a/tests/lexer_test.nim b/compiler/tests/lexer_test.nim similarity index 98% rename from tests/lexer_test.nim rename to compiler/tests/lexer_test.nim index beb6c5e..21c452e 100644 --- a/tests/lexer_test.nim +++ b/compiler/tests/lexer_test.nim @@ -1,5 +1,5 @@ import std/[unittest, os, strutils] -import ../src/[lexer, token, source_location] +import ../bootstrap/[lexer, token, source_location] proc tokenKinds(source: string): seq[TokenKind] = let res = tokenize(source, "") diff --git a/compiler/tests/parser_test b/compiler/tests/parser_test new file mode 100755 index 0000000..e459771 Binary files /dev/null and b/compiler/tests/parser_test differ diff --git a/tests/parser_test.nim b/compiler/tests/parser_test.nim similarity index 99% rename from tests/parser_test.nim rename to compiler/tests/parser_test.nim index a2f5cea..fad2503 100644 --- a/tests/parser_test.nim +++ b/compiler/tests/parser_test.nim @@ -1,5 +1,5 @@ import std/[unittest, os, strutils] -import ../src/[lexer, parser, ast, token] +import ../bootstrap/[lexer, parser, ast, token] proc parseSource(source: string): ParseResult = let lexRes = tokenize(source, "") diff --git a/compiler/tests/sema_test b/compiler/tests/sema_test new file mode 100755 index 0000000..588622d Binary files /dev/null and b/compiler/tests/sema_test differ diff --git a/tests/sema_test.nim b/compiler/tests/sema_test.nim similarity index 98% rename from tests/sema_test.nim rename to compiler/tests/sema_test.nim index e1a1231..70d2cd8 100644 --- a/tests/sema_test.nim +++ b/compiler/tests/sema_test.nim @@ -1,5 +1,5 @@ import std/[unittest, strutils] -import ../src/[lexer, parser, sema, types] +import ../bootstrap/[lexer, parser, sema, types] proc checkSource(source: string): SemaResult = let lexRes = tokenize(source, "") diff --git a/tests/test_ast_fields.nim b/compiler/tests/test_ast_fields.nim similarity index 89% rename from tests/test_ast_fields.nim rename to compiler/tests/test_ast_fields.nim index 2b3cf51..278d10b 100644 --- a/tests/test_ast_fields.nim +++ b/compiler/tests/test_ast_fields.nim @@ -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 lexRes = tokenize(source, "ast.bux") diff --git a/tests/test_parse_ast.nim b/compiler/tests/test_parse_ast.nim similarity index 84% rename from tests/test_parse_ast.nim rename to compiler/tests/test_parse_ast.nim index b7f2bfa..32841f4 100644 --- a/tests/test_parse_ast.nim +++ b/compiler/tests/test_parse_ast.nim @@ -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 let source = readFile("../_selfhost/src/ast.bux") diff --git a/tests/testdata/sample.bux b/compiler/tests/testdata/sample.bux similarity index 100% rename from tests/testdata/sample.bux rename to compiler/tests/testdata/sample.bux diff --git a/docs/BuildAndTest.md b/docs/BuildAndTest.md index cd60bb8..4cfe005 100644 --- a/docs/BuildAndTest.md +++ b/docs/BuildAndTest.md @@ -34,7 +34,7 @@ make dev 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 make selfhost ``` @@ -140,7 +140,7 @@ bux/ │ ├── hir_lower.nim # AST → HIR lowering │ ├── c_backend.nim # HIR → C code generation │ └── ... -├── stdlib/ # Standard library +├── library/ # Standard library │ ├── Std/ │ │ ├── Io.bux │ │ ├── Array.bux @@ -186,7 +186,7 @@ cat build/main.c | 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 | | `C compilation failed` | Generated C has errors | Check `build/main.c` for issues | diff --git a/docs/Stdlib.md b/docs/Stdlib.md index 5385938..95466ec 100644 --- a/docs/Stdlib.md +++ b/docs/Stdlib.md @@ -454,8 +454,59 @@ func Main() -> int { - `Std::Mem` — Memory wrappers ✅ - `Std::Set` — Hash set ✅ - `Std::Path` — Path manipulation ✅ -- `Std::Os` — `Args`, `Env`, `Exit`, `Cwd` ⏳ -- `Std::Process` — Spawn subprocess ⏳ +- `Std::Os` — `Args`, `Env`, `Cwd`, `Chdir` ✅ +- `Std::Time` — `NowMs`, `NowUs`, `SleepMs` ✅ +- `Std::Process` — `Run`, `Output` ✅ - `Std::Fmt` — String formatting with interpolation ⏳ - `Std::Iter` — Iterator trait and combinators ⏳ -- `Std::Task` / `Std::Channel` — Lightweight concurrency (pthread-based threads) ✅ \ No newline at end of file +- `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 | + diff --git a/examples/os_time.bux b/examples/os_time.bux new file mode 100644 index 0000000..c24d387 --- /dev/null +++ b/examples/os_time.bux @@ -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; +} diff --git a/examples/process.bux b/examples/process.bux new file mode 100644 index 0000000..51573cf --- /dev/null +++ b/examples/process.bux @@ -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; +} diff --git a/stdlib/io.c b/library/runtime/io.c similarity index 90% rename from stdlib/io.c rename to library/runtime/io.c index c8b6893..a13bbb7 100644 --- a/stdlib/io.c +++ b/library/runtime/io.c @@ -25,6 +25,11 @@ void PrintInt(int n) { printf("%d", n); } +/* PrintInt64 - print 64-bit integer */ +void PrintInt64(int64_t n) { + printf("%lld", (long long)n); +} + /* PrintFloat - print float */ void PrintFloat(double f) { printf("%g", f); diff --git a/stdlib/runtime.c b/library/runtime/runtime.c similarity index 92% rename from stdlib/runtime.c rename to library/runtime/runtime.c index 7547547..ffc0a00 100644 --- a/stdlib/runtime.c +++ b/library/runtime/runtime.c @@ -1061,3 +1061,97 @@ void* bux_async_result(void* handle) { bux_async_task_t* task = (bux_async_task_t*)handle; return task->result; } + +/* ============================================================================ + * OS primitives + * ============================================================================ */ + +#include + +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 + +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; +} diff --git a/stdlib/Std/Array.bux b/library/std/Array.bux similarity index 100% rename from stdlib/Std/Array.bux rename to library/std/Array.bux diff --git a/stdlib/Std/Channel.bux b/library/std/Channel.bux similarity index 100% rename from stdlib/Std/Channel.bux rename to library/std/Channel.bux diff --git a/stdlib/Std/Fs.bux b/library/std/Fs.bux similarity index 100% rename from stdlib/Std/Fs.bux rename to library/std/Fs.bux diff --git a/stdlib/Std/Io.bux b/library/std/Io.bux similarity index 95% rename from stdlib/Std/Io.bux rename to library/std/Io.bux index ea0cf02..58a38a9 100644 --- a/stdlib/Std/Io.bux +++ b/library/std/Io.bux @@ -3,6 +3,7 @@ module Std::Io { extern func PrintLine(s: String); extern func Print(s: String); extern func PrintInt(n: int); +extern func PrintInt64(n: int64); extern func PrintFloat(f: float64); extern func PrintBool(b: bool); extern func ReadLine() -> String; diff --git a/stdlib/Std/Map.bux b/library/std/Map.bux similarity index 100% rename from stdlib/Std/Map.bux rename to library/std/Map.bux diff --git a/stdlib/Std/Math.bux b/library/std/Math.bux similarity index 100% rename from stdlib/Std/Math.bux rename to library/std/Math.bux diff --git a/stdlib/Std/Mem.bux b/library/std/Mem.bux similarity index 100% rename from stdlib/Std/Mem.bux rename to library/std/Mem.bux diff --git a/library/std/Os.bux b/library/std/Os.bux new file mode 100644 index 0000000..edfe9fe --- /dev/null +++ b/library/std/Os.bux @@ -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; +} + +} diff --git a/stdlib/Std/Path.bux b/library/std/Path.bux similarity index 100% rename from stdlib/Std/Path.bux rename to library/std/Path.bux diff --git a/library/std/Process.bux b/library/std/Process.bux new file mode 100644 index 0000000..1a8c667 --- /dev/null +++ b/library/std/Process.bux @@ -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); +} + +} diff --git a/stdlib/Std/Set.bux b/library/std/Set.bux similarity index 100% rename from stdlib/Std/Set.bux rename to library/std/Set.bux diff --git a/stdlib/Std/String.bux b/library/std/String.bux similarity index 100% rename from stdlib/Std/String.bux rename to library/std/String.bux diff --git a/stdlib/Std/Task.bux b/library/std/Task.bux similarity index 100% rename from stdlib/Std/Task.bux rename to library/std/Task.bux diff --git a/library/std/Time.bux b/library/std/Time.bux new file mode 100644 index 0000000..a7d4fc2 --- /dev/null +++ b/library/std/Time.bux @@ -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); +} + +} diff --git a/src/parser b/src/parser deleted file mode 100755 index 4f251ac..0000000 Binary files a/src/parser and /dev/null differ diff --git a/src/sema b/src/sema deleted file mode 100755 index 1437252..0000000 Binary files a/src/sema and /dev/null differ