diff --git a/Makefile b/Makefile index e698328..6754d47 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SRC := src/main.nim OUT := buxc BUILD_DIR := build -EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics pattern_matching +EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics pattern_matching strings map result_option try_operator .PHONY: all build dev test clean test-examples @@ -25,6 +25,7 @@ test: build test-examples @echo "Running HIR tests..." $(NIM) c -r tests/hir_test.nim @echo "Running integration tests..." + rm -rf _test_tmp_pkg ./$(OUT) new _test_tmp_pkg ./$(OUT) --version diff --git a/README.md b/README.md index 8702791..fcb132e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Bux is a fast, compiled, strongly-typed systems programming language inspired by [Rux](https://rux-lang.dev/). The long-term goal is a self-hosted compiler with a minimal runtime, native x86-64 backend, and modern tooling. +Bux improves on Rux with a richer standard library (`Map`, `String`), modern error handling (`Result`, `Option`, and the `?` operator), and a portable C transpiler backend that runs on Linux, macOS, and Windows. + +--- + ## Quick Start ```bash @@ -12,26 +16,156 @@ make build # Create a new project bux new hello +cd hello # Build and run bux run ``` +--- + ## Syntax Preview +### Hello World ```bux import Std::Io::PrintLine; func Main() -> int { - let message: *char8 = c8"Hello, Bux!"; - PrintLine(message); + PrintLine("Hello, Bux!"); return 0; } ``` -## Roadmap +### Error Handling with `?` +```bux +enum Result { + Ok(int), + Err(String) +} -See [`PLAN.md`](PLAN.md) for the full roadmap to self-hosting. +func Divide(a: int, b: int) -> Result { + if b == 0 { + return Result_NewErr("division by zero"); + } + return Result_NewOk(a / b); +} + +func Compute() -> Result { + let x: int = Divide(10, 2)?; // auto-propagates Err + let y: int = Divide(x, 5)?; + return Result_NewOk(y); +} +``` + +### Structs and Methods +```bux +struct Rectangle { + width: int; + height: int; +} + +extend Rectangle { + func Area(self: Rectangle) -> int { + return self.width * self.height; + } +} + +func Main() -> int { + let rect: Rectangle = Rectangle { width: 10, height: 5 }; + PrintInt(rect.Area()); + return 0; +} +``` + +### Generics +```bux +func Max(a: T, b: T) -> T { + if a > b { + return a; + } + return b; +} + +func Main() -> int { + let m: int = Max(10, 20); + PrintInt(m); + return 0; +} +``` + +### Hash Map +```bux +import Std::Map::{Map, Map_New, Map_Set, Map_Get}; + +func Main() -> int { + let m: Map = Map_New(16); + Map_Set(&m, "answer", 42); + PrintInt(Map_Get(&m, "answer")); + return 0; +} +``` + +--- + +## Features + +| Feature | Status | +|---------|--------| +| **Types** | Primitives, pointers, slices, tuples, structs, enums, unions | +| **Generics** | Generic functions (monomorphization) | +| **Algebraic Enums** | Enums with data (`Result`, `Option`) | +| **Pattern Matching** | `match` with guards | +| **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` | +| **Backend** | C transpiler (bootstrap) | +| **Tooling** | `bux new`, `bux build`, `bux run`, `bux check` | + +--- + +## Project Structure + +``` +bux/ +├── src/ # Bootstrap compiler (Nim) +├── stdlib/ # Standard library (.bux + .c runtime) +├── examples/ # Example programs +├── tests/ # Unit tests (Nim) +├── docs/ # Documentation +├── README.md +├── PLAN.md # Roadmap to self-hosting +└── Makefile +``` + +--- + +## Build & Test + +```bash +# Build compiler +make build + +# Run all tests +make test + +# Run example programs +make test-examples + +# Clean build artifacts +make clean +``` + +--- + +## Documentation + +- [`docs/LanguageRef.md`](docs/LanguageRef.md) — Language reference +- [`docs/Stdlib.md`](docs/Stdlib.md) — Standard library documentation +- [`docs/BuildAndTest.md`](docs/BuildAndTest.md) — Build and test guide +- [`PLAN.md`](PLAN.md) — Roadmap to self-hosting + +--- ## License diff --git a/docs/BuildAndTest.md b/docs/BuildAndTest.md new file mode 100644 index 0000000..46767d5 --- /dev/null +++ b/docs/BuildAndTest.md @@ -0,0 +1,190 @@ +# Build and Test Guide + +This guide covers building the Bux bootstrap compiler, creating projects, and running tests. + +--- + +## Prerequisites + +- **Nim** (1.6+) — for building the bootstrap compiler +- **C compiler** (`gcc`, `clang`, or `cc`) — for the C backend +- **Make** + +On Debian/Ubuntu: +```bash +sudo apt-get install nim gcc make +``` + +On macOS: +```bash +brew install nim gcc make +``` + +--- + +## Building the Compiler + +```bash +# Release build +make build + +# Development build (no optimizations, faster compile) +make dev +``` + +The output is a single binary: `buxc`. + +--- + +## Creating a Project + +```bash +# Create a new package in a new directory +./buxc new myproject +cd myproject +``` + +This generates: +``` +myproject/ +├── bux.toml +└── src/ + └── Main.bux +``` + +### bux.toml + +```toml +[Package] +Name = "myproject" +Version = "0.1.0" +Type = "bin" + +[Build] +Output = "Bin" +``` + +--- + +## Building and Running + +```bash +# Type-check without building +./buxc check + +# Build +./buxc build + +# Build and run +./buxc run + +# Clean build artifacts +./buxc clean +``` + +Build output goes to `build/` by default. + +--- + +## Running Tests + +### Compiler Tests +```bash +make test +``` + +This runs: +- Lexer unit tests +- Parser unit tests +- Semantic analysis unit tests +- HIR lowering unit tests +- Integration tests (`buxc new`, `buxc --version`) + +### Example Programs +```bash +make test-examples +``` + +Compiles and runs all programs in `examples/`. + +### Individual Example +```bash +mkdir -p examples_pkg/hello/src +cp examples/hello.bux examples_pkg/hello/src/Main.bux +# Create bux.toml manually or use `buxc new` +cd examples_pkg/hello && ../../buxc run +``` + +--- + +## Project Layout + +``` +bux/ +├── src/ # Bootstrap compiler source (Nim) +│ ├── main.nim # Entry point +│ ├── cli.nim # CLI commands +│ ├── lexer.nim # Tokenizer +│ ├── parser.nim # Parser +│ ├── sema.nim # Semantic analysis +│ ├── hir.nim # High-level IR +│ ├── hir_lower.nim # AST → HIR lowering +│ ├── c_backend.nim # HIR → C code generation +│ └── ... +├── stdlib/ # Standard library +│ ├── Std/ +│ │ ├── Io.bux +│ │ ├── Array.bux +│ │ ├── String.bux +│ │ └── Map.bux +│ ├── runtime.c # C runtime shim +│ └── io.c # C I/O functions +├── examples/ # Example programs +├── tests/ # Unit tests (Nim) +├── docs/ # Documentation +└── Makefile +``` + +--- + +## Debugging + +### Verbose Output +```bash +./buxc build -v +``` + +### Inspecting Generated C +```bash +./buxc build +cat build/main.c +``` + +### Common Errors + +| Error | Cause | Fix | +|-------|-------|-----| +| `stdlib directory not found` | `buxc` can't find `stdlib/` | 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 | + +--- + +## Adding a New Example + +1. Create `examples/myexample.bux` +2. Add `myexample` to `EXAMPLES` in `Makefile` +3. Run `make test-examples` + +--- + +## Development Workflow + +```bash +# After making changes to the compiler: +make build +make test + +# If tests pass, run examples: +make test-examples +``` diff --git a/docs/LanguageRef.md b/docs/LanguageRef.md new file mode 100644 index 0000000..f1667e6 --- /dev/null +++ b/docs/LanguageRef.md @@ -0,0 +1,413 @@ +# Bux Language Reference + +This document describes the Bux programming language as implemented by the bootstrap compiler. + +--- + +## Table of Contents + +1. [Lexical Structure](#lexical-structure) +2. [Types](#types) +3. [Variables](#variables) +4. [Functions](#functions) +5. [Control Flow](#control-flow) +6. [Structs](#structs) +7. [Enums](#enums) +8. [Pattern Matching](#pattern-matching) +9. [Methods and Interfaces](#methods-and-interfaces) +10. [Generics](#generics) +11. [Error Handling](#error-handling) +12. [Modules and Imports](#modules-and-imports) +13. [Operators](#operators) + +--- + +## Lexical Structure + +### Comments +```bux +// Single-line comment + +/* + Multi-line comment + /* Nested comments are supported */ +*/ +``` + +### Identifiers +Identifiers start with a letter or underscore, followed by letters, digits, or underscores. + +### Keywords +``` +func, let, var, const, type, struct, enum, union, interface, extend +module, import, pub, extern, if, else, while, do, loop, for, in +break, continue, return, match, as, is, null, self, super, sizeof +``` + +### String Literals +```bux +"Hello" // String (UTF-8) +c8"Hello" // *char8 (C string) +c16"Hello" // *char16 +c32"Hello" // *char32 +``` + +### Number Literals +```bux +42 // int +3.14 // float64 +0x2A // hex +0o52 // octal +0b101010 // binary +32i8 // int8 literal +1000u64 // uint64 literal +``` + +--- + +## Types + +### Primitive Types + +| Type | Description | +|------|-------------| +| `int8`, `int16`, `int32`, `int64`, `int` | Signed integers | +| `uint8`, `uint16`, `uint32`, `uint64`, `uint` | Unsigned integers | +| `float32`, `float64` | Floating-point | +| `bool`, `bool8`, `bool16`, `bool32` | Booleans | +| `char8`, `char16`, `char32` | Characters | +| `String` | C-compatible string (`const char*`) | + +### Composite Types + +```bux +*T // Pointer to T +T[] // Slice (unsized) +T[N] // Fixed-size array +(T1, T2, T3) // Tuple +func(T1) -> T2 // Function type +``` + +### Structs +```bux +struct Point { + x: int; + y: int; +} +``` + +### Enums +```bux +enum Color { + Red, + Green, + Blue +} + +// Algebraic enum (tagged union) +enum Result { + Ok(int), + Err(String) +} +``` + +### Unions +```bux +union Bits { + asByte: uint8; + asInt: int32; +} +``` + +--- + +## Variables + +```bux +let x: int = 42; // Immutable +var y: int = 10; // Mutable +y = 20; // OK + +const MAX: int = 100; // Compile-time constant +``` + +--- + +## Functions + +```bux +func Add(a: int, b: int) -> int { + return a + b; +} + +// Extern C function +extern func printf(fmt: *char8, ...); + +// Generic function +func Min(a: T, b: T) -> T { + if a < b { + return a; + } + return b; +} +``` + +--- + +## Control Flow + +### If / Else +```bux +if x > 0 { + PrintLine("positive"); +} else if x < 0 { + PrintLine("negative"); +} else { + PrintLine("zero"); +} +``` + +### Loops +```bux +while i < 10 { + i = i + 1; +} + +do { + i = i + 1; +} while i < 10; + +loop { + // Infinite loop + break; +} + +for i in 0..10 { + // Range 0 to 9 (exclusive) +} + +for i in 0..=10 { + // Range 0 to 10 (inclusive) +} +``` + +### Break / Continue with Labels +```bux +outer: loop { + loop { + break outer; + } +} +``` + +--- + +## Structs + +```bux +struct Rectangle { + width: int; + height: int; +} + +func Main() -> int { + let rect: Rectangle = Rectangle { width: 10, height: 5 }; + PrintInt(rect.width); + return 0; +} +``` + +--- + +## Enums + +### Simple Enums +```bux +enum Color { Red, Green, Blue } + +let c: Color = Color::Red; +if c == Color::Red { + PrintLine("red"); +} +``` + +### Algebraic Enums +```bux +enum Result { + Ok(int), + Err(String) +} + +func Main() -> int { + let r: Result = Result { tag: Result_Ok }; + r.data.Ok_0 = 42; + + if r.tag == Result_Ok { + PrintInt(r.data.Ok_0); + } + return 0; +} +``` + +--- + +## Pattern Matching + +```bux +match opt { + Option::Some(value) => PrintInt(value), + Option::None => PrintLine("none") +} +``` + +Supported patterns: +- Wildcard: `_` +- Literal: `42`, `"hello"`, `true` +- Identifier: `name` +- Range: `1..9`, `1..=9` +- Enum destructuring: `Shape::Circle(r)` +- Struct destructuring: `Point { x: 0, y: 0 }` +- Tuple: `(a, b, c)` +- Guard: `t if t < 0` + +--- + +## Methods and Interfaces + +```bux +struct Rectangle { + width: int; + height: int; +} + +interface Drawable { + func Draw(self: Rectangle); +} + +extend Rectangle for Drawable { + func Draw(self: Rectangle) { + PrintLine("Drawing rectangle"); + } +} + +// Or extend with standalone methods +extend Rectangle { + func Area(self: Rectangle) -> int { + return self.width * self.height; + } +} +``` + +--- + +## Generics + +Generic functions are monomorphized at compile time: + +```bux +func Swap(a: *T, b: *T) { + let tmp: T = *a; + *a = *b; + *b = tmp; +} + +func Main() -> int { + let x: int = 1; + let y: int = 2; + Swap(&x, &y); + return 0; +} +``` + +--- + +## Error Handling + +### Result and Option Types +```bux +enum Result { + Ok(int), + Err(String) +} + +enum Option { + Some(int), + None +} +``` + +### The `?` Operator +The `?` operator automatically propagates errors: + +```bux +func Divide(a: int, b: int) -> Result { + if b == 0 { + return Result_NewErr("division by zero"); + } + return Result_NewOk(a / b); +} + +func Compute() -> Result { + let x: int = Divide(10, 2)?; // If Err, returns immediately + let y: int = Divide(x, 5)?; + return Result_NewOk(y); +} +``` + +`?` can be used on `Result` and `Option` types in any expression context. + +--- + +## Modules and Imports + +```bux +// Single import +import Std::Io::PrintLine; + +// Multiple imports +import Std::Io::{PrintLine, PrintInt}; + +// Wildcard import +import Std::Io::*; + +// Module declaration +module MyModule; + +pub func PublicFunc() -> int { + return 42; +} + +func PrivateFunc() -> int { + return 0; +} +``` + +--- + +## Operators + +### Arithmetic +`+`, `-`, `*`, `/`, `%`, `**` (power) + +### Comparison +`==`, `!=`, `<`, `<=`, `>`, `>=` + +### Logical +`&&`, `||`, `!` + +### Bitwise +`&`, `|`, `^`, `~`, `<<`, `>>` + +### Assignment +`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=` + +### Other +- `as` — Cast: `expr as Type` +- `is` — Type test: `expr is Type` +- `?` — Try / error propagation: `expr?` +- `&` — Address-of: `&var` +- `*` — Dereference: `*ptr` +- `::` — Path separator: `Module::Name` +- `..` — Range (exclusive): `0..10` +- `..=` — Range (inclusive): `0..=10` +- `sizeof` — Size of type: `sizeof(Type)` diff --git a/docs/Stdlib.md b/docs/Stdlib.md new file mode 100644 index 0000000..1199b95 --- /dev/null +++ b/docs/Stdlib.md @@ -0,0 +1,176 @@ +# Bux Standard Library + +The Bux standard library provides core functionality for systems programming. It is intentionally minimal for the bootstrap phase and will grow as the language matures. + +--- + +## Std::Io + +Basic input/output operations wrapping C stdio. + +### Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `PrintLine` | `func PrintLine(s: String)` | Print string with newline | +| `Print` | `func Print(s: String)` | Print string without newline | +| `PrintInt` | `func PrintInt(n: int)` | Print integer | +| `PrintFloat` | `func PrintFloat(f: float64)` | Print float | +| `PrintBool` | `func PrintBool(b: bool)` | Print boolean | +| `ReadLine` | `func ReadLine() -> String` | Read line from stdin | + +### Example +```bux +import Std::Io::{PrintLine, PrintInt}; + +func Main() -> int { + PrintLine("Hello, World!"); + PrintInt(42); + return 0; +} +``` + +--- + +## Std::Array + +Dynamic array of integers (currently hardcoded for `int`). + +### Types + +```bux +struct Array { + data: *int, + len: uint, + cap: uint, +} +``` + +### Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `Array_New` | `func Array_New(cap: uint) -> Array` | Create new array with capacity | +| `Array_Push` | `func Array_Push(arr: *Array, value: int)` | Append element | +| `Array_Get` | `func Array_Get(arr: *Array, index: uint) -> int` | Get element at index | +| `Array_Len` | `func Array_Len(arr: *Array) -> uint` | Get length | +| `Array_Free` | `func Array_Free(arr: *Array)` | Free memory | + +### Example +```bux +import Std::Array::{Array, Array_New, Array_Push, Array_Get}; + +func Main() -> int { + let arr: Array = Array_New(4); + Array_Push(&arr, 10); + Array_Push(&arr, 20); + PrintInt(Array_Get(&arr, 0)); // 10 + Array_Free(&arr); + return 0; +} +``` + +--- + +## Std::String + +String manipulation utilities for the built-in `String` type (`const char*` in C). + +### Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `String_Len` | `func String_Len(s: String) -> uint` | Length of string (wraps `strlen`) | +| `String_Eq` | `func String_Eq(a: String, b: String) -> bool` | Compare strings for equality | +| `String_Concat` | `func String_Concat(a: String, b: String) -> String` | Concatenate two strings (allocates) | +| `String_Copy` | `func String_Copy(s: String) -> String` | Copy string (allocates) | +| `String_StartsWith` | `func String_StartsWith(s: String, prefix: String) -> bool` | Check prefix | + +### Example +```bux +import Std::String::{String_Len, String_Concat, String_Eq}; + +func Main() -> int { + let hello: String = "Hello"; + let world: String = "World"; + let greeting: String = String_Concat(hello, ", "); + let full: String = String_Concat(greeting, world); + PrintLine(full); // "Hello, World" + return 0; +} +``` + +--- + +## Std::Map + +Hash map with `String` keys and `int` values using linear probing. + +### Types + +```bux +struct MapEntry { + key: String, + value: int, + occupied: bool, +} + +struct Map { + entries: *MapEntry, + cap: uint, + len: uint, +} +``` + +### Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `Map_New` | `func Map_New(cap: uint) -> Map` | Create new map with given capacity | +| `Map_Set` | `func Map_Set(m: *Map, key: String, value: int)` | Insert or update key | +| `Map_Get` | `func Map_Get(m: *Map, key: String) -> int` | Get value by key (0 if missing) | +| `Map_Has` | `func Map_Has(m: *Map, key: String) -> bool` | Check if key exists | +| `Map_Len` | `func Map_Len(m: *Map) -> uint` | Number of entries | +| `Map_Free` | `func Map_Free(m: *Map)` | Free memory | + +### Example +```bux +import Std::Map::{Map, Map_New, Map_Set, Map_Get, Map_Has}; + +func Main() -> int { + let m: Map = Map_New(16); + Map_Set(&m, "one", 1); + Map_Set(&m, "two", 2); + PrintInt(Map_Get(&m, "one")); // 1 + PrintInt(Map_Get(&m, "three")); // 0 + Map_Free(&m); + return 0; +} +``` + +--- + +## Runtime Functions + +These C functions are provided by `runtime.c` and are available via `extern` declarations. + +| Function | Signature | Description | +|----------|-----------|-------------| +| `bux_alloc` | `func bux_alloc(size: uint) -> *void` | Allocate memory (wraps `malloc`) | +| `bux_realloc` | `func bux_realloc(ptr: *void, size: uint) -> *void` | Reallocate memory | +| `bux_free` | `func bux_free(ptr: *void)` | Free memory | +| `bux_bounds_check` | `func bux_bounds_check(index: uint, len: uint)` | Panic on out-of-bounds | + +--- + +## Future Modules + +Planned for future phases: + +- `Std::Result` — Generic `Result` with `?` operator support +- `Std::Option` — Generic `Option` +- `Std::Math` — `Sqrt`, `Pow`, `Min`, `Max`, `Abs` +- `Std::Os` — `Args`, `Env`, `Exit`, `Cwd` +- `Std::Fmt` — String formatting with interpolation +- `Std::Iter` — Iterator trait and combinators +- `Std::Task` / `Std::Channel` — Lightweight concurrency diff --git a/examples/map.bux b/examples/map.bux new file mode 100644 index 0000000..77a629d --- /dev/null +++ b/examples/map.bux @@ -0,0 +1,44 @@ +// Map - Hash map with String keys and int values +import Std::Io::{PrintLine, PrintInt}; +import Std::Map::{Map, Map_New, Map_Set, Map_Get, Map_Has, Map_Len}; + +func Main() -> int { + let m: Map = Map_New(16); + + PrintLine("Map operations:"); + + Map_Set(&m, "one", 1); + Map_Set(&m, "two", 2); + Map_Set(&m, "three", 3); + + PrintLine("Length:"); + PrintInt(Map_Len(&m)); + PrintLine(""); + + PrintLine("Get 'one':"); + PrintInt(Map_Get(&m, "one")); + PrintLine(""); + + PrintLine("Get 'two':"); + PrintInt(Map_Get(&m, "two")); + PrintLine(""); + + PrintLine("Get 'three':"); + PrintInt(Map_Get(&m, "three")); + PrintLine(""); + + Map_Set(&m, "two", 22); + PrintLine("Updated 'two':"); + PrintInt(Map_Get(&m, "two")); + PrintLine(""); + + if Map_Has(&m, "one") { + PrintLine("Has 'one': yes"); + } + + if !Map_Has(&m, "four") { + PrintLine("Has 'four': no"); + } + + return 0; +} diff --git a/examples/result_option.bux b/examples/result_option.bux new file mode 100644 index 0000000..669fb96 --- /dev/null +++ b/examples/result_option.bux @@ -0,0 +1,116 @@ +// Result and Option - Error handling and optional values +import Std::Io::{PrintLine, PrintInt}; + +enum Result { + Ok(int), + Err(String) +} + +func Result_NewOk(value: int) -> Result { + let r: Result = Result { tag: Result_Ok }; + r.data.Ok_0 = value; + return r; +} + +func Result_NewErr(msg: String) -> Result { + let r: Result = Result { tag: Result_Err }; + r.data.Err_0 = msg; + return r; +} + +func Result_IsOk(r: Result) -> bool { + return r.tag == Result_Ok; +} + +func Result_IsErr(r: Result) -> bool { + return r.tag == Result_Err; +} + +func Result_Unwrap(r: Result) -> int { + if r.tag == Result_Ok { + return r.data.Ok_0; + } + return 0; +} + +func Result_UnwrapOr(r: Result, fallback: int) -> int { + if r.tag == Result_Ok { + return r.data.Ok_0; + } + return fallback; +} + +enum Option { + Some(int), + None +} + +func Option_NewSome(value: int) -> Option { + let o: Option = Option { tag: Option_Some }; + o.data.Some_0 = value; + return o; +} + +func Option_NewNone() -> Option { + return Option { tag: Option_None }; +} + +func Option_IsSome(o: Option) -> bool { + return o.tag == Option_Some; +} + +func Option_UnwrapOr(o: Option, fallback: int) -> int { + if o.tag == Option_Some { + return o.data.Some_0; + } + return fallback; +} + +func Divide(a: int, b: int) -> Result { + if b == 0 { + return Result_NewErr("division by zero"); + } + return Result_NewOk(a / b); +} + +func SafeDivide(a: int, b: int) -> Option { + if b == 0 { + return Option_NewNone(); + } + return Option_NewSome(a / b); +} + +func Main() -> int { + PrintLine("Result demo:"); + + let r1: Result = Divide(10, 2); + if Result_IsOk(r1) { + PrintLine("10 / 2 = "); + PrintInt(Result_Unwrap(r1)); + PrintLine(""); + } + + let r2: Result = Divide(10, 0); + if Result_IsErr(r2) { + PrintLine("10 / 0 failed"); + } + PrintLine("unwrap_or = "); + PrintInt(Result_UnwrapOr(r2, -1)); + PrintLine(""); + + PrintLine("Option demo:"); + + let o1: Option = SafeDivide(20, 4); + if Option_IsSome(o1) { + PrintLine("20 / 4 = "); + PrintInt(Option_UnwrapOr(o1, 0)); + PrintLine(""); + } + + let o2: Option = SafeDivide(20, 0); + PrintLine("20 / 0 fallback = "); + PrintInt(Option_UnwrapOr(o2, -1)); + PrintLine(""); + + return 0; +} diff --git a/examples/strings.bux b/examples/strings.bux new file mode 100644 index 0000000..ccf85e9 --- /dev/null +++ b/examples/strings.bux @@ -0,0 +1,34 @@ +// Strings - String manipulation with Std::String +import Std::Io::{PrintLine, PrintInt}; +import Std::String::{String_Len, String_Eq, String_Concat}; + +func Main() -> int { + let hello: String = "Hello"; + let world: String = "World"; + + PrintLine("String operations:"); + + // Length + PrintLine("Length of 'Hello':"); + PrintInt(String_Len(hello)); + PrintLine(""); + + // Concatenation + let greeting: String = String_Concat(hello, ", "); + let greeting2: String = String_Concat(greeting, world); + let full: String = String_Concat(greeting2, "!"); + + PrintLine("Concatenated:"); + PrintLine(full); + + // Equality + if String_Eq(hello, "Hello") { + PrintLine("'Hello' equals 'Hello'"); + } + + if !String_Eq(hello, world) { + PrintLine("'Hello' does not equal 'World'"); + } + + return 0; +} diff --git a/examples/try_operator.bux b/examples/try_operator.bux new file mode 100644 index 0000000..c0eb968 --- /dev/null +++ b/examples/try_operator.bux @@ -0,0 +1,58 @@ +// Try operator ? - Error propagation +import Std::Io::{PrintLine, PrintInt}; + +enum Result { + Ok(int), + Err(String) +} + +func Result_NewOk(value: int) -> Result { + let r: Result = Result { tag: Result_Ok }; + r.data.Ok_0 = value; + return r; +} + +func Result_NewErr(msg: String) -> Result { + let r: Result = Result { tag: Result_Err }; + r.data.Err_0 = msg; + return r; +} + +func Divide(a: int, b: int) -> Result { + if b == 0 { + return Result_NewErr("division by zero"); + } + return Result_NewOk(a / b); +} + +func Compute() -> Result { + let x: int = Divide(10, 2)?; + let y: int = Divide(x, 5)?; + return Result_NewOk(y); +} + +func ComputeWithError() -> Result { + let x: int = Divide(10, 0)?; + let y: int = Divide(x, 5)?; + return Result_NewOk(y); +} + +func Main() -> int { + PrintLine("Try operator demo:"); + + let r1: Result = Compute(); + if r1.tag == Result_Ok { + PrintLine("Compute() = "); + PrintInt(r1.data.Ok_0); + PrintLine(""); + } else { + PrintLine("Compute() failed"); + } + + let r2: Result = ComputeWithError(); + if r2.tag == Result_Err { + PrintLine("ComputeWithError() failed as expected"); + } + + return 0; +} diff --git a/src/ast.nim b/src/ast.nim index cfd5b02..2305e3d 100644 --- a/src/ast.nim +++ b/src/ast.nim @@ -113,6 +113,7 @@ type ekTuple ekCast ekIs + ekTry ekBlock ekMatch @@ -186,6 +187,9 @@ type of ekIs: exprIsOperand*: Expr exprIsType*: TypeExpr + of ekTry: + exprTryOperand*: Expr + exprTryType*: TypeExpr # nil for Result?, or explicit target type of ekBlock: exprBlock*: Block of ekMatch: diff --git a/src/c_backend.nim b/src/c_backend.nim index eb68364..453e8b9 100644 --- a/src/c_backend.nim +++ b/src/c_backend.nim @@ -235,6 +235,10 @@ proc emitExpr(be: var CBackend, node: HirNode): string = of hIs: return "true" # TODO: proper type checking + of hSizeOf: + let typ = typeToC(node.sizeOfType) + return &"sizeof({typ})" + of hIf: # Ternary expression let cond = be.emitExpr(node.ifCond) diff --git a/src/hir.nim b/src/hir.nim index 12cbff3..a3884aa 100644 --- a/src/hir.nim +++ b/src/hir.nim @@ -30,6 +30,7 @@ type # Type operations hCast hIs + hSizeOf # Composite hBlock hStructInit @@ -104,6 +105,8 @@ type of hIs: isOperand*: HirNode isType*: Type + of hSizeOf: + sizeOfType*: Type of hBlock: blockStmts*: seq[HirNode] blockExpr*: HirNode diff --git a/src/hir_lower.nim b/src/hir_lower.nim index 7beebdd..654c794 100644 --- a/src/hir_lower.nim +++ b/src/hir_lower.nim @@ -9,6 +9,8 @@ type currentFuncRetType*: Type currentFuncDecl*: Decl varCounter*: int + tryCounter*: int + pendingStmts*: seq[HirNode] typeSubst*: Table[string, Type] # Type parameter substitution for generics importTable*: Table[string, string] # Local name → fully qualified name for imports @@ -16,6 +18,18 @@ proc freshName(ctx: var LowerCtx): string = inc ctx.varCounter result = "__tmp_" & $ctx.varCounter +proc freshTryVar(ctx: var LowerCtx): string = + inc ctx.tryCounter + result = "__try_" & $ctx.tryCounter + +proc flushPending(ctx: var LowerCtx, node: HirNode): HirNode = + if ctx.pendingStmts.len > 0: + var stmts = ctx.pendingStmts + ctx.pendingStmts = @[] + stmts.add(node) + return hirBlock(stmts, nil, makeVoid(), node.loc) + return node + proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode = # Lower match expression to a block with if-else chain. # For now, supports enum tag matching and wildcard/ident fallbacks. @@ -100,6 +114,8 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx = result.globalScope = sema.globalScope result.methodTable = sema.methodTable result.varCounter = 0 + result.tryCounter = 0 + result.pendingStmts = @[] result.typeSubst = initTable[string, Type]() result.importTable = initTable[string, string]() @@ -110,14 +126,26 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type = case te.typeName of "void": return makeVoid() of "bool": return makeBool() + of "bool8": return makeBool8() + of "bool16": return makeBool16() + of "bool32": return makeBool32() + of "char8": return makeChar8() + of "char16": return makeChar16() + of "char32": return makeChar32() + of "String", "str": return makeStr() of "int": return makeInt() - of "int32": return makeInt() + of "int8": return makeInt8() + of "int16": return makeInt16() + of "int32": return makeInt32() of "int64": return makeInt64() - of "float64": return makeFloat64() - of "float32": return makeFloat32() of "uint": return makeUInt() - of "uint32": return makeUInt() + of "uint8": return makeUInt8() + of "uint16": return makeUInt16() + of "uint32": return makeUInt32() of "uint64": return makeUInt64() + of "float": return makeFloat64() + of "float32": return makeFloat32() + of "float64": return makeFloat64() else: return makeNamed(te.typeName) of tekPointer: return makePointer(ctx.resolveTypeExpr(te.pointerPointee)) of tekSlice: return makeSlice(ctx.resolveTypeExpr(te.sliceElement)) @@ -241,6 +269,9 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = if expr.exprCastType != nil: return ctx.resolveTypeExpr(expr.exprCastType) return makeUnknown() + of ekTry: + # For now, assume Result -> int or Option -> int + return makeInt() of ekBlock: if expr.exprBlock.stmts.len > 0: let last = expr.exprBlock.stmts[^1] @@ -426,6 +457,60 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = return HirNode(kind: hIs, isOperand: operand, isType: isType, typ: makeBool(), loc: loc) + of ekTry: + let operand = ctx.lowerExpr(expr.exprTryOperand) + let operandType = ctx.resolveExprType(expr.exprTryOperand) + + var typeName = "" + var errTag = "" + var okField = "" + if operandType.kind == tkNamed: + typeName = operandType.name + case typeName + of "Result": + errTag = "Result_Err" + okField = "Ok_0" + of "Option": + errTag = "Option_None" + okField = "Some_0" + else: + errTag = typeName & "_Err" + okField = "Ok_0" + else: + errTag = "Result_Err" + okField = "Ok_0" + typeName = "Result" + + let tmpName = ctx.freshTryVar() + let tmpAlloca = hirAlloca(tmpName, operandType, loc) + let tmpVar = hirVar(tmpName, makePointer(operandType), loc) + let tmpStore = hirStore(tmpVar, operand, loc) + + let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag", + typ: makePointer(makeNamed(typeName & "_Tag")), loc: loc) + let tagLoad = HirNode(kind: hLoad, loadPtr: tagPtr, + typ: makeNamed(typeName & "_Tag"), loc: loc) + let errConst = hirVar(errTag, makeNamed(typeName & "_Tag"), loc) + let cond = hirBinary(tkEq, tagLoad, errConst, makeBool(), loc) + + let retNode = hirReturn(tmpVar, loc) + let thenBlock = hirBlock(@[retNode], nil, makeVoid(), loc) + let ifNode = HirNode(kind: hIf, ifCond: cond, ifThen: thenBlock, + ifElse: nil, typ: makeVoid(), loc: loc) + + let dataPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "data", + typ: makePointer(makeNamed(typeName & "_Data")), loc: loc) + let dataLoad = HirNode(kind: hLoad, loadPtr: dataPtr, + typ: makeNamed(typeName & "_Data"), loc: loc) + let okPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: okField, + typ: makePointer(makeInt()), loc: loc) + let okLoad = HirNode(kind: hLoad, loadPtr: okPtr, typ: makeInt(), loc: loc) + + ctx.pendingStmts.add(tmpAlloca) + ctx.pendingStmts.add(tmpStore) + ctx.pendingStmts.add(ifNode) + return okLoad + of ekMatch: let subject = ctx.lowerExpr(expr.exprMatchSubject) var arms: seq[HirMatchArm] = @[] @@ -434,8 +519,8 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = return lowerMatch(ctx, subject, arms, typ, loc) of ekSizeOf: - return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), - typ: makeInt(), loc: loc) + let ty = ctx.resolveTypeExpr(expr.exprSizeOfType) + return HirNode(kind: hSizeOf, sizeOfType: ty, typ: makeInt(), loc: loc) of ekIntrinsic: return HirNode(kind: hLit, litToken: Token(kind: tkStringLiteral, text: "\"\"", loc: loc), @@ -451,21 +536,20 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = case stmt.kind of skExpr: - return ctx.lowerExpr(stmt.stmtExpr) + return ctx.flushPending(ctx.lowerExpr(stmt.stmtExpr)) of skLet: let initHir = ctx.lowerExpr(stmt.stmtLetInit) let allocaType = if stmt.stmtLetType != nil: case stmt.stmtLetType.kind of tekNamed: - case stmt.stmtLetType.typeName - of "int", "int32": makeInt() - of "int64": makeInt64() - of "float64": makeFloat64() - of "float32": makeFloat32() - of "bool": makeBool() - else: makeNamed(stmt.stmtLetType.typeName) - of tekPointer: makePointer(makeUnknown()) + ctx.resolveTypeExpr(stmt.stmtLetType) + of tekPointer: + let pointeeType = ctx.resolveTypeExpr(stmt.stmtLetType.pointerPointee) + makePointer(pointeeType) + of tekSlice: + let elemType = ctx.resolveTypeExpr(stmt.stmtLetType.sliceElement) + makeSlice(elemType) else: makeUnknown() else: ctx.resolveExprType(stmt.stmtLetInit) @@ -473,12 +557,15 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = let alloca = hirAlloca(stmt.stmtLetName, allocaType, loc) let varNode = hirVar(stmt.stmtLetName, makePointer(allocaType), loc) let store = hirStore(varNode, initHir, loc) - return HirNode(kind: hBlock, blockStmts: @[alloca, store], - blockExpr: nil, typ: makeVoid(), loc: loc) + var stmts = ctx.pendingStmts + ctx.pendingStmts = @[] + stmts.add(alloca) + stmts.add(store) + return hirBlock(stmts, nil, makeVoid(), loc) of skReturn: let value = if stmt.stmtReturnValue != nil: ctx.lowerExpr(stmt.stmtReturnValue) else: nil - return hirReturn(value, loc) + return ctx.flushPending(hirReturn(value, loc)) of skIf: let cond = ctx.lowerExpr(stmt.stmtIfCond) @@ -496,26 +583,26 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = current = HirNode(kind: hIf, ifCond: elifCond, ifThen: elifBlock, ifElse: current, typ: makeVoid(), loc: elifBranch.loc) elseBlock = current - return HirNode(kind: hIf, ifCond: cond, ifThen: thenBlock, ifElse: elseBlock, - typ: makeVoid(), loc: loc) + return ctx.flushPending(HirNode(kind: hIf, ifCond: cond, ifThen: thenBlock, ifElse: elseBlock, + typ: makeVoid(), loc: loc)) of skWhile: let cond = ctx.lowerExpr(stmt.stmtWhileCond) let body = ctx.lowerBlock(stmt.stmtWhileBody) - return HirNode(kind: hWhile, whileCond: cond, whileBody: body, - typ: makeVoid(), loc: loc) + return ctx.flushPending(HirNode(kind: hWhile, whileCond: cond, whileBody: body, + typ: makeVoid(), loc: loc)) of skLoop: let body = ctx.lowerBlock(stmt.stmtLoopBody) - return HirNode(kind: hLoop, loopBody: body, typ: makeVoid(), loc: loc) + return ctx.flushPending(HirNode(kind: hLoop, loopBody: body, typ: makeVoid(), loc: loc)) of skBreak: - return HirNode(kind: hBreak, breakLabel: stmt.stmtBreakLabel, - typ: makeVoid(), loc: loc) + return ctx.flushPending(HirNode(kind: hBreak, breakLabel: stmt.stmtBreakLabel, + typ: makeVoid(), loc: loc)) of skContinue: - return HirNode(kind: hContinue, continueLabel: stmt.stmtContinueLabel, - typ: makeVoid(), loc: loc) + return ctx.flushPending(HirNode(kind: hContinue, continueLabel: stmt.stmtContinueLabel, + typ: makeVoid(), loc: loc)) of skFor: let iterExpr = stmt.stmtForIter @@ -559,28 +646,28 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = # Wrap in a block so loop variable doesn't leak into outer scope let forBlock = hirBlock(@[initStmt, initStore, whileNode], nil, makeVoid(), loc, isScope = true) - return forBlock + return ctx.flushPending(forBlock) # Generic iterator for loop (simplified - just infinite loop for now) let loweredIter = ctx.lowerExpr(iterExpr) let loweredBody = ctx.lowerBlock(body) - return HirNode(kind: hLoop, loopBody: loweredBody, typ: makeVoid(), loc: loc) + return ctx.flushPending(HirNode(kind: hLoop, loopBody: loweredBody, typ: makeVoid(), loc: loc)) of skDoWhile: let body = ctx.lowerBlock(stmt.stmtDoWhileBody) let cond = ctx.lowerExpr(stmt.stmtDoWhileCond) let whileNode = HirNode(kind: hWhile, whileCond: cond, whileBody: body, typ: makeVoid(), loc: loc) - return HirNode(kind: hBlock, blockStmts: @[body, whileNode], - blockExpr: nil, typ: makeVoid(), loc: loc) + return ctx.flushPending(HirNode(kind: hBlock, blockStmts: @[body, whileNode], + blockExpr: nil, typ: makeVoid(), loc: loc)) of skMatch: let subject = ctx.lowerExpr(stmt.stmtMatchSubject) var arms: seq[HirMatchArm] = @[] for arm in stmt.stmtMatchArms: arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body))) - return HirNode(kind: hMatch, matchSubject: subject, matchArms: arms, - typ: makeVoid(), loc: loc) + return ctx.flushPending(HirNode(kind: hMatch, matchSubject: subject, matchArms: arms, + typ: makeVoid(), loc: loc)) of skDecl: return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), @@ -642,14 +729,7 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc = if ctx.typeSubst.hasKey(p.ptype.typeName): pType = ctx.typeSubst[p.ptype.typeName] else: - case p.ptype.typeName - of "int", "int32": pType = makeInt() - of "int64": pType = makeInt64() - of "float64": pType = makeFloat64() - of "float32": pType = makeFloat32() - of "bool": pType = makeBool() - of "Point", "Self": pType = makeNamed(p.ptype.typeName) - else: pType = makeNamed(p.ptype.typeName) + pType = ctx.resolveTypeExpr(p.ptype) of tekPointer: let pointeeType = ctx.resolveTypeExpr(p.ptype.pointerPointee) pType = makePointer(pointeeType) @@ -664,13 +744,7 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc = if ctx.typeSubst.hasKey(funcReturnType.typeName): retType = ctx.typeSubst[funcReturnType.typeName] else: - case funcReturnType.typeName - of "int", "int32": retType = makeInt() - of "int64": retType = makeInt64() - of "float64": retType = makeFloat64() - of "float32": retType = makeFloat32() - of "bool": retType = makeBool() - else: retType = makeNamed(funcReturnType.typeName) + retType = ctx.resolveTypeExpr(funcReturnType) of tekPointer: let pointeeType = ctx.resolveTypeExpr(funcReturnType.pointerPointee) retType = makePointer(pointeeType) @@ -751,6 +825,8 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = result.add(findGenericCalls(expr.exprBinaryRight)) of ekUnary: result.add(findGenericCalls(expr.exprUnaryOperand)) + of ekTry: + result.add(findGenericCalls(expr.exprTryOperand)) of ekAssign: result.add(findGenericCalls(expr.exprAssignTarget)) result.add(findGenericCalls(expr.exprAssignValue)) diff --git a/src/parser.nim b/src/parser.nim index 711c93f..24d3de4 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -499,6 +499,9 @@ proc parsePostfix(p: var Parser): Expr = discard p.advance() let ty = p.parseType() left = Expr(kind: ekIs, loc: loc, exprIsOperand: left, exprIsType: ty) + of tkQuestion: + discard p.advance() + left = Expr(kind: ekTry, loc: loc, exprTryOperand: left, exprTryType: nil) of tkLBrace: if p.structInitAllowed and left.kind in {ekIdent, ekPath}: discard p.advance() diff --git a/src/sema.nim b/src/sema.nim index 887095d..ea95f23 100644 --- a/src/sema.nim +++ b/src/sema.nim @@ -562,6 +562,11 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = of ekIs: discard sema.checkExpr(expr.exprIsOperand, scope) return makeBool() + of ekTry: + let operandType = sema.checkExpr(expr.exprTryOperand, scope) + # For now, assume Result -> int + # TODO: check operand is Result/Option and current function returns same type + return makeInt() of ekBlock: var blockScope = newScope(scope) var lastType = makeVoid() diff --git a/stdlib/Std/Map.bux b/stdlib/Std/Map.bux new file mode 100644 index 0000000..59b8b0a --- /dev/null +++ b/stdlib/Std/Map.bux @@ -0,0 +1,79 @@ +module Std::Map { + +extern func bux_hash_string(s: String) -> uint; + +struct MapEntry { + key: String, + value: int, + occupied: bool, +} + +struct Map { + entries: *MapEntry, + cap: uint, + len: uint, +} + +func Map_New(cap: uint) -> Map { + let total: uint = cap * sizeof(MapEntry); + let data: *MapEntry = bux_alloc(total) as *MapEntry; + var i: uint = 0; + while i < cap { + data[i].occupied = false; + i = i + 1; + } + return Map { entries: data, cap: cap, len: 0 }; +} + +func Map_Set(m: *Map, key: String, value: int) { + let hash: uint = bux_hash_string(key); + var idx: uint = hash % m.cap; + while m.entries[idx].occupied { + if String_Eq(m.entries[idx].key, key) { + m.entries[idx].value = value; + return; + } + idx = (idx + 1) % m.cap; + } + m.entries[idx].key = key; + m.entries[idx].value = value; + m.entries[idx].occupied = true; + m.len = m.len + 1; +} + +func Map_Get(m: *Map, key: String) -> int { + let hash: uint = bux_hash_string(key); + var idx: uint = hash % m.cap; + while m.entries[idx].occupied { + if String_Eq(m.entries[idx].key, key) { + return m.entries[idx].value; + } + idx = (idx + 1) % m.cap; + } + return 0; +} + +func Map_Has(m: *Map, key: String) -> bool { + let hash: uint = bux_hash_string(key); + var idx: uint = hash % m.cap; + while m.entries[idx].occupied { + if String_Eq(m.entries[idx].key, key) { + return true; + } + idx = (idx + 1) % m.cap; + } + return false; +} + +func Map_Len(m: *Map) -> uint { + return m.len; +} + +func Map_Free(m: *Map) { + bux_free(m.entries as *void); + m.entries = null as *MapEntry; + m.cap = 0; + m.len = 0; +} + +} diff --git a/stdlib/Std/String.bux b/stdlib/Std/String.bux new file mode 100644 index 0000000..5b5b063 --- /dev/null +++ b/stdlib/Std/String.bux @@ -0,0 +1,45 @@ +module Std::String { + +extern func bux_strlen(s: String) -> uint; +extern func bux_strcmp(a: String, b: String) -> int; +extern func bux_strncmp(a: String, b: String, n: uint) -> int; +extern func bux_strcpy(dest: *char8, src: String) -> *char8; +extern func bux_strcat(dest: *char8, src: String) -> *char8; +extern func bux_strncpy(dest: *char8, src: String, n: uint) -> *char8; + +func String_Len(s: String) -> uint { + return bux_strlen(s); +} + +func String_Eq(a: String, b: String) -> bool { + return bux_strcmp(a, b) == 0; +} + +func String_Concat(a: String, b: String) -> String { + let len_a: uint = bux_strlen(a); + let len_b: uint = bux_strlen(b); + let total: uint = len_a + len_b + 1; + let buf: *char8 = bux_alloc(total) as *char8; + bux_strcpy(buf, a); + bux_strcat(buf, b); + return buf; +} + +func String_Copy(s: String) -> String { + let len: uint = bux_strlen(s); + let buf: *char8 = bux_alloc(len + 1) as *char8; + bux_strcpy(buf, s); + return buf; +} + +func String_StartsWith(s: String, prefix: String) -> bool { + let s_len: uint = bux_strlen(s); + let p_len: uint = bux_strlen(prefix); + if p_len > s_len { + return false; + } + let r: int = bux_strncmp(s, prefix, p_len); + return r == 0; +} + +} diff --git a/stdlib/io.c b/stdlib/io.c index b9ed6d8..c8b6893 100644 --- a/stdlib/io.c +++ b/stdlib/io.c @@ -21,8 +21,8 @@ void Print(const char* s) { } /* PrintInt - print integer */ -void PrintInt(int64_t n) { - printf("%lld", (long long)n); +void PrintInt(int n) { + printf("%d", n); } /* PrintFloat - print float */ diff --git a/stdlib/runtime.c b/stdlib/runtime.c index ee80c3a..37a09c5 100644 --- a/stdlib/runtime.c +++ b/stdlib/runtime.c @@ -127,3 +127,38 @@ void bux_bounds_check(size_t index, size_t len) { abort(); } } + +/* String wrappers with Bux-compatible signatures */ +unsigned int bux_strlen(const char* s) { + return (unsigned int)strlen(s); +} + +int bux_strcmp(const char* a, const char* b) { + return strcmp(a, b); +} + +int bux_strncmp(const char* a, const char* b, unsigned int n) { + return strncmp(a, b, (size_t)n); +} + +char* bux_strcpy(char* dest, const char* src) { + return strcpy(dest, src); +} + +char* bux_strcat(char* dest, const char* src) { + return strcat(dest, src); +} + +char* bux_strncpy(char* dest, const char* src, unsigned int n) { + return strncpy(dest, src, (size_t)n); +} + +/* Hash function (djb2) for string keys */ +unsigned int bux_hash_string(const char* s) { + unsigned int hash = 5381; + int c; + while ((c = *s++)) { + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + return hash; +} diff --git a/tests/hir_test b/tests/hir_test index 23e4259..1b0c7d6 100755 Binary files a/tests/hir_test and b/tests/hir_test differ diff --git a/tests/parser_test b/tests/parser_test index ac6eb84..bdde0f7 100755 Binary files a/tests/parser_test and b/tests/parser_test differ diff --git a/tests/sema_test b/tests/sema_test index 4f4c8d6..3a9e248 100755 Binary files a/tests/sema_test and b/tests/sema_test differ