Compare commits

...

135 Commits

Author SHA1 Message Date
dimgigov 94e6806dda fix(compiler): allow integer coercion in range bounds; use for loops in boko-framework
- sema: range bounds now accept compatible integer types (e.g. int..uint)
  instead of requiring exact type equality.
- hir_lower: derive loop variable type from the common range type.
- boko-framework: replace manual while loops with for loops in Query_Parse,
  Request_Parse, Path_Match and App_Run.
2026-06-17 12:08:36 +03:00
dimgigov 9c632ce389 Rewrite jwt-pitbul and boko-framework with modern Bux constructs
- jwt-pitbul: algebraic enums, Array<String>, methods on JwtAlg,
  string interpolation, structured Result/Option error handling.
- boko-framework: StringMap<String> for headers/query/path params,
  Request/Response methods, StringBuilder, for loops, algebraic enums.
- Update READMEs and bump versions to 0.2.0.
2026-06-16 10:12:52 +03:00
dimgigov aaeb01e518 feat(nexus): production modular HTTP/1.1 server + compiler fixes
- Rewrite apps/nexus with modular architecture:
  Config, Http, Errors, Parser, Router, Handlers, Server, Main
- Use algebraic enums for ParseResult/FileResult/HttpError
- Thread-pool server via Channel<ConnectionTask> and spawn
- Fix C backend type ordering for generic struct instances
  (Array_T, Iter_T) and algebraic enum struct payloads
- Collect Slice_T types from struct fields and enum payloads
- Fix match lowering for simple enums (direct value compare)
- Resolve match expression return type from first arm
- Infer element type for for-in over Array<UserStruct>
- Preserve generic type args in field access resolution
- Add fflush to PrintLine/Print for immediate server logs
- Add modern_features golden regression test
- Regenerate golden expected.c files
2026-06-15 00:54:03 +03:00
dimgigov fc0a560e60 docs(roadmap): mark concurrency done in selfhost
Green threads (lib/Task.bux) and channels (lib/Channel.bux) already work.

Verified by _test_green_threads and _test_forin_channel.
2026-06-14 17:50:40 +03:00
dimgigov e11dce56d2 docs(roadmap): mark slice bounds checking done in selfhost
Slice<T> bounds-checked indexing already works via lib/Slice.bux
2026-06-14 17:48:45 +03:00
dimgigov e5e490605a feat(selfhost): auto-drop for interface-based Drop implementations
- Extend Lcx_BuildAutoDropFree to detect TypeName_Drop methods

  registered by sema for extend Type for Drop impl blocks.

- Preserve existing @[Drop] attribute path and move semantics.

- Add design doc and implementation plan.

- Mark Destructors/Drop roadmap item done in selfhost.
2026-06-14 17:44:35 +03:00
dimgigov 2c8223f94e fix(bootstrap): avoid emitting malformed Slice_T* typedefs
- Strip pointer suffix when scanning for slice types in C backend
- Skip slice typedefs already emitted from module.structs

Fixes _test_slice
2026-06-14 17:23:58 +03:00
dimgigov 44bac83471 fix(bootstrap): implement collection for-in lowering
- Export typeToTypeExpr from sema and preserve generic type args
- Derive loop variable type from collection element type in sema
- Substitute generic struct type params on field access
- Add getCollectionElementTypeExpr helper in hir_lower
- Replace placeholder collection for-in lowering
- Add Array/Iter lowering: Array_Iter_T / Iter_HasNext_T / Iter_Next_T
- Add Channel lowering: Channel_Recv_Ok_T loop
- Register loop variable in varTypeExprs before body lowering

Fixes _test_forin_stdlib, _test_forin_channel, _test_generic_trait, _test_import, _test_mono
2026-06-14 17:17:56 +03:00
dimgigov 06db4926a7 fix(bootstrap): substitute generic type args in operator [] resolution
- Preserve type args on tkNamed in resolveType
- Resolve explicit generic call return types with concrete substitutions
- Substitute method type params when looking up operator_index_get
- Skip strict arg checks for generic function calls (deferred to inference)
- Fix generic struct monomorphization when no caller substitution map exists
- Fix parameter varTypeExprs ordering so pointer params are visible in bodies

Fixes _test_drop_trait and _test_checked_index
2026-06-14 13:15:13 +03:00
dimgigov 84df4bba9d feat: bux test runner, bitwise-not parsing, stdlib discovery hardening\n\n- Add 'bux test' runner: discovers tests/*.bux, builds each in a temp package, runs and reports PASS/FAIL.\n- Fix unary ~ parsing and C emission in self-hosted compiler.\n- Harden stdlib discovery (require Fs.bux, avoid system /lib) and lstat in directory traversal.\n- Selfhost loop passes: buxc2 builds src/ and produces identical C output + stripped binary.\n- Update PLAN.md with completed milestones. 2026-06-12 22:19:13 +03:00
dimgigov a668127721 docs: update README and PLAN for v0.5.0
- README: add @[Release], green threads, drop trait, --release flag
- README: fix Task_Join → Task_Wait, update project structure
- README: add make selfhost-loop to build instructions
- PLAN: mark selfhost loop, borrow checker, release mode as done
- PLAN: update milestones summary
2026-06-11 10:05:10 +03:00
dimgigov 9bbeb4fd1d feat(selfhost): achieve binary-identical selfhost loop
- Add -Wl,--build-id=none to C compilation flags (removes non-deterministic build ID)
- Update Makefile selfhost-loop to strip debug info before ELF comparison
- Bootstrap compiler and selfhost compiler both deterministic
2026-06-11 10:00:14 +03:00
dimgigov 0e93ea911c feat(diag): format lexer errors with Diagnostic_Print in Cli_Check and Cli_BuildProject 2026-06-11 09:51:50 +03:00
dimgigov 290cbc8f98 feat(release): add @[Release] attribute and --release CLI flag
- @[Release] disables borrow checking and bounds checking in the function
- --release passes -O3 -flto to the C compiler for optimized builds
- Selfhost loop still deterministic
2026-06-11 09:39:11 +03:00
dimgigov 224542df7f fix(examples): Task_Join -> Task_Wait to match stdlib API 2026-06-11 09:21:06 +03:00
dimgigov a619247f06 test(slice): add operator indexing test for Array and Slice 2026-06-11 01:29:09 +03:00
dimgigov a4f25bc737 feat(operator): add operator_index_get/set to Array and Slice with generic monomorphization 2026-06-11 01:28:59 +03:00
dimgigov aa2d6f9632 test(drop): verify auto-drop works without @[Checked] 2026-06-11 01:09:23 +03:00
dimgigov b549e09a3b feat(drop): remove checkedFunc gate from auto-drop 2026-06-11 01:08:57 +03:00
dimgigov 2259567ac2 docs: add Drop trait implementation plan 2026-06-11 01:08:27 +03:00
dimgigov f4f09602c6 docs: add Drop trait / destructors design document 2026-06-11 01:07:34 +03:00
dimgigov ef4aa42b0b feat: add web playground with Docker sandbox
- playground/backend/main.go — Go HTTP server with /compile endpoint
- playground/frontend/index.html — Monaco Editor with 8 code examples
- playground/sandbox/Dockerfile — isolated Docker container with buxc2
- playground/docker-compose.yml — orchestrates backend + sandbox
- playground/Makefile — build and run targets
- playground/README.md — deployment guide for VPS with nginx + SSL
2026-06-10 20:33:26 +03:00
dimgigov 8f68a9da1a docs: document --target cross-compilation flag
- Add cross-compilation to README.md features and quick start
- Add cross-compilation section to docs/BuildAndTest.md
2026-06-10 20:26:31 +03:00
dimgigov e4e4e0d8ef feat: add --target <triple> cross-compilation flag
- Parse --target flag in Cli_Run before command dispatch
- Pass targetTriple through Cli_Build, Cli_BuildProject, Cli_RunProject
- Use clang with -target when cross-compiling, cc otherwise
- Selfhost loop: C output identical
2026-06-10 20:24:46 +03:00
dimgigov 18aa232b1c docs: add cross-compilation design document 2026-06-10 20:19:17 +03:00
dimgigov c6bae8b9ee feat(diag): add parser severity filtering and source map heuristic for bux build 2026-06-10 20:15:57 +03:00
dimgigov aef0cd7936 docs: add source location error fixes design 2026-06-10 20:12:20 +03:00
dimgigov 62d92b0aa6 test: add error snippet formatting test 2026-06-10 20:08:24 +03:00
dimgigov 9a63dcca11 fix(diag): remove parser diag printing, fix String_CharAt and sourceName issues 2026-06-10 20:08:20 +03:00
dimgigov 10a2b64ac4 feat(diag): print parser diagnostics in Parser_Parse 2026-06-10 20:04:31 +03:00
dimgigov 92962fa1b0 feat(diag): format all lexer and sema errors with Diagnostic_Print 2026-06-10 20:03:56 +03:00
dimgigov b971775baf feat(diag): add Diagnostic struct and Rust-style formatter 2026-06-10 20:02:33 +03:00
dimgigov 85d15cd16d docs: add source location error messages implementation plan 2026-06-10 20:00:09 +03:00
dimgigov de3ac76581 docs: add source location error messages design document 2026-06-10 19:56:55 +03:00
dimgigov 2824d25369 test: add green thread integration test
Also fix runtime scheduler bugs:
- g_task_creating was thread-local but set on main thread and read on worker
- g_scheduler_context was global shared by all workers instead of per-thread
2026-06-10 19:48:47 +03:00
dimgigov 9cd0580832 feat(task): add full green thread API (Init, Wait, Yield, CurrentId, Shutdown) 2026-06-10 19:45:24 +03:00
dimgigov 9e547f123a fix(scheduler): make Task_Sleep yield instead of block (MVP) 2026-06-10 19:44:55 +03:00
dimgigov 211a811f32 feat(scheduler): implement green thread scheduler with work-stealing 2026-06-10 19:43:31 +03:00
dimgigov 4456bb00af docs: add green threads implementation plan 2026-06-10 19:38:24 +03:00
dimgigov 6a0a0425dd docs: add green threads / task scheduler design document 2026-06-10 19:33:53 +03:00
dimgigov 9e515c157d feat(pkg): add basic package manager (add, remove, fetch, build deps)
- manifest.bux: parse [dependencies] section with up to 8 deps
- manifest.bux: Manifest_AddDep, Manifest_RemoveDep, Manifest_ToString
- cli.bux: bux add <name> <url> — adds dependency to bux.toml
- cli.bux: bux remove <name> — removes dependency from bux.toml
- cli.bux: bux fetch — git clone/pull dependencies into deps/
- cli.bux: Cli_BuildProject now merges deps/<name>/src/*.bux before user code
- Selfhost bootstrap loop verified: C output deterministic ✓
2026-06-10 19:18:36 +03:00
dimgigov 8f8708c5df feat(drop): enforce @[Drop] attribute and add basic move semantics
- Auto-drop now only triggers for user-defined types explicitly marked
  with @[Drop] on the struct declaration.
- Added move tracking in C backend to skip auto-drop for variables
  that have been moved (via let y = x, return x, or assignment).
- Prevents double-free when ownership is transferred.
- Selfhost bootstrap loop verified: C output is deterministic.
2026-06-10 19:08:15 +03:00
dimgigov 7392013301 docs: add Phase 8.7 — Optimization & Release Mode to PLAN.md
- @[Release] attribute for zero-cost abstractions
- Auto-drop inline, dead-store elimination, -O3 by default, PGO
- Milestone M15: C-speed benchmarks vs Nim/Crystal
2026-06-10 13:51:10 +03:00
dimgigov 0b887b92d3 selfhost: for-in support for Channel<T>; fix break/continue C codegen
- lib/Channel.bux: add Channel_Recv_Ok<T> for use in for-in desugaring
- hir_lower.bux: Channel for-in desugars to while+recv_ok pattern
- c_backend.bux: emit 'break;' / 'continue;' with semicolon, fix missing
  semicolon when break/continue inside if-block
- bootstrap/hir_lower.nim: skip generic methods in vtable generation to
  avoid referencing non-existent monomorphized C functions
2026-06-10 13:46:06 +03:00
dimgigov 7d6cf7f511 selfhost: add lib/Drop.bux with interface Drop; extend methods visible in scope
- lib/Drop.bux: interface Drop { func Drop(self: *Self); }
- sema.bux: define TypeName_MethodName in scope for impl block methods
  so auto-drop (Lcx_BuildAutoDropFree) can find Drop implementations
  via extend Type for Drop, not just @[Drop] or standalone functions
- Verified: extend Buffer for Drop auto-calls Buffer_Drop in @[Checked]
2026-06-10 13:37:55 +03:00
dimgigov 0a41ce85f4 selfhost: add @[Drop] attribute for user-defined structs
- ast.bux: add isDrop field to Decl
- parser.bux: parse @[Drop] attribute and attach to struct Decls
- hir_lower.bux: auto-drop now looks for TypeName_Drop for any type
- Test: _test_drop_user verifies Buffer_Drop is auto-called
2026-06-10 13:32:40 +03:00
dimgigov d6f0a30948 selfhost: add *_Drop wrappers to stdlib types; auto-drop uses _Drop instead of _Free
- Array.bux, Channel.bux, Set.bux, Map.bux: add *_Drop<T> delegating to *_Free
- hir_lower.bux: Lcx_BuildAutoDropFree searches for _Drop suffix instead of _Free
- Skip direct lowering of generic functions (only monomorphized instances)
2026-06-10 13:25:11 +03:00
dimgigov a2236de69a feat(selfhost): add operator overloading support (+,-,==,[], etc.)
- Auto-register func Type_Method(self: T, ...) as methods in sema.
- Add method table lookup for binary operators in sema.bux ekBinary.
- Add method table lookup for operator_index_get in sema.bux ekIndex.
- Add HIR lowering for binary operators to method calls in hir_lower.bux.
- Add HIR lowering for operator_index_get/set in hir_lower.bux.
- Set expr.refType for overloaded operators so chained ops work.
2026-06-10 13:11:46 +03:00
dimgigov a4e410fb30 fix(selfhost): for-in with monomorphized Array/Iter types + golden test updates + ROADMAP cleanup
- Fix hir_lower.bux to recognize Array_int/Iter_string etc. in for-in loops
  by checking Array_/Iter_ prefix outside the empty-elemTypeName branch.
- Update all golden expected.c files to include duplicate bux_bounds_check
  declaration (matches actual C codegen).
- Remove duplicate Drop trait item from ROADMAP.md.
2026-06-10 12:57:53 +03:00
dimgigov 51bd3c172b Add Slice<T> generic struct support and fix C backend generic filtering
- Add lib/Slice.bux with Slice<T> generic struct
- Fix C backend to recognize Slice/Slice* as generic types
- Use var instead of let for uninitialized Slice<T> locals
- Selfhost loop passes, tests pass
2026-06-10 12:03:31 +03:00
dimgigov 3a7b4bd5c1 selfhost: extend auto-Drop to Channel<T>, Set<T>, Map<K,V>
In @[Checked] functions, variables of type Channel<T>, Set<T>, and
Map<K,V> now automatically get defer Free_T(&var) injected.

- Added Lcx_BuildAutoDropFree helper that matches typeName prefix
  and generates the appropriate Free instance:
  - Array_<T>  → Array_Free_<T>
  - Channel_<T> → Channel_Free_<T>
  - Set_<T>    → Set_Free_<T>
  - Map_<K>_<V> → Map_Free_<K>_<V>

Selfhost loop passes, C output deterministic.
2026-06-10 11:47:50 +03:00
dimgigov a099999e02 selfhost: auto-Drop for Array<T> in @[Checked] functions
In @[Checked] functions, variables of type Array<T> automatically get
a defer Array_Free_T(&var) injected at declaration site. This ensures
Array values are freed when they go out of scope, preventing leaks.

Implementation:
- HIR lowering detects Array_<T> types in skLet statements
- Generates Array_Free_T generic instance if needed
- Builds synthetic defer node: defer Array_Free_T(&var)
- Wraps let+defer in hBlock for proper statement chaining

Also adds checkedFunc flag to HirFunc and CEmitter for future use.
2026-06-10 11:40:11 +03:00
dimgigov 5eec4328f3 docs: update ROADMAP and PHASE8 with completed features
Mark as done:
- for x in collection iterator loops
- Trait bounds (T: Comparable)
- CTFE (compile-time const evaluation)
- Selfhost bootstrap loop (deterministic C output)
- Implicit generic type argument inference
2026-06-10 11:21:04 +03:00
dimgigov c7d4c87fcf selfhost: basic CTFE — compile-time const expression evaluator
Evaluate const declarations at compile time:
- Arithmetic: +, -, *, /, %
- Comparisons: <, <=, >, >=, ==, !=
- Bitwise: &, |, ^, <<, >>
- Logical: &&, ||
- Unary: -, !, ~
- Ternary: cond ? a : b
- Cast: (T)expr (evaluates operand)
- Cross-const references: const C = A + B

Uses multi-pass evaluation to handle forward references
(caused by parser push-front ordering).
2026-06-10 11:11:52 +03:00
dimgigov d70ccba5ca selfhost: implicit generic type argument inference for Array/Iter stdlib functions
- sema: add Sema_InferGenericArg + Sema_ExtractElemType helpers
- Infers T from first argument's collection type:
  - Array_Push(&arr, 10) → Array_Push_int (from &arr's Array<int> type)
  - Iter_HasNext(&it) → Iter_HasNext_int (from &it's Iter<int> type)
- Supports both explicit generic types (Array<int>) and mangled types (Array_int)
- Works with reference expressions (&x) by unwrapping to x's type
- Selfhost loop: C output IDENTICAL
2026-06-10 10:26:08 +03:00
dimgigov 01677001c3 selfhost: for x in collection works with non-identifier expressions + buxc2 build merges stdlib
- cli: buxc2 build now delegates to Cli_BuildProject for src/Main.bux,
  enabling stdlib merging (Array_Iter, Iter_HasNext, etc. available)
- sema: set expr.refType on ekCall from function return type
  (needed for for-loop type extraction from MakeArr()-style expressions)
- hir_lower: remove ekIdent restriction on collection-based for loops
  - supports arbitrary expressions: for x in MakeArr() { ... }
  - creates temp variable __tmp_coll_N for non-identifier iterators
  - generates proper Array_T struct instances for temp variables
- Test: for x in MakeArr() sums [10,20,30] → 60
- Selfhost loop: C output IDENTICAL
2026-06-10 10:20:16 +03:00
dimgigov bb84693acb selfhost: fix extend-for method linked list + trait bounds check before indirect call return
- parser: extend-for only linked first 2 methods; now uses lastMethod pointer
- sema: trait bounds checking was after indirect-call early return (tyFunc path),
  so Max<Circle>(...) skipped bounds check when callee had tekFunc refType.
  Moved bounds check before both indirect/direct call returns.
- Negative test: Circle without Comparable impl now correctly errors.
- Selfhost loop: C output IDENTICAL.
2026-06-10 09:02:22 +03:00
dimgigov f63cbd1bf0 selfhost: trait bounds parsing, sema checks, and generic monomorphization fix
- AST: add typeParam0Bound/typeParam1Bound to Decl, strValue2 to dkImpl
- Parser: parse <T: Trait> bounds, Self type in interfaces, extend-for blocks
- Sema: add interfaceTable/methodTable, Sema_CheckTraitBounds, Sema_TypeImplements
- HIR lower: two-pass decl iteration — collect generic funcs before lowering bodies
  Fixes Max<Circle>(...) not generating Max_Circle when caller precedes callee
- C backend: skip generic funcs in emission (only emit monomorphized instances)
- Array.bux: fix for-loop over monomorphized Array types
- All debug prints removed; selfhost loop passes (C output IDENTICAL)
2026-06-10 08:48:10 +03:00
dimgigov 499b389ba8 fix(selfhost): collection-based for-in with Std::Array + monomorphization fixes
- Fix Fmt_Format name conflict between lib/Fmt.bux and src/fmt.bux
  by renaming selfhost formatter function to Fmt_FormatSource.

- Fix collection-based for-in loops to handle already-monomorphized
  types like Array_int and Iter_string (typeArgCount=0 cases).

- Fix ekSizeOf and ekCast in HIR lowering to apply Lcx_SubstituteType
  during generic monomorphization, so sizeof(T) and *T casts emit
  correct concrete types in C output.

- Fix sema to infer stmt.refStmtType from initializer expression
  when no explicit type annotation is given, ensuring C backend
  emits correct pointer types for let data = ... as *T.
2026-06-10 01:15:21 +03:00
dimgigov 632fa1535c fix(test): update wrong-args error message expectation 2026-06-10 00:28:18 +03:00
dimgigov 40bcbeb710 Fix generic struct monomorphization and C output for imports
- Fix nested Lcx_GenerateStructInstance overwriting parent slot by
  incrementing structCount BEFORE field processing
- Fix Lcx_LowerModule to skip generic structs from hm.structs (was missing
  continue due to bootstrap compiler codegen limitation)
- Fix C backend to forward-declare ALL structs at top, removing second
  generic-only forward declaration section that came too late
- Add empty-name guard in CBE_EmitStructDef
- Import system: parser supports ::* glob imports, sema two-pass resolution
2026-06-09 23:33:44 +03:00
dimgigov b36df0f5b7 feat(selfhost): generic monomorphization + collection-based for-in loops
- Add on-demand generic function/struct monomorphization in HIR lower
  - Lcx_SubstituteType: substitute type params and mangle names (e.g. Array<int> -> Array_int)
  - Lcx_GenerateFuncInstance: clone generic func with active substitution
  - Lcx_GenerateStructInstance: create concrete struct definitions
  - Detect generic calls (ekCall) and struct inits (ekStructInit)

- Parser: propagate generic type args from ident to ekStructInit node

- Fix duplicate HIR constant: hIndexPtr and hCall both had value 18,
  causing index expressions to emit as comma expressions in C backend

- Implement collection-based for-in desugaring:
  for x in arr { body } -> __iter = Array_Iter_T(&arr);
                           while Iter_HasNext_T(&__iter) { x = Iter_Next_T(&__iter); body }

Selfhost loop remains deterministic. All golden tests and examples pass.
2026-06-09 22:33:36 +03:00
dimgigov b3141dbcd5 docs: update ROADMAP + PHASE8 for for-in loops
- Mark range-based for-in as done
- Note collection-based for-in blocked by generic monomorphization
2026-06-09 21:55:40 +03:00
dimgigov 74db8a5790 feat: for-in range loops in selfhost + bootstrap
- Parser: add parseRange for selfhost (.. / ..=)
- Sema: determine loop variable type from range bounds
- HIR Lower: desugar range for-in to while loop with counter
- Bootstrap sema: fix range-based for variable type (was unknown)
- Selfhost loop remains deterministic
2026-06-09 21:53:12 +03:00
dimgigov 6414ef236b docs: update ROADMAP and PHASE8 for closures with captures 2026-06-09 21:23:58 +03:00
dimgigov 9b473c667c closures with captures: env struct + global instance + body rewriting
- AST: captureCount + captureName0..7 + captureType0..7 in Expr
- Sema: Scope_LookupUpTo + closureScope for capture analysis
- HIR Lower: env struct generation, capture assignments in skLet,
  body rewriting (ekIdent -> hFieldAccess on env instance)
- C Backend: env struct def + global instance emission for closures
- Bootstrap: full capture support in sema, hir_lower, lir_lower, lir_c_backend
- Selfhost loop remains deterministic
2026-06-09 21:23:05 +03:00
dimgigov ba54aa240e docs: update ROADMAP and LanguageRef with closures
- Mark capture-less closures as  Done in ROADMAP
- Add closure syntax examples to LanguageRef.md Functions section
- Update PHASE8_STRATEGY.md Milestone A with closures
2026-06-09 20:25:34 +03:00
dimgigov c83f6d5994 feat: capture-less anonymous functions (closures)
Implement MVP closures — anonymous functions without captures.

Syntax:
  |a: int, b: int| -> int { return a + b; }

Changes:
- ast.bux + ast.nim: add ekClosure AST node
- parser.bux + parser.nim: parse |params| -> Ret { body }
- sema.bux + sema.nim: type-check closure params/body, return tyFunc
- hir_lower.bux + hir_lower.nim: generate __closure_N function + hAddrOf
- lir_c_backend.nim: fix function-pointer variable declaration (cParamDecl)
- C backend: closures compile to global functions with unique names

Test: _test_closure/src/Main.bux
- Closure as variable
- Closure passed to higher-order function
- Address of named function as function pointer

Both bootstrap and selfhost compilers build and pass the test.
2026-06-09 20:24:10 +03:00
dimgigov 34504d1647 docs: sync all docs with recent features
Updated documentation to reflect changes from the last 2 days:

ROADMAP.md:
- Mark string interpolation, named/default params, bux fmt/test/new/init,
  and basic borrow checker as  Done
- Update Recommended Order (next: closures)

PHASE8_STRATEGY.md:
- Update date, achievements, and milestone statuses
- Borrow checker is now  working in selfhost
- CTFE, LSP, formatter, test runner marked done where applicable

LanguageRef.md:
- Add defer, switch/case, string interpolation (f"...")
- Add &T, &mut T, own T to composite types
- Add named/default parameters and operator overloading sections
- Expand @[Checked] rules with double-mut-borrow and use-after-move
- Update keywords and operators

STRATEGY.md:
- Update date and version (0.3.0 selfhost)
- Mark 8.2 borrow checker, 8.4 CTFE, 8.7 LSP as 

BuildAndTest.md:
- Add bux init, bux fmt, bux test commands
- Expand test sections with golden tests and Std::Test
2026-06-09 19:19:53 +03:00
dimgigov 079b76ae71 feat(selfhost): add 'bux fmt' code formatter
Line-based indentation formatter that:
- Adjusts indentation based on { } brace depth (4 spaces)
- Preserves original line structure and comments
- Skips braces inside string/char literals and // comments
- Handles } at line start and mixed brace lines
- 'bux fmt <file>' formats a single file
- 'bux fmt <dir>' formats all .bux files in directory
2026-06-09 17:48:20 +03:00
dimgigov 3ce7ba51d2 feat(selfhost): use-after-move tracking in @[Checked] functions
- Add inline movedName0..7 fields to Sema (avoids dynamic allocation issues)
- Sema_AddMoved: track variable moves from let/var init expressions
- Sema_IsMoved: check if variable was moved before use
- Sema_RemoveMoved: clear moved status on reinitialization (x = value)
- Report 'use of moved value' error for moved variable access
2026-06-09 17:40:20 +03:00
dimgigov 41c0fb1d6f feat(selfhost): double mutable borrow detection in @[Checked] functions
Detect when the same variable is passed as &mut argument twice
in a single function call (e.g. Swap(&x, &x)) inside checked functions.
Matches bootstrap sema.nim behavior (lines 1078-1089).
2026-06-09 17:32:21 +03:00
dimgigov f809827bea feat(selfhost): add &T (shared ref) and &mut T (mutable ref) type syntax
- Add tekRef and tekMutRef to TypeExpr enum (ast.bux)
- Parse &T and &mut T in parserParseType, before *T handling (parser.bux)
- Map tekRef/tekMutRef to tyPointer in HIR lowering (hir_lower.bux)
- Both emit as T* in C backend (same as raw pointer)
- Borrow checker rejects write-through-pointer in @[Checked] functions
  for all pointer types (safe default)
2026-06-09 17:30:19 +03:00
dimgigov 81281cbb11 feat(selfhost): @[Checked] borrow checker — basic write-through-ptr detection
- Add isChecked field to Decl struct (ast.bux)
- Parse @[Checked] attribute in parserParseDecl (parser.bux)
- Add checkedFunc flag and movedVars to Sema struct (sema.bux)
- Enable borrow checking per-function in Sema_Analyze
- Reject assignment through raw pointer (*T) in @[Checked] functions
- Skip newlines between @[Checked] attribute and the declaration keyword
  to avoid parser seeing the attribute and function as separate decls
2026-06-09 17:25:28 +03:00
dimgigov c14578d9dd feat(selfhost): add 'bux test' command
- 'bux test [dir]' builds the project and runs the resulting binary,
  reporting 'Tests passed' or 'Tests failed (exit code N)'.
- Builds on existing Cli_BuildProject pipeline — test code goes in
  src/ alongside production code; test assertions use lib/Test.bux.
2026-06-09 17:11:31 +03:00
dimgigov 0ac254facc feat(selfhost): add 'new' and 'init' CLI commands
- 'buxc new <name>': creates a new Bux project directory with bux.toml
  and src/Main.bux. Supports both relative names and absolute paths.
- 'buxc init': initializes a Bux project in the current directory.
- Adds extern bux_getcwd() and bux_path_parent() declarations.
- Fix mkdir return value check (bux_mkdir_if_needed returns 0 on success).
- Extract package name from path for absolute path arguments.
2026-06-09 17:09:03 +03:00
dimgigov 392dd64f97 cleanup(selfhost): remove 17 dead functions across 5 files
Remove unused helper functions, factories, accessors, and merge helpers:
- c_backend.bux: CBE_Init, CBE_ClearDefers, CBE_EmitLine, CBE_Build, CBackend_Free
- hir_lower.bux: Lcx_FreshName, Lcx_LowerType, HirLower_Free
- lexer.bux: Lexer_Init, lexIsIdentKind, Lexer_TokenCount, Lexer_GetToken
- parser.bux: parserIsBinaryOp, parserPrevious, parserMergeAddDecl,
  Parser_DiagCount, Parser_Free, Parser_MergeModules
- sema.bux: Sema_TypeName

None of these functions are referenced anywhere in src/.
2026-06-09 16:14:31 +03:00
dimgigov 90e67079f4 cleanup(selfhost): remove unused Print/PrintLine externs + dead debug comments from sema.bux 2026-06-09 15:59:57 +03:00
dimgigov 265bda850d test: expand golden C-codegen tests from 2 to 8 examples
Add golden tests for: structs, generics, algebraic_enums, enums,
methods, strings. Each test captures the expected C output from the
bootstrap compiler for regression detection.

Also remove leftover 'PARSER FIX V2 ACTIVE' debug print from parser.bux.
2026-06-09 15:58:44 +03:00
dimgigov 3ed5891de2 fix(selfhost): block scoping for if/while/loop/match bodies
- Create child scope in Sema_CheckBlock for every block, matching
  bootstrap sema.nim behavior (newScope for ekBlock).
- Fixes "var scoping bug in selfhost sema" where variables declared
  with var inside if/while blocks shared parent function scope, causing
  duplicate-definition errors between sibling if/else blocks, and C
  scoping mismatch between user variables and compiled output.
- Remove stale workaround comments from lib/Json.bux (let instead of
  var inside if blocks — no longer needed).
- Update sema.bux TODO comment for type table.
2026-06-09 15:56:16 +03:00
dimgigov 64bd41067a cleanup(selfhost): remove 24 DEBUG prints + stale parser HACK
- Remove DEBUG prints from c_backend.bux, hir_lower.bux, lexer.bux, parser.bux
- Remove HACK for stray } in parserParseFuncDecl (no longer needed after
  Decl refactor in 4a86009)
- Clean selfhost check/build output
2026-06-09 15:48:38 +03:00
dimgigov 4a860095fd fix(selfhost): resolve segfault in buxc2 compiling src/
- Replace Decl's 256 inline struct fields with dynamic fields: *StructField
  array to avoid parser field limit truncation and name shifts.
- Update parser.bux and hir_lower.bux to use decl.fields[i].
- Fix uninitialized Symbol.refType in three stack-allocated Symbol sites:
  loopSym (for-loop iterator), vSym (enum variants), and HIR param sym.
  Garbage refType values (e.g. 0xFF) were leaking into scope tables,
  later copied to expr->refType, causing SIGSEGV in Lcx_LowerExpr.
2026-06-09 15:03:45 +03:00
dimgigov 0c41c7bb25 feat(selfhost): function types (func(Params) -> Ret)
- Add tekFunc AST node and TypeExprList linked list for params
- Parse func(T, U) -> R syntax in parser.bux
- Resolve tekFunc to tyFunc in sema.bux
- Track original TypeExpr via Symbol.refType for params/lets/consts/funcs
- Build C function-pointer type names in hir_lower.bux (Ret (*)(Params))
- Lower indirect calls through function-typed values as hCallIndirect
- Add CBE_CParamDecl helper in c_backend.bux to embed name inside (*)
- Emit hCallIndirect and function-pointer variable declarations
- _test_funcptr now builds and runs with selfhost buxc2
2026-06-08 23:39:15 +03:00
dimgigov 2e536488e6 feat(bootstrap): function pointer types + C emission
- Add tekFunc AST node and parser support for func(Params) -> Ret
- Add tkFunc type resolution in sema and hir_lower
- Add &FuncName address-taking yielding tkMutRef(tkFunc)
- Fix C emission: function pointer params use Ret (*name)(Params) syntax
  via cParamDecl helper that embeds the name inside (*)
- Fix forward declarations and temp declarations for function pointers
- Add funcPtrTypes table in C backend so &Fn temps get proper type
- Add _test_funcptr integration test
2026-06-08 22:31:32 +03:00
dimgigov 3f0a3901ca feat: named and default parameters
Bootstrap compiler:
- ast.nim: add exprCallArgNames to ekCall for named arg tracking
- parser.nim: detect name: value syntax in call args
- sema.nim: add resolveCallArgs helper that injects default values
  for missing params and reorders named args into param order.
  Parser already parsed default param values; sema now uses them.

Selfhost compiler:
- ast.bux: add defaultExpr to Param, argName to ExprList
- parser.bux: parse = defaultExpr in param list, detect name: value
  syntax in call arguments
- sema.bux: add Sema_ResolveCallArgs with same logic as bootstrap

Tests:
- _test_named_params verifies defaults, named args, mixed positional+named,
  and named args with defaults in both compilers.

All verifications pass: build, selfhost-loop, test-examples, test-golden.
2026-06-08 22:02:19 +03:00
dimgigov 17c896c4dc feat: string interpolation via f"..." syntax
Bootstrap compiler:
- Add ekStringInterp AST node with text/expr parts
- Lexer: recognize f"..." prefix for interpolation strings
- Parser: split f"..." into ekStringInterp, parse inner expressions
  via sub-lexer/sub-parser. Supports escaped braces \{ and \}.
- Sema: type-check interpolated expressions, return String type
- HIR lowerer: desugar ekStringInterp to chained String_Concat
  calls with automatic type conversions:
  - int/uint types -> String_FromInt
  - float types -> String_FromFloat
  - bool -> String_FromBool
  - String -> as-is

Selfhost compiler:
- ast.bux: add ekStringInterp constant (reserved for future)
- lexer.bux: accept f" prefix, treat as plain string literal
  (selfhost parser does not implement interpolation yet)

Tests:
- _test_string_interp verifies int, float, bool, String,
  multiple interpolations, plain strings, and Fmt-style {0} templates
2026-06-08 21:48:10 +03:00
dimgigov cfce2a93e5 feat: operator overloading in bootstrap compiler
- Add operatorMethodName mapping (tkPlus -> operator_add, etc.)
- Add tryLowerOperatorCall / tryLowerIndexCall in HIR lowerer
- Modify ekBinary lowering to check method table before builtins
- Modify ekAssign lowering to detect ekIndex target and emit
  operator_index_set method call when available
- Add sema type-checking for operator overloads in ekBinary
  and ekIndex paths (method-table lookup before builtin rules)
- Fix sema compile errors: minfo.params[N].typ -> minfo.params[N]
- Fix hir_lower forward decl ordering for resolveExprType
- Update golden test expected.c files for current C backend
- Update ROADMAP.md and PHASE8_STRATEGY.md status
2026-06-08 21:31:42 +03:00
dimgigov d9aceeac6e feat: switch/case statement
- Add  syntax
- Desugars to if-else chain in HIR lowering (both compilers)
- No new HIR/C backend nodes needed — reuses hIf/hBinary/hBlock
- Supports integer, char, and enum tag dispatch
- Case body can be single statement (no braces required)
2026-06-08 21:08:55 +03:00
dimgigov a67271b08c feat: defer statement support in both compilers
- Add  statement for function-level deferred execution
- Deferred expressions run in LIFO order on function exit (return or implicit)
- Bootstrap: desugar defers in hir_lower.nim (inject before return + end of func)
- Selfhost: emit defers in c_backend.bux via CEmitter defer stack
- Both: add tkDefer token, skDefer AST node, hDefer HIR node
- Parser, lexer, sema, token files updated in both bootstrap/ and src/
2026-06-08 20:59:27 +03:00
dimgigov cf92b63ba6 fix: bootstrap parser newline skipping + nexus compile errors
- Add tkNewLine skipping in bootstrap parser parsePrimary, parseUnary, parsePostfix
- Fix nexus: req.String_Len(path) -> String_Len(req.path)
- Fix nexus: bux_sb_append_char with char literal -> bux_sb_append with string
2026-06-08 20:28:37 +03:00
dimgigov 9c3d6dc723 fix: EVP_SignFinal unsigned int* type mismatch in OpenSSL calls 2026-06-08 20:24:47 +03:00
dimgigov 7d1d722cba fix: String_IsNull/String_Offset helpers + remove pointer-to-int casts
- Add String_IsNull and String_Offset wrappers in lib/String.bux
- Add bux_str_is_null and bux_str_offset runtime functions in rt/runtime.c
- Update String_Replace to use String_IsNull instead of String_Len(pos)==0
- Replace pointer-to-uint casts (as uint == 0, pointer arithmetic) across apps/
- Parser newline skipping fixes in src/parser.bux
2026-06-08 20:23:30 +03:00
dimgigov 3c7938163c fix(runtime): detach StringBuilder buffer on build to prevent use-after-free
bux_sb_build returned sb->buf and then bux_sb_free freed it,
causing dangling pointers in all callers of StringBuilder_Build
followed by StringBuilder_Free (e.g. JsonParser_ParseString, Fmt).

Fix: bux_sb_build now detaches the buffer (sets sb->buf = NULL)
before returning it, so bux_sb_free only frees the sb struct.
This transfers buffer ownership to the caller.
2026-06-08 20:10:30 +03:00
dimgigov c4e3f80405 fix(selfhost/c_backend): handle backtick raw string literals
Backtick strings were emitted directly into C code without
stripping the backticks or escaping the content, causing C syntax errors.

Fix: detect backtick-wrapped literals in CBE_EmitExpr, strip
the backticks, escape inner content, and wrap in double quotes.
2026-06-08 20:03:33 +03:00
dimgigov d253a01aa9 fix(selfhost/c_backend): emit _Tag + struct wrapper for all enums
Simple enums were emitted as plain C enums, but the rest of the codegen
(and source code) expected them to have a .tag field like algebraic enums.
This caused C compilation errors:
- 'request for member tag in something not a structure or union'
- 'field name not in record or union initializer'

Fix: always emit the _Tag typedef + struct wrapper for every enum.
For simple enums, the struct only contains the tag (no _Data union).
For algebraic enums, keep the existing _Data union + struct behavior.

This matches the bootstrap compiler's enum codegen.
2026-06-08 20:00:58 +03:00
dimgigov 2cb971a483 fix(selfhost/parser): skip newlines inside expressions
The selfhost parser did not skip tkNewLine tokens inside expression parsing,
causing multi-line expressions (e.g. if conditions spanning lines with ||)
to be truncated. When parserParseBinaryPrec hit a newline it treated it as
an invalid primary, returning a malformed literal and leaving p.pos at the
newline. parserParseBlock then failed to find '{' and its depth-counter
fallback consumed far too many tokens, causing subsequent function bodies
to be parsed into the wrong AST nodes.

Fix: add newline skipping in parserParsePrimary, parserParseUnary, and
parserParseBinaryPrec, matching the bootstrap parser's skipNewlines() calls
in parseAnd/parseOr.

Also complete the variant cap raise from 8->9 in src/sema.bux (was already
done in ast.bux, parser.bux, hir_lower.bux).
2026-06-08 19:45:05 +03:00
dimgigov b1deff7fd5 fix(selfhost/parser): skip newlines inside multi-import brace blocks
parserParseImportDecl now skips tkNewLine tokens inside {..} blocks,
matching the bootstrap parser behavior. This fixes parsing of imports
with names on separate lines, e.g.:

  import Std::String::{
      String_Eq,
      String_Len
  };

Previously the parser failed because it expected tkIdent immediately
after { but encountered tkNewLine instead.
2026-06-08 18:56:43 +03:00
dimgigov 38bc469be7 fix(bootstrap): resolve algebraic enum _Data union field types in HIR lowering
hir_lower.resolveExprType for ekField now correctly resolves variant
positional/named fields (e.g., r.data.Ok_0, r.data.Err_0) on _Data
union types, matching the existing logic in sema.checkExpr.

This fixes the runtime segfault in examples/algebraic_enums.bux where
the C backend emitted an int temporary for a String field, causing
PrintLine to dereference a garbage pointer.
2026-06-08 18:48:30 +03:00
dimgigov bebc362ae6 fix(bootstrap): simple enum codegen, crypto parser compatibility, import ordering
- Fix bootstrap sema to type simple enum variants as the enum itself
  (not _Tag), and .tag access returns the enum type.
- Fix hir_lower to lower simple enum .tag as no-op and Enum{tag:X}
  init as X directly.
- Fix sema collectGlobals to register imports AFTER real declarations,
  preventing duplicate-symbol errors when stdlib files import symbols
  defined in later files (e.g. jwt.bux importing rsa.bux).
- Rewrite lib/crypto/*.bux as single-line to work around bootstrap
  parser limitations with multi-line function calls.
- Rename 'pub' -> 'pubBuf' in ed25519.bux and jwt-pitbul/Main.bux
  to avoid reserved keyword collision.
- Remove algTag:int workaround from jwt-pitbul/Main.bux; enum
  comparisons now type-check without manual casts.
- Update .gitignore to exclude _test_*/ and backup files.
- Delete accidentally-committed _test_array/ directory.
2026-06-08 18:43:26 +03:00
dimgigov a7a1b45588 chore: add MIT license 2026-06-08 14:44:05 +03:00
dimgigov a13cee8a0f feat(simpledb): file-backed key-value database with CLI
- Add SimpleDB: plain-text key=value store with get/set/del/has/keys/count
- Fix lib/Fmt.bux: replace *(ptr+N) pointer arithmetic with ptr[N] indexing
- Fix lib/crypto/jwt.bux: use alg.tag directly instead of int-comparison
2026-06-08 12:26:24 +03:00
dimgigov 3b7fc455b0 fix(parser): infinite loop on newlines in multi-import, multi-line params, type args, struct patterns
Parser would hang (infinite loop) when encountering newline tokens
inside certain constructs because expect() returns without advancing
position when the expected token doesn't match.

Fixed 5 places in parseBaseType, parsePrimaryPattern, parseTypeParams,
parseParamList, and parseUseDecl by adding newline-skipping at the
start of each iteration.

Verified: compiler no longer hangs/crashes on all 3 real-world apps
from apps/ (boko-framework, jwt-pitbul, nexus).
2026-06-07 23:02:43 +03:00
dimgigov e7e900973f feat: crypto library, Nexus HTTP server, JWT CLI, Boko web framework
- lib/crypto/: 9-module crypto library (Hash, HMAC, Base64/URL, Random, AES, RSA, ECDSA, Ed25519, JWT)
- rt/runtime.c: +346 lines OpenSSL crypto primitives (SHA-1/384/512, HMAC, Base64URL, AES-CBC/GCM, RSA, ECDSA, Ed25519)
- apps/nexus/: multi-threaded HTTP/1.1 + HTTP/2 detect + WebSocket server
- apps/jwt-pitbul/: JWT CLI tool (sign, verify, decode, keygen)
- apps/boko-framework/: FastAPI-inspired async web framework
- test_crypto/: crypto library test suite (23 tests)
2026-06-07 22:15:00 +03:00
dimgigov a2617e9954 feat: VS Code extension, web playground, LSP server 2026-06-07 18:34:41 +03:00
dimgigov 3ffa9f7d54 feat: web playground + LSP server + docs update 2026-06-07 18:29:05 +03:00
dimgigov b3af1208e8 feat(lsp): add LSP server with completion, definition, hover, diagnostics 2026-06-07 18:20:35 +03:00
dimgigov 0d7a30835f feat: borrow expr, parser fix, borrow tests, borrow example 2026-06-07 18:09:40 +03:00
dimgigov adca52b269 docs: update README v0.4.0, update stdlib docs to 23 modules
- README: bump version to v0.4.0, add selfhost loop & borrow/shared
- Stdlib.md: update sections 1-4, add String_Chars, FromBool, Fmt, Iter
- BuildAndTest.md: add make selfhost-loop and make test-golden targets
- Standard library list updated to all 23 modules
2026-06-07 18:08:34 +03:00
dimgigov 04be9eb400 feat(stdlib): add String_Chars, String_FromBool, fix Fmt and Net
- String.bux: add String_Chars(s, index) for char-at-index access
- String.bux: add String_FromBool(b) conversion
- Fmt.bux: rewrite with correct stdlib imports (was using non-existent names)
- Net.bux: fix String_Len -> bux_strlen (String_Len not imported)
- All lib modules compile successfully
2026-06-07 18:05:04 +03:00
dimgigov bfe87a8acb feat: add borrow test example, fix borrow &mut parsing order
- examples/borrow.bux: working borrow expression example
- tests/borrow_test.nim: add 4 new tests for borrow & @[Shared]
- parser.nim: fix borrow parsing order (& before mut)
- Build verified: example compiles and runs
2026-06-07 17:50:27 +03:00
dimgigov 46814b2abc feat: selfhost-loop determinism, golden tests, sema diagnostics 2026-06-07 17:37:22 +03:00
dimgigov 9758b17b1a fix(parser): add stray } workaround in parserParseFuncDecl, enable selfhost build
This workaround consumes a stray } that parserParseBlock sometimes leaves
behind, which was causing the module handler to exit early. With this fix,
the selfhost compiler (buxc2) can now compile the entire src/ project.

- buxc2 project build: 491 user + 310 stdlib = 801 decls, no sema errors
- make selfhost-loop: builds and runs both passes
- C compilation succeeds, resulting binary works
2026-06-07 17:33:17 +03:00
dimgigov 8347599d23 fix(parser): save/restore + depth counter for reliable } consumption 2026-06-07 17:25:30 +03:00
dimgigov 4794335673 chore: clean up experimental changes, keep core parser improvements 2026-06-07 17:11:45 +03:00
dimgigov 134e8e877d fix(parser): depth counter in module handler, stray } cleanup, token count debug 2026-06-07 17:06:36 +03:00
dimgigov 88af8b09d4 fix(parser): improve parserParseBlock infinite-loop safeguard for stuck at } 2026-06-07 17:01:15 +03:00
dimgigov e41a8d31df fix(parser): use parserExpect for module closing brace 2026-06-07 16:43:14 +03:00
dimgigov a11071ab95 fix(parser): skip newlines before checking decl kind 2026-06-07 16:38:42 +03:00
dimgigov 093e08a2b9 chore(cli): remove diagnostic scan, keep FIFO merge and per-file counts 2026-06-07 16:25:23 +03:00
dimgigov 1500575982 fix(parser): replace destructive skip with single-token skip 2026-06-07 16:24:51 +03:00
dimgigov fe048eb4aa feat(cli): diagnostic scan for missing function defs, FIFO merge order, per-file counts 2026-06-07 16:20:22 +03:00
dimgigov 9dd964dc48 test: add fibonacci golden test, expand GOLDEN_TESTS 2026-06-07 16:10:05 +03:00
dimgigov e1038059c2 fix(cli): change user merge from LIFO to FIFO order, add per-file decl counts
- Cli_BuildProject now appends declarations (preserving original file order)
  instead of prepending (which reversed order)
- Added per-file declaration count debug output
- This makes merge order match Nim bootstrap behavior
2026-06-07 16:08:51 +03:00
dimgigov 1f0baefa5a feat: add selfhost-loop determinism check, golden tests, improve sema diagnostics
- Makefile: new targets `test-golden` and `selfhost-loop`
  - selfhost-loop verifies C codegen determinism (2-pass bootstrap)
  - test-golden compares C output against expected.c golden files
  - Updated clean-all to clean new build dirs
- tests/golden/hello/: first golden test (hello world)
- src/sema.bux:
  - Fix: import stubs only registered if symbol doesn't already exist
  - Improve: error message now includes identifier name
2026-06-07 15:47:48 +03:00
dimgigov 3b4456f01f selfhost: expand Decl to 256 fields, fix unary precedence, add discard/var parsing, fix diag buffer overflow
- ast.bux: Decl expanded with field0..field255, variant0..7, param0..8
- parser.bux: add parserParseUnary for correct unary precedence; handle tkDiscard statements; allow var without initializer (matching bootstrap); add diagCount bounds check in parserEmitDiag
- hir.bux: HirParam changed to pointers
- hir_lower.bux: full field chains for struct lowering
- c_backend.bux: hardened -> vs . field access; null checks in CBE_GetExprTypeName
- cli.bux: add -lcrypto to linker
2026-06-07 14:58:23 +03:00
dimgigov 0bdef58182 selfhost: fix pointer field access, algebraic enums, string literal escaping
- hir_lower: use per-function scope instead of global scope for params
  fixes p->error emission for pointer parameters
- parser: assign variant4..variant7 in enum decls (was only 0..3)
- c_backend: escape quotes, backslash, newline, tab, cr in string literals
- runtime: bux_write_file now writes raw bytes (was un-escaping sequences)
- c_backend: emit algebraic enums as tagged unions (tag enum + data union + struct)
- hir_lower: add hBreak/hContinue lowering
- sema: skip empty variant names when collecting enum globals
2026-06-06 23:54:09 +03:00
dimgigov 3ae8c60ebb fix(bootstrap): resolve lir lower crashes and struct-copy bugs
- Fix lirValToC crash on lvkInt in hStore raw C emission
- Add hAlloca handling to lowerExpr (returns &name, avoids unhandled fallback)
- Fix nested comment syntax in unhandled expr fallback
- Rewrite buildLval to handle hIndexPtr and nested hFieldPtr/hArrowField
  so assignments like arr[i].field = val emit direct C lvalues
- Make &&/|| temporaries unique (__and_tmp_N/__or_tmp_N) to avoid
  duplicate declarations in the same function
- Selfhost null-deref fix in sema.bux (unchained nested conditions)
2026-06-06 21:46:25 +03:00
dimgigov 9416c486b2 docs: update PLAN.md — Phase 11 roadmap, 26 examples, M10 completed 2026-06-06 19:53:24 +03:00
dimgigov ba0a55f467 docs: update README — 26 examples passing, selfhost working 2026-06-06 19:49:52 +03:00
dimgigov 797ca7c80c fix(lir): resolve all LIR backend regressions — 26/26 examples + selfhost passing
- hir_lower: add isScope=true to lowerBlock; fix resolveExprType for ekIndex,
  ekField on monomorphized structs, and ekTry/ekUnwrap tmpVar typing
- lir_lower: add loop label stack for break/continue; pass null arg to
  bux_task_spawn when spawn has no arguments
- lir_c_backend: multi-pass type inference for temps; include params in
  varTypes; handle lvkTemp in lirLoad; use inferred type in lirAlloca
2026-06-06 19:49:24 +03:00
dimgigov 4f7653a410 docs: update project status — buxc2 is now a working compiler, not a PoC 2026-06-06 19:02:54 +03:00
dimgigov 62a9c8b84a feat: LIR backend replaces direct HIR→C emission (v0.3.0)
- 42 LIR instructions in bootstrap/lir.nim

- HIR→LIR lowering in bootstrap/lir_lower.nim

- LIR→C emission with full struct/enum/vtable support

- All 22 examples + 5 Nim unit tests passing

- Updated README status and backend description
2026-06-06 18:55:59 +03:00
dimgigov aff03ffb5a fix(selfhost): resolve all sema errors — 0 undeclared identifiers
- Add forward declarations for mutually-recursive parser functions
  (parserParseExpr, parserParseStmt, parserParseBlock,
   parserParsePrimary, parserParsePostfixExpr, parserParseBinaryPrec)
- Fix HIR lowering: skip forward decls (refBody==null) to avoid
  duplicate function entries and array overflow
- Bump funcs/externFuncs array size 256→512 to handle large modules
- Update PLAN.md with progress

Sema: 0 errors (was 7, then 2, now 0)
Remaining: C backend struct ordering (pre-existing, Iter/StringBuilder)
2026-06-06 05:34:19 +03:00
dimgigov a8b909726b v0.4.0: selfhost loop progress
- Fix stdlib merge: merge ALL lib/*.bux via bux_list_dir (was 0)
- Fix Cli_FindStdlibDir: search for lib/ not stdlib/
- Fix stdlib path: remove Std/ subdir (now flat in lib/)
- Fix runtime paths: add ../../ fallback for build/selfhost depth
- Remove hardcoded /home/ziko paths from src/cli.bux
- Fix Json.bux: avoid var-in-if-block scoping bug (5 errors)
- Add improved sema error messages (reverted due to crash)
- Remaining: 2 parserParsePostfix sema forward-ref errors
- Update PLAN.md with v0.3.0-v1.0.0 roadmap
2026-06-06 05:15:04 +03:00
dimgigov 2698ca92b7 fix(selfhost): stdlib path + merge all modules, not just imports
- Fix Cli_FindStdlibDir: search for lib/ instead of stdlib/
- Fix Cli_CollectStdlibImports → merge ALL lib/*.bux via bux_list_dir
- Fix Cli_CollectStdlibImports path: remove Std/ subdir (now flat)
- Add ../../ fallback for rt/ paths (build/selfhost depth)
- Update PLAN.md with v0.3.0 structure + v1.0.0 roadmap
- Remaining: 7 sema cross-module resolution bugs (pre-existing)
2026-06-06 05:02:48 +03:00
218 changed files with 76541 additions and 8151 deletions
+11
View File
@@ -1,5 +1,6 @@
# Compiled binary
buxc
buxc_lir
src/main
# Nim cache
@@ -10,6 +11,7 @@ tests/lexer_test
tests/parser_test
tests/sema_test
tests/hir_test
tests/borrow_test
tests/test_generics_parse
tests/*.exe
@@ -21,3 +23,12 @@ build/
_test_tmp_pkg/
test_pkg/
examples_pkg/
# Temporary test directories
_test_*/
# Backup files
*.bak
# Log files
*.log
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Dimitar Gigov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+83 -16
View File
@@ -1,17 +1,17 @@
NIM := nim
SRC := compiler/bootstrap/main.nim
SRC := bootstrap/main.nim
OUT := buxc
BUILD_DIR := build
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt
.PHONY: all build dev debug test clean clean-all test-examples selfhost
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden selfhost-loop lsp
all: build
build:
$(NIM) c -o:$(OUT) -d:release --opt:size $(SRC)
strip $(OUT)
# strip $(OUT)
dev:
$(NIM) c -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC)
@@ -21,15 +21,15 @@ debug: dev
test: build test-examples
@echo "Running lexer tests..."
$(NIM) c -r compiler/tests/lexer_test.nim
$(NIM) c -r tests/lexer_test.nim
@echo "Running parser tests..."
$(NIM) c -r compiler/tests/parser_test.nim
$(NIM) c -r tests/parser_test.nim
@echo "Running sema tests..."
$(NIM) c -r compiler/tests/sema_test.nim
$(NIM) c -r tests/sema_test.nim
@echo "Running HIR tests..."
$(NIM) c -r compiler/tests/hir_test.nim
$(NIM) c -r tests/hir_test.nim
@echo "Running borrow checker tests..."
$(NIM) c -r compiler/tests/borrow_test.nim
$(NIM) c -r tests/borrow_test.nim
@echo "Running integration tests..."
rm -rf _test_tmp_pkg
./$(OUT) new _test_tmp_pkg
@@ -62,14 +62,81 @@ clean:
rm -rf _test_cast _test_cast2 _test_cast3 _test_channel
clean-all: clean
rm -rf _selfhost/src _selfhost/build
rm -rf build/selfhost build/selfhost-loop-a build/selfhost-loop-b
rm -rf tests/golden/*/build
selfhost: build
@echo "=== Building self-hosted compiler ==="
@rm -rf _selfhost/src _selfhost/build
@mkdir -p _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
@strip _selfhost/build/buxc2
@rm -rf build/selfhost
@mkdir -p build/selfhost/src
@cp src/*.bux build/selfhost/src/
@cp src/bux.toml build/selfhost/
@mv build/selfhost/src/main.bux build/selfhost/src/Main.bux 2>/dev/null || true
@cd build/selfhost && ../../$(OUT) build
# strip removed for debug
@echo "=== Self-hosted compiler built successfully ==="
.PHONY: test-golden
GOLDEN_TESTS := hello fibonacci structs generics algebraic_enums enums methods strings modern_features
test-golden: build
@echo "=== Golden tests ==="
@passed=0; failed=0; \
for test in $(GOLDEN_TESTS); do \
gd="tests/golden/$$test"; \
if [ ! -d "$$gd" ]; then echo " SKIP $$test (no dir)"; continue; fi; \
if [ ! -f "$$gd/expected.c" ]; then echo " SKIP $$test (no expected.c)"; continue; fi; \
rm -rf "$$gd/build"; \
(cd "$$gd" && ../../../$(OUT) build > /dev/null 2>&1); \
if diff "$$gd/build/main.c" "$$gd/expected.c" > /dev/null 2>&1; then \
echo " PASS $$test"; \
passed=$$((passed + 1)); \
else \
echo " FAIL $$test — C output differs from expected"; \
failed=$$((failed + 1)); \
fi; \
done; \
echo "Golden tests: $$passed passed, $$failed failed"; \
if [ $$failed -gt 0 ]; then exit 1; fi
selfhost-loop: build
@echo "=== Selfhost loop: bootstrap determinism check ==="
@echo "Build A..."
@rm -rf build/selfhost-loop-a
@mkdir -p build/selfhost-loop-a/src
@cp src/*.bux build/selfhost-loop-a/src/
@cp src/bux.toml build/selfhost-loop-a/
@mv build/selfhost-loop-a/src/main.bux build/selfhost-loop-a/src/Main.bux 2>/dev/null || true
@cd build/selfhost-loop-a && ../../$(OUT) build
@echo "Build B..."
@rm -rf build/selfhost-loop-b
@mkdir -p build/selfhost-loop-b/src
@cp src/*.bux build/selfhost-loop-b/src/
@cp src/bux.toml build/selfhost-loop-b/
@mv build/selfhost-loop-b/src/main.bux build/selfhost-loop-b/src/Main.bux 2>/dev/null || true
@cd build/selfhost-loop-b && ../../$(OUT) build
@echo ""
@echo "Comparing C output..."
@if diff build/selfhost-loop-a/build/main.c build/selfhost-loop-b/build/main.c > /dev/null 2>&1; then \
echo " C output: IDENTICAL ✓"; \
else \
echo " C output: DIFFERENT ✗"; \
fi
@echo "Comparing ELF binaries (stripped)..."
@cp build/selfhost-loop-a/build/buxc2 /tmp/buxc2_a && strip -d /tmp/buxc2_a
@cp build/selfhost-loop-b/build/buxc2 /tmp/buxc2_b && strip -d /tmp/buxc2_b
@if diff /tmp/buxc2_a /tmp/buxc2_b > /dev/null 2>&1; then \
echo " ELF binary: IDENTICAL ✓"; \
echo "=== Selfhost loop PASSED ==="; \
else \
echo " ELF binary: DIFFERENT ✗"; \
echo "=== Selfhost loop FAILED ==="; \
exit 1; \
fi
lsp: tools/bux-lsp
@echo "LSP server ready at tools/bux-lsp"
tools/bux-lsp: tools/lsp_server.nim
cd tools && $(NIM) c -o:bux-lsp lsp_server.nim
+275 -62
View File
@@ -1,16 +1,20 @@
# Bux Programming Language — Roadmap to Self-Hosting
# Bux Programming Language — Roadmap to v1.0.0
> **Bootstrap Implementation:** Nim
> **Target:** Bux compiler written in Bux (self-hosting)
> **Version:** 0.3.1 (2026-06-06)
> **Bootstrap:** Nim (`bootstrap/`) — compiles `src/` → `buxc`
> **Self-host:** Bux (`src/`) — compiles via `buxc` → `buxc2`
> **Target:** Bux v1.0.0 — fully self-hosting, gradual ownership, tooling
---
## Overview
Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming language. The strategy is **bootstrap via Nim** — we build the first Bux compiler in Nim, then progressively rewrite it in Bux until it compiles itself.
Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming language. The strategy is **bootstrap via Nim** — we built the first Bux compiler in Nim, then rewrote it in Bux. The Nim bootstrap now exists only as a build scaffold.
**Core philosophy:** Systems-level control with modern ergonomics. No hidden costs, no hidden allocations, no hidden control flow.
**Killer feature:** Gradual ownership — write fast like C, add safety like Rust, but only where you choose (`@[Checked]`).
---
## Language Design Goals (Bux vs Rust vs Nim vs Zig)
@@ -286,11 +290,11 @@ Phase 7.5 — Driver (depends on all):
---
## Phase 7 — Self-Hosting: The Great Rewrite 🔄 (In Progress)
## Phase 7 — Self-Hosting: The Great Rewrite (Complete)
**Goal:** Bux compiler compiles itself. This is the **main milestone**.
**All 14 modules ported** in `compiler/selfhost/` (4094 LOC total). Self-hosted project structure in `_selfhost/`.
**All 14 modules ported** in `src/` (4094 LOC total). Built via `make selfhost`.
| Task | Status | Details | LOC |
|------|--------|---------|-----|
@@ -342,7 +346,7 @@ Phase 7.5 — Driver (depends on all):
**Self-hosted compiler stats:**
```
$ _selfhost/build/buxc2 version
$ src/build/buxc2 version
Bux 0.2.0 (self-hosting bootstrap)
Pipeline modules:
Lexer ✅ 695 lines
@@ -355,8 +359,8 @@ Pipeline modules:
**Bootstrap loop goal:**
```
buxc (Nim) → compile compiler/selfhost/*.bux → buxc2 (Bux binary)
buxc2 (Bux) → compile compiler/selfhost/*.bux → buxc3 (Bux binary)
buxc (Nim) → compile src/*.bux → buxc2 (Bux binary)
buxc2 (Bux) → compile src/*.bux → buxc3 (Bux binary)
compare buxc2 == buxc3 → SELF-HOSTED ✅
```
@@ -516,6 +520,34 @@ const TABLE_SIZE = Factorial(10); // Computed at compile time
| `8.6.2` Procedural macros | `#[derive(Clone)]`, `#[derive(Debug)]` |
| `8.6.3` Reflection | Compile-time type introspection for serialization |
### 8.7 — Optimization & Release Mode
**Goal:** C-speed for hot paths. `@[Release]` disables all runtime checks and inlines auto-drop.
| Task | Status | Details |
|------|--------|---------|
| `8.7.1` `@[Release]` attribute | ⏳ | Disables bounds checking, borrow checking, null checks inside the function |
| `8.7.2` Auto-drop inline | ⏳ | Emit `free`/`close` directly instead of `Type_Drop(&var)` call |
| `8.7.3` Dead-store elimination | ⏳ | C backend removes redundant temp variables (`_t1`, `_t2`) |
| `8.7.4` `-O3` by default | ⏳ | `bux build --release` passes `-O3 -flto` to C compiler |
| `8.7.5` Profile-guided optimization | ⏳ | `bux profile` + `bux build --pgo` for guided inlining |
```bux
// Default: safe but slower — bounds checks + borrow checks
@[Checked]
func SafeSum(arr: Array<int>) -> int { ... }
// Hot path: zero-cost — straight C
@[Release]
func HotLoop(data: *float64, n: int) {
for i in 0..n {
data[i] = data[i] * 2.0; // no bounds check, inlined
}
}
```
**Why this matters:** Nim compiles to C but has ARC/ORC overhead. Crystal has Boehm GC. Bux with `@[Release]` has **literally zero overhead** — the generated C is hand-written quality.
---
## Phase 9 — Ecosystem & Tooling (Week 35+)
@@ -534,60 +566,188 @@ const TABLE_SIZE = Factorial(10); // Computed at compile time
---
## File Structure (Target)
## File Structure (v0.3.0 — Current)
```
bux/
├── bux.toml # Compiler package manifest
├── bux.toml # Bootstrap compiler manifest (v0.3.0)
├── README.md
├── PLAN.md
├── PLAN.md # This file
├── Makefile # build, test, selfhost
├── src/
│ ├── Main.bux # CLI entry point
│ ├── Lexer.bux
│ ├── Parser.bux
│ ├── Ast.bux
│ ├── Sema.bux
│ ├── Type.bux
│ ├── Hir.bux
│ ├── Lir.bux
│ ├── CBackend.bux # C transpiler (primary backend)
│ ├── X64Backend.bux # Native x86-64 backend (optional)
│ ├── Linker.bux # Custom linker / build driver
│ ├── Manifest.bux # bux.toml parser
── Package.bux # Package resolution
├── library/
── Std/
│ │ ├── Io.bux
│ ├── Memory.bux
│ ├── String.bux
│ ├── Array.bux
│ │ ├── Map.bux
│ ├── Math.bux
│ ├── Os.bux
│ ├── Path.bux
│ ├── Process.bux
│ ├── Result.bux # Result<T,E> and Option<T>
│ ├── Iter.bux # Iterator trait and combinators
│ ├── Fmt.bux # String formatting
│ ├── Task.bux # Lightweight concurrency
│ ├── Channel.bux # Message passing
│ └── Sync.bux # Mutex, RwLock, atomic
── Runtime.c # C runtime shim
├── tests/
│ ├── Lexer/
│ ├── Parser/
│ ├── Sema/
│ ├── Codegen/
── Integration/
└── docs/
├── LanguageRef.md
├── Ownership.md
── Concurrency.md
├── src/ # 🎯 CANONICAL: Bux compiler source
│ ├── bux.toml # Self-host compiler manifest
│ ├── main.bux # Entry point (renamed → Main.bux at build)
│ ├── lexer.bux # Tokenizer (UTF-8 state machine, 697 LOC)
│ ├── parser.bux # Pratt parser (1004 LOC)
│ ├── ast.bux # AST node types (algebraic enums)
│ ├── sema.bux # Type checker / semantic analysis (393 LOC)
│ ├── types.bux # Type factories
│ ├── scope.bux # Symbol table
│ ├── hir.bux # High-level IR definitions
│ ├── hir_lower.bux # AST → HIR lowering (307 LOC)
│ ├── c_backend.bux # HIR → C code generator (264 LOC)
│ ├── cli.bux # CLI command dispatch
── manifest.bux # bux.toml / TOML parser
│ ├── token.bux # Token kind definitions
── source_location.bux # Source location tracking
├── bootstrap/ # 🔧 Nim bootstrap (build scaffold only)
│ ├── main.nim # Entry point
│ ├── cli.nim # CLI commands + build driver
└── ... # (mirrors src/ structure)
├── lib/ # 📦 Standard library (23 modules)
│ ├── Io.bux # Print, ReadFile, WriteFile
│ ├── String.bux # Full string API (len, concat, split, format...)
│ ├── Array.bux # Generic Array<T>
│ ├── Map.bux # Generic Map<K,V> + StringMap
│ ├── Set.bux # Generic Set<T>
│ ├── Math.bux # Sqrt, Pow, Min, Max, Abs
│ ├── Mem.bux # Alloc, Realloc, Free
│ ├── Path.bux # Path_Join, Path_Parent, Path_Ext
│ ├── Fs.bux # DirExists, Mkdir, ListDir
├── Task.bux # Lightweight tasks (spawn/await)
── Channel.bux # Producer/consumer channels
│ ├── Sync.bux # Mutex, RwLock
│ ├── Result.bux # Result<T,E> + Option<T> + ? operator
│ ├── Iter.bux # Iterator trait
│ ├── Fmt.bux # String formatting
│ ├── Os.bux # Args, Env, Exit, Cwd
── Time.bux # Time measurement
│ ├── Process.bux # Subprocess spawning
├── Net.bux # TCP client/server
├── Crypto.bux # SHA256, HMAC, Base64
── Json.bux # JSON parse/stringify
│ └── Test.bux # Test framework
├── rt/ # ⚙️ C runtime
│ ├── runtime.c # Memory, string, path helpers
│ └── io.c # File I/O wrappers
├── tests/ # 🧪 Unit tests (Nim)
│ ├── lexer_test.nim
│ ├── parser_test.nim
│ ├── sema_test.nim
│ ├── hir_test.nim
│ ├── borrow_test.nim
│ └── testdata/
├── examples/ # Example programs
├── docs/ # Documentation
│ ├── LanguageRef.md
│ ├── BuildAndTest.md
│ ├── STRATEGY.md
│ ├── PHASE8_STRATEGY.md
│ └── Packages.md
├── build/ # Build artifacts (gitignored)
│ └── selfhost/ # Self-host compiler build dir
└── vendor/ # Vendored dependencies
```
---
## Phase 10 🔄 — Path to v1.0.0 (In Progress)
### 10.0 — v0.3.0 Restructuring ✅ (Completed 2026-06-06)
| Task | Status | Details |
|------|--------|---------|
| Directory restructure | ✅ | `compiler/selfhost/``src/`, `compiler/bootstrap/``bootstrap/`, `library/std/``lib/`, `library/runtime/``rt/`, `compiler/tests/``tests/` |
| Path updates | ✅ | Updated Makefile, cli.nim, cli.bux, test files, docs |
| Selfhost fix | ✅ | Build via `build/selfhost/` (project wrapper) |
| Push to GitHub | ✅ | `ac969b3` — v0.3.0 restructure |
### 10.1 — Selfhost Loop ✅ (v0.5.0 target)
**Goal:** `buxc2` can compile itself producing a binary-identical `buxc3`.
```
buxc (Nim) → src/*.bux → buxc2 ✅
buxc2 → src/*.bux → buxc3 ✅
buxc2 == buxc3 ✅ (binary-identical)
```
| Task | Status | Details |
|------|--------|---------|
| `10.1.1` Verify buxc2 can build src/ | ✅ | `buxc2 build` works on selfhost project |
| `10.1.2` Deterministic C codegen | ✅ | C output identical on every iteration |
| `10.1.3` Binary-identical loop | ✅ | `make selfhost-loop` passes (C + ELF parity) |
| `10.1.4` Remove hardcoded paths | ✅ | Paths resolved via `bux_getcwd()` / `bux_path_join()` |
| `10.1.5` Selfhost test in CI | ⏳ | Add to `make test` |
### 10.2 — Gradual Ownership ✅ (v0.5.0 target) ⭐ Killer Feature
**Goal:** `@[Checked]` functions get full borrow checking. `@[Release]` disables checks for zero-cost hot paths.
| Task | Status | Details |
|------|--------|---------|
| `10.2.1` `@[Checked]` attribute gate | ✅ | Enable/disable borrow checker per function |
| `10.2.2` `&T` shared reference check | ✅ | No mutation through shared refs |
| `10.2.3` `&mut T` exclusive mutable check | ✅ | No aliasing of mutable refs |
| `10.2.4` Bounds checking on slices | ✅ | `Slice_Get` / `Array_Get` with `bux_bounds_check` |
| `10.2.5` `@[Release]` zero-cost mode | ✅ | Disables borrow + bounds checks, passes `-O3 -flto` |
| `10.2.6` Lifetime elision (simple rules) | ⏳ | 80% of cases without annotations |
| `10.2.7` Explicit lifetimes `'a` | ⏳ | Only for complex cases |
### 10.3 — Compiler Architecture Upgrade (v0.6.0 target)
**Goal:** Proper module structure instead of flat file mirror of Nim bootstrap.
```
src/
├── main.bux
├── frontend/
│ ├── lexer.bux
│ ├── parser.bux
│ └── ast.bux
├── analysis/
│ ├── sema.bux
│ ├── types.bux
│ ├── scope.bux
│ └── borrow.bux # NEW — borrow checker
├── lowering/
│ ├── hir.bux
│ └── hir_lower.bux
├── backend/
│ └── c_backend.bux
└── driver/
├── cli.bux
├── manifest.bux
├── token.bux
└── source_location.bux
```
### 10.4 — Stdlib Completion (v0.7.0 target)
| Module | Status | Priority |
|--------|--------|----------|
| `Std::Os` — Args, Env, Exit, Cwd | ⏳ | P0 |
| `Std::Process` — spawn subprocess | ⏳ | P0 |
| `Std::Iter` — map, filter, fold | ⏳ | P1 |
| `Std::Fmt` — string interpolation | ⏳ | P1 |
### 10.5 — Tooling (v0.8.0 target)
| Tool | Status | Priority |
|------|--------|----------|
| `bux test` — test runner | ⏳ | P0 |
| `bux fmt` — code formatter | ⏳ | P1 |
| `bux doc` — doc generator | ⏳ | P2 |
### 10.6 — Native Backend (v0.9.0 target)
| Task | Status |
|------|--------|
| Direct x86-64 codegen (no C) | ⏳ |
| ELF64 output | ⏳ |
| System V AMD64 ABI | ⏳ |
### 10.7 — v1.0.0 Release Criteria
- [ ] Compiler self-hosts binary-identically
- [ ] `@[Checked]` borrow checker works on real code
- [ ] Stdlib complete enough for CLI tools
- [ ] `bux test`, `bux fmt`, `bux doc` exist
- [ ] Documentation + 30+ examples
- [ ] CI/CD pipeline (build + test on push)
---
## Language Design Decisions (Bux Improvements)
### What Bux learns from Rust
@@ -689,12 +849,18 @@ func Main() -> int {
| **M2** | 2 | ✅ | Type-checker rejects invalid programs |
| **M3** | 3 | ✅ | HIR lowering works for all constructs |
| **M4** | 5A | ✅ | `bux run` produces working binary via C transpiler |
| **M5** | 6 | ✅ | Can write compiler-adjacent tools in Bux (18 examples) |
| **M6** | 7 | ✅ | **Self-hosted**: `buxc2` (Bux) compiles via `buxc` (Nim) — 88KB working binary |
| **M5** | 6 | ✅ | Can write compiler-adjacent tools in Bux (26 examples) |
| **M6** | 7 | ✅ | **Self-hosted**: `buxc2` (Bux) compiles via `buxc` (Nim) — working binary |
| **M7** | 8 | ✅ | Result/Option/`?`/`!` done; **borrow checker working**; **CTFE working** |
| **M8** | 8-9 | ✅ | **Borrow checker**, **CTFE**, **Package manager** working |
| **M9** | 8.5 | ✅ | **Trait bounds** (`<T: Comparable>`) — semantic checking implemented |
| **M8** | 9 | | Package manager + LSP + formatter shipped |
| **M10** | 10 | | **LIR backend** replaces HIR→C; 26/26 examples pass; selfhost builds |
| **M11** | 11 | ✅ | **Selfhost loop**: `buxc2` compiles itself → binary-identical `buxc3` |
| **M12** | 11.3 | ✅ | `@[Checked]` borrow checker works on real code |
| **M13** | 11.4 | ✅ | `@[Release]` zero-cost mode; Rust-style error snippets |
| **M14** | 8.3 | ✅ | **Green threads** — M:N scheduler with channels |
| **M15** | 11.5 | ⏳ | Native x86-64 backend (no C transpiler) |
| **M16** | 11.4 | ⏳ | `bux test`, `bux fmt`, `bux doc` shipped |
---
@@ -752,11 +918,58 @@ func Main() -> int {
### Next Actions (Priority Order)
1. **Fix parameter/return types**`String*` instead of `int` in function signatures
2. **Debug ast/lexer/parser** — Get all 14 modules passing check
3. **Full 14-module project build** — Complete bootstrap loop: buxc3 produced by buxc2
4. **Compare buxc2 vs buxc3 output** — True self-hosting verification
5. **Phase 8** — Advanced features: ownership checker, CTFE evaluation, string interpolation
1. **Fix LIR backend type inference** — struct temps, undeclared vars, break/continue, duplicate declarations
2. **All 30 examples passing** — bootstrap and self-host compilers build and run every example
3. **Selfhost build working**`make selfhost` produces working `buxc2`
4. **Selfhost loop verification**`buxc2` compiles `src/``buxc3`; C output and stripped binary identical
5. **Golden tests for C codegen**`make test-golden` passes for 8 critical examples
6.**`bux test` runner** — discovers `tests/*.bux`, builds each as a temp package, and reports PASS/FAIL
---
## Phase 11 — Post-LIR Stabilization & Path to v0.4.0 🔄
### 11.1 — LIR Backend Hardening (v0.3.1)
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.1.1` Golden tests for C codegen | ⏳ | P0 | Expected `.c` output for 56 critical examples; diff on regression |
| `11.1.2` LIR debug dump | ⏳ | P1 | `bux build --emit-lir` produces readable LIR |
| `11.1.3` Type info in `LirInstr` | ⏳ | P1 | Add `cType` field to instructions; eliminate inference hacks |
| `11.1.4` Dead code cleanup | ⏳ | P2 | Remove unused `typeFromValue`, `setTempType`, `emit` |
### 11.2 — Selfhost Loop (v0.4.0) ⭐
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.2.1` `buxc2 build` on `src/` | ✅ | P0 | Verify selfhost compiler can build itself |
| `11.2.2` Deterministic C codegen | ✅ | P0 | Remove non-deterministic ordering in `emitModule` |
| `11.2.3` `make selfhost-loop` | ✅ | P0 | Makefile target: buxc2 → buxc3 → binary compare |
| `11.2.4` Cross-module generics in selfhost | ⏳ | P1 | `Map<K,V>` and `Array<T>` from stdlib in selfhost context |
### 11.3 — Gradual Ownership (v0.5.0) ⭐⭐
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.3.1` `&T` / `&mut T` lifetime analysis | ⏳ | P0 | Basic borrow checker integrated in LIR lowering |
| `11.3.2` Bounds checking on slices | ⏳ | P0 | `Slice<T>` index checks `.len` at runtime |
| `11.3.3` Lifetime elision (simple rules) | ⏳ | P1 | 80% of cases without explicit annotations |
### 11.4 — Tooling & Ecosystem (v0.6.0v0.8.0)
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.4.1` `bux test` runner | ✅ | P0 | Built-in test framework with assertions |
| `11.4.2` `bux fmt` formatter | ⏳ | P1 | Auto-format Bux source |
| `11.4.3` LSP prototype | ⏳ | P2 | Autocomplete, hover types, go-to-definition |
| `11.4.4` `bux doc` generator | ⏳ | P2 | HTML from `///` doc comments |
### 11.5 — Native Backend (v0.9.0)
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.5.1` x86-64 codegen (no C) | ⏳ | P1 | ELF64 output, System V AMD64 ABI |
| `11.5.2` Custom linker / `.bcu` format | ⏳ | P2 | Bespoke object format for faster builds |
---
+66 -23
View File
@@ -2,10 +2,13 @@
![Bux Language](bux-lang-01.jpeg)
> **Status:** Bootstrap compiler (`buxc`, Nim) compiles `.bux` → C → native binary.
> Self-hosted compiler (`buxc2`) exists as a proof-of-concept but is **deprioritized** in favor of language features and stdlib maturity.
> **Status:** v0.5.0 — Bootstrap compiler (`buxc`, Nim) and self-hosted compiler (`buxc2`, Bux) both compile `.bux` → C → native binary.
> **Selfhost loop:** `buxc2` compiles itself → binary-identical `buxc3` ✅ Deterministic C codegen + ELF verified.
> **Gradual Ownership:** `@[Checked]` borrow checker, `@[Release]` zero-cost mode, `borrow &mut` expressions.
> **Green Threads:** M:N scheduler with channels (Go-style goroutines without GC).
> **All 26 examples pass.** Compiler successfully parses all 3 real-world apps (`apps/boko-framework`, `apps/jwt-pitbul`, `apps/nexus`).
Bux is a fast, compiled, strongly-typed systems programming language. Features a C backend for native code generation, raw multi-line strings, gradual ownership, async/await, generics, algebraic enums, and a package manager.
Bux is a fast, compiled, strongly-typed systems programming language. Features a C backend for native code generation, raw multi-line strings, gradual ownership (opt-in borrow checking), async/await, generics, algebraic enums, and a package manager.
---
@@ -21,6 +24,12 @@ cd hello
# Build and run
bux run
# Build optimized release binary
bux build --release
# Cross-compile for ARM Linux (requires clang)
bux build --target aarch64-linux-gnu
```
---
@@ -128,7 +137,7 @@ func Main() -> int {
### Channels (Producer/Consumer)
```bux
import Std::Io::{PrintLine, PrintInt};
import Std::Task::{Task_Join, TaskHandle};
import Std::Task::{Task_Wait, TaskHandle};
import Std::Channel::{Channel, Channel_New, Channel_SendInt, Channel_RecvInt, Channel_Close};
func Producer(chPtr: *Channel<int>) {
@@ -158,8 +167,8 @@ func Main() -> int {
let ch: Channel<int> = Channel_New<int>(3);
let p: *void = spawn Producer(&ch);
let c: *void = spawn Consumer(&ch);
Task_Join(TaskHandle { handle: p });
Task_Join(TaskHandle { handle: c });
Task_Wait(TaskHandle { handle: p });
Task_Wait(TaskHandle { handle: c });
return 0;
}
```
@@ -190,14 +199,17 @@ func Main() -> int {
| **Interfaces** | `interface` + `extend` for trait-like behavior |
| **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator |
| **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel`, `Sync`, `Os`, `Time`, `Process` |
| **Backend** | C transpiler (self-hosting + bootstrap) |
| **Backend** | LIR → C transpiler (clean 3-address code, then gcc/clang) |
| **Strings** | Raw multi-line backtick strings (`...`), C-string interop |
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
| **Gradual Ownership** | `@[Checked]` + `@[Release]` + `@[Shared]` + `borrow &mut` / `borrow &` |
| **Drop Trait** | Auto-drop for `@[Drop]` types (Array, Map, user-defined structs) |
| **Green Threads** | M:N scheduler (ucontext + SIGVTALRM), work-stealing queues |
| **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines |
| **Concurrency** | `Task`/`Channel`/`Sync` (pthread-based), `bux_async_yield`/`spawn` |
| **CTFE** | `const func` — compile-time function execution |
| **Trait Bounds** | `func Max<T: Comparable>(a: T, b: T) -> T` |
| **Package Manager** | `bux add`, `bux install`, `bux.lock`, path + git deps |
| **Cross-Compilation** | `--target <triple>` via clang (e.g. `aarch64-linux-gnu`) |
| **Tooling** | `bux new`, `bux build`, `bux run`, `bux test`, `bux check` |
---
@@ -206,24 +218,38 @@ func Main() -> int {
```
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
├── src/ # 🎯 Self-hosting compiler source (Bux)
│ ├── main.bux # Entry point
│ ├── lexer.bux # Tokenizer
── parser.bux # Pratt parser
│ ├── ast.bux # AST node types
│ ├── sema.bux # Type checker
│ ├── hir_lower.bux # AST → HIR lowering
│ ├── c_backend.bux # HIR → C code generator
└── cli.bux # CLI driver
├── bootstrap/ # 🔧 Bootstrap compiler (Nim)
── main.nim # Entry point
│ ├── cli.nim # CLI commands + build driver
│ └── ... # (mirrors src/ structure)
├── lib/ # 📦 Standard library (23 modules)
│ ├── Io.bux # Print, ReadFile, WriteFile
│ ├── String.bux # Full string API
│ ├── Array.bux # Generic Array<T>
│ ├── Map.bux # Generic Map<K,V> + StringMap
│ ├── Set.bux # Generic Set<T>
│ ├── Task.bux # Green threads (spawn/await)
│ ├── Channel.bux # Producer/consumer channels
│ ├── Drop.bux # Drop trait interface
│ └── ... # Math, Fs, Path, Sync, Result, ...
├── rt/ # ⚙️ C runtime
│ ├── runtime.c # Memory, scheduler, channels
│ └── io.c # File I/O wrappers
├── tests/ # 🧪 Unit tests (Nim)
├── examples/ # Example programs
├── tools/ # Additional tooling
├── apps/ # Real-world applications
├── docs/ # Documentation
├── buxs/ # Windows-compatible project root
├── README.md
├── PLAN.md # Roadmap to self-hosting
├── PLAN.md # Roadmap to v1.0.0
└── Makefile
```
@@ -244,6 +270,9 @@ make test
# Run example programs
make test-examples
# Verify selfhost binary parity (buxc2 → buxc3, identical)
make selfhost-loop
# Clean build artifacts
make clean
```
@@ -252,6 +281,20 @@ make clean
---
## Applications
The `apps/` directory contains real-world Bux applications that serve as integration tests for the compiler:
| App | Description | Lines |
|-----|-------------|-------|
| **boko-framework** | Async web framework (like FastAPI), multi-threaded HTTP server | ~660 |
| **jwt-pitbul** | JWT CLI tool — sign, verify, decode (HS256/384/512, RS256/384/512, ES256/384, EdDSA) | ~326 |
| **nexus** | High-performance HTTP/1.1, HTTP/2 & WebSocket server | ~550 |
The bootstrap compiler successfully parses and type-checks all three applications without hanging or crashing.
---
## Documentation
- [`docs/LanguageRef.md`](docs/LanguageRef.md) — Language reference
-7
View File
@@ -1,7 +0,0 @@
[Package]
Name = "buxc2"
Version = "0.2.0"
Type = "bin"
[Build]
Output = "Bin"
-23
View File
@@ -1,23 +0,0 @@
// main.bux — Entry point for the Bux self-hosting compiler
module Main {
// C runtime for command-line args
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
// Forward declaration from Cli module
func Cli_Run(args: *String, argCount: int) -> int;
func Main() -> int {
let count: int = bux_argc();
// Allocate array of String pointers
let args: *String = bux_alloc(count as uint * 8) as *String;
var i: int = 0;
while i < count {
args[i] = bux_argv(i);
i = i + 1;
}
return Cli_Run(args, count);
}
}
-431
View File
@@ -1,431 +0,0 @@
// ast.bux — AST node types (Expr, Stmt, Decl, Pattern, TypeExpr)
module Ast {
// ---------------------------------------------------------------------------
// SourceLocation (inline for convenience)
// ---------------------------------------------------------------------------
struct SourceLoc {
line: uint32,
column: uint32,
}
// ---------------------------------------------------------------------------
// Token (lightweight inline)
// ---------------------------------------------------------------------------
struct AstToken {
kind: int,
text: String,
line: uint32,
column: uint32,
}
// ---------------------------------------------------------------------------
// TypeExpr — type expressions
// ---------------------------------------------------------------------------
const tekNamed: int = 0;
const tekPath: int = 1;
const tekSlice: int = 2;
const tekPointer: int = 3;
const tekTuple: int = 4;
const tekSelf: int = 5;
struct TypeExpr {
kind: int,
line: uint32,
column: uint32,
typeName: String, // for tekNamed
pathStr: String, // for tekPath (segments joined with ::)
pathCount: int, // number of path segments
typeArgName0: String, // up to 2 type args
typeArgName1: String,
typeArgCount: int,
sliceElement: *TypeExpr, // for tekSlice
pointerPointee: *TypeExpr, // for tekPointer
}
// ---------------------------------------------------------------------------
// Pattern — match patterns
// ---------------------------------------------------------------------------
const pkWildcard: int = 0;
const pkLiteral: int = 1;
const pkIdent: int = 2;
const pkRange: int = 3;
const pkEnum: int = 4;
const pkStruct: int = 5;
const pkTuple: int = 6;
struct Pattern {
kind: int,
line: uint32,
column: uint32,
patIdent: String, // for pkIdent
patLitKind: int, // for pkLiteral (token kind)
patLitText: String, // for pkLiteral (token text)
patRangeInclusive: bool, // for pkRange
patEnumPath: String, // for pkEnum (path joined)
patStructName: String, // for pkStruct
}
// ---------------------------------------------------------------------------
// Expr — expressions (tagged union)
// ---------------------------------------------------------------------------
const ekLiteral: int = 0;
const ekIdent: int = 1;
const ekSelf: int = 2;
const ekPath: int = 3;
const ekSizeOf: int = 4;
const ekUnary: int = 5;
const ekPostfix: int = 6;
const ekBinary: int = 7;
const ekAssign: int = 8;
const ekTernary: int = 9;
const ekRange: int = 10;
const ekCall: int = 11;
const ekGenericCall: int = 12;
const ekIndex: int = 13;
const ekField: int = 14;
const ekStructInit: int = 15;
const ekSlice: int = 16;
const ekTuple: int = 17;
const ekCast: int = 18;
const ekIs: int = 19;
const ekTry: int = 20;
const ekUnwrap: int = 23;
const ekBlock: int = 21;
const ekMatch: int = 22;
const ekSpawn: int = 24;
const ekAwait: int = 25;
struct ExprList {
expr: *Expr,
next: *ExprList,
}
struct Expr {
kind: int,
line: uint32,
column: uint32,
// Common fields
strValue: String, // ident name, path segments, field name, callee
intValue: int, // operator kind, intrinsic kind
boolValue: bool, // range inclusive
tokKind: int, // literal token kind
tokText: String, // literal token text
// Children (up to 3 sub-expressions)
child1: *Expr, // left, operand, callee, cond, subject
child2: *Expr, // right, index, then, value
child3: *Expr, // else, third
// Extra references
refType: *TypeExpr, // cast type, is type, sizeof type
refBlock: *Block, // for ekBlock
// Generic call
genericCallee: String,
genericTypeArg0: String,
genericTypeArg1: String,
genericTypeArgCount: int,
// Struct init fields
structName: String,
structFieldCount: int,
// Call arguments (linked list for multi-arg support)
callArgs: *ExprList,
callArgCount: int,
}
// ---------------------------------------------------------------------------
// Block — sequence of statements
// ---------------------------------------------------------------------------
struct Block {
line: uint32,
column: uint32,
stmtCount: int,
firstStmt: *Stmt,
lastStmt: *Stmt,
}
// ---------------------------------------------------------------------------
// Stmt — statements
// ---------------------------------------------------------------------------
const skExpr: int = 0;
const skLet: int = 1;
const skIf: int = 2;
const skWhile: int = 3;
const skDoWhile: int = 4;
const skLoop: int = 5;
const skFor: int = 6;
const skMatch: int = 7;
const skReturn: int = 8;
const skBreak: int = 9;
const skContinue: int = 10;
const skDecl: int = 11;
struct ElseIf {
line: uint32;
column: uint32;
cond: *Expr;
block: *Block;
}
struct Stmt {
kind: int,
line: uint32,
column: uint32,
// Common fields
strValue: String, // let name, pattern ident, label, for var
boolValue: bool, // let mutable
// Children
child1: *Expr, // init expr, condition, iter expr, return value
child2: *Expr, // match subject
child3: *Expr, // extra
refStmtType: *TypeExpr, // let type annotation
refStmtPattern: *Pattern,// let pattern
refStmtDecl: *Decl, // for skDecl
refStmtBlock: *Block, // then/body block
refStmtElse: *Block, // else block
// Else-if chain
elseIfCount: int,
// Linked list
nextStmt: *Stmt,
}
// ---------------------------------------------------------------------------
// Decl — declarations
// ---------------------------------------------------------------------------
const dkFunc: int = 0;
const dkStruct: int = 1;
const dkEnum: int = 2;
const dkUnion: int = 3;
const dkInterface: int = 4;
const dkImpl: int = 5;
const dkModule: int = 6;
const dkUse: int = 7;
const dkConst: int = 8;
const dkTypeAlias: int = 9;
const dkExternFunc: int = 10;
const dkExternVar: int = 11;
struct Param {
line: uint32;
column: uint32;
name: String;
refParamType: *TypeExpr;
isVariadic: bool;
}
struct StructField {
line: uint32;
column: uint32;
isPublic: bool;
name: String;
refFieldType: *TypeExpr;
}
struct EnumVariant {
line: uint32;
column: uint32;
name: String;
fieldCount: int;
fieldTypeName0: String;
fieldTypeName1: String;
}
struct Decl {
kind: int,
line: uint32,
column: uint32,
isPublic: bool,
isAsync: bool,
// Names
strValue: String, // decl name
strValue2: String, // interface name, dll name, module path
// Type params (up to 2)
typeParam0: String,
typeParam1: String,
typeParamCount: int,
// Params (for functions)
paramCount: int,
param0: Param,
param1: Param,
param2: Param,
param3: Param,
param4: Param,
param5: Param,
param6: Param,
param7: Param,
param8: Param,
retType: *TypeExpr,
// Body
refBody: *Block,
// Struct fields (up to 64)
fieldCount: int,
field0: StructField,
field1: StructField,
field2: StructField,
field3: StructField,
field4: StructField,
field5: StructField,
field6: StructField,
field7: StructField,
field8: StructField,
field9: StructField,
field10: StructField,
field11: StructField,
field12: StructField,
field13: StructField,
field14: StructField,
field15: StructField,
field16: StructField,
field17: StructField,
field18: StructField,
field19: StructField,
field20: StructField,
field21: StructField,
field22: StructField,
field23: StructField,
field24: StructField,
field25: StructField,
field26: StructField,
field27: StructField,
field28: StructField,
field29: StructField,
field30: StructField,
field31: StructField,
field32: StructField,
field33: StructField,
field34: StructField,
field35: StructField,
field36: StructField,
field37: StructField,
field38: StructField,
field39: StructField,
field40: StructField,
field41: StructField,
field42: StructField,
field43: StructField,
field44: StructField,
field45: StructField,
field46: StructField,
field47: StructField,
field48: StructField,
field49: StructField,
field50: StructField,
field51: StructField,
field52: StructField,
field53: StructField,
field54: StructField,
field55: StructField,
field56: StructField,
field57: StructField,
field58: StructField,
field59: StructField,
field60: StructField,
field61: StructField,
field62: StructField,
field63: StructField,
// Enum variants (up to 8)
variantCount: int,
variant0: EnumVariant,
variant1: EnumVariant,
variant2: EnumVariant,
variant3: EnumVariant,
variant4: EnumVariant,
variant5: EnumVariant,
variant6: EnumVariant,
variant7: EnumVariant,
// Impl methods (up to 4)
methodCount: int,
// Use/import
useKind: int,
usePath: String,
useNames: String, // joined names for multi-import
// Const
constType: *TypeExpr,
constValue: *Expr,
// Type alias
aliasType: *TypeExpr,
// Extern func
extFuncDll: String,
extFuncVariadic: bool,
extFuncRetType: *TypeExpr,
// Children
childDecl1: *Decl, // linked list of decls (for module items, impl methods)
childDecl2: *Decl,
}
// ---------------------------------------------------------------------------
// Module — AST root
// ---------------------------------------------------------------------------
struct Module {
name: String,
path: String, // path segments joined
itemCount: int,
firstItem: *Decl,
}
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
return Expr { kind: kind, line: line, column: col,
strValue: "", intValue: 0, boolValue: false,
tokKind: 0, tokText: "",
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refType: null as *TypeExpr, refBlock: null as *Block,
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
structName: "", structFieldCount: 0,
callArgs: null as *ExprList, callArgCount: 0 };
}
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekIdent, line, col);
e.strValue = name;
return e;
}
func Ast_MakeLiteral(tokKind: int, text: String, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekLiteral, line, col);
e.tokKind = tokKind;
e.tokText = text;
return e;
}
func Ast_MakeBinary(op: int, left: *Expr, right: *Expr, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekBinary, line, col);
e.intValue = op;
e.child1 = left;
e.child2 = right;
return e;
}
func Ast_MakeCall(callee: *Expr, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekCall, line, col);
e.child1 = callee;
return e;
}
func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
return Stmt { kind: kind, line: line, column: col,
strValue: "", boolValue: false,
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refStmtType: null as *TypeExpr, refStmtPattern: null as *Pattern,
refStmtDecl: null as *Decl, refStmtBlock: null as *Block, refStmtElse: null as *Block,
elseIfCount: 0 };
}
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
return Decl { kind: kind, line: line, column: col, isPublic: false,
strValue: "", strValue2: "",
typeParam0: "", typeParam1: "", typeParamCount: 0,
paramCount: 0,
retType: null as *TypeExpr,
refBody: null as *Block,
fieldCount: 0,
variantCount: 0,
methodCount: 0,
useKind: 0, usePath: "", useNames: "",
constType: null as *TypeExpr, constValue: null as *Expr,
aliasType: null as *TypeExpr,
extFuncDll: "", extFuncVariadic: false, extFuncRetType: null as *TypeExpr,
childDecl1: null as *Decl, childDecl2: null as *Decl };
}
}
-689
View File
@@ -1,689 +0,0 @@
// c_backend.bux — C transpiler backend (ported from c_backend.nim)
// Generates C code from the HIR.
module CBackend {
// ---------------------------------------------------------------------------
// Type → C type name
// ---------------------------------------------------------------------------
func CBackend_TypeToC(kind: int) -> String {
if kind == tyVoid { return "void"; }
if kind == tyBool { return "bool"; }
if kind == tyBool8 { return "bool"; }
if kind == tyBool16 { return "bool"; }
if kind == tyBool32 { return "bool"; }
if kind == tyChar8 { return "char"; }
if kind == tyChar16 { return "uint16"; }
if kind == tyChar32 { return "uint32"; }
if kind == tyStr { return "String"; }
if kind == tyInt8 { return "int8"; }
if kind == tyInt16 { return "int16"; }
if kind == tyInt32 { return "int32"; }
if kind == tyInt64 { return "int64"; }
if kind == tyInt{ return "int"; }
if kind == tyUInt8 { return "uint8"; }
if kind == tyUInt16 { return "uint16"; }
if kind == tyUInt32 { return "uint32"; }
if kind == tyUInt64 { return "uint64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat32 { return "float32"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyPointer { return "void*"; }
if kind == tyNamed { return "int"; }
return "int";
}
func CBackend_OpToC(op: int) -> String {
if op == tkPlus { return "+"; }
if op == tkMinus { return "-"; }
if op == tkStar { return "*"; }
if op == tkSlash { return "/"; }
if op == tkPercent { return "%"; }
if op == tkEq { return "=="; }
if op == tkNe { return "!="; }
if op == tkLt { return "<"; }
if op == tkLe { return "<="; }
if op == tkGt { return ">"; }
if op == tkGe { return ">="; }
if op == tkAmpAmp { return "&&"; }
if op == tkPipePipe { return "||"; }
if op == tkBang { return "!"; }
if op == tkAmp { return "&"; }
if op == tkPipe { return "|"; }
if op == tkCaret { return "^"; }
if op == tkShl { return "<<"; }
if op == tkShr { return ">>"; }
if op == tkAssign { return "="; }
return "?";
}
// ---------------------------------------------------------------------------
// StringBuilder-based C emitter
// ---------------------------------------------------------------------------
struct CEmitter {
sb: StringBuilder,
indent: int,
}
func CBE_Init() -> CEmitter {
var sb: StringBuilder = StringBuilder_NewCap(4096);
return CEmitter { sb: sb, indent: 0 };
}
func CBE_Emit(cbe: *CEmitter, text: String) {
var i: int = 0;
while i < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, text);
}
func CBE_EmitLine(cbe: *CEmitter, text: String) {
CBE_Emit(cbe, text);
StringBuilder_Append(&cbe.sb, "\n");
}
func CBE_Build(cbe: *CEmitter) -> String {
return StringBuilder_Build(&cbe.sb);
}
// ---------------------------------------------------------------------------
// Emit HIR node
// ---------------------------------------------------------------------------
func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if node == null as *HirNode { return; }
let kind: int = node.kind;
// Literal
if kind == hLit {
if node.intValue == tkNull {
StringBuilder_Append(&cbe.sb, "0");
} else {
StringBuilder_Append(&cbe.sb, node.strValue);
}
return;
}
// Variable
if kind == hVar {
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Binary
if kind == hBinary {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Unary
if kind == hUnary {
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
CBE_EmitExpr(cbe, node.child1);
return;
}
// Call
if kind == hCall {
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "(");
var needsComma: bool = false;
if node.child1 != null as *HirNode {
CBE_EmitExpr(cbe, node.child1);
needsComma = true;
}
if node.child2 != null as *HirNode {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, node.child2);
needsComma = true;
}
// Emit extra args from linked list
var ai: int = 0;
var curExtra: *HirArgList = node.extraData as *HirArgList;
while ai < node.extraCount {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, curExtra.node);
needsComma = true;
curExtra = curExtra.next;
ai = ai + 1;
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// spawn Callee(args)
if kind == hSpawn {
StringBuilder_Append(&cbe.sb, "bux_async_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", (void*)");
CBE_EmitExpr(cbe, node.child1);
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// await
if kind == hAwait {
StringBuilder_Append(&cbe.sb, "bux_async_await(");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Return
if kind == hReturn {
StringBuilder_Append(&cbe.sb, "return");
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, node.child1);
}
return;
}
// Alloca
if kind == hAlloca {
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, "int");
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Store: combine alloca + value into single declaration
if kind == hStore {
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
// Declaration with initializer: Type x = value;
if !String_Eq(node.child1.typeName, "") {
StringBuilder_Append(&cbe.sb, node.child1.typeName);
} else {
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.child1.intValue));
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.child1.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Plain assignment
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// If
if kind == hIf {
StringBuilder_Append(&cbe.sb, "if (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
let elseBlock: *HirNode = node.extraData as *HirNode;
if elseBlock != null as *HirNode {
StringBuilder_Append(&cbe.sb, " else {\n");
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, elseBlock);
cbe.indent = cbe.indent - 1;
sp = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}\n");
} else {
StringBuilder_Append(&cbe.sb, "\n");
}
return;
}
// While
if kind == hWhile {
StringBuilder_Append(&cbe.sb, "while (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
return;
}
// Loop (infinite)
if kind == hLoop {
StringBuilder_Append(&cbe.sb, "while (1) {\n");
if node.child1 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child1);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
return;
}
// Field access: obj.field — emit as obj->field (since most are pointers)
if kind == hFieldPtr {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "->");
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Sizeof: sizeof(Type) — emit as sizeof(Type)
if kind == hSizeOf {
StringBuilder_Append(&cbe.sb, "sizeof(");
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, "int");
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Index: arr[idx] — emit as arr[idx]
if kind == hIndexPtr {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "[");
CBE_EmitExpr(cbe, node.child2);
StringBuilder_Append(&cbe.sb, "]");
return;
}
// Load: *ptr — emit as *ptr (dereference)
if kind == hLoad {
StringBuilder_Append(&cbe.sb, "(*");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Assign: target = value
if kind == hAssign {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Cast
if kind == hCast {
StringBuilder_Append(&cbe.sb, "((");
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.typeKind));
}
StringBuilder_Append(&cbe.sb, ")");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Struct init: ((TypeName){.field = value, ...})
if kind == hStructInit {
StringBuilder_Append(&cbe.sb, "((");
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "){");
// Emit fields (chained via child3)
var field: *HirNode = node.child1;
var first: bool = true;
while field != null as *HirNode {
if !first {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, ".");
StringBuilder_Append(&cbe.sb, field.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, field.child1);
first = false;
field = field.child3;
}
StringBuilder_Append(&cbe.sb, "})");
return;
}
// Block — emit each statement via child3 linked list
if kind == hBlock {
var child: *HirNode = node.child1;
while child != null as *HirNode {
// Indent
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, child);
// Add semicolon for statements that need it
if child.kind != hBlock && child.kind != hIf && child.kind != hWhile && child.kind != hLoop {
StringBuilder_Append(&cbe.sb, ";");
}
StringBuilder_Append(&cbe.sb, "\n");
child = child.child3;
}
return;
}
}
// ---------------------------------------------------------------------------
// Emit function declaration
// ---------------------------------------------------------------------------
func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
// Return type
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
StringBuilder_Append(&cbe.sb, "void ");
} else {
StringBuilder_Append(&cbe.sb, f.retTypeName);
StringBuilder_Append(&cbe.sb, " ");
}
StringBuilder_Append(&cbe.sb, f.name);
StringBuilder_Append(&cbe.sb, "(");
// Parameters
var i: int = 0;
while i < f.paramCount {
if i > 0 {
StringBuilder_Append(&cbe.sb, ", ");
}
// Use param type info
var pname: String = "";
var ptype: String = "";
if i == 0 { pname = f.param0.name; ptype = f.param0.typeName; }
if i == 1 { pname = f.param1.name; ptype = f.param1.typeName; }
if i == 2 { pname = f.param2.name; ptype = f.param2.typeName; }
if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; }
if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; }
if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; }
if i == 6 { pname = f.param6.name; ptype = f.param6.typeName; }
if i == 7 { pname = f.param7.name; ptype = f.param7.typeName; }
if i == 8 { pname = f.param8.name; ptype = f.param8.typeName; }
// Emit type
if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
StringBuilder_Append(&cbe.sb, "String ");
} else if String_Eq(ptype, "bool") {
StringBuilder_Append(&cbe.sb, "bool ");
} else if String_Eq(ptype, "int") {
StringBuilder_Append(&cbe.sb, "int ");
} else if String_Eq(ptype, "int64") {
StringBuilder_Append(&cbe.sb, "int64 ");
} else if String_Eq(ptype, "uint") {
StringBuilder_Append(&cbe.sb, "uint ");
} else if String_Eq(ptype, "float64") {
StringBuilder_Append(&cbe.sb, "float64 ");
} else if !String_Eq(ptype, "") {
StringBuilder_Append(&cbe.sb, ptype);
StringBuilder_Append(&cbe.sb, " ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
StringBuilder_Append(&cbe.sb, pname);
i = i + 1;
}
StringBuilder_Append(&cbe.sb, ")");
}
// ---------------------------------------------------------------------------
// Helpers for detecting generic declarations (not yet monomorphized)
// ---------------------------------------------------------------------------
func CBE_IsGenericTypeName(name: String) -> bool {
if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; }
if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; }
// Generic container structs and their pointer variants
if String_Eq(name, "Array") || String_Eq(name, "Array*") { return true; }
if String_Eq(name, "SetEntry") || String_Eq(name, "SetEntry*") { return true; }
if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; }
if String_Eq(name, "MapEntry") || String_Eq(name, "MapEntry*") { return true; }
return false;
}
func CBE_StructHasGeneric(st: *HirStruct) -> bool {
var fi: int = 0;
while fi < st.fieldCount {
if CBE_IsGenericTypeName(st.fields[fi].typeName) { return true; }
fi = fi + 1;
}
return false;
}
func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
var pi: int = 0;
while pi < f.paramCount {
var ptype: String = "";
if pi == 0 { ptype = f.param0.typeName; }
if pi == 1 { ptype = f.param1.typeName; }
if pi == 2 { ptype = f.param2.typeName; }
if pi == 3 { ptype = f.param3.typeName; }
if pi == 4 { ptype = f.param4.typeName; }
if pi == 5 { ptype = f.param5.typeName; }
if pi == 6 { ptype = f.param6.typeName; }
if pi == 7 { ptype = f.param7.typeName; }
if pi == 8 { ptype = f.param8.typeName; }
if CBE_IsGenericTypeName(ptype) { return true; }
pi = pi + 1;
}
if CBE_IsGenericTypeName(f.retTypeName) { return true; }
return false;
}
// ---------------------------------------------------------------------------
// Generate complete C module
// ---------------------------------------------------------------------------
func CBackend_Generate(mod: *HirModule) -> String {
let cbe: *CEmitter = bux_alloc(sizeof(CEmitter)) as *CEmitter;
cbe.sb = StringBuilder_NewCap(8192);
cbe.indent = 0;
// Header
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend\n");
StringBuilder_Append(&cbe.sb, "#include <stdint.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdbool.h>\n");
StringBuilder_Append(&cbe.sb, "#include <string.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdio.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdlib.h>\n\n");
// Type aliases
StringBuilder_Append(&cbe.sb, "typedef const char* String;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned char uint8;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned short uint16;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned int uint32;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned long long uint64;\n");
StringBuilder_Append(&cbe.sb, "typedef signed char int8;\n");
StringBuilder_Append(&cbe.sb, "typedef short int16;\n");
StringBuilder_Append(&cbe.sb, "typedef long long int64;\n");
StringBuilder_Append(&cbe.sb, "typedef float float32;\n");
StringBuilder_Append(&cbe.sb, "typedef double float64;\n");
StringBuilder_Append(&cbe.sb, "typedef char char8;\n\n");
// Runtime declarations
StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n");
StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n\n");
// Forward declare all struct types (skip generics)
var si: int = 0;
while si < mod.structCount {
if !CBE_StructHasGeneric(&mod.structs[si]) {
StringBuilder_Append(&cbe.sb, "typedef struct ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, ";\n");
}
si = si + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Enum definitions (typedef to int)
var ei: int = 0;
while ei < mod.enumCount {
StringBuilder_Append(&cbe.sb, "typedef int ");
StringBuilder_Append(&cbe.sb, mod.enums[ei].name);
StringBuilder_Append(&cbe.sb, ";\n");
ei = ei + 1;
}
if mod.enumCount > 0 {
StringBuilder_Append(&cbe.sb, "\n");
}
// Constant definitions
var ci: int = 0;
while ci < mod.constCount {
StringBuilder_Append(&cbe.sb, "#define ");
StringBuilder_Append(&cbe.sb, mod.consts[ci].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, String_FromInt(mod.consts[ci].value as int64));
StringBuilder_Append(&cbe.sb, "\n");
ci = ci + 1;
}
if mod.constCount > 0 {
StringBuilder_Append(&cbe.sb, "\n");
}
// Struct definitions (skip generics)
si = 0;
while si < mod.structCount {
if CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
StringBuilder_Append(&cbe.sb, "struct ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, " {\n");
var fi: int = 0;
while fi < mod.structs[si].fieldCount {
StringBuilder_Append(&cbe.sb, " ");
var ft: String = mod.structs[si].fields[fi].typeName;
if String_Eq(ft, "int") || String_Eq(ft, "") {
StringBuilder_Append(&cbe.sb, "int");
} else if String_Eq(ft, "String") {
StringBuilder_Append(&cbe.sb, "String");
} else if String_Eq(ft, "bool") {
StringBuilder_Append(&cbe.sb, "bool");
} else if String_Eq(ft, "uint32") {
StringBuilder_Append(&cbe.sb, "uint32");
} else {
// Use the type name directly (handles "Foo*", "Bar", etc.)
StringBuilder_Append(&cbe.sb, ft);
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.structs[si].fields[fi].name);
StringBuilder_Append(&cbe.sb, ";\n");
fi = fi + 1;
}
StringBuilder_Append(&cbe.sb, "};\n\n");
si = si + 1;
}
// Forward declarations for all functions (skip generics)
var i: int = 0;
while i < mod.funcCount {
if !CBE_FuncHasGeneric(&mod.funcs[i]) {
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
}
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Extern declarations
i = 0;
while i < mod.externCount {
CBE_EmitFuncDecl(cbe, &mod.externFuncs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Function definitions (skip generics)
var hasMain: bool = false;
i = 0;
while i < mod.funcCount {
if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; }
// Skip generic functions
if CBE_FuncHasGeneric(&mod.funcs[i]) {
i = i + 1;
continue;
}
// Skip forward declarations (functions without body)
let body: *HirNode = mod.funcs[i].body;
if body == null as *HirNode {
i = i + 1;
continue;
}
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, " {\n");
// Body
var hasReturn: bool = false;
cbe.indent = 1;
CBE_EmitExpr(cbe, body);
cbe.indent = 0;
// Check if body has a return statement
var stmt: *HirNode = body.child1;
while stmt != null as *HirNode {
if stmt.kind == hReturn { hasReturn = true; }
stmt = stmt.child3;
}
// Add default return 0 only if function returns int and has no explicit return
if String_Eq(mod.funcs[i].retTypeName, "int") && !hasReturn {
StringBuilder_Append(&cbe.sb, " return 0;\n");
}
StringBuilder_Append(&cbe.sb, "}\n\n");
i = i + 1;
}
// Generate C main wrapper if Main function exists
if hasMain {
StringBuilder_Append(&cbe.sb, "extern int g_argc;\n");
StringBuilder_Append(&cbe.sb, "extern char** g_argv;\n");
StringBuilder_Append(&cbe.sb, "int main(int argc, char** argv) {\n");
StringBuilder_Append(&cbe.sb, " g_argc = argc;\n");
StringBuilder_Append(&cbe.sb, " g_argv = argv;\n");
StringBuilder_Append(&cbe.sb, " return Main();\n");
StringBuilder_Append(&cbe.sb, "}\n");
}
return StringBuilder_Build(&cbe.sb);
}
func CBackend_Free(cbe: *CEmitter) {
StringBuilder_Free(&cbe.sb);
bux_free(cbe as *void);
}
}
-761
View File
@@ -1,761 +0,0 @@
// cli.bux — CLI driver for the Bux self-hosting compiler
// Wires together: Lexer → Parser → Sema → HirLower → CBackend
module Cli {
extern func PrintLine(s: String);
extern func Print(s: String);
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_file_exists(path: String) -> int;
extern func bux_dir_exists(path: String) -> int;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
extern func bux_system(cmd: String) -> int;
func ReadFile(path: String) -> String {
return bux_read_file(path);
}
func WriteFile(path: String, content: String) -> bool {
return bux_write_file(path, content);
}
func FileExists(path: String) -> bool {
return bux_file_exists(path) != 0;
}
func DirExists(path: String) -> bool {
return bux_dir_exists(path) != 0;
}
// Import the compiler pipeline
// In self-hosting mode, these are compiled together from compiler/selfhost/
// ---------------------------------------------------------------------------
// Compile a single .bux source file
// ---------------------------------------------------------------------------
func Cli_Compile(source: String, sourceName: String) -> String {
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
PrintLine("Lex errors:");
var i: int = 0;
while i < Lexer_DiagCount(lex) {
Print(" ");
PrintLine(lex.diags[i].message);
i = i + 1;
}
return "";
}
// Phase 2: Parse
PrintLine(" Parsing...");
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
PrintLine("Parse failed");
return "";
}
PrintLine(" Parse done");
// Flatten module wrappers: find module decl and hoist its children
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkModule && decl.childDecl1 != null as *Decl {
mod.firstItem = decl.childDecl1;
break;
}
decl = decl.childDecl2;
}
// Phase 3: Semantic analysis
PrintLine(" Sema...");
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
PrintLine("Sema errors:");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return "";
}
PrintLine(" Sema done");
// Phase 4: HIR lowering
PrintLine(" HIR lowering...");
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
if hirMod == null as *HirModule {
PrintLine("HIR lowering failed");
return "";
}
Print("HIR funcCount=");
PrintInt(hirMod.funcCount);
// Phase 5: C code generation
let cCode: String = CBackend_Generate(hirMod);
// Cleanup
Lexer_Free(lex);
Sema_Free(sema);
return cCode;
}
// ---------------------------------------------------------------------------
// Build command
// ---------------------------------------------------------------------------
func Cli_Build(srcPath: String, outPath: String) -> int {
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") || !FileExists(srcPath) {
Print("Error: cannot read source file: ");
PrintLine(srcPath);
return 1;
}
Print("Compiling ");
PrintLine(srcPath);
let cCode: String = Cli_Compile(source, srcPath);
if String_Eq(cCode, "") {
PrintLine("Compilation failed");
return 1;
}
// Write C output
let cFile: String = String_Concat(outPath, ".c");
let ok: bool = WriteFile(cFile, cCode);
if !ok {
PrintLine("Error: cannot write output file");
return 1;
}
Print(" → C written to ");
PrintLine(cFile);
// Find runtime.c and io.c for linking
var rtPath: String = "library/runtime/runtime.c";
var ioPath: String = "library/runtime/io.c";
if !FileExists(rtPath) {
rtPath = "../library/runtime/runtime.c";
}
if !FileExists(ioPath) {
ioPath = "../library/runtime/io.c";
}
if !FileExists(rtPath) {
rtPath = "/home/ziko/z-git/bux/bux/library/runtime/runtime.c";
}
if !FileExists(ioPath) {
ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c";
}
// Compile with cc
PrintLine("Compiling C...");
var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&cmdBuf, "cc -O2 -pthread -o ");
StringBuilder_Append(&cmdBuf, outPath);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, cFile);
StringBuilder_Append(&cmdBuf, " ");
if FileExists(rtPath) {
StringBuilder_Append(&cmdBuf, rtPath);
StringBuilder_Append(&cmdBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&cmdBuf, ioPath);
StringBuilder_Append(&cmdBuf, " ");
}
StringBuilder_Append(&cmdBuf, "-lm");
let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
Print(" → Binary: ");
PrintLine(outPath);
return 0;
}
// ---------------------------------------------------------------------------
// Check command (compile only, no output)
// ---------------------------------------------------------------------------
func Cli_Check(srcPath: String) -> int {
Print("Check: ");
PrintLine(srcPath);
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") {
PrintLine("Error: cannot read source file");
return 1;
}
PrintLine(" File read OK");
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
PrintLine("Lex errors found");
return 1;
}
// Phase 2: Parse
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
PrintLine("Parse failed");
return 1;
}
PrintLine(" Parse done");
// Flatten module wrappers
var decl2: *Decl = mod.firstItem;
while decl2 != null as *Decl {
if decl2.kind == dkModule && decl2.childDecl1 != null as *Decl {
mod.firstItem = decl2.childDecl1;
break;
}
decl2 = decl2.childDecl2;
}
// Phase 3: Sema
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
PrintLine("Sema errors found");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" line ");
PrintInt(sema.diags[i].line as int64);
Print(": ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return 1;
}
// Phase 4: HIR lowering
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
var dc: *Decl = mod.firstItem;
while dc != null as *Decl {
if dc.kind == dkFunc {
Print("check func ");
PrintLine(dc.strValue);
}
dc = dc.childDecl2;
}
PrintLine("Check passed");
return 0;
}
// ---------------------------------------------------------------------------
// Project build — compile all .bux files in src/ directory
// ---------------------------------------------------------------------------
func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
Print("Lex errors in ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 2: Parse
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
Print("Parse failed for ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 3: Semantic analysis
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
Print("Sema errors in ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 4: HIR lowering
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
return hirMod;
}
// ---------------------------------------------------------------------------
// Stdlib discovery and merging
// ---------------------------------------------------------------------------
func Cli_FindStdlibDir(projectDir: String) -> String {
var path: String = bux_path_join(projectDir, "stdlib");
if DirExists(path) { return path; }
path = bux_path_join(projectDir, "../stdlib");
if DirExists(path) { return path; }
path = "/home/ziko/z-git/bux/bux/stdlib";
if DirExists(path) { return path; }
return "";
}
func Cli_DeclName(decl: *Decl) -> String {
if decl == null as *Decl { return ""; }
return decl.strValue;
}
func Cli_NameInList(name: String, names: *String, count: int) -> bool {
if String_Eq(name, "") { return false; }
var i: int = 0;
while i < count {
if String_Eq(names[i], name) {
return true;
}
i = i + 1;
}
return false;
}
func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int {
if mod == null as *Module { return 0; }
var count: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl && count < maxCount {
let next: *Decl = decl.childDecl2;
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl && count < maxCount {
let iname: String = Cli_DeclName(inner);
if !String_Eq(iname, "") {
names[count] = iname;
count = count + 1;
}
inner = inner.childDecl2;
}
} else {
let dname: String = Cli_DeclName(decl);
if !String_Eq(dname, "") {
names[count] = dname;
count = count + 1;
}
}
decl = next;
}
return count;
}
// Helper: convert single char int to String
func String_FromChar(c: int) -> String {
var buf: *char8 = bux_alloc(2) as *char8;
buf[0] = c as char8;
buf[1] = 0 as char8;
return buf as String;
}
// Collect stdlib module paths imported by user code.
// Returns number of unique stdlib files to merge (paths written into outPaths).
func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, stdlibDir: String) -> int {
if mod == null as *Module { return 0; }
var count: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl && count < maxCount {
if decl.kind == dkUse {
let path: String = decl.usePath;
// Only handle Std::* imports
if String_StartsWith(path, "Std::") {
// Extract module name after Std:: (e.g., "Std::Io" -> "Io")
var j: int = 5; // skip "Std::"
var modName: String = "";
while j < 100 {
let c: int = path[j] as int;
if c == 0 || c == 58 { break; } // null or ':'
modName = String_Concat(modName, String_FromChar(c));
j = j + 1;
}
if !String_Eq(modName, "") {
let filePath: String = bux_path_join(bux_path_join(stdlibDir, "Std"), String_Concat(modName, ".bux"));
// Check for duplicates
var dup: bool = false;
var di: int = 0;
while di < count {
if String_Eq(outPaths[di], filePath) { dup = true; }
di = di + 1;
}
if !dup {
outPaths[count] = filePath;
count = count + 1;
}
}
}
}
decl = decl.childDecl2;
}
return count;
}
func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int {
if !FileExists(path) {
Print("Error: stdlib file not found: ");
PrintLine(path);
return 0;
}
let source: String = ReadFile(path);
if source == null as String || String_Eq(source, "") { return 0; }
let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 { return 0; }
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module { return 0; }
var added: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl {
let innerNext: *Decl = inner.childDecl2;
let iname: String = Cli_DeclName(inner);
if !String_Eq(iname, "") && Cli_NameInList(iname, skipNames, skipCount) {
// Skip shadowed stdlib decl
} else {
inner.childDecl2 = target.firstItem;
target.firstItem = inner;
target.itemCount = target.itemCount + 1;
added = added + 1;
}
inner = innerNext;
}
} else {
let dname: String = Cli_DeclName(decl);
if !String_Eq(dname, "") && Cli_NameInList(dname, skipNames, skipCount) {
// Skip shadowed stdlib decl
} else {
decl.childDecl2 = target.firstItem;
target.firstItem = decl;
target.itemCount = target.itemCount + 1;
added = added + 1;
}
}
decl = next;
}
return added;
}
func Cli_CopyModuleDecls(target: *Module, source: *Module) {
if source == null as *Module || source.firstItem == null as *Decl { return; }
var decl: *Decl = source.firstItem;
var limit: int = 0;
while decl != null as *Decl && limit < 10000 {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = target.firstItem;
target.firstItem = decl;
target.itemCount = target.itemCount + 1;
decl = next;
limit = limit + 1;
}
source.firstItem = null as *Decl;
source.itemCount = 0;
}
func Cli_BuildProject(projectDir: String) -> int {
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let srcDir: String = bux_path_join(projectDir, "src");
if !DirExists(srcDir) {
Print("Error: no src/ directory in ");
PrintLine(projectDir);
return 1;
}
Print("Scanning ");
PrintLine(srcDir);
// List all .bux files in src/
var fileCount: int = 0;
let files: *String = bux_list_dir(srcDir, ".bux", &fileCount);
if fileCount == 0 {
PrintLine("Error: no .bux files found in src/");
return 1;
}
Print("Found ");
PrintInt(fileCount);
PrintLine(" source files");
// Create user merged module (collect user decls first)
let userMerged: *Module = bux_alloc(sizeof(Module)) as *Module;
userMerged.name = "main";
userMerged.path = "";
userMerged.itemCount = 0;
userMerged.firstItem = null as *Decl;
// Parse each file and merge declarations into userMerged module
var i: int = 0;
while i < fileCount {
let path: String = files[i];
Print(" Parsing ");
PrintLine(path);
let source: String = ReadFile(path);
if String_Eq(source, "") {
Print(" Error: cannot read ");
PrintLine(path);
return 1;
}
let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 {
Print(" Lex errors in ");
PrintLine(path);
return 1;
}
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
Print(" Parse failed for ");
PrintLine(path);
return 1;
}
// Merge declarations from this module into userMerged
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
// Flatten module declarations
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl {
let innerNext: *Decl = inner.childDecl2;
inner.childDecl2 = userMerged.firstItem;
userMerged.firstItem = inner;
userMerged.itemCount = userMerged.itemCount + 1;
inner = innerNext;
}
} else {
decl.childDecl2 = userMerged.firstItem;
userMerged.firstItem = decl;
userMerged.itemCount = userMerged.itemCount + 1;
}
decl = next;
}
i = i + 1;
}
// Collect user declaration names for shadow detection
let maxNames: int = 2048;
let userNames: *String = bux_alloc(maxNames * 8) as *String;
let userNameCount: int = Cli_CollectNames(userMerged, userNames, maxNames);
// Create final merged module
let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
merged.name = "main";
merged.path = "";
merged.itemCount = 0;
merged.firstItem = null as *Decl;
// Find and merge stdlib declarations (only imported modules)
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
if !String_Eq(stdlibDir, "") {
Print("Stdlib found: ");
PrintLine(stdlibDir);
// Collect imported stdlib modules from user code
let maxImports: int = 64;
let importPaths: *String = bux_alloc(maxImports * 8) as *String;
let importCount: int = Cli_CollectStdlibImports(userMerged, importPaths, maxImports, stdlibDir);
Print("Imported stdlib modules: ");
PrintInt(importCount);
PrintLine("");
if importCount > 0 {
var si: int = 0;
var stdAdded: int = 0;
while si < importCount {
Print(" Merging ");
PrintLine(importPaths[si]);
let added: int = Cli_MergeFileInto(merged, importPaths[si], userNames, userNameCount);
stdAdded = stdAdded + added;
si = si + 1;
}
Print("Stdlib declarations added: ");
PrintInt(stdAdded);
}
}
// Copy user declarations into merged (user shadows stdlib)
Cli_CopyModuleDecls(merged, userMerged);
Print("Merged ");
PrintInt(merged.itemCount);
PrintLine(" declarations");
// Semantic analysis
PrintLine("Running sema...");
let sema: *Sema = Sema_Analyze(merged);
if Sema_HasError(sema) {
PrintLine("Sema errors found");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" line ");
PrintInt(sema.diags[i].line as int64);
Print(": ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return 1;
}
// HIR lowering
PrintLine("Lowering to HIR...");
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
// C code generation
PrintLine("Generating C code...");
let cCode: String = CBackend_Generate(hirMod);
// Create build directory
let buildDir: String = bux_path_join(projectDir, "build");
discard bux_mkdir_if_needed(buildDir);
// Write C output
let cFile: String = bux_path_join(buildDir, "main.c");
if !WriteFile(cFile, cCode) {
PrintLine("Error: cannot write main.c");
return 1;
}
Print("C code written to ");
PrintLine(cFile);
// Find runtime.c and io.c (use stdlibDir if available)
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, "library/runtime/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "library/runtime/io.c");
}
if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../library/runtime/io.c");
}
// Compile with cc
PrintLine("Compiling C...");
let outBin: String = bux_path_join(buildDir, outName);
var ccBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&ccBuf, "cc -O2 -pthread -o ");
StringBuilder_Append(&ccBuf, outBin);
StringBuilder_Append(&ccBuf, " ");
StringBuilder_Append(&ccBuf, cFile);
StringBuilder_Append(&ccBuf, " ");
if FileExists(rtPath) {
StringBuilder_Append(&ccBuf, rtPath);
StringBuilder_Append(&ccBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&ccBuf, ioPath);
StringBuilder_Append(&ccBuf, " ");
}
StringBuilder_Append(&ccBuf, "-lm");
let ccRc: int = bux_system(StringBuilder_Build(&ccBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
Print("Build successful: ");
PrintLine(outBin);
return 0;
}
// ---------------------------------------------------------------------------
// Run command — build project and execute
// ---------------------------------------------------------------------------
func Cli_RunProject(projectDir: String) -> int {
let rc: int = Cli_BuildProject(projectDir);
if rc != 0 {
return rc;
}
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName);
Print("Running: ");
PrintLine(outBin);
return bux_system(outBin);
}
// ---------------------------------------------------------------------------
// Main entry — dispatch based on args
// ---------------------------------------------------------------------------
func Cli_Run(args: *String, argCount: int) -> int {
if argCount < 2 {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, run, project, help, version");
return 0;
}
let cmd: String = args[1];
if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
PrintLine("Bux 0.2.0 (self-hosting bootstrap)");
return 0;
}
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, run, project, help, version");
PrintLine("Pipeline modules:");
PrintLine(" Lexer ✅ 695 lines");
PrintLine(" Parser ✅ 1004 lines");
PrintLine(" Sema ✅ 393 lines");
PrintLine(" HirLower ✅ 307 lines");
PrintLine(" CBackend ✅ 585 lines");
PrintLine(" Total: 3830 lines of Bux");
return 0;
}
if String_Eq(cmd, "check") {
if argCount < 3 {
PrintLine("Usage: buxc check <file.bux>");
return 1;
}
return Cli_Check(args[2]);
}
if String_Eq(cmd, "build") {
let src: String = "src/Main.bux";
let out: String = "build/main";
if argCount >= 3 { src = args[2]; }
if argCount >= 4 { out = args[3]; }
return Cli_Build(src, out);
}
if String_Eq(cmd, "project") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_BuildProject(dir);
}
if String_Eq(cmd, "run") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_RunProject(dir);
}
Print("Unknown command: ");
PrintLine(cmd);
return 1;
}
}
-229
View File
@@ -1,229 +0,0 @@
// hir.bux — HIR (High-level Intermediate Representation) node types
module Hir {
// HIR node kinds
const hLit: int = 0;
const hVar: int = 1;
const hSelf: int = 2;
const hUnary: int = 3;
const hBinary: int = 4;
const hAssign: int = 5;
const hIf: int = 6;
const hWhile: int = 7;
const hLoop: int = 8;
const hBreak: int = 9;
const hContinue: int = 10;
const hReturn: int = 11;
const hAlloca: int = 12;
const hLoad: int = 13;
const hStore: int = 14;
const hFieldPtr: int = 15;
const hArrowField: int = 16;
const hIndexPtr: int = 17;
const hCall: int = 18;
const hCallIndirect: int = 19;
const hCast: int = 20;
const hIs: int = 21;
const hSizeOf: int = 22;
const hBlock: int = 23;
const hStructInit: int = 24;
const hSliceInit: int = 25;
const hRange: int = 26;
const hTupleInit: int = 27;
const hMatch: int = 28;
const hSpawn: int = 29;
const hAwait: int = 30;
// ---------------------------------------------------------------------------
// HirArgList — linked list for call arguments beyond 2
// ---------------------------------------------------------------------------
struct HirArgList {
node: *HirNode,
next: *HirArgList,
}
// ---------------------------------------------------------------------------
// HirNode — unified struct with tagged union pattern
// ---------------------------------------------------------------------------
struct HirNode {
kind: int;
line: uint32;
column: uint32;
typeKind: int;
typeName: String;
// Common fields (used by multiple kinds)
strValue: String; // var name; callee name; field name; label
intValue: int; // token kind (for lit; unary op; binary op)
boolValue: bool; // range inclusive; isScope
// Child nodes (up to 3)
child1: *HirNode; // left/operand/condition/base
child2: *HirNode; // right/value/then/body
child3: *HirNode; // else/third
// Extra data pointer (for children arrays, field lists, etc.)
extraData: *void;
extraCount: int;
}
// ---------------------------------------------------------------------------
// HirFunc
// ---------------------------------------------------------------------------
struct HirParam {
name: String;
typeKind: int;
typeName: String;
}
struct HirFunc {
name: String;
paramCount: int;
param0: HirParam;
param1: HirParam;
param2: HirParam;
param3: HirParam;
param4: HirParam;
param5: HirParam;
param6: HirParam;
param7: HirParam;
param8: HirParam;
retTypeKind: int;
retTypeName: String;
body: *HirNode;
isPublic: bool;
}
// ---------------------------------------------------------------------------
// HirEnumVariant
// ---------------------------------------------------------------------------
struct HirEnumVariant {
name: String;
fieldCount: int;
fieldType0: int;
fieldName0: String;
fieldType1: int;
fieldName1: String;
}
// ---------------------------------------------------------------------------
// HirModule
// ---------------------------------------------------------------------------
struct HirStructField {
name: String;
typeName: String;
}
struct HirStruct {
name: String;
fieldCount: int;
fields: *HirStructField;
}
struct HirConst {
name: String;
value: int;
}
struct HirEnum {
name: String;
}
struct HirModule {
funcCount: int;
funcs: *HirFunc;
externCount: int;
externFuncs: *HirFunc;
structCount: int;
structs: *HirStruct;
enumCount: int;
enums: *HirEnum;
constCount: int;
consts: *HirConst;
}
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
func Hir_MakeNode(kind: int, line: uint32, column: uint32) -> HirNode {
return HirNode { kind: kind, line: line, column: column,
typeKind: 0, typeName: "",
strValue: "", intValue: 0, boolValue: false,
child1: null as *HirNode, child2: null as *HirNode, child3: null as *HirNode,
extraData: null as *void, extraCount: 0 };
}
func Hir_MakeLit(tokKind: int, tokText: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hLit, line, col);
n.intValue = tokKind;
n.strValue = tokText;
return n;
}
func Hir_MakeVar(name: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hVar, line, col);
n.strValue = name;
return n;
}
func Hir_MakeBinary(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hBinary, line, col);
n.intValue = op;
n.child1 = left;
n.child2 = right;
return n;
}
func Hir_MakeCall(callee: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hCall, line, col);
n.strValue = callee;
return n;
}
func Hir_MakeReturn(value: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hReturn, line, col);
n.child1 = value;
return n;
}
func Hir_MakeBlock(line: uint32, col: uint32) -> HirNode {
return Hir_MakeNode(hBlock, line, col);
}
func Hir_MakeIf(cond: *HirNode, thenBody: *HirNode, elseBody: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hIf, line, col);
n.child1 = cond;
n.child2 = thenBody;
n.child3 = elseBody;
return n;
}
func Hir_MakeWhile(cond: *HirNode, body: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hWhile, line, col);
n.child1 = cond;
n.child2 = body;
return n;
}
func Hir_MakeAlloca(name: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hAlloca, line, col);
n.strValue = name;
return n;
}
func Hir_MakeLoad(ptr: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hLoad, line, col);
n.child1 = ptr;
return n;
}
func Hir_MakeStore(ptr: *HirNode, value: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hStore, line, col);
n.child1 = ptr;
n.child2 = value;
return n;
}
}
-740
View File
@@ -1,740 +0,0 @@
// hir_lower.bux — HIR lowering: AST → HIR transformation (ported from hir_lower.nim)
// Transforms the typed AST into a lower-level IR suitable for code generation.
module HirLower {
// ---------------------------------------------------------------------------
// Lowering context
// ---------------------------------------------------------------------------
struct LowerCtx {
module: *Module,
scope: *Scope,
funcs: *HirFunc,
funcCount: int,
externFuncs: *HirFunc,
externCount: int,
varCounter: int,
tryCounter: int,
}
// ---------------------------------------------------------------------------
// TypeExpr.kind → Type.kind resolver
// TypeExpr.kind values (0-5) overlap with Type.kind values — this
// resolves the correct Type.kind for codegen.
// ---------------------------------------------------------------------------
func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer { return tyPointer; }
if te.kind == tekSlice { return tySlice; }
if te.kind == tekTuple { return tyTuple; }
// Named types: resolve by name
if String_Eq(name, "void") { return tyVoid; }
if String_Eq(name, "bool") { return tyBool; }
if String_Eq(name, "bool8") { return tyBool8; }
if String_Eq(name, "bool16") { return tyBool16; }
if String_Eq(name, "bool32") { return tyBool32; }
if String_Eq(name, "char8") { return tyChar8; }
if String_Eq(name, "char16") { return tyChar16; }
if String_Eq(name, "char32") { return tyChar32; }
if String_Eq(name, "String") { return tyStr; }
if String_Eq(name, "str") { return tyStr; }
if String_Eq(name, "int8") { return tyInt8; }
if String_Eq(name, "int16") { return tyInt16; }
if String_Eq(name, "int32") { return tyInt32; }
if String_Eq(name, "int64") { return tyInt64; }
if String_Eq(name, "int") { return tyInt; }
if String_Eq(name, "uint8") { return tyUInt8; }
if String_Eq(name, "uint16") { return tyUInt16; }
if String_Eq(name, "uint32") { return tyUInt32; }
if String_Eq(name, "uint64") { return tyUInt64; }
if String_Eq(name, "uint") { return tyUInt; }
if String_Eq(name, "float32") { return tyFloat32; }
if String_Eq(name, "float64") { return tyFloat64; }
if String_Eq(name, "float") { return tyFloat64; }
return tyNamed;
}
// ---------------------------------------------------------------------------
// Fresh name generation
// ---------------------------------------------------------------------------
func Lcx_FreshName(ctx: *LowerCtx) -> String {
ctx.varCounter = ctx.varCounter + 1;
return String_FromInt(ctx.varCounter as int64);
}
// ---------------------------------------------------------------------------
// Type → HIR type
// ---------------------------------------------------------------------------
func Lcx_LowerType(typeKind: int, typeName: String) -> int {
return typeKind;
}
// ---------------------------------------------------------------------------
// Expression lowering
// ---------------------------------------------------------------------------
func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
if expr == null as *Expr { return null as *HirNode; }
let line: uint32 = expr.line;
let col: uint32 = expr.column;
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = line;
n.column = col;
let kind: int = expr.kind;
// Literal
if kind == ekLiteral {
n.kind = hLit;
n.intValue = expr.tokKind;
n.strValue = expr.tokText;
return n;
}
// Identifier → variable reference
if kind == ekIdent {
n.kind = hVar;
n.strValue = expr.strValue;
let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
n.typeKind = sym.typeKind;
if expr.refType != null as *TypeExpr {
n.typeName = expr.refType.typeName;
}
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
n.typeName = sym.typeName;
}
return n;
}
// self → variable reference named "self"
if kind == ekSelf {
n.kind = hVar;
n.strValue = "self";
let sym: Symbol = Scope_Lookup(ctx.scope, "self");
n.typeKind = sym.typeKind;
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
n.typeName = sym.typeName;
}
return n;
}
// Binary
if kind == ekBinary {
// Assignment operator → use hAssign
if expr.intValue == tkAssign {
n.kind = hAssign;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
n.kind = hBinary;
n.intValue = expr.intValue; // operator
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
// Unary
if kind == ekUnary {
n.kind = hUnary;
n.intValue = expr.intValue;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
return n;
}
// Call
if kind == ekCall {
n.kind = hCall;
// Method call desugaring: obj.method(args) → Type_method(obj, args)
if expr.child1 != null as *Expr && expr.child1.kind == ekField {
let methodName: String = expr.child1.strValue;
var receiverTypeName: String = "";
if expr.child1.child1 != null as *Expr && expr.child1.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.child1.strValue);
receiverTypeName = sym.typeName;
}
if String_Eq(receiverTypeName, "") && expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr {
receiverTypeName = expr.child1.child1.refType.typeName;
}
if !String_Eq(receiverTypeName, "") {
// Strip trailing '*' from pointer type names (e.g. "Box*" → "Box")
var baseName: String = receiverTypeName;
let len: int = bux_strlen(baseName) as int;
if len > 0 {
let lastChar: String = bux_str_slice(baseName, (len - 1) as uint, 1);
if String_Eq(lastChar, "*") {
baseName = bux_str_slice(baseName, 0, (len - 1) as uint);
}
}
n.strValue = String_Concat(baseName, "_");
n.strValue = String_Concat(n.strValue, methodName);
}
// Lower receiver as first argument
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
n.child1 = recv;
// Lower remaining arguments from linked list
var arg: *ExprList = expr.callArgs;
var argIdx: int = 0;
while arg != null as *ExprList {
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child2 = lowered;
} else if argIdx == 1 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
var cur: *HirArgList = n.extraData as *HirArgList;
while cur.next != null as *HirArgList {
cur = cur.next;
}
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
newNode.node = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
}
return n;
}
// Get callee name
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue;
}
// Lower arguments from linked list
var arg: *ExprList = expr.callArgs;
var argIdx: int = 0;
while arg != null as *ExprList {
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child1 = lowered;
} else if argIdx == 1 {
n.child2 = lowered;
} else if argIdx == 2 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
var cur: *HirArgList = n.extraData as *HirArgList;
while cur.next != null as *HirArgList {
cur = cur.next;
}
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
newNode.node = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
}
return n;
}
// Sizeof
if kind == ekSizeOf {
n.kind = hSizeOf;
if expr.refType != null as *TypeExpr {
n.typeName = expr.refType.typeName;
}
return n;
}
// Field access
if kind == ekField {
// Check if this is enum variant access: Color::Green
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue);
if sym.decl != null as *Decl && sym.decl.kind == dkEnum {
// Emit as variable reference: Color_Green
n.kind = hVar;
n.strValue = String_Concat(String_Concat(expr.child1.strValue, "_"), expr.strValue);
return n;
}
}
n.kind = hFieldPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.strValue = expr.strValue;
// Get struct type from base expr refType
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
n.typeName = expr.child1.refType.typeName;
}
return n;
}
// spawn Callee(args)
if kind == ekSpawn {
n.kind = hSpawn;
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue;
}
if expr.child2 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, expr.child2);
}
return n;
}
// expr.await
if kind == ekAwait {
n.kind = hAwait;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
return n;
}
// Index: arr[idx]
if kind == ekIndex {
n.kind = hIndexPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
// Assign: target = value
if kind == ekAssign {
n.kind = hAssign;
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value
return n;
}
// Cast
if kind == ekCast {
n.kind = hCast;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
if expr.refType != null as *TypeExpr {
let resolvedKind: int = Lcx_ResolveTypeKind(expr.refType);
n.typeKind = resolvedKind;
// For pointer types, construct "PointeeType*"
if expr.refType.kind == tekPointer && expr.refType.pointerPointee != null as *TypeExpr {
n.typeName = String_Concat(expr.refType.pointerPointee.typeName, "*");
} else if !String_Eq(expr.refType.typeName, "") {
n.typeName = expr.refType.typeName;
}
}
return n;
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
n.kind = hStructInit;
n.strValue = expr.structName;
// Lower each field (fields are chained via child3 on synthetic ekField exprs)
var field: *Expr = expr.child1;
var firstField: *HirNode = null as *HirNode;
var lastField: *HirNode = null as *HirNode;
while field != null as *Expr {
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fNode.kind = hBlock; // placeholder, field is identified by name+value
fNode.line = expr.line;
fNode.column = expr.column;
fNode.strValue = field.strValue; // field name
fNode.child1 = Lcx_LowerExpr(ctx, field.child1); // field value
if firstField == null as *HirNode {
firstField = fNode;
lastField = fNode;
} else {
lastField.child3 = fNode;
lastField = fNode;
}
field = field.child3;
}
n.child1 = firstField;
return n;
}
return n;
}
// ---------------------------------------------------------------------------
// Statement lowering
// ---------------------------------------------------------------------------
func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
if stmt == null as *Stmt { return null as *HirNode; }
let line: uint32 = stmt.line;
let col: uint32 = stmt.column;
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = line;
n.column = col;
let kind: int = stmt.kind;
// Let/var → alloca + store
if kind == skLet {
let init: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// alloca for the variable
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = stmt.strValue;
// Set type from the declared type expression
alloca.typeName = "";
if stmt.refStmtType != null as *TypeExpr {
alloca.intValue = stmt.refStmtType.kind;
alloca.typeKind = Lcx_ResolveTypeKind(stmt.refStmtType);
// For pointer types, construct "PointeeType*"
if stmt.refStmtType.kind == tekPointer && stmt.refStmtType.pointerPointee != null as *TypeExpr {
alloca.typeName = String_Concat(stmt.refStmtType.pointerPointee.typeName, "*");
} else if !String_Eq(stmt.refStmtType.typeName, "") {
alloca.typeName = stmt.refStmtType.typeName;
}
}
// Add to scope for field offset lookups
var sym: Symbol;
sym.kind = skVar;
sym.name = stmt.strValue;
sym.typeKind = alloca.typeKind;
sym.typeName = alloca.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym);
// store the init value
n.kind = hStore;
n.child1 = alloca;
n.child2 = init;
return n;
}
// Return
if kind == skReturn {
n.kind = hReturn;
if stmt.child1 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
}
return n;
}
// Expression statement
if kind == skExpr && stmt.child1 != null as *Expr {
return Lcx_LowerExpr(ctx, stmt.child1);
}
// If
if kind == skIf {
n.kind = hIf;
n.child1 = Lcx_LowerExpr(ctx, stmt.child1); // condition
if stmt.refStmtBlock != null as *Block {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
if stmt.refStmtElse != null as *Block {
// Store else block in extraData (child3 is reserved for block chaining)
n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void;
}
return n;
}
// While
if kind == skWhile {
n.kind = hWhile;
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
if stmt.refStmtBlock != null as *Block {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
return n;
}
// Loop
if kind == skLoop {
n.kind = hLoop;
if stmt.refStmtBlock != null as *Block {
n.child1 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
return n;
}
return n;
}
// ---------------------------------------------------------------------------
// Block lowering
// ---------------------------------------------------------------------------
func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode {
if block == null as *Block { return null as *HirNode; }
if block.stmtCount == 0 { return null as *HirNode; }
// Build a linked list of HirNodes via child3:
// node1 (stmt1) → child3 → node2 (stmt2) → child3 → node3 (stmt3) → null
// child3 is safe for chaining because:
// hStore: child1=alloca, child2=value, child3 unused
// hReturn: child1=value, child2/child3 unused
// hCall: child1=arg1, child2=arg2, child3 unused
var firstNode: *HirNode = null as *HirNode;
var prevNode: *HirNode = null as *HirNode;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
let lowered: *HirNode = Lcx_LowerStmt(ctx, stmt);
if lowered != null as *HirNode {
if firstNode == null as *HirNode {
firstNode = lowered;
prevNode = lowered;
} else {
prevNode.child3 = lowered;
prevNode = lowered;
}
}
stmt = stmt.nextStmt;
}
// Wrap in an hBlock node with child1 = first statement in chain
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = block.line;
n.column = block.column;
n.boolValue = true;
n.child1 = firstNode;
return n;
}
// ---------------------------------------------------------------------------
// Param → HirParam conversion
// ---------------------------------------------------------------------------
func Lcx_LowerParam(out: *HirParam, p: *Param) {
out.name = p.name;
if p.refParamType != null as *TypeExpr {
out.typeKind = Lcx_ResolveTypeKind(p.refParamType);
// Use typeName directly if set (parser may have constructed "String*")
if !String_Eq(p.refParamType.typeName, "") {
out.typeName = p.refParamType.typeName;
} else if p.refParamType.kind == tekPointer && p.refParamType.pointerPointee != null as *TypeExpr {
out.typeName = String_Concat(p.refParamType.pointerPointee.typeName, "*");
} else {
out.typeName = "";
}
} else {
out.typeKind = 0;
out.typeName = "";
}
}
// ---------------------------------------------------------------------------
// Function lowering
// ---------------------------------------------------------------------------
func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
f.name = decl.strValue;
f.isPublic = decl.isPublic;
f.paramCount = decl.paramCount;
Lcx_LowerParam(&f.param0, &decl.param0);
Lcx_LowerParam(&f.param1, &decl.param1);
Lcx_LowerParam(&f.param2, &decl.param2);
Lcx_LowerParam(&f.param3, &decl.param3);
Lcx_LowerParam(&f.param4, &decl.param4);
Lcx_LowerParam(&f.param5, &decl.param5);
Lcx_LowerParam(&f.param6, &decl.param6);
Lcx_LowerParam(&f.param7, &decl.param7);
Lcx_LowerParam(&f.param8, &decl.param8);
if decl.retType != null as *TypeExpr {
f.retTypeName = decl.retType.typeName;
f.retTypeKind = Lcx_ResolveTypeKind(decl.retType);
} else {
f.retTypeName = "";
f.retTypeKind = 0;
}
// Add parameters to hir_lower scope for field offset lookups
var pi: int = 0;
while pi < decl.paramCount {
var p: *Param = null as *Param;
if pi == 0 { p = &decl.param0; }
else if pi == 1 { p = &decl.param1; }
else if pi == 2 { p = &decl.param2; }
else if pi == 3 { p = &decl.param3; }
else if pi == 4 { p = &decl.param4; }
else if pi == 5 { p = &decl.param5; }
else if pi == 6 { p = &decl.param6; }
else if pi == 7 { p = &decl.param7; }
else if pi == 8 { p = &decl.param8; }
if p != null as *Param && p.refParamType != null as *TypeExpr {
var sym: Symbol;
sym.kind = skVar;
sym.name = p.name;
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
sym.typeName = p.refParamType.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym);
}
pi = pi + 1;
}
if decl.refBody != null as *Block {
f.body = Lcx_LowerBlock(ctx, decl.refBody, -1);
} else {
f.body = null as *HirNode;
}
return f;
}
// ---------------------------------------------------------------------------
// Module lowering — main entry point
// ---------------------------------------------------------------------------
func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
let ctx: *LowerCtx = bux_alloc(sizeof(LowerCtx)) as *LowerCtx;
ctx.module = mod;
ctx.scope = sema.scope;
ctx.funcs = bux_alloc(256 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.funcCount = 0;
ctx.externFuncs = bux_alloc(256 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.externCount = 0;
ctx.varCounter = 0;
let hm: *HirModule = bux_alloc(sizeof(HirModule)) as *HirModule;
hm.funcCount = 0;
hm.funcs = ctx.funcs;
hm.structCount = 0;
hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct;
hm.enumCount = 0;
hm.enums = bux_alloc(64 as uint * sizeof(HirEnum)) as *HirEnum;
hm.constCount = 0;
hm.consts = bux_alloc(512 as uint * sizeof(HirConst)) as *HirConst;
// First pass: count structs (to allocate field arrays later)
// Second pass: actually collect them
// For simplicity, do single pass with pre-allocated field arrays
// Iterate declarations
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc {
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
ctx.funcs[ctx.funcCount] = *f;
ctx.funcCount = ctx.funcCount + 1;
}
if decl.kind == dkImpl {
let implTypeName: String = decl.strValue;
var implDecl: *Decl = decl.childDecl1;
while implDecl != null as *Decl {
if implDecl.kind == dkFunc {
let mangled: String = String_Concat(implTypeName, "_");
implDecl.strValue = String_Concat(mangled, implDecl.strValue);
let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl);
ctx.funcs[ctx.funcCount] = *f;
ctx.funcCount = ctx.funcCount + 1;
}
implDecl = implDecl.childDecl2;
}
}
if decl.kind == dkExternFunc {
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
ctx.externFuncs[ctx.externCount] = *f;
ctx.externCount = ctx.externCount + 1;
}
if decl.kind == dkStruct {
// Collect struct definition for C codegen
let si: int = hm.structCount;
hm.structs[si].name = decl.strValue;
hm.structs[si].fieldCount = decl.fieldCount;
hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
var fi: int = 0;
while fi < decl.fieldCount {
var fname: String = "";
var ftype: *TypeExpr = null as *TypeExpr;
if fi == 0 { fname = decl.field0.name; ftype = decl.field0.refFieldType; }
else if fi == 1 { fname = decl.field1.name; ftype = decl.field1.refFieldType; }
else if fi == 2 { fname = decl.field2.name; ftype = decl.field2.refFieldType; }
else if fi == 3 { fname = decl.field3.name; ftype = decl.field3.refFieldType; }
else if fi == 4 { fname = decl.field4.name; ftype = decl.field4.refFieldType; }
else if fi == 5 { fname = decl.field5.name; ftype = decl.field5.refFieldType; }
else if fi == 6 { fname = decl.field6.name; ftype = decl.field6.refFieldType; }
else if fi == 7 { fname = decl.field7.name; ftype = decl.field7.refFieldType; }
// Skip empty field names
if String_Eq(fname, "") {
fi = fi + 1;
continue;
}
hm.structs[si].fields[fi].name = fname;
if ftype != null as *TypeExpr {
if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr {
// Pointer type: emit "TypeName*"
if !String_Eq(ftype.pointerPointee.typeName, "") {
hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*");
}
} else if !String_Eq(ftype.typeName, "") {
hm.structs[si].fields[fi].typeName = ftype.typeName;
}
}
fi = fi + 1;
}
hm.structCount = hm.structCount + 1;
}
if decl.kind == dkConst && hm.constCount < 512 {
let ci: int = hm.constCount;
hm.consts[ci].name = decl.strValue;
var val: int = 0;
if decl.constValue != null as *Expr {
if decl.constValue.kind == ekLiteral {
val = decl.constValue.intValue;
}
}
hm.consts[ci].value = val;
hm.constCount = hm.constCount + 1;
}
if decl.kind == dkEnum {
let ei: int = hm.enumCount;
hm.enums[ei].name = decl.strValue;
hm.enumCount = hm.enumCount + 1;
// Emit enum variants as constants: EnumName_VariantName = value
var vi: int = 0;
while vi < decl.variantCount && hm.constCount < 512 {
var vname: String = "";
if vi == 0 { vname = decl.variant0.name; }
if vi == 1 { vname = decl.variant1.name; }
if vi == 2 { vname = decl.variant2.name; }
if vi == 3 { vname = decl.variant3.name; }
if !String_Eq(vname, "") {
let ci: int = hm.constCount;
hm.consts[ci].name = String_Concat(String_Concat(decl.strValue, "_"), vname);
hm.consts[ci].value = vi;
hm.constCount = hm.constCount + 1;
}
vi = vi + 1;
}
}
decl = decl.childDecl2;
}
hm.funcCount = ctx.funcCount;
hm.funcs = ctx.funcs;
hm.externCount = ctx.externCount;
hm.externFuncs = ctx.externFuncs;
return hm;
}
func HirLower_Free(ctx: *LowerCtx) {
bux_free(ctx.funcs as *void);
bux_free(ctx.externFuncs as *void);
bux_free(ctx as *void);
}
}
-816
View File
@@ -1,816 +0,0 @@
// lexer.bux — Lexer state machine (ported from lexer.nim)
// Tokenizes Bux source into a stream of tokens.
module Lexer {
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Character helpers (wrap C ctype)
// ---------------------------------------------------------------------------
func Lex_IsDigit(c: uint32) -> bool {
return c >= 48 && c <= 57; // '0'..'9'
}
func Lex_IsHexDigit(c: uint32) -> bool {
if c >= 48 && c <= 57 { return true; } // 0-9
if c >= 65 && c <= 70 { return true; } // A-F
if c >= 97 && c <= 102 { return true; } // a-f
return false;
}
func Lex_IsBinDigit(c: uint32) -> bool {
return c == 48 || c == 49; // '0' or '1'
}
func Lex_IsOctDigit(c: uint32) -> bool {
return c >= 48 && c <= 55; // '0'..'7'
}
func Lex_IsIdentStart(c: uint32) -> bool {
if c >= 97 && c <= 122 { return true; } // a-z
if c >= 65 && c <= 90 { return true; } // A-Z
return c == 95; // '_'
}
func Lex_IsIdentChar(c: uint32) -> bool {
if Lex_IsIdentStart(c) { return true; }
return Lex_IsDigit(c);
}
// ---------------------------------------------------------------------------
// Lexer state
// ---------------------------------------------------------------------------
const maxTokens: int = 32768;
const maxDiags: int = 128;
struct LexerDiag {
line: uint32;
column: uint32;
message: String;
}
struct Lexer {
source: String;
sourceLen: int;
pos: int;
line: uint32;
column: uint32;
startLine: uint32;
startColumn: uint32;
startPos: int;
tokenCount: int;
tokens: *LexToken;
diagCount: int;
diags: *LexerDiag;
}
struct LexToken {
kind: int;
text: String;
line: uint32;
column: uint32;
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
func Lexer_Init(source: String) -> Lexer {
let len: uint = bux_strlen(source);
let tokBuf: *LexToken = bux_alloc(maxTokens as uint * sizeof(LexToken)) as *LexToken;
let diagBuf: *LexerDiag = bux_alloc(maxDiags as uint * sizeof(LexerDiag)) as *LexerDiag;
return Lexer {
source: source, sourceLen: len as int,
pos: 0, line: 1, column: 1,
startLine: 0, startColumn: 0, startPos: 0,
tokenCount: 0, tokens: tokBuf,
diagCount: 0, diags: diagBuf
};
}
// ---------------------------------------------------------------------------
// Core primitives
// ---------------------------------------------------------------------------
func lexIsAtEnd(lex: *Lexer) -> bool {
return lex.pos >= lex.sourceLen;
}
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
let i: int = lex.pos + ahead;
if i < lex.sourceLen {
return (lex.source[i] as uint32) & 255;
}
return 0;
}
func lexAdvance(lex: *Lexer) -> uint32 {
let c: uint32 = lexPeek(lex, 0);
if !lexIsAtEnd(lex) {
lex.pos = lex.pos + 1;
if c == 10 { // '\n'
lex.line = lex.line + 1;
lex.column = 1;
} else {
lex.column = lex.column + 1;
}
}
return c;
}
func lexMatch(lex: *Lexer, expected: uint32) -> bool {
if lexIsAtEnd(lex) { return false; }
if lexPeek(lex, 0) != expected { return false; }
discard lexAdvance(lex);
return true;
}
func lexMatchStr(lex: *Lexer, s: String) -> bool {
let len: uint = bux_strlen(s);
var i: int = 0;
while i < (len as int) {
if lexPeek(lex, i) != (s[i] as uint32) {
return false;
}
i = i + 1;
}
i = 0;
while i < (len as int) {
discard lexAdvance(lex);
i = i + 1;
}
return true;
}
// ---------------------------------------------------------------------------
// Token emission
// ---------------------------------------------------------------------------
func lexMarkStart(lex: *Lexer) {
lex.startLine = lex.line;
lex.startColumn = lex.column;
lex.startPos = lex.pos;
}
func lexMakeToken(lex: *Lexer, kind: int) -> LexToken {
var text: String = "";
let endPos: int = lex.pos;
let startPos: int = lex.startPos;
if endPos > startPos {
let len: int = endPos - startPos;
let buf: *char8 = bux_alloc((len + 1) as uint) as *char8;
var i: int = 0;
while i < len {
buf[i] = lex.source[startPos + i] as char8;
i = i + 1;
}
buf[len] = 0 as char8;
text = buf;
} else {
text = "";
}
return LexToken {
kind: kind, text: text,
line: lex.startLine, column: lex.startColumn
};
}
func lexEmitToken(lex: *Lexer, kind: int) {
let tok: LexToken = lexMakeToken(lex, kind);
if lex.tokenCount < maxTokens {
lex.tokens[lex.tokenCount] = tok;
lex.tokenCount = lex.tokenCount + 1;
}
}
func lexSetLastTokenText(lex: *Lexer, text: String) {
if lex.tokenCount > 0 {
lex.tokens[lex.tokenCount - 1].text = text;
}
}
func lexEmitDiag(lex: *Lexer, msg: String) {
if lex.diagCount < maxDiags {
lex.diags[lex.diagCount] = LexerDiag {
line: lex.line, column: lex.column, message: msg
};
lex.diagCount = lex.diagCount + 1;
}
}
// ---------------------------------------------------------------------------
// Whitespace / comments
// ---------------------------------------------------------------------------
func lexSkipLineComment(lex: *Lexer) {
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 10 { // '\n'
discard lexAdvance(lex);
}
}
func lexSkipBlockComment(lex: *Lexer) {
var depth: int = 1;
while !lexIsAtEnd(lex) && depth > 0 {
if lexPeek(lex, 0) == 47 && lexPeek(lex, 1) == 42 { // /*
discard lexAdvance(lex);
discard lexAdvance(lex);
depth = depth + 1;
} else if lexPeek(lex, 0) == 42 && lexPeek(lex, 1) == 47 { // */
discard lexAdvance(lex);
discard lexAdvance(lex);
depth = depth - 1;
} else {
discard lexAdvance(lex);
}
}
if depth > 0 {
lexEmitDiag(lex, "unterminated block comment");
}
}
func lexSkipWhitespace(lex: *Lexer) {
while !lexIsAtEnd(lex) {
let c: uint32 = lexPeek(lex, 0);
if c == 32 || c == 9 || c == 13 {
discard lexAdvance(lex);
} else {
if c == 47 && lexPeek(lex, 1) == 47 {
lexSkipLineComment(lex);
} else {
if c == 47 && lexPeek(lex, 1) == 42 {
discard lexAdvance(lex);
discard lexAdvance(lex);
lexSkipBlockComment(lex);
} else {
break;
}
}
}
}
}
// ---------------------------------------------------------------------------
// Identifiers
// ---------------------------------------------------------------------------
func lexIsIdentKind(tok: int) -> bool {
return tok == tkFunc || tok == tkLet || tok == tkVar || tok == tkConst
|| tok == tkType || tok == tkStruct || tok == tkEnum || tok == tkUnion
|| tok == tkInterface || tok == tkExtend || tok == tkModule || tok == tkImport
|| tok == tkPub || tok == tkExtern || tok == tkIf || tok == tkElse
|| tok == tkWhile || tok == tkDo || tok == tkLoop || tok == tkFor
|| tok == tkIn || tok == tkBreak || tok == tkContinue || tok == tkReturn
|| tok == tkMatch || tok == tkAs || tok == tkIs || tok == tkNull
|| tok == tkSelf || tok == tkSuper;
}
func lexKeywordKind(text: String) -> int {
if String_Eq(text, "true") { return tkBoolLiteral; }
if String_Eq(text, "false") { return tkBoolLiteral; }
if String_Eq(text, "func") { return tkFunc; }
if String_Eq(text, "let") { return tkLet; }
if String_Eq(text, "var") { return tkVar; }
if String_Eq(text, "const") { return tkConst; }
if String_Eq(text, "type") { return tkType; }
if String_Eq(text, "struct") { return tkStruct; }
if String_Eq(text, "enum") { return tkEnum; }
if String_Eq(text, "union") { return tkUnion; }
if String_Eq(text, "interface") { return tkInterface; }
if String_Eq(text, "extend") { return tkExtend; }
if String_Eq(text, "module") { return tkModule; }
if String_Eq(text, "import") { return tkImport; }
if String_Eq(text, "pub") { return tkPub; }
if String_Eq(text, "extern") { return tkExtern; }
if String_Eq(text, "if") { return tkIf; }
if String_Eq(text, "else") { return tkElse; }
if String_Eq(text, "while") { return tkWhile; }
if String_Eq(text, "do") { return tkDo; }
if String_Eq(text, "loop") { return tkLoop; }
if String_Eq(text, "for") { return tkFor; }
if String_Eq(text, "in") { return tkIn; }
if String_Eq(text, "break") { return tkBreak; }
if String_Eq(text, "continue") { return tkContinue; }
if String_Eq(text, "return") { return tkReturn; }
if String_Eq(text, "match") { return tkMatch; }
if String_Eq(text, "as") { return tkAs; }
if String_Eq(text, "is") { return tkIs; }
if String_Eq(text, "null") { return tkNull; }
if String_Eq(text, "self") { return tkSelf; }
if String_Eq(text, "super") { return tkSuper; }
if String_Eq(text, "sizeof") { return tkSizeOf; }
if String_Eq(text, "discard") { return tkDiscard; }
if String_Eq(text, "async") { return tkAsync; }
if String_Eq(text, "await") { return tkAwait; }
if String_Eq(text, "spawn") { return tkSpawn; }
return tkIdent;
}
func lexScanIdent(lex: *Lexer) {
lexMarkStart(lex);
while !lexIsAtEnd(lex) && Lex_IsIdentChar(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
let tok: LexToken = lexMakeToken(lex, tkIdent);
let kind: int = lexKeywordKind(tok.text);
tok.kind = kind;
if lex.tokenCount < maxTokens {
lex.tokens[lex.tokenCount] = tok;
lex.tokenCount = lex.tokenCount + 1;
}
}
// ---------------------------------------------------------------------------
// Numbers
// ---------------------------------------------------------------------------
func lexScanDigits(lex: *Lexer) {
while !lexIsAtEnd(lex) && Lex_IsDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
func lexScanHexDigits(lex: *Lexer) {
while !lexIsAtEnd(lex) && Lex_IsHexDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
func lexScanNumber(lex: *Lexer) {
lexMarkStart(lex);
var isFloat: bool = false;
if lexPeek(lex, 0) == 48 { // '0'
let p1: uint32 = lexPeek(lex, 1);
if p1 == 120 || p1 == 88 || p1 == 98 || p1 == 66 || p1 == 111 || p1 == 79 { // x X b B o O
discard lexAdvance(lex); // 0
discard lexAdvance(lex); // prefix
if p1 == 120 || p1 == 88 { lexScanHexDigits(lex); }
else if p1 == 98 || p1 == 66 {
while !lexIsAtEnd(lex) && Lex_IsBinDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
else {
while !lexIsAtEnd(lex) && Lex_IsOctDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
lexEmitToken(lex, tkIntLiteral);
return;
}
}
lexScanDigits(lex);
if lexPeek(lex, 0) == 46 && Lex_IsDigit(lexPeek(lex, 1)) { // . digit
isFloat = true;
discard lexAdvance(lex); // .
lexScanDigits(lex);
}
let e: uint32 = lexPeek(lex, 0);
if e == 101 || e == 69 { // e E
isFloat = true;
discard lexAdvance(lex);
let sign: uint32 = lexPeek(lex, 0);
if sign == 43 || sign == 45 { discard lexAdvance(lex); } // + -
lexScanDigits(lex);
}
if isFloat {
lexEmitToken(lex, tkFloatLiteral);
} else {
lexEmitToken(lex, tkIntLiteral);
}
}
// ---------------------------------------------------------------------------
// Strings and chars
// ---------------------------------------------------------------------------
func lexScanBacktickString(lex: *Lexer) {
lexMarkStart(lex);
if lexPeek(lex, 0) == 96 { discard lexAdvance(lex); } // opening backtick
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 96 {
discard lexAdvance(lex);
}
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated backtick string literal");
} else {
discard lexAdvance(lex); // closing backtick
}
lexEmitToken(lex, tkStringLiteral);
}
func lexScanString(lex: *Lexer) {
lexMarkStart(lex);
// Collect the prefix (before opening quote) for the token text
var prefix: String = "";
var prefixLen: int = 0;
if lex.pos > lex.startPos {
let plen: int = lex.pos - lex.startPos;
let pbuf: *char8 = bux_alloc((plen + 1) as uint) as *char8;
var pi: int = 0;
while pi < plen {
pbuf[pi] = lex.source[lex.startPos + pi] as char8;
pi = pi + 1;
}
pbuf[plen] = 0 as char8;
prefix = pbuf;
prefixLen = plen;
}
// Allocate buffer for resolved content (max = remaining source length)
let maxLen: int = 4096;
let resolved: *char8 = bux_alloc(maxLen as uint) as *char8;
var rpos: int = 0;
if lexPeek(lex, 0) == 34 { discard lexAdvance(lex); } // opening "
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 34 { // closing "
if lexPeek(lex, 0) == 10 { // \n
lexEmitDiag(lex, "unterminated string literal");
break;
}
if lexPeek(lex, 0) == 92 { // backslash
discard lexAdvance(lex);
if !lexIsAtEnd(lex) {
let ec: uint32 = lexAdvance(lex);
var rc: char8 = ec as char8;
if ec == 110 { rc = 10 as char8; } // \n
else if ec == 114 { rc = 13 as char8; } // \r
else if ec == 116 { rc = 9 as char8; } // \t
else if ec == 48 { rc = 0 as char8; } // \0
else if ec == 92 { rc = 92 as char8; } // \\
else if ec == 34 { rc = 34 as char8; } // \"
else if ec == 39 { rc = 39 as char8; } // \'
resolved[rpos] = rc;
rpos = rpos + 1;
}
} else {
let c: uint32 = lexAdvance(lex);
resolved[rpos] = c as char8;
rpos = rpos + 1;
}
}
resolved[rpos] = 0 as char8;
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated string literal");
} else {
discard lexAdvance(lex); // closing "
}
lexEmitToken(lex, tkStringLiteral);
// Replace token text with resolved version: prefix + " + resolved + "
let totalLen: int = prefixLen + 1 + rpos + 1;
let finalBuf: *char8 = bux_alloc((totalLen + 1) as uint) as *char8;
var fi: int = 0;
var pi2: int = 0;
while pi2 < prefixLen { finalBuf[fi] = prefix[pi2] as char8; fi = fi + 1; pi2 = pi2 + 1; }
finalBuf[fi] = 34 as char8; fi = fi + 1; // opening "
var ri: int = 0;
while ri < rpos { finalBuf[fi] = resolved[ri]; fi = fi + 1; ri = ri + 1; }
finalBuf[fi] = 34 as char8; fi = fi + 1; // closing "
finalBuf[fi] = 0 as char8;
lexSetLastTokenText(lex, finalBuf);
}
func lexScanChar(lex: *Lexer) {
lexMarkStart(lex);
// Collect the prefix for the token text
var prefix: String = "";
var prefixLen: int = 0;
if lex.pos > lex.startPos {
let plen: int = lex.pos - lex.startPos;
let pbuf: *char8 = bux_alloc((plen + 1) as uint) as *char8;
var pi: int = 0;
while pi < plen {
pbuf[pi] = lex.source[lex.startPos + pi] as char8;
pi = pi + 1;
}
pbuf[plen] = 0 as char8;
prefix = pbuf;
prefixLen = plen;
}
var resolved: char8 = 0 as char8;
if lexPeek(lex, 0) == 39 { discard lexAdvance(lex); } // opening '
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated char literal");
lexEmitToken(lex, tkCharLiteral);
return;
}
if lexPeek(lex, 0) == 10 { // \n
lexEmitDiag(lex, "newline in char literal");
} else if lexPeek(lex, 0) == 92 { // backslash
discard lexAdvance(lex);
if !lexIsAtEnd(lex) {
let ec: uint32 = lexAdvance(lex);
if ec == 110 { resolved = 10 as char8; } // \n
else if ec == 114 { resolved = 13 as char8; } // \r
else if ec == 116 { resolved = 9 as char8; } // \t
else if ec == 48 { resolved = 0 as char8; } // \0
else if ec == 92 { resolved = 92 as char8; } // \\
else if ec == 34 { resolved = 34 as char8; } // \"
else if ec == 39 { resolved = 39 as char8; } // \'
else { resolved = ec as char8; }
}
} else {
resolved = lexAdvance(lex) as char8;
}
if lexIsAtEnd(lex) || lexPeek(lex, 0) != 39 {
lexEmitDiag(lex, "expected closing ' for char literal");
} else {
discard lexAdvance(lex); // closing '
}
lexEmitToken(lex, tkCharLiteral);
// Replace token text with resolved version
let totalLen: int = prefixLen + 1 + 1 + 1;
let finalBuf: *char8 = bux_alloc((totalLen + 1) as uint) as *char8;
var fi: int = 0;
var pi2: int = 0;
while pi2 < prefixLen { finalBuf[fi] = prefix[pi2] as char8; fi = fi + 1; pi2 = pi2 + 1; }
finalBuf[fi] = 39 as char8; fi = fi + 1; // opening '
finalBuf[fi] = resolved; fi = fi + 1; // resolved char
finalBuf[fi] = 39 as char8; fi = fi + 1; // closing '
finalBuf[fi] = 0 as char8;
lexSetLastTokenText(lex, finalBuf);
}
// ---------------------------------------------------------------------------
// Symbols / operators
// ---------------------------------------------------------------------------
func lexScanSymbol(lex: *Lexer) {
lexMarkStart(lex);
let c: uint32 = lexAdvance(lex);
// Single-char tokens
if c == 40 { lexEmitToken(lex, tkLParen); return; }
if c == 41 { lexEmitToken(lex, tkRParen); return; }
if c == 123 { lexEmitToken(lex, tkLBrace); return; }
if c == 125 { lexEmitToken(lex, tkRBrace); return; }
if c == 91 { lexEmitToken(lex, tkLBracket); return; }
if c == 93 { lexEmitToken(lex, tkRBracket); return; }
if c == 44 { lexEmitToken(lex, tkComma); return; }
if c == 59 { lexEmitToken(lex, tkSemicolon); return; }
if c == 64 { lexEmitToken(lex, tkAt); return; }
if c == 63 { lexEmitToken(lex, tkQuestion); return; }
if c == 126 { lexEmitToken(lex, tkTilde); return; }
// : ::
if c == 58 {
if lexMatch(lex, 58) { lexEmitToken(lex, tkColonColon); }
else { lexEmitToken(lex, tkColon); }
return;
}
// . .. ... ..=
if c == 46 {
if lexPeek(lex, 0) == 46 && lexPeek(lex, 1) == 46 {
discard lexAdvance(lex); discard lexAdvance(lex);
lexEmitToken(lex, tkDotDotDot); return;
}
if lexPeek(lex, 0) == 46 && lexPeek(lex, 1) == 61 {
discard lexAdvance(lex); discard lexAdvance(lex);
lexEmitToken(lex, tkDotDotEqual); return;
}
if lexMatch(lex, 46) { lexEmitToken(lex, tkDotDot); return; }
lexEmitToken(lex, tkDot); return;
}
// - -> -- -=
if c == 45 {
if lexMatch(lex, 62) { lexEmitToken(lex, tkArrow); return; }
if lexMatch(lex, 45) { lexEmitToken(lex, tkMinusMinus); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkMinusAssign); return; }
lexEmitToken(lex, tkMinus); return;
}
// + ++ +=
if c == 43 {
if lexMatch(lex, 43) { lexEmitToken(lex, tkPlusPlus); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkPlusAssign); return; }
lexEmitToken(lex, tkPlus); return;
}
// * ** *=
if c == 42 {
if lexMatch(lex, 42) { lexEmitToken(lex, tkStarStar); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkStarAssign); return; }
lexEmitToken(lex, tkStar); return;
}
// / /=
if c == 47 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkSlashAssign); return; }
lexEmitToken(lex, tkSlash); return;
}
// % %=
if c == 37 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkPercentAssign); return; }
lexEmitToken(lex, tkPercent); return;
}
// = == =>
if c == 61 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkEq); return; }
if lexMatch(lex, 62) { lexEmitToken(lex, tkFatArrow); return; }
lexEmitToken(lex, tkAssign); return;
}
// ! !=
if c == 33 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkNe); return; }
lexEmitToken(lex, tkBang); return;
}
// < <= << <<=
if c == 60 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkLe); return; }
if lexMatch(lex, 60) {
if lexMatch(lex, 61) { lexEmitToken(lex, tkShlAssign); return; }
lexEmitToken(lex, tkShl); return;
}
lexEmitToken(lex, tkLt); return;
}
// > >= >> >>=
if c == 62 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkGe); return; }
if lexMatch(lex, 62) {
if lexMatch(lex, 61) { lexEmitToken(lex, tkShrAssign); return; }
lexEmitToken(lex, tkShr); return;
}
lexEmitToken(lex, tkGt); return;
}
// & && &=
if c == 38 {
if lexMatch(lex, 38) { lexEmitToken(lex, tkAmpAmp); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkAmpAssign); return; }
lexEmitToken(lex, tkAmp); return;
}
// | || |=
if c == 124 {
if lexMatch(lex, 124) { lexEmitToken(lex, tkPipePipe); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkPipeAssign); return; }
lexEmitToken(lex, tkPipe); return;
}
// ^ ^=
if c == 94 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkCaretAssign); return; }
lexEmitToken(lex, tkCaret); return;
}
// # intrinsics
if c == 35 {
if lexMatchStr(lex, "line") { lexEmitToken(lex, tkHashLine); return; }
if lexMatchStr(lex, "column") { lexEmitToken(lex, tkHashColumn); return; }
if lexMatchStr(lex, "file") { lexEmitToken(lex, tkHashFile); return; }
if lexMatchStr(lex, "function") { lexEmitToken(lex, tkHashFunction); return; }
if lexMatchStr(lex, "date") { lexEmitToken(lex, tkHashDate); return; }
if lexMatchStr(lex, "time") { lexEmitToken(lex, tkHashTime); return; }
if lexMatchStr(lex, "module") { lexEmitToken(lex, tkHashModule); return; }
lexEmitToken(lex, tkHash); return;
}
lexEmitDiag(lex, "unexpected character");
lexEmitToken(lex, tkUnknown);
}
// ---------------------------------------------------------------------------
// Next token
// ---------------------------------------------------------------------------
func lexNextToken(lex: *Lexer) {
lexSkipWhitespace(lex);
if lexIsAtEnd(lex) {
lexMarkStart(lex);
lexEmitToken(lex, tkEndOfFile);
return;
}
let c: uint32 = lexPeek(lex, 0);
if c == 10 { // \n
lexMarkStart(lex);
discard lexAdvance(lex);
lexEmitToken(lex, tkNewLine);
return;
}
// String prefixes: c8" c16" c32"
if c == 99 { // 'c'
let d: uint32 = lexPeek(lex, 1);
if d == 56 && lexPeek(lex, 2) == 34 { // c8"
discard lexAdvance(lex); discard lexAdvance(lex); // c 8
lexScanString(lex); return;
}
if d == 49 && lexPeek(lex, 2) == 54 && lexPeek(lex, 3) == 34 { // c16"
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanString(lex); return;
}
if d == 51 && lexPeek(lex, 2) == 50 && lexPeek(lex, 3) == 34 { // c32"
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanString(lex); return;
}
}
if c == 34 { // "
lexScanString(lex); return;
}
// Backtick raw string
if c == 96 { // backtick
lexScanBacktickString(lex); return;
}
// Char prefixes: c8' c16' c32'
if c == 99 { // 'c'
let d: uint32 = lexPeek(lex, 1);
if d == 56 && lexPeek(lex, 2) == 39 { // c8'
discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
if d == 49 && lexPeek(lex, 2) == 54 && lexPeek(lex, 3) == 39 { // c16'
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
if d == 51 && lexPeek(lex, 2) == 50 && lexPeek(lex, 3) == 39 { // c32'
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
}
if c == 39 { // '
lexScanChar(lex); return;
}
if Lex_IsIdentStart(c) {
lexScanIdent(lex); return;
}
if Lex_IsDigit(c) {
lexScanNumber(lex); return;
}
lexScanSymbol(lex);
}
// ---------------------------------------------------------------------------
// Tokenize — main entry point
// ---------------------------------------------------------------------------
func Lexer_Tokenize(source: String) -> *Lexer {
let lex: *Lexer = bux_alloc(sizeof(Lexer)) as *Lexer;
lex.source = source;
lex.sourceLen = bux_strlen(source) as int;
lex.pos = 0;
lex.line = 1;
lex.column = 1;
lex.startLine = 0;
lex.startColumn = 0;
lex.startPos = 0;
let tokBuf: *LexToken = bux_alloc(maxTokens as uint * sizeof(LexToken)) as *LexToken;
let diagBuf: *LexerDiag = bux_alloc(maxDiags as uint * sizeof(LexerDiag)) as *LexerDiag;
lex.tokens = tokBuf;
lex.diags = diagBuf;
lex.tokenCount = 0;
lex.diagCount = 0;
while true {
lexNextToken(lex);
if lex.tokenCount >= maxTokens {
lexEmitDiag(lex, "too many tokens");
break;
}
if lex.tokens[lex.tokenCount - 1].kind == tkEndOfFile {
break;
}
}
return lex;
}
func Lexer_TokenCount(lex: *Lexer) -> int {
return lex.tokenCount;
}
func Lexer_GetToken(lex: *Lexer, index: int) -> LexToken {
if index >= 0 && index < lex.tokenCount {
return lex.tokens[index];
}
var empty: LexToken = LexToken { kind: tkEndOfFile, text: "", line: 0, column: 0 };
return empty;
}
func Lexer_DiagCount(lex: *Lexer) -> int {
return lex.diagCount;
}
func Lexer_Free(lex: *Lexer) {
bux_free(lex.tokens as *void);
bux_free(lex.diags as *void);
bux_free(lex as *void);
}
}
-89
View File
@@ -1,89 +0,0 @@
// manifest.bux — Manifest parser for bux.toml files
// Parses package metadata: name, version, type, build output.
module Manifest {
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Manifest struct
// ---------------------------------------------------------------------------
struct Manifest {
name: String;
version: String;
pkgType: String;
output: String;
}
// ---------------------------------------------------------------------------
// Simple TOML parser (handles [Package] and [Build] sections)
// ---------------------------------------------------------------------------
func Manifest_Parse(content: String) -> Manifest {
var m: Manifest;
m.name = "";
m.version = "0.1.0";
m.pkgType = "bin";
m.output = "Bin";
if String_Eq(content, "") { return m; }
var currentSection: String = "";
let count: uint = String_SplitCount(content, "\n");
var i: uint = 0;
while i < count {
let line: String = String_SplitPart(content, "\n", i);
// Skip empty lines and comments
if String_Eq(line, "") { i = i + 1; continue; }
if String_StartsWith(line, "#") { i = i + 1; continue; }
// Section header: [Section]
if String_StartsWith(line, "[") {
if String_StartsWith(line, "[Package]") {
currentSection = "Package";
} else if String_StartsWith(line, "[Build]") {
currentSection = "Build";
} else {
currentSection = "";
}
i = i + 1; continue;
}
// Key = Value
let eqCount: uint = String_SplitCount(line, "=");
if eqCount >= 2 {
let key: String = String_Trim(String_SplitPart(line, "=", 0));
let rawVal: String = String_Trim(String_SplitPart(line, "=", 1));
// Strip quotes from value
var val: String = rawVal;
if String_StartsWith(val, "\"") && String_EndsWith(val, "\"") {
let vlen: uint = bux_strlen(val);
if vlen >= 2 {
val = String_Slice(val, 1, vlen - 2);
}
}
if String_Eq(currentSection, "Package") {
if String_Eq(key, "Name") { m.name = val; }
if String_Eq(key, "Version") { m.version = val; }
if String_Eq(key, "Type") { m.pkgType = val; }
} else if String_Eq(currentSection, "Build") {
if String_Eq(key, "Output") { m.output = val; }
}
}
i = i + 1;
}
return m;
}
// ---------------------------------------------------------------------------
// Load manifest from file
// ---------------------------------------------------------------------------
func Manifest_Load(path: String) -> Manifest {
let content: String = ReadFile(path);
return Manifest_Parse(content);
}
}
File diff suppressed because it is too large Load Diff
-95
View File
@@ -1,95 +0,0 @@
// scope.bux — Symbol table with parent-chain lookup
module Scope {
// Symbol kinds
const skVar: int = 0;
const skFunc: int = 1;
const skType: int = 2;
const skConst: int = 3;
const skModule: int = 4;
// Maximum symbols per scope
const maxSymbols: int = 8192;
struct Symbol {
kind: int;
name: String;
typeKind: int;
typeName: String;
isMutable: bool;
isPublic: bool;
decl: *Decl; // associated declaration (for funcs, structs, enums)
}
struct Scope {
symbols: *Symbol;
count: int;
parent: *Scope;
}
// ---------------------------------------------------------------------------
// Scope operations
// ---------------------------------------------------------------------------
func Scope_New() -> Scope {
let sz: uint = maxSymbols as uint * sizeof(Symbol);
let data: *Symbol = bux_alloc(sz) as *Symbol;
return Scope { symbols: data, count: 0, parent: null as *Scope };
}
func Scope_NewChild(parent: *Scope) -> Scope {
let sz: uint = maxSymbols as uint * sizeof(Symbol);
let data: *Symbol = bux_alloc(sz) as *Symbol;
return Scope { symbols: data, count: 0, parent: parent };
}
func Scope_Define(scope: *Scope, sym: Symbol) -> bool {
// Check local scope for duplicates
var i: int = 0;
while i < scope.count {
if String_Eq(scope.symbols[i].name, sym.name) {
return false;
}
i = i + 1;
}
if scope.count < maxSymbols {
scope.symbols[scope.count] = sym;
scope.count = scope.count + 1;
return true;
}
return false;
}
func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
var cur: *Scope = scope;
while cur != null as *Scope {
var i: int = 0;
while i < cur.count {
if String_Eq(cur.symbols[i].name, name) {
return cur.symbols[i];
}
i = i + 1;
}
cur = cur.parent;
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
var i: int = 0;
while i < scope.count {
if String_Eq(scope.symbols[i].name, name) {
return scope.symbols[i];
}
i = i + 1;
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
func Scope_Free(scope: *Scope) {
bux_free(scope.symbols as *void);
}
}
-753
View File
@@ -1,753 +0,0 @@
// sema.bux — Semantic analysis (type checker, ported from sema.nim)
// Validates types, resolves identifiers, checks function calls.
module Sema {
// ---------------------------------------------------------------------------
// Sema context
// ---------------------------------------------------------------------------
struct Sema {
module: *Module;
scope: *Scope;
typeTable: *void;
methodTable: *void;
diagCount: int;
diags: *SemaDiag;
hasError: bool;
currentRetType: int; // return type of the function being checked
}
struct SemaDiag {
line: uint32;
column: uint32;
message: String;
}
// ---------------------------------------------------------------------------
// Diagnostics
// ---------------------------------------------------------------------------
func Sema_EmitError(sema: *Sema, line: uint32, col: uint32, msg: String) {
if sema.diagCount < 256 {
sema.diags[sema.diagCount] = SemaDiag { line: line, column: col, message: msg };
sema.diagCount = sema.diagCount + 1;
}
sema.hasError = true;
}
// ---------------------------------------------------------------------------
// Symbol zero-init helper (bootstrap C backend does not zero-init structs)
// ---------------------------------------------------------------------------
func Sema_ZeroInitSymbol(sym: *Symbol) {
sym.kind = 0;
sym.name = "";
sym.typeKind = 0;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
}
// ---------------------------------------------------------------------------
// Type resolution from TypeExpr → Type constants
// ---------------------------------------------------------------------------
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer {
return tyPointer;
}
if String_Eq(name, "void") { return tyVoid; }
if String_Eq(name, "bool") { return tyBool; }
if String_Eq(name, "bool8") { return tyBool8; }
if String_Eq(name, "bool16") { return tyBool16; }
if String_Eq(name, "bool32") { return tyBool32; }
if String_Eq(name, "char8") { return tyChar8; }
if String_Eq(name, "char16") { return tyChar16; }
if String_Eq(name, "char32") { return tyChar32; }
if String_Eq(name, "String") { return tyStr; }
if String_Eq(name, "str") { return tyStr; }
if String_Eq(name, "int8") { return tyInt8; }
if String_Eq(name, "int16") { return tyInt16; }
if String_Eq(name, "int32") { return tyInt32; }
if String_Eq(name, "int64") { return tyInt64; }
if String_Eq(name, "int") { return tyInt; }
if String_Eq(name, "uint8") { return tyUInt8; }
if String_Eq(name, "uint16") { return tyUInt16; }
if String_Eq(name, "uint32") { return tyUInt32; }
if String_Eq(name, "uint64") { return tyUInt64; }
if String_Eq(name, "uint") { return tyUInt; }
if String_Eq(name, "float32") { return tyFloat32; }
if String_Eq(name, "float64") { return tyFloat64; }
if String_Eq(name, "float") { return tyFloat64; }
// Check type table for user-defined types (StringMap not yet supported)
// TODO: re-enable when StringMap generic is available
// if sema.typeTable != null as *void { ... }
return tyNamed; // assume named type
}
// ---------------------------------------------------------------------------
// Type predicates
// ---------------------------------------------------------------------------
func Sema_IsNumeric(kind: int) -> bool {
if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; }
if kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt { return true; }
if kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt { return true; }
if kind == tyFloat32 || kind == tyFloat64 { return true; }
return false;
}
func Sema_IsBool(kind: int) -> bool {
return kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32;
}
func Sema_TypeName(kind: int) -> String {
if kind == tyUnknown { return "?"; }
if kind == tyVoid { return "void"; }
if kind == tyBool { return "bool"; }
if kind == tyInt{ return "int"; }
if kind == tyInt64 { return "int64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyStr { return "String"; }
if kind == tyPointer { return "*ptr"; }
if kind == tyNamed { return "user-type"; }
if kind == tyTypeParam { return "type-param"; }
return "?";
}
// ---------------------------------------------------------------------------
// Block checking helper
// ---------------------------------------------------------------------------
func Sema_CheckBlock(sema: *Sema, block: *Block) {
if block == null as *Block { return; }
var blockScope: Scope = Scope_NewChild(sema.scope);
let prevScope: *Scope = sema.scope;
sema.scope = &blockScope;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
Sema_CheckStmt(sema, stmt);
stmt = stmt.nextStmt;
}
sema.scope = prevScope;
}
// ---------------------------------------------------------------------------
// Expression type checking
// ---------------------------------------------------------------------------
func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if expr == null as *Expr { return tyUnknown; }
let kind: int = expr.kind;
// Debug: print expr kind
// Print("Sema_CheckExpr kind=");
// PrintInt(kind as int64);
// PrintLine("");
// Literal
if kind == ekLiteral {
let tk: int = expr.tokKind;
if tk == tkIntLiteral { return tyInt; }
if tk == tkFloatLiteral { return tyFloat64; }
if tk == tkStringLiteral { return tyStr; }
if tk == tkBoolLiteral { return tyBool; }
if tk == tkCharLiteral { return tyChar32; }
if tk == tkNull { return tyPointer; }
return tyUnknown;
}
// Identifier — look up in scope
if kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.strValue);
if sym.kind == 0 && !String_Eq(sym.name, expr.strValue) {
Sema_EmitError(sema, expr.line, expr.column, "undeclared identifier");
return tyUnknown;
}
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = sym.typeName;
expr.refType = te;
}
return sym.typeKind;
}
// self
if kind == ekSelf {
let sym: Symbol = Scope_Lookup(sema.scope, "self");
if sym.kind == 0 && !String_Eq(sym.name, "self") {
Sema_EmitError(sema, expr.line, expr.column, "self outside method");
return tyUnknown;
}
return sym.typeKind;
}
// Binary
if kind == ekBinary {
let left: int = Sema_CheckExpr(sema, expr.child1);
let right: int = Sema_CheckExpr(sema, expr.child2);
let op: int = expr.intValue;
// Assignment operators return target type
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
return left;
}
// Comparison operators return bool
if op >= tkEq && op <= tkGe { return tyBool; }
// Logical operators return bool
if op == tkAmpAmp || op == tkPipePipe || op == tkBang { return tyBool; }
// Arithmetic returns wider type
if !Sema_IsNumeric(left) || !Sema_IsNumeric(right) {
Sema_EmitError(sema, expr.line, expr.column, "arithmetic requires numeric operands");
}
if left == tyFloat64 || right == tyFloat64 { return tyFloat64; }
return tyInt;
}
// Unary
if kind == ekUnary {
let operand: int = Sema_CheckExpr(sema, expr.child1);
let op: int = expr.intValue;
if op == tkBang { return tyBool; }
if op == tkStar { return tyUnknown; } // dereference — resolve pointee
if op == tkAmp { return tyPointer; }
return operand;
}
// Assign
if kind == ekAssign {
let target: int = Sema_CheckExpr(sema, expr.child1);
let value: int = Sema_CheckExpr(sema, expr.child2);
// Basic type compatibility (permissive for now)
return target;
}
// Call
if kind == ekCall {
discard Sema_CheckExpr(sema, expr.child1);
var arg: *ExprList = expr.callArgs;
while arg != null as *ExprList {
discard Sema_CheckExpr(sema, arg.expr);
arg = arg.next;
}
// Try to resolve return type from function declaration
if expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl && sym.decl.retType != null as *TypeExpr {
return Sema_ResolveType(sema, sym.decl.retType);
}
}
return tyUnknown;
}
// Ternary
if kind == ekTernary {
return Sema_CheckExpr(sema, expr.child2); // then type
}
// Cast — return target type
if kind == ekCast {
if expr.refType != null as *TypeExpr {
return Sema_ResolveType(sema, expr.refType);
}
return tyUnknown;
}
// Try (?)
if kind == ekTry {
let inner: int = Sema_CheckExpr(sema, expr.child1);
return inner; // simplified
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
return tyNamed;
}
// Field access
if kind == ekField {
discard Sema_CheckExpr(sema, expr.child1);
return tyUnknown;
}
// Index
if kind == ekIndex {
let obj: int = Sema_CheckExpr(sema, expr.child1);
let idx: int = Sema_CheckExpr(sema, expr.child2);
if !Sema_IsNumeric(idx) && idx != tyUnknown {
Sema_EmitError(sema, expr.line, expr.column, "index must be integer");
}
return tyUnknown;
}
// Block expression
if kind == ekBlock {
if expr.refBlock != null as *Block {
Sema_CheckBlock(sema, expr.refBlock);
}
return tyVoid;
}
return tyUnknown;
}
// ---------------------------------------------------------------------------
// Statement checking
// ---------------------------------------------------------------------------
func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
if stmt == null as *Stmt { return; }
let kind: int = stmt.kind;
// Let/var
if kind == skLet {
let initType: int = Sema_CheckExpr(sema, stmt.child1);
// Register variable in scope
var sym: Symbol;
sym.kind = skVar;
sym.name = stmt.strValue;
sym.typeKind = initType;
sym.typeName = "";
if stmt.refStmtType != null as *TypeExpr {
sym.typeName = stmt.refStmtType.typeName;
}
sym.isMutable = stmt.boolValue;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
return;
}
// Return
if kind == skReturn {
if stmt.child1 != null as *Expr {
let retType: int = Sema_CheckExpr(sema, stmt.child1);
if sema.currentRetType != tyUnknown && retType != tyUnknown {
if retType != sema.currentRetType {
// Be permissive: allow numeric widening, pointer compatibility
if !Sema_IsNumeric(retType) || !Sema_IsNumeric(sema.currentRetType) {
// Allow pointer-type compatibility (String <-> *T, *T <-> *U)
let retIsPtr: bool = retType == tyPointer || retType == tyStr;
let expectedIsPtr: bool = sema.currentRetType == tyPointer || sema.currentRetType == tyStr;
if !retIsPtr || !expectedIsPtr {
Sema_EmitError(sema, stmt.line, stmt.column, "return type mismatch");
}
}
}
}
} else {
if sema.currentRetType != tyVoid && sema.currentRetType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "missing return value");
}
}
return;
}
// If
if kind == skIf {
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "if condition must be bool");
}
Sema_CheckBlock(sema, stmt.refStmtBlock);
Sema_CheckBlock(sema, stmt.refStmtElse);
return;
}
// While
if kind == skWhile {
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "while condition must be bool");
}
Sema_CheckBlock(sema, stmt.refStmtBlock);
return;
}
// Do-while
if kind == skDoWhile {
Sema_CheckBlock(sema, stmt.refStmtBlock);
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "do-while condition must be bool");
}
return;
}
// Loop
if kind == skLoop {
Sema_CheckBlock(sema, stmt.refStmtBlock);
return;
}
// For
if kind == skFor {
discard Sema_CheckExpr(sema, stmt.child1);
var forScope: Scope = Scope_NewChild(sema.scope);
var loopSym: Symbol;
loopSym.kind = skVar;
loopSym.name = stmt.strValue;
loopSym.typeKind = tyUnknown;
loopSym.typeName = "";
loopSym.isMutable = true;
loopSym.isPublic = false;
loopSym.decl = null as *Decl;
discard Scope_Define(&forScope, loopSym);
let prevScope: *Scope = sema.scope;
sema.scope = &forScope;
Sema_CheckBlock(sema, stmt.refStmtBlock);
sema.scope = prevScope;
return;
}
// Match
if kind == skMatch {
discard Sema_CheckExpr(sema, stmt.child1);
return;
}
// Break / Continue
if kind == skBreak || kind == skContinue {
return;
}
// Expression statement
if kind == skExpr && stmt.child1 != null as *Expr {
discard Sema_CheckExpr(sema, stmt.child1);
return;
}
// Decl (nested)
if kind == skDecl {
if stmt.refStmtDecl != null as *Decl && stmt.refStmtDecl.kind == dkFunc {
Sema_EmitError(sema, stmt.line, stmt.column, "nested functions not yet supported");
}
return;
}
}
// ---------------------------------------------------------------------------
// Collect globals (register functions, structs, enums in scope)
// ---------------------------------------------------------------------------
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;
// Function
if dk == dkFunc {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skFunc;
sym.name = decl.strValue;
sym.typeKind = tyFunc;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Struct
if dk == dkStruct {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skType;
sym.name = decl.strValue;
sym.typeKind = tyNamed;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Enum
if dk == dkEnum {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skType;
sym.name = decl.strValue;
sym.typeKind = tyNamed;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
// Register enum variants as constants
var vi: int = 0;
while vi < decl.variantCount && vi < 8 {
var v: EnumVariant;
if vi == 0 { v = decl.variant0; }
else if vi == 1 { v = decl.variant1; }
else if vi == 2 { v = decl.variant2; }
else if vi == 3 { v = decl.variant3; }
else if vi == 4 { v = decl.variant4; }
else if vi == 5 { v = decl.variant5; }
else if vi == 6 { v = decl.variant6; }
else if vi == 7 { v = decl.variant7; }
let variantName: String = String_Concat(decl.strValue, "_");
let variantName2: String = String_Concat(variantName, v.name);
var vSym: Symbol;
vSym.kind = skConst;
vSym.name = variantName2;
vSym.typeKind = tyNamed;
vSym.typeName = String_Concat(decl.strValue, "_Tag");
vSym.isMutable = false;
vSym.isPublic = decl.isPublic;
vSym.decl = decl;
discard Scope_Define(sema.scope, vSym);
vi = vi + 1;
}
}
// 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 {
// Multi-import: names are comma-separated in useNames
// For now, register the whole useNames string as a single func name
// (permissive fallback — real fix needs string split)
let namesStr: String = decl.useNames;
if !String_Eq(namesStr, "") {
// Simple split by comma using available string functions
var start: uint = 0;
var pos: uint = 0;
let totalLen: uint = String_Len(namesStr);
while pos <= totalLen {
let atEnd: bool = pos == totalLen;
let isComma: bool = false;
if pos < totalLen {
// Check if character at pos is comma
let chStr: String = bux_str_slice(namesStr, pos, 1);
isComma = String_Eq(chStr, ",");
}
if atEnd || isComma {
let nameLen: uint = pos - start;
if nameLen > 0 {
let name: String = bux_str_slice(namesStr, start, nameLen);
var sym: Symbol;
sym.kind = skFunc;
sym.name = name;
sym.typeKind = tyUnknown;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = true;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
}
start = pos + 1;
}
pos = pos + 1;
}
}
} else {
// Single import or glob — add last path segment
let path: String = decl.usePath;
if !String_Eq(path, "") {
// Find last :: segment
var lastSeg: String = path;
let containsColons: int = bux_str_contains(path, "::");
if containsColons != 0 {
// Try to get last segment by finding last ::
// Use a simple heuristic: slice from various positions
let pathLen: uint = String_Len(path);
var tryPos: uint = 0;
while tryPos < pathLen {
let slice: String = bux_str_slice(path, tryPos, pathLen - tryPos);
if String_StartsWith(slice, "::") {
lastSeg = bux_str_slice(slice, 2, String_Len(slice) - 2);
}
tryPos = tryPos + 1;
}
}
var sym: Symbol;
sym.kind = skFunc;
sym.name = lastSeg;
sym.typeKind = tyUnknown;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = true;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
}
}
}
// Const
if dk == dkConst {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skConst;
sym.name = decl.strValue;
sym.typeKind = tyUnknown;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Extern function
if dk == dkExternFunc {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skFunc;
sym.name = decl.strValue;
sym.typeKind = tyFunc;
sym.isPublic = true;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
decl = decl.childDecl2;
}
}
// ---------------------------------------------------------------------------
// Analyze — main entry point
// ---------------------------------------------------------------------------
func Sema_Analyze(mod: *Module) -> *Sema {
let s: *Sema = bux_alloc(sizeof(Sema)) as *Sema;
s.module = mod;
s.scope = bux_alloc(sizeof(Scope)) as *Scope;
s.scope.symbols = bux_alloc(1024 as uint * sizeof(Symbol)) as *Symbol;
s.scope.count = 0;
s.scope.parent = null as *Scope;
s.hasError = false;
s.diagCount = 0;
s.diags = bux_alloc(256 as uint * sizeof(SemaDiag)) as *SemaDiag;
s.typeTable = null as *void;
s.methodTable = null as *void;
s.currentRetType = tyVoid;
// First pass: collect globals
Sema_CollectGlobals(s);
// Second pass: check function bodies
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc && decl.refBody != null as *Block {
// Create function scope (child of global)
var funcScope: Scope = Scope_NewChild(s.scope);
// Add type params to scope
if decl.typeParamCount >= 1 {
var tpSym: Symbol;
Sema_ZeroInitSymbol(&tpSym);
tpSym.kind = skType;
tpSym.name = decl.typeParam0;
tpSym.typeKind = tyTypeParam;
tpSym.decl = null as *Decl;
discard Scope_Define(&funcScope, tpSym);
}
if decl.typeParamCount >= 2 {
var tpSym2: Symbol;
Sema_ZeroInitSymbol(&tpSym2);
tpSym2.kind = skType;
tpSym2.name = decl.typeParam1;
tpSym2.typeKind = tyTypeParam;
tpSym2.decl = null as *Decl;
discard Scope_Define(&funcScope, tpSym2);
}
// Add parameters to scope
var i: int = 0;
while i < decl.paramCount {
var p: *Param = null as *Param;
if i == 0 { p = &decl.param0; }
else if i == 1 { p = &decl.param1; }
else if i == 2 { p = &decl.param2; }
else if i == 3 { p = &decl.param3; }
else if i == 4 { p = &decl.param4; }
else if i == 5 { p = &decl.param5; }
else if i == 6 { p = &decl.param6; }
else if i == 7 { p = &decl.param7; }
else if i == 8 { p = &decl.param8; }
var pSym: Symbol;
Sema_ZeroInitSymbol(&pSym);
pSym.kind = skVar;
if p != null as *Param && p.refParamType != null as *TypeExpr {
pSym.typeKind = Sema_ResolveType(s, p.refParamType);
pSym.typeName = p.refParamType.typeName;
} else {
pSym.typeKind = tyInt;
pSym.typeName = "";
}
pSym.isMutable = false;
pSym.isPublic = false;
pSym.decl = null as *Decl;
if i == 0 { pSym.name = decl.param0.name; }
else if i == 1 { pSym.name = decl.param1.name; }
else if i == 2 { pSym.name = decl.param2.name; }
else if i == 3 { pSym.name = decl.param3.name; }
else if i == 4 { pSym.name = decl.param4.name; }
else if i == 5 { pSym.name = decl.param5.name; }
else if i == 6 { pSym.name = decl.param6.name; }
else if i == 7 { pSym.name = decl.param7.name; }
else if i == 8 { pSym.name = decl.param8.name; }
discard Scope_Define(&funcScope, pSym);
i = i + 1;
}
// Switch to function scope and check body statements
let prevScope: *Scope = s.scope;
s.scope = &funcScope;
// Set current function return type
if decl.retType != null as *TypeExpr {
s.currentRetType = Sema_ResolveType(s, decl.retType);
} else {
s.currentRetType = tyVoid;
}
// Check body statements
var stmt: *Stmt = decl.refBody.firstStmt;
while stmt != null as *Stmt {
Sema_CheckStmt(s, stmt);
stmt = stmt.nextStmt;
}
s.scope = prevScope;
}
decl = decl.childDecl2;
}
return s;
}
func Sema_HasError(sema: *Sema) -> bool {
return sema.hasError;
}
func Sema_DiagCount(sema: *Sema) -> int {
return sema.diagCount;
}
func Sema_Free(sema: *Sema) {
bux_free(sema.scope.symbols as *void);
bux_free(sema.scope as *void);
bux_free(sema.diags as *void);
bux_free(sema as *void);
}
}
-13
View File
@@ -1,13 +0,0 @@
// source_location.bux — Source position tracking
module SourceLocation {
struct SourceLocation {
line: uint32;
column: uint32;
offset: uint32;
}
func SourceLocation_New(line: uint32, column: uint32, offset: uint32) -> SourceLocation {
return SourceLocation { line: line, column: column, offset: offset };
}
}
-322
View File
@@ -1,322 +0,0 @@
// token.bux — Token kinds and helpers
module Token {
// ---------------------------------------------------------------------------
// TokenKind enum
// ---------------------------------------------------------------------------
// Literals
const tkIntLiteral: int = 0;
const tkFloatLiteral: int = 1;
const tkStringLiteral: int = 2;
const tkCharLiteral: int = 3;
const tkBoolLiteral: int = 4;
// Identifiers
const tkIdent: int = 5;
const tkUnderscore: int = 6;
// Control flow keywords
const tkIf: int = 7;
const tkElse: int = 8;
const tkWhile: int = 9;
const tkDo: int = 10;
const tkLoop: int = 11;
const tkFor: int = 12;
const tkIn: int = 13;
const tkBreak: int = 14;
const tkContinue: int = 15;
const tkReturn: int = 16;
const tkMatch: int = 17;
// Declaration keywords
const tkFunc: int = 18;
const tkLet: int = 19;
const tkVar: int = 20;
const tkConst: int = 21;
const tkType: int = 22;
const tkStruct: int = 23;
const tkEnum: int = 24;
const tkUnion: int = 25;
const tkInterface: int = 26;
const tkExtend: int = 27;
const tkModule: int = 28;
const tkImport: int = 29;
const tkPub: int = 30;
const tkExtern: int = 31;
// Other keywords
const tkAs: int = 32;
const tkIs: int = 33;
const tkNull: int = 34;
const tkSelf: int = 35;
const tkSuper: int = 36;
const tkSizeOf: int = 37;
// Punctuation
const tkLParen: int = 38;
const tkRParen: int = 39;
const tkLBrace: int = 40;
const tkRBrace: int = 41;
const tkLBracket: int = 42;
const tkRBracket: int = 43;
const tkComma: int = 44;
const tkSemicolon: int = 45;
const tkColon: int = 46;
const tkColonColon: int = 47;
const tkDot: int = 48;
const tkDotDot: int = 49;
const tkDotDotDot: int = 50;
const tkDotDotEqual: int = 51;
const tkArrow: int = 52;
const tkFatArrow: int = 53;
const tkAt: int = 54;
const tkHash: int = 55;
const tkQuestion: int = 56;
// Arithmetic operators
const tkPlus: int = 57;
const tkMinus: int = 58;
const tkStar: int = 59;
const tkSlash: int = 60;
const tkPercent: int = 61;
const tkStarStar: int = 62;
const tkPlusPlus: int = 63;
const tkMinusMinus: int = 64;
// Bitwise operators
const tkAmp: int = 65;
const tkPipe: int = 66;
const tkCaret: int = 67;
const tkTilde: int = 68;
const tkShl: int = 69;
const tkShr: int = 70;
// Logical operators
const tkAmpAmp: int = 71;
const tkPipePipe: int = 72;
const tkBang: int = 73;
// Comparison operators
const tkEq: int = 74;
const tkNe: int = 75;
const tkLt: int = 76;
const tkLe: int = 77;
const tkGt: int = 78;
const tkGe: int = 79;
// Assignment operators
const tkAssign: int = 80;
const tkPlusAssign: int = 81;
const tkMinusAssign: int = 82;
const tkStarAssign: int = 83;
const tkSlashAssign: int = 84;
const tkPercentAssign: int = 85;
const tkAmpAssign: int = 86;
const tkPipeAssign: int = 87;
const tkCaretAssign: int = 88;
const tkShlAssign: int = 89;
const tkShrAssign: int = 90;
// Compile-time intrinsics
const tkHashLine: int = 91;
const tkHashColumn: int = 92;
const tkHashFile: int = 93;
const tkHashFunction: int = 94;
const tkHashDate: int = 95;
const tkHashTime: int = 96;
const tkHashModule: int = 97;
// Special
const tkOwn: int = 98;
const tkNewLine: int = 99;
const tkEndOfFile: int = 100;
const tkUnknown: int = 101;
// Async / concurrency
const tkAsync: int = 102;
const tkAwait: int = 103;
const tkSpawn: int = 104;
const tkDiscard: int = 105;
// ---------------------------------------------------------------------------
// Token struct
// ---------------------------------------------------------------------------
struct Token {
kind: int;
text: String;
line: uint32;
column: uint32;
offset: uint32;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
func Token_IsKeyword(kind: int) -> bool {
if kind >= tkIf && kind <= tkIn { return true; }
if kind >= tkBreak && kind <= tkMatch { return true; }
if kind >= tkFunc && kind <= tkExtern { return true; }
if kind >= tkAs && kind <= tkSuper { return true; }
if kind == tkSizeOf { return true; }
if kind >= tkAsync && kind <= tkSpawn { return true; }
return false;
}
func Token_IsLiteral(kind: int) -> bool {
if kind >= tkIntLiteral && kind <= tkBoolLiteral { return true; }
return false;
}
func Token_IsOperator(kind: int) -> bool {
if kind >= tkPlus && kind <= tkShrAssign { return true; }
return false;
}
func Token_IsEof(kind: int) -> bool {
return kind == tkEndOfFile;
}
func Token_KeywordKind(text: String) -> int {
if String_Eq(text, "func") { return tkFunc; }
if String_Eq(text, "let") { return tkLet; }
if String_Eq(text, "var") { return tkVar; }
if String_Eq(text, "const") { return tkConst; }
if String_Eq(text, "type") { return tkType; }
if String_Eq(text, "struct") { return tkStruct; }
if String_Eq(text, "enum") { return tkEnum; }
if String_Eq(text, "union") { return tkUnion; }
if String_Eq(text, "interface") { return tkInterface; }
if String_Eq(text, "extend") { return tkExtend; }
if String_Eq(text, "module") { return tkModule; }
if String_Eq(text, "import") { return tkImport; }
if String_Eq(text, "pub") { return tkPub; }
if String_Eq(text, "extern") { return tkExtern; }
if String_Eq(text, "if") { return tkIf; }
if String_Eq(text, "else") { return tkElse; }
if String_Eq(text, "while") { return tkWhile; }
if String_Eq(text, "do") { return tkDo; }
if String_Eq(text, "loop") { return tkLoop; }
if String_Eq(text, "for") { return tkFor; }
if String_Eq(text, "in") { return tkIn; }
if String_Eq(text, "break") { return tkBreak; }
if String_Eq(text, "continue") { return tkContinue; }
if String_Eq(text, "return") { return tkReturn; }
if String_Eq(text, "match") { return tkMatch; }
if String_Eq(text, "as") { return tkAs; }
if String_Eq(text, "is") { return tkIs; }
if String_Eq(text, "null") { return tkNull; }
if String_Eq(text, "self") { return tkSelf; }
if String_Eq(text, "super") { return tkSuper; }
if String_Eq(text, "sizeof") { return tkSizeOf; }
if String_Eq(text, "true") { return tkBoolLiteral; }
if String_Eq(text, "false") { return tkBoolLiteral; }
return tkIdent;
}
func Token_KindName(kind: int) -> String {
if kind == tkIntLiteral { return "integer literal"; }
if kind == tkFloatLiteral { return "float literal"; }
if kind == tkStringLiteral { return "string literal"; }
if kind == tkCharLiteral { return "char literal"; }
if kind == tkBoolLiteral { return "boolean literal"; }
if kind == tkIdent { return "identifier"; }
if kind == tkUnderscore { return "_"; }
if kind == tkSizeOf { return "sizeof"; }
if kind == tkIf { return "if"; }
if kind == tkElse { return "else"; }
if kind == tkWhile { return "while"; }
if kind == tkDo { return "do"; }
if kind == tkLoop { return "loop"; }
if kind == tkFor { return "for"; }
if kind == tkIn { return "in"; }
if kind == tkBreak { return "break"; }
if kind == tkContinue { return "continue"; }
if kind == tkReturn { return "return"; }
if kind == tkMatch { return "match"; }
if kind == tkFunc { return "func"; }
if kind == tkLet { return "let"; }
if kind == tkVar { return "var"; }
if kind == tkConst { return "const"; }
if kind == tkType { return "type"; }
if kind == tkStruct { return "struct"; }
if kind == tkEnum { return "enum"; }
if kind == tkUnion { return "union"; }
if kind == tkInterface { return "interface"; }
if kind == tkExtend { return "extend"; }
if kind == tkModule { return "module"; }
if kind == tkImport { return "import"; }
if kind == tkPub { return "pub"; }
if kind == tkExtern { return "extern"; }
if kind == tkAs { return "as"; }
if kind == tkIs { return "is"; }
if kind == tkNull { return "null"; }
if kind == tkSelf { return "self"; }
if kind == tkSuper { return "super"; }
if kind == tkLParen { return "("; }
if kind == tkRParen { return ")"; }
if kind == tkLBrace { return "{"; }
if kind == tkRBrace { return "}"; }
if kind == tkLBracket { return "["; }
if kind == tkRBracket { return "]"; }
if kind == tkComma { return ","; }
if kind == tkSemicolon { return ";"; }
if kind == tkColon { return ":"; }
if kind == tkColonColon { return "::"; }
if kind == tkDot { return "."; }
if kind == tkDotDot { return ".."; }
if kind == tkDotDotDot { return "..."; }
if kind == tkDotDotEqual { return "..="; }
if kind == tkArrow { return "->"; }
if kind == tkFatArrow { return "=>"; }
if kind == tkAt { return "@"; }
if kind == tkHash { return "#"; }
if kind == tkQuestion { return "?"; }
if kind == tkPlus { return "+"; }
if kind == tkMinus { return "-"; }
if kind == tkStar { return "*"; }
if kind == tkSlash { return "/"; }
if kind == tkPercent { return "%"; }
if kind == tkStarStar { return "**"; }
if kind == tkPlusPlus { return "++"; }
if kind == tkMinusMinus { return "--"; }
if kind == tkAmp { return "&"; }
if kind == tkPipe { return "|"; }
if kind == tkCaret { return "^"; }
if kind == tkTilde { return "~"; }
if kind == tkShl { return "<<"; }
if kind == tkShr { return ">>"; }
if kind == tkAmpAmp { return "&&"; }
if kind == tkPipePipe { return "||"; }
if kind == tkBang { return "!"; }
if kind == tkEq { return "=="; }
if kind == tkNe { return "!="; }
if kind == tkLt { return "<"; }
if kind == tkLe { return "<="; }
if kind == tkGt { return ">"; }
if kind == tkGe { return ">="; }
if kind == tkAssign { return "="; }
if kind == tkPlusAssign { return "+="; }
if kind == tkMinusAssign { return "-="; }
if kind == tkStarAssign { return "*="; }
if kind == tkSlashAssign { return "/="; }
if kind == tkPercentAssign { return "%="; }
if kind == tkAmpAssign { return "&="; }
if kind == tkPipeAssign { return "|="; }
if kind == tkCaretAssign { return "^="; }
if kind == tkShlAssign { return "<<="; }
if kind == tkShrAssign { return ">>="; }
if kind == tkHashLine { return "#line"; }
if kind == tkHashColumn { return "#column"; }
if kind == tkHashFile { return "#file"; }
if kind == tkHashFunction { return "#function"; }
if kind == tkHashDate { return "#date"; }
if kind == tkHashTime { return "#time"; }
if kind == tkHashModule { return "#module"; }
if kind == tkNewLine { return "newline"; }
if kind == tkEndOfFile { return "end of file"; }
return "unknown token";
}
}
-188
View File
@@ -1,188 +0,0 @@
// types.bux — Type system definitions and factories
module Types {
// ---------------------------------------------------------------------------
// TypeKind constants
// ---------------------------------------------------------------------------
const tyUnknown: int = 0;
const tyVoid: int = 1;
const tyBool: int = 2;
const tyBool8: int = 3;
const tyBool16: int = 4;
const tyBool32: int = 5;
const tyChar8: int = 6;
const tyChar16: int = 7;
const tyChar32: int = 8;
const tyStr: int = 9;
const tyInt8: int = 10;
const tyInt16: int = 11;
const tyInt32: int = 12;
const tyInt64: int = 13;
const tyInt: int = 14;
const tyUInt8: int = 15;
const tyUInt16: int = 16;
const tyUInt32: int = 17;
const tyUInt64: int = 18;
const tyUInt: int = 19;
const tyFloat32: int = 20;
const tyFloat64: int = 21;
const tyPointer: int = 22;
const tySlice: int = 23;
const tyRange: int = 24;
const tyTuple: int = 25;
const tyNamed: int = 26;
const tyTypeParam: int = 27;
const tyFunc: int = 28;
// ---------------------------------------------------------------------------
// Type struct
// ---------------------------------------------------------------------------
struct Type {
kind: int;
name: String;
// inner types stored as array of pointers (simplified)
innerKind1: int;
innerName1: String;
innerKind2: int;
innerName2: String;
innerKind3: int;
innerName3: String;
innerCount: int;
}
// ---------------------------------------------------------------------------
// Factories
// ---------------------------------------------------------------------------
func Type_MakeUnknown() -> Type {
return Type { kind: tyUnknown, name: "", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeVoid() -> Type {
return Type { kind: tyVoid, name: "void", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeBool() -> Type {
return Type { kind: tyBool, name: "bool", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeInt() -> Type {
return Type { kind: tyInt, name: "int", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeInt64() -> Type {
return Type { kind: tyInt64, name: "int64", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeUInt() -> Type {
return Type { kind: tyUInt, name: "uint", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeFloat64() -> Type {
return Type { kind: tyFloat64, name: "float64", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeStr() -> Type {
return Type { kind: tyStr, name: "String", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakePointer(pointee: Type) -> Type {
return Type { kind: tyPointer, name: "", innerCount: 1,
innerKind1: pointee.kind, innerName1: pointee.name,
innerKind2: 0, innerName2: "", innerKind3: 0, innerName3: "" };
}
func Type_MakeNamed(name: String) -> Type {
return Type { kind: tyNamed, name: name, innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeTypeParam(name: String) -> Type {
return Type { kind: tyTypeParam, name: name, innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
// ---------------------------------------------------------------------------
// Predicates
// ---------------------------------------------------------------------------
func Type_IsNumeric(t: Type) -> bool {
let k: int = t.kind;
if k == tyInt8 || k == tyInt16 || k == tyInt32 || k == tyInt64 || k == tyInt { return true; }
if k == tyUInt8 || k == tyUInt16 || k == tyUInt32 || k == tyUInt64 || k == tyUInt { return true; }
if k == tyFloat32 || k == tyFloat64 { return true; }
if k == tyUnknown || k == tyNamed || k == tyTypeParam { return true; }
return false;
}
func Type_IsInteger(t: Type) -> bool {
let k: int = t.kind;
if k == tyInt8 || k == tyInt16 || k == tyInt32 || k == tyInt64 || k == tyInt { return true; }
if k == tyUInt8 || k == tyUInt16 || k == tyUInt32 || k == tyUInt64 || k == tyUInt { return true; }
if k == tyUnknown || k == tyNamed || k == tyTypeParam { return true; }
return false;
}
func Type_IsBool(t: Type) -> bool {
let k: int = t.kind;
return k == tyBool || k == tyBool8 || k == tyBool16 || k == tyBool32;
}
func Type_IsPointer(t: Type) -> bool {
return t.kind == tyPointer;
}
func Type_IsSlice(t: Type) -> bool {
return t.kind == tySlice;
}
// ---------------------------------------------------------------------------
// Comparison (structural, limited to kind + name for simplicity)
// ---------------------------------------------------------------------------
func Type_Eq(a: Type, b: Type) -> bool {
if a.kind != b.kind { return false; }
if a.kind == tyNamed || a.kind == tyTypeParam {
return String_Eq(a.name, b.name);
}
return true;
}
// ---------------------------------------------------------------------------
// toString
// ---------------------------------------------------------------------------
func Type_ToString(t: Type) -> String {
if t.kind == tyVoid { return "void"; }
if t.kind == tyBool { return "bool"; }
if t.kind == tyStr { return "String"; }
if t.kind == tyInt { return "int"; }
if t.kind == tyInt64 { return "int64"; }
if t.kind == tyUInt { return "uint"; }
if t.kind == tyFloat64 { return "float64"; }
if t.kind == tyNamed { return t.name; }
if t.kind == tyTypeParam { return t.name; }
if t.kind == tyPointer { return String_Concat("*", t.innerName1); }
return "?";
}
}
-2
View File
@@ -1,2 +0,0 @@
name = "test_array"
version = "0.1.0"
-33
View File
@@ -1,33 +0,0 @@
module Main {
import Std::Array::Array;
import Std::Iter::Iter;
import Std::Test::Test;
import Std::Io::Io;
func Main() -> int {
var arr: Array<int> = Array_New<int>(4);
Array_Push<int>(&arr, 10);
Array_Push<int>(&arr, 20);
Array_Push<int>(&arr, 30);
Test_AssertEqInt(Array_Len<int>(&arr) as int, 3);
Test_AssertEqInt(Array_Get<int>(&arr, 0), 10);
Test_AssertEqInt(Array_Get<int>(&arr, 1), 20);
Test_AssertEqInt(Array_Get<int>(&arr, 2), 30);
var it: Iter<int> = Array_Iter<int>(&arr);
Test_AssertTrue(Iter_HasNext<int>(&it));
Test_AssertEqInt(Iter_Next<int>(&it), 10);
Test_AssertEqInt(Iter_Next<int>(&it), 20);
Test_AssertEqInt(Iter_Next<int>(&it), 30);
Test_AssertFalse(Iter_HasNext<int>(&it));
var it2: Iter<int> = Array_Iter<int>(&arr);
let count: uint = Iter_Count<int>(&it2);
Test_AssertEqInt(count as int, 3);
Print("All array+iter tests passed!");
Array_Free<int>(&arr);
return 0;
}
}
BIN
View File
Binary file not shown.
+54
View File
@@ -0,0 +1,54 @@
/* Bux Standard Library - I/O functions */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
/* PrintLine - print string with newline */
void PrintLine(const char* s) {
if (s != NULL) {
puts(s);
} else {
puts("");
}
}
/* Print - print string without newline */
void Print(const char* s) {
if (s != NULL) {
printf("%s", s);
}
}
/* PrintInt - print integer */
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);
}
/* PrintBool - print boolean */
void PrintBool(int b) {
printf("%s", b ? "true" : "false");
}
/* ReadLine - read line from stdin (simplified) */
const char* ReadLine(void) {
static char buffer[1024];
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
}
return buffer;
}
return "";
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
[Package]
Name = "closure_test"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
+34
View File
@@ -0,0 +1,34 @@
module Main {
extern func PrintInt(x: int);
extern func PrintLine(msg: String);
func Apply(x: int, op: func(int) -> int) -> int {
return op(x);
}
func Double(x: int) -> int {
return x * 2;
}
func main() -> int {
// Closure as variable
let add: func(int, int) -> int = |a: int, b: int| -> int { return a + b; };
let sum: int = add(3, 4);
PrintInt(sum);
PrintLine("");
// Closure passed to function
let result: int = Apply(5, |x: int| -> int { return x * 3; });
PrintInt(result);
PrintLine("");
// Address of named function as function pointer
let d: func(int) -> int = &Double;
PrintInt(d(7));
PrintLine("");
return 0;
}
}
+15
View File
@@ -0,0 +1,15 @@
@[Drop]
struct Buffer {
ptr: *int
}
func Buffer_Drop(self: *Buffer) {
bux_free(self.ptr as *void);
}
func main() -> int {
let buf: Buffer = Buffer { ptr: bux_alloc(10 as uint * sizeof(int)) as *int };
// Buffer should be auto-dropped here via Buffer_Drop(&buf)
return 0;
}
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
[package]
name = "error_snippet_test"
version = "0.1.0"
pkgType = "bin"
+4
View File
@@ -0,0 +1,4 @@
func Main() -> int {
let x: int = undefined_variable;
return 0;
}
+2
View File
@@ -0,0 +1,2 @@
name = "test_funcptr"
version = "0.1.0"
+17
View File
@@ -0,0 +1,17 @@
module Main {
func Apply(x: int, op: func(int) -> int) -> int {
return op(x);
}
func Double(n: int) -> int {
return n * 2;
}
func main() -> int {
let result: int = Apply(5, &Double);
if result != 10 { return 1; }
return 0;
}
}
+4
View File
@@ -0,0 +1,4 @@
[package]
name = "green_threads_test"
version = "0.1.0"
pkgType = "bin"
+29
View File
@@ -0,0 +1,29 @@
import Std::Task;
import Std::String;
func Worker(id: int) {
PrintLine(String_Concat(String_Concat("Worker ", String_FromInt(id)), " starting"));
Task_Sleep(50);
PrintLine(String_Concat(String_Concat("Worker ", String_FromInt(id)), " done"));
}
func Main() -> int {
PrintLine("=== Green Thread Test ===");
Task_Init(4);
let h1: TaskHandle = Task_Spawn(Worker as *void, 1 as *void);
let h2: TaskHandle = Task_Spawn(Worker as *void, 2 as *void);
let h3: TaskHandle = Task_Spawn(Worker as *void, 3 as *void);
PrintLine("Waiting for tasks...");
Task_Wait(h1);
Task_Wait(h2);
Task_Wait(h3);
Task_Shutdown();
PrintLine("=== All tasks complete ===");
return 0;
}
+36
View File
@@ -0,0 +1,36 @@
import Std::Io;
import Std::Array;
import Std::Slice;
func Main() -> int {
// Test Array operator_index_get/set
let arr: Array<int> = Array_New<int>(10);
Array_Push<int>(&arr, 10);
Array_Push<int>(&arr, 20);
Array_Push<int>(&arr, 30);
// Use [] syntax (operator_index_get)
if arr[0] != 10 { return 1; }
if arr[1] != 20 { return 2; }
if arr[2] != 30 { return 3; }
// Use [] syntax (operator_index_set)
arr[1] = 99;
if arr[1] != 99 { return 4; }
// Test Slice operator_index_get/set
let s: Slice<int> = Slice_FromArray<int>(&arr);
if s[0] != 10 { return 5; }
if s[1] != 99 { return 6; }
if s[2] != 30 { return 7; }
s[2] = 42;
if s[2] != 42 { return 8; }
PrintInt(s[0]);
PrintLine("");
PrintInt(Slice_Len<int>(&s));
PrintLine("");
return 0;
}
+182
View File
@@ -0,0 +1,182 @@
# Boko Framework
**Async web framework for Bux — inspired by FastAPI.**
Boko is a lightweight, multi-threaded web framework that brings FastAPI-style routing to the Bux programming language. It handles HTTP parsing, path pattern matching, query parameter extraction, and response building so you can focus on your application logic.
This version (0.2.0) is rewritten with modern Bux: `struct` methods, generic `StringMap<String>` for headers/query/path params, `for` loops, algebraic enums, `StringBuilder`, and string interpolation.
## Quick Start
```bash
cd apps/boko-framework
../../buxc build
./build/boko-framework
```
Open `http://localhost:8080` — you'll see the demo landing page.
## How It Works
Boko follows a **single-dispatch-function** pattern. You define one function — `Boko_Router` — and the framework calls it for every incoming request:
```bux
import Boko::{Request, Response, Response_Json, Response_Html, Response_NotFound,
Path_Match,
App, App_New, App_Run};
func Boko_Router(req: Request) -> Response {
// Route: GET /
if String_Eq(req.path, "/") {
return Response_Html("<h1>Hello Boko!</h1>");
}
// Route: GET /api/health
if String_Eq(req.path, "/api/health") {
return Response_Json("{\"status\":\"ok\"}");
}
// Route: GET /users/{id} — path parameter
if Path_Match("/users/{id}", req.path, &req) {
let id: String = req.GetPathParam("id");
// Build JSON response with id
return Response_Json(...);
}
// Route: GET /search?q=... — query parameter
if String_Eq(req.path, "/search") {
let q: String = req.GetQuery("q");
return Response_Json(...);
}
return Response_NotFound();
}
func Main() -> int {
let app: App = App_New(8080, 4); // port, threads
App_Run(&app);
return 0;
}
```
## API Reference
### Request
| Field | Type | Description |
|-------|------|-------------|
| `method` | `HttpVerb` | GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS |
| `path` | `String` | Request path (without query string) |
| `body` | `String` | Request body (for POST/PUT) |
| `headers` | `StringMap<String>` | Parsed request headers |
| `query` | `StringMap<String>` | Parsed query parameters |
| `pathParams` | `StringMap<String>` | Extracted path parameters |
| Method | Returns | Description |
|--------|---------|-------------|
| `req.GetHeader(name)` | `String` | Get header value by name |
| `req.GetQuery(name)` | `String` | Get query parameter by name |
| `req.HasQuery(name)` | `bool` | Check if query parameter exists |
| `req.GetPathParam(name)` | `String` | Get extracted path parameter |
### Response
| Constructor | Content-Type | Status |
|-------------|-------------|--------|
| `Response_Html(html)` | `text/html` | 200 |
| `Response_Json(json)` | `application/json` | 200 |
| `Response_Text(text)` | `text/plain` | 200 |
| `Response_Redirect(url)` | — | 302 |
| `Response_NotFound()` | `application/json` | 404 |
| `Response_Error(code, msg)` | `application/json` | custom |
| `Response_NoContent()` | — | 204 |
### Path Matching
`Path_Match(pattern, actualPath, req)` matches a pattern with `{param}` placeholders:
```bux
// Pattern: /users/{id}/posts/{postId}
// Actual: /users/42/posts/7
// Extracts: id=42, postId=7
if Path_Match("/users/{id}/posts/{postId}", req.path, &req) {
let userId: String = req.GetPathParam("id"); // "42"
let postId: String = req.GetPathParam("postId"); // "7"
}
```
The function returns `true` if the pattern matches and populates `req.pathParams`.
### App
```bux
let app: App = App_New(8080, 4); // port 8080, 4 worker threads
App_Run(&app); // blocks, handles requests
```
## Architecture
```
Incoming connection
Net_Accept() ─── worker thread (1 of N)
Net_Recv() → raw HTTP bytes
Request_Parse() → method, path, query, headers, body
Boko_Router(req) ←── YOUR CODE
Response_Build() → HTTP/1.1 response string
Net_Send() → bytes to client
Net_Close()
```
## Endpoints in Demo App
| Method | Path | Description |
|--------|------|-------------|
| GET | `/` | Landing page (HTML) |
| GET | `/api/health` | Health check (JSON) |
| GET | `/api/info` | Framework info (JSON) |
| GET | `/hello?name=X` | Query param demo |
| GET | `/users/{id}` | Path param demo |
| GET | `/posts/{id}/comments/{cid}` | Multi path param demo |
| GET | `/redirect` | 302 redirect to `/` |
| POST | `/api/echo` | Echo request body |
## Project Structure
```
apps/boko-framework/
├── bux.toml # Package manifest
├── README.md # This file
└── src/
├── Boko.bux # Framework core (~500 lines)
└── Main.bux # Example app (~150 lines)
```
## Design Philosophy
Boko is intentionally simple. Instead of complex DSLs or code generation, it gives you:
- **One function to write** — `Boko_Router(req) -> Response`
- **Direct control** — you write plain if/else or match for routing
- **No magic** — path params, query params, headers are explicit function calls
- **Fast** — multi-threaded accept loop, zero allocations where possible
This is the same philosophy as Go's `net/http` — simple, explicit, composable.
## License
Part of the Bux project. See root [LICENSE](../../LICENSE).
+1
View File
@@ -0,0 +1 @@
EXIT:137
+8
View File
@@ -0,0 +1,8 @@
[Package]
Name = "boko-framework"
Version = "0.2.0"
Type = "bin"
Description = "Boko — async web framework for Bux, inspired by FastAPI"
[Build]
Output = "Bin"
+1
View File
@@ -0,0 +1 @@
EXIT:137
+1
View File
@@ -0,0 +1 @@
EXIT:143
+1
View File
@@ -0,0 +1 @@
EXIT:137
+2
View File
@@ -0,0 +1,2 @@
bash: ред 1: ../../buxc_lir: Няма такъв файл или директория
EXIT:127
+1
View File
@@ -0,0 +1 @@
EXIT:143
+1
View File
@@ -0,0 +1 @@
EXIT:143
+2
View File
@@ -0,0 +1,2 @@
bux 0.1.0 (bootstrap)
EXIT:0
+40
View File
@@ -0,0 +1,40 @@
nim c -o:buxc -d:release --opt:size bootstrap/main.nim
Hint: used config file '/etc/nim/nim.cfg' [Conf]
Hint: used config file '/etc/nim/config.nims' [Conf]
...........................................................................................................................................
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(340, 12) Hint: 'check3' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(350, 12) Hint: 'checkEq' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(64, 6) Hint: 'match' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(84, 6) Hint: 'emitWarning' is declared but not used [XDeclaredButNotUsed]
..
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(663, 7) Hint: 'loc' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(1001, 9) Hint: 'isVar' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(59, 6) Hint: 'match' is declared but not used [XDeclaredButNotUsed]
.......
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1150, 9) Hint: 'operandType' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1155, 9) Hint: 'operandType' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1165, 9) Hint: 'subjectType' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1200, 9) Hint: 'operand' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(94, 6) Hint: 'emitWarning' is declared but not used [XDeclaredButNotUsed]
.
/home/ziko/z-git/bux/bux/bootstrap/manifest.nim(93, 11) Hint: 'val' is declared but not used [XDeclaredButNotUsed]
..
/home/ziko/z-git/bux/bux/bootstrap/hir_lower.nim(1057, 9) Hint: 'loweredIter' is declared but not used [XDeclaredButNotUsed]
.
/home/ziko/z-git/bux/bux/bootstrap/lir.nim(5, 8) Warning: imported and not used: 'types' [UnusedImport]
.
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(419, 9) Hint: 't' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(595, 9) Hint: 'bodyLbl' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(734, 11) Hint: 'exprVal' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(746, 9) Hint: 'exprVal' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(6, 8) Warning: imported and not used: 'ast' [UnusedImport]
.
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(20, 6) Hint: 'emit' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(46, 6) Hint: 'typeFromValue' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(58, 6) Hint: 'setTempType' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/cli.nim(2, 55) Warning: imported and not used: 'lir' [UnusedImport]
Hint: [Link]
Hint: mm: orc; threads: on; opt: size; options: -d:release
76982 lines; 1.552s; 174.824MiB peakmem; proj: /home/ziko/z-git/bux/bux/bootstrap/main.nim; out: /home/ziko/z-git/bux/bux/buxc [SuccessX]
# strip buxc
EXIT:0
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
cd /home/ziko/z-git/bux/bux/apps/boko-framework
echo "=== CHECK ==="
../../buxc check 2>&1
echo "CHECK_EXIT=$?"
echo ""
echo "=== BUILD ==="
../../buxc build 2>&1
echo "BUILD_EXIT=$?"
echo ""
echo "=== FILES ==="
find . -type f -newer bux.toml 2>/dev/null | head -30
echo ""
echo "=== BINARY SEARCH ==="
file boko-framework 2>/dev/null || echo "no boko-framework"
ls -la build/ 2>/dev/null || echo "no build dir"
+467
View File
@@ -0,0 +1,467 @@
// =============================================================================
// Boko — Async Web Framework for Bux (inspired by FastAPI)
// Rewritten with modern Bux: methods on structs, generic StringMap, for-in
// loops, algebraic enums, StringBuilder, and string interpolation.
// =============================================================================
module Boko {
import Std::Io::{PrintLine, Print, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{
String_Len, String_Eq, String_IsNull,
String_StartsWith, String_Contains,
String_Find, String_Offset, String_Slice,
String_SplitCount, String_SplitPart,
StringBuilder, StringBuilder_New, StringBuilder_Append,
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
};
import Std::Map::{StringMap, StringMap_New, StringMap_Set, StringMap_Get, StringMap_Has};
// =============================================================================
// HTTP Methods
// =============================================================================
enum HttpVerb {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
}
func HttpVerb_MethodName(verb: HttpVerb) -> String {
if verb.tag == HttpVerb_GET { return "GET"; }
if verb.tag == HttpVerb_POST { return "POST"; }
if verb.tag == HttpVerb_PUT { return "PUT"; }
if verb.tag == HttpVerb_DELETE { return "DELETE"; }
if verb.tag == HttpVerb_PATCH { return "PATCH"; }
if verb.tag == HttpVerb_HEAD { return "HEAD"; }
if verb.tag == HttpVerb_OPTIONS { return "OPTIONS"; }
return "?";
}
func HttpVerb_Parse(methodStr: String) -> HttpVerb {
if String_Eq(methodStr, "GET") { return HttpVerb { tag: HttpVerb_GET }; }
if String_Eq(methodStr, "POST") { return HttpVerb { tag: HttpVerb_POST }; }
if String_Eq(methodStr, "PUT") { return HttpVerb { tag: HttpVerb_PUT }; }
if String_Eq(methodStr, "DELETE") { return HttpVerb { tag: HttpVerb_DELETE }; }
if String_Eq(methodStr, "PATCH") { return HttpVerb { tag: HttpVerb_PATCH }; }
if String_Eq(methodStr, "HEAD") { return HttpVerb { tag: HttpVerb_HEAD }; }
if String_Eq(methodStr, "OPTIONS") { return HttpVerb { tag: HttpVerb_OPTIONS }; }
return HttpVerb { tag: HttpVerb_GET };
}
// =============================================================================
// Request — parsed incoming HTTP request
// =============================================================================
struct Request {
method: HttpVerb,
path: String,
body: String,
headers: StringMap<String>,
query: StringMap<String>,
pathParams: StringMap<String>,
}
extend Request {
func GetHeader(self: Request, name: String) -> String {
if StringMap_Has<String>(&self.headers, name) {
return StringMap_Get<String>(&self.headers, name);
}
return "";
}
func GetQuery(self: Request, name: String) -> String {
if StringMap_Has<String>(&self.query, name) {
return StringMap_Get<String>(&self.query, name);
}
return "";
}
func HasQuery(self: Request, name: String) -> bool {
return StringMap_Has<String>(&self.query, name);
}
func GetPathParam(self: Request, name: String) -> String {
if StringMap_Has<String>(&self.pathParams, name) {
return StringMap_Get<String>(&self.pathParams, name);
}
return "";
}
}
// =============================================================================
// Response — outgoing HTTP response
// =============================================================================
struct Response {
statusCode: int,
contentType: String,
body: String,
extraHeaders: String,
}
// --- Constructors ---
func Response_New(status: int, contentType: String, body: String) -> Response {
return Response { statusCode: status, contentType: contentType, body: body, extraHeaders: "" };
}
func Response_Ok(body: String) -> Response {
return Response_New(200, "text/html; charset=utf-8", body);
}
func Response_Html(html: String) -> Response {
return Response_New(200, "text/html; charset=utf-8", html);
}
func Response_Json(json: String) -> Response {
return Response_New(200, "application/json; charset=utf-8", json);
}
func Response_Text(text: String) -> Response {
return Response_New(200, "text/plain; charset=utf-8", text);
}
func Response_Redirect(url: String) -> Response {
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "Location: ");
StringBuilder_Append(&sb, url);
StringBuilder_Append(&sb, "\r\n");
let headers: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return Response { statusCode: 302, contentType: "", body: "", extraHeaders: headers };
}
func Response_NotFound() -> Response {
return Response_New(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
}
func Response_Error(status: int, message: String) -> Response {
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "{\"error\":\"");
StringBuilder_Append(&sb, message);
StringBuilder_Append(&sb, "\"}");
let body: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return Response_New(status, "application/json; charset=utf-8", body);
}
func Response_NoContent() -> Response {
return Response_New(204, "", "");
}
// --- Instance helpers ---
extend Response {
func WithHeader(self: Response, key: String, value: String) -> Response {
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, self.extraHeaders);
StringBuilder_Append(&sb, key);
StringBuilder_Append(&sb, ": ");
StringBuilder_Append(&sb, value);
StringBuilder_Append(&sb, "\r\n");
self.extraHeaders = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return self;
}
}
// =============================================================================
// Status phrase lookup
// =============================================================================
func StatusPhrase(code: int) -> String {
if code == 200 { return "OK"; }
if code == 201 { return "Created"; }
if code == 204 { return "No Content"; }
if code == 301 { return "Moved Permanently"; }
if code == 302 { return "Found"; }
if code == 400 { return "Bad Request"; }
if code == 401 { return "Unauthorized"; }
if code == 403 { return "Forbidden"; }
if code == 404 { return "Not Found"; }
if code == 500 { return "Internal Server Error"; }
if code == 502 { return "Bad Gateway"; }
if code == 503 { return "Service Unavailable"; }
return "OK";
}
// =============================================================================
// Build HTTP response string from Response struct
// =============================================================================
func Response_Build(resp: Response) -> String {
let sb: StringBuilder = StringBuilder_New();
// Status line
StringBuilder_Append(&sb, "HTTP/1.1 ");
StringBuilder_AppendInt(&sb, resp.statusCode as int64);
StringBuilder_Append(&sb, " ");
StringBuilder_Append(&sb, StatusPhrase(resp.statusCode));
StringBuilder_Append(&sb, "\r\n");
// Server
StringBuilder_Append(&sb, "Server: Boko/0.2.0 (Bux)\r\n");
// Extra headers
if String_Len(resp.extraHeaders) > 0 {
StringBuilder_Append(&sb, resp.extraHeaders);
}
// Content-Type
if String_Len(resp.contentType) > 0 {
StringBuilder_Append(&sb, "Content-Type: ");
StringBuilder_Append(&sb, resp.contentType);
StringBuilder_Append(&sb, "\r\n");
}
// Content-Length
let bodyLen: uint = String_Len(resp.body);
StringBuilder_Append(&sb, "Content-Length: ");
StringBuilder_AppendInt(&sb, bodyLen as int64);
StringBuilder_Append(&sb, "\r\n");
// Connection close
StringBuilder_Append(&sb, "Connection: close\r\n");
StringBuilder_Append(&sb, "\r\n");
if bodyLen > 0 {
StringBuilder_Append(&sb, resp.body);
}
let result: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return result;
}
// =============================================================================
// Parse query string: ?key=val&key2=val2 → generic StringMap
// =============================================================================
func Query_Parse(queryString: String) -> StringMap<String> {
let map: StringMap<String> = StringMap_New<String>(8);
if String_Len(queryString) == 0 {
return map;
}
let pairCount: uint = String_SplitCount(queryString, "&");
for i in 0..pairCount {
let pair: String = String_SplitPart(queryString, "&", i);
let eqPos: String = String_Find(pair, "=");
var key: String = pair;
var value: String = "";
if !String_IsNull(eqPos) {
let keyLen: uint = String_Offset(eqPos, pair);
key = String_Slice(pair, 0, keyLen);
let valStart: uint = keyLen + 1;
let pairLen: uint = String_Len(pair);
if valStart < pairLen {
value = String_Slice(pair, valStart, pairLen - valStart);
}
}
StringMap_Set<String>(&map, key, value);
}
return map;
}
// =============================================================================
// Parse incoming HTTP request from raw bytes
// =============================================================================
func Request_Parse(raw: String) -> Request {
var req: Request;
req.method = HttpVerb { tag: HttpVerb_GET };
req.path = "/";
req.body = "";
req.headers = StringMap_New<String>(8);
req.query = StringMap_New<String>(4);
req.pathParams = StringMap_New<String>(4);
if String_Len(raw) == 0 { return req; }
// Find header/body boundary
let boundary: String = String_Find(raw, "\r\n\r\n");
let rawLen: uint = String_Len(raw);
var headerLen: uint = rawLen;
if !String_IsNull(boundary) {
headerLen = String_Offset(boundary, raw);
let bodyStart: uint = headerLen + 4;
if bodyStart < rawLen {
req.body = String_Slice(raw, bodyStart, rawLen - bodyStart);
}
}
if headerLen == 0 { return req; }
let headerBlock: String = String_Slice(raw, 0, headerLen);
let lineCount: uint = String_SplitCount(headerBlock, "\r\n");
if lineCount == 0 { return req; }
// Parse request line
let requestLine: String = String_SplitPart(headerBlock, "\r\n", 0);
if String_Len(requestLine) > 0 {
let methodStr: String = String_SplitPart(requestLine, " ", 0);
req.method = HttpVerb_Parse(methodStr);
let fullPath: String = String_SplitPart(requestLine, " ", 1);
let qmark: String = String_Find(fullPath, "?");
if !String_IsNull(qmark) {
let pathLen: uint = String_Offset(qmark, fullPath);
req.path = String_Slice(fullPath, 0, pathLen);
let qsStart: uint = pathLen + 1;
let fullLen: uint = String_Len(fullPath);
if qsStart < fullLen {
let qs: String = String_Slice(fullPath, qsStart, fullLen - qsStart);
req.query = Query_Parse(qs);
}
} else {
req.path = fullPath;
}
}
// Parse headers
for i in 1..lineCount {
let line: String = String_SplitPart(headerBlock, "\r\n", i);
let colonPos: String = String_Find(line, ": ");
if !String_IsNull(colonPos) {
let keyLen: uint = String_Offset(colonPos, line);
let key: String = String_Slice(line, 0, keyLen);
let valStart: uint = keyLen + 2;
let lineLen: uint = String_Len(line);
var value: String = "";
if valStart < lineLen {
value = String_Slice(line, valStart, lineLen - valStart);
}
StringMap_Set<String>(&req.headers, key, value);
}
}
return req;
}
// =============================================================================
// Path pattern matching: /users/{id} against /users/42 → extracts id=42
// =============================================================================
func Path_Match(pattern: String, path: String, req: *Request) -> bool {
let patParts: uint = String_SplitCount(pattern, "/");
let pathParts: uint = String_SplitCount(path, "/");
if patParts != pathParts { return false; }
req.pathParams = StringMap_New<String>(8);
for i in 0..patParts {
let patPart: String = String_SplitPart(pattern, "/", i);
let pathPart: String = String_SplitPart(path, "/", i);
if String_StartsWith(patPart, "{") && String_Contains(patPart, "}") {
let nameLen: uint = String_Len(patPart) - 2;
let name: String = String_Slice(patPart, 1, nameLen);
StringMap_Set<String>(&req.pathParams, name, pathPart);
} else {
if !String_Eq(patPart, pathPart) { return false; }
}
}
return true;
}
// =============================================================================
// App — server instance
// =============================================================================
struct App {
port: int,
threadCount: int,
serverName: String,
}
func App_New(port: int, threadCount: int) -> App {
return App { port: port, threadCount: threadCount, serverName: "Boko/0.2.0 (Bux)" };
}
// =============================================================================
// Forward declaration — user must implement this in their module
// =============================================================================
func Boko_Router(req: Request) -> Response;
// =============================================================================
// Handle a single connection: parse → dispatch → respond
// =============================================================================
func App_HandleConnection(clientFd: int) {
let raw: String = Net_Recv(clientFd, 8192);
if String_Len(raw) == 0 { return; }
let req: Request = Request_Parse(raw);
let resp: Response = Boko_Router(req);
// Log
Print(HttpVerb_MethodName(req.method));
Print(" ");
Print(req.path);
Print(" → ");
PrintInt(resp.statusCode);
PrintLine("");
// Send
let respStr: String = Response_Build(resp);
Net_Send(clientFd, respStr);
}
// =============================================================================
// Worker thread: accept loop
// =============================================================================
func App_Worker(serverFd: int) {
while true {
let clientFd: int = Net_Accept(serverFd);
if clientFd < 0 { continue; }
App_HandleConnection(clientFd);
Net_Close(clientFd);
}
}
// =============================================================================
// App_Run: start the server (blocking)
// =============================================================================
func App_Run(app: *App) {
PrintLine("╔══════════════════════════════════════╗");
PrintLine("║ Boko Framework v0.2.0 ║");
PrintLine("║ Async web framework for Bux ║");
PrintLine("╚══════════════════════════════════════════════════════╝");
PrintLine("");
let fd: int = Net_Create();
if fd < 0 {
PrintLine("FATAL: socket() failed");
return;
}
Net_SetReuse(fd);
if !Net_Bind(fd, "0.0.0.0", app.port) {
Print("FATAL: bind(:");
PrintInt(app.port);
Print(") failed: ");
PrintLine(Net_LastError());
Net_Close(fd);
return;
}
if !Net_Listen(fd, 128) {
PrintLine("FATAL: listen() failed");
Net_Close(fd);
return;
}
Print("✓ Boko running on http://0.0.0.0:");
PrintInt(app.port);
PrintLine("");
Print("✓ Workers: ");
PrintInt(app.threadCount);
PrintLine("");
PrintLine("");
// Spawn workers
for i in 0..(app.threadCount - 1) {
spawn App_Worker(fd);
}
// Main thread is the last worker
App_Worker(fd);
}
} // module Boko
+152
View File
@@ -0,0 +1,152 @@
// =============================================================================
// Boko Example App — demonstrates the modern framework API
// =============================================================================
module Main {
import Std::Io::{PrintLine, Print};
import Std::String::{
String_Eq, String_Len,
StringBuilder, StringBuilder_New, StringBuilder_Append,
StringBuilder_Build, StringBuilder_Free
};
import Boko::{
App, App_New, App_Run,
Request, Response,
Response_Html, Response_Json, Response_NotFound, Response_Redirect,
Path_Match,
HttpVerb
};
// =============================================================================
// Boko_Router — user-defined dispatch (called by the framework)
// =============================================================================
func Boko_Router(req: Request) -> Response {
// --- GET / ---
if String_Eq(req.path, "/") && req.method.tag == HttpVerb_GET {
return Response_Html(PageHome());
}
// --- GET /api/health ---
if String_Eq(req.path, "/api/health") {
return Response_Json("{\"status\":\"ok\",\"framework\":\"Boko\",\"version\":\"0.2.0\"}");
}
// --- GET /api/info ---
if String_Eq(req.path, "/api/info") {
return Response_Json("{\"name\":\"Boko\",\"language\":\"Bux\",\"inspiration\":\"FastAPI\",\"features\":[\"routing\",\"path-params\",\"query-params\",\"json\"]}");
}
// --- GET /hello?name=World ---
if String_Eq(req.path, "/hello") {
var name: String = req.GetQuery("name");
if String_Len(name) == 0 { name = "World"; }
let html: String = f"<h1>Hello, {name}!</h1>";
return Response_Html(html);
}
// --- GET /users/{id} ---
if Path_Match("/users/{id}", req.path, &req) {
let id: String = req.GetPathParam("id");
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "{\"id\":");
StringBuilder_Append(&sb, id);
StringBuilder_Append(&sb, ",\"name\":\"User ");
StringBuilder_Append(&sb, id);
StringBuilder_Append(&sb, "\"}");
let json: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return Response_Json(json);
}
// --- GET /posts/{postId}/comments/{commentId} ---
if Path_Match("/posts/{postId}/comments/{commentId}", req.path, &req) {
let pid: String = req.GetPathParam("postId");
let cid: String = req.GetPathParam("commentId");
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "{\"postId\":");
StringBuilder_Append(&sb, pid);
StringBuilder_Append(&sb, ",\"commentId\":");
StringBuilder_Append(&sb, cid);
StringBuilder_Append(&sb, ",\"text\":\"Great post!\"}");
let json: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return Response_Json(json);
}
// --- GET /redirect → 302 to / ---
if String_Eq(req.path, "/redirect") {
return Response_Redirect("/");
}
// --- POST /api/echo ---
if String_Eq(req.path, "/api/echo") && req.method.tag == HttpVerb_POST {
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "{\"echo\":");
StringBuilder_Append(&sb, req.body);
StringBuilder_Append(&sb, "}");
let json: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return Response_Json(json);
}
// --- 404 ---
return Response_NotFound();
}
// =============================================================================
// HTML landing page (raw multi-line string via backticks)
// =============================================================================
func PageHome() -> String {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boko Framework</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh;display:flex;align-items:center;justify-content:center}
.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:3rem;max-width:560px;width:100%}
h1{color:#58a6ff;font-size:2rem;margin-bottom:.5rem}
.tag{color:#8b949e;font-size:.9rem;margin-bottom:2rem}
h3{color:#d2a8ff;margin:1.5rem 0 .75rem}
.ep{display:flex;gap:1rem;padding:.35rem 0;font-family:monospace;font-size:.9rem}
.ep .m{color:#3fb950;font-weight:bold;min-width:52px}
.ep .p{color:#c9d1d9}
.ep .d{color:#484f58;margin-left:auto}
a{color:#58a6ff}
</style>
</head>
<body>
<div class="card">
<h1>⚡ Boko</h1>
<p class="tag">Async web framework for Bux — inspired by FastAPI</p>
<h3>Try it</h3>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/hello?name=Bux">/hello?name=Bux</a></span><span class="d">query param</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/users/42">/users/42</a></span><span class="d">path param</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/posts/7/comments/3">/posts/7/comments/3</a></span><span class="d">multi params</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/redirect">/redirect</a></span><span class="d">302 → /</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/health">/api/health</a></span><span class="d">JSON</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/info">/api/info</a></span><span class="d">JSON</span></div>
<h3>Features</h3>
<div class="ep"><span class="m">✓</span><span class="p">Path routing with {params}</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Query parameter extraction</span></div>
<div class="ep"><span class="m">✓</span><span class="p">JSON / HTML / Text responses</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Multi-threaded (configurable)</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Redirects (302)</span></div>
<div class="ep"><span class="m">✓</span><span class="p">POST body access</span></div>
</div>
</body>
</html>`;
}
// =============================================================================
// Main — start the server
// =============================================================================
func Main() -> int {
let app: App = App_New(8080, 4);
App_Run(&app);
return 0;
}
} // module Main
+177
View File
@@ -0,0 +1,177 @@
# jwt-pitbul
**JWT CLI tool for the Bux ecosystem — sign, verify, decode, and key generation.**
A standalone command-line utility for working with JSON Web Tokens (RFC 7519). Built on `Std::Crypto`, backed by OpenSSL.
This version (0.2.0) is rewritten with modern Bux: algebraic enums, generic `Array<String>`, methods on `JwtAlg`, string interpolation, `for` loops, and structured `Result`/`Option`-style error handling.
## Quick Start
```bash
cd apps/jwt-pitbul
../../buxc build
./build/jwt-pitbul
```
## Commands
### `sign` — Create a JWT
```
jwt-pitbul sign <alg> <key> <claims_json>
```
| Argument | Description |
|----------|-------------|
| `<alg>` | Algorithm: `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `EdDSA` |
| `<key>` | HMAC secret string, or path to PEM file (RSA/ECDSA), or base64 raw key (Ed25519) |
| `<claims_json>` | JSON payload string (e.g. `{"sub":"123","exp":1735689600}`) |
```bash
# HMAC
jwt-pitbul sign HS256 'my-secret-key' '{"sub":"123","role":"admin"}'
# RSA (private key from file)
jwt-pitbul sign RS256 private.pem '{"sub":"456"}'
# ECDSA
jwt-pitbul sign ES256 ec_private.pem '{"sub":"789"}'
# Ed25519 (base64-encoded 32-byte private key)
jwt-pitbul sign EdDSA 'MC4CAQAwBQYDK2VwBCIEIJ...' '{"sub":"000"}'
```
### `verify` — Verify and decode
```
jwt-pitbul verify <token> <alg> <key>
```
```bash
jwt-pitbul verify eyJhbGciOiJ... HS256 'my-secret-key'
# ✓ Signature valid
#
# Header:
# {"alg":"HS256","typ":"JWT"}
#
# Payload:
# {"sub":"123","role":"admin"}
```
### `decode` — Decode without verification
```
jwt-pitbul decode <token>
```
```bash
jwt-pitbul decode eyJhbGciOiJ...
# Decoded (no verification):
#
# Header:
# {"alg":"HS256","typ":"JWT"}
#
# Payload:
# {"sub":"123"}
#
# Signature (base64url): xxxxxxxxx...
```
### `keygen` — Generate keys
```
jwt-pitbul keygen <type>
```
| Type | Output |
|------|--------|
| `ed25519` | Generates an Ed25519 keypair (prints base64 public/private keys) |
| `rsa` | Prints OpenSSL CLI commands to generate RSA 2048-bit keys |
| `ecdsa` | Prints OpenSSL CLI commands to generate ECDSA P-256 keys |
```bash
jwt-pitbul keygen ed25519
# Ed25519 keypair (base64):
# Public: MCowBQYDK2VwAyEA...
# Private: MC4CAQAwBQYDK2VwBCIEIJ...
jwt-pitbul keygen rsa
# RSA key generation requires OpenSSL CLI:
# openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
# openssl rsa -in private.pem -pubout -out public.pem
```
## Supported Algorithms
| Algorithm | Type | Key Format |
|-----------|------|------------|
| HS256 | HMAC-SHA256 | Raw secret string |
| HS384 | HMAC-SHA384 | Raw secret string |
| HS512 | HMAC-SHA512 | Raw secret string |
| RS256 | RSA PKCS#1 v1.5 SHA-256 | PEM file path |
| RS384 | RSA PKCS#1 v1.5 SHA-384 | PEM file path |
| RS512 | RSA PKCS#1 v1.5 SHA-512 | PEM file path |
| ES256 | ECDSA P-256 | PEM file path |
| ES384 | ECDSA P-384 | PEM file path |
| EdDSA | Ed25519 | Base64 32-byte raw key |
## Generating Keys with OpenSSL
### RSA 2048-bit
```bash
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -in private.pem -pubout -out public.pem
```
### ECDSA P-256
```bash
openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
openssl ec -in ec_private.pem -pubout -out ec_public.pem
```
### ECDSA P-384
```bash
openssl ecparam -genkey -name secp384r1 -noout -out ec_private.pem
openssl ec -in ec_private.pem -pubout -out ec_public.pem
```
### Ed25519
```bash
# In-app generation:
jwt-pitbul keygen ed25519
# Or via OpenSSL:
openssl genpkey -algorithm Ed25519 -out ed25519_private.pem
openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem
```
## Error Codes
| Exit | Meaning |
|------|---------|
| 0 | Success |
| 1 | Error (invalid arguments, bad signature, missing file, etc.) |
## Building
```bash
cd apps/jwt-pitbul
../../buxc build # Bootstrap compiler (Nim)
# or
../../buxc_lir build # Self-hosted compiler
```
## Dependencies
- Bux standard library (`Std::Crypto`, `Std::Io`, `Std::String`)
- OpenSSL 1.1.1+ (linked via runtime)
- Bux compiler (`buxc` or `buxc_lir`)
## License
Part of the Bux project. See root [LICENSE](../../LICENSE).
+8
View File
@@ -0,0 +1,8 @@
[Package]
Name = "jwt-pitbul"
Version = "0.2.0"
Type = "bin"
Description = "JWT CLI tool — encode, decode, verify, and key generation"
[Build]
Output = "Bin"
+468
View File
@@ -0,0 +1,468 @@
// =============================================================================
// jwt-pitbul — JWT CLI Tool (encode, decode, verify, keygen)
// Rewritten with modern Bux: algebraic enums, methods, string interpolation,
// for-in loops, generic arrays, and structured error handling.
// =============================================================================
module Main {
import Std::Io::{PrintLine, Print, PrintInt};
import Std::String::{
String_Len, String_Eq,
String_SplitCount, String_SplitPart,
StringBuilder, StringBuilder_New, StringBuilder_Append,
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
};
import Std::Crypto::Jwt::{
JwtAlg,
Jwt_MakeHeader,
Jwt_Encode,
Jwt_Decode,
Jwt_EncodeHS256, Jwt_EncodeHS384, Jwt_EncodeHS512,
Jwt_EncodeRS256, Jwt_EncodeES256, Jwt_EncodeEdDSA
};
import Std::Crypto::Base64::{Base64URL_Decode, Base64_Encode};
import Std::Crypto::Ed25519::{Ed25519_Keypair};
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
extern func bux_read_file(path: String) -> String;
extern func bux_file_exists(path: String) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
// =============================================================================
// Constants
// =============================================================================
const AppName: String = "jwt-pitbul";
const Version: String = "0.2.0";
// =============================================================================
// Algebraic enums for optionals and results
// =============================================================================
enum AlgOption {
Some(JwtAlg),
None,
}
func AlgOption_MakeSome(value: JwtAlg) -> AlgOption {
let o: AlgOption = AlgOption { tag: AlgOption_Some };
o.data.Some_0 = value;
return o;
}
func AlgOption_MakeNone() -> AlgOption {
return AlgOption { tag: AlgOption_None };
}
enum CmdOption {
Some(Cmd),
None,
}
func CmdOption_MakeSome(value: Cmd) -> CmdOption {
let o: CmdOption = CmdOption { tag: CmdOption_Some };
o.data.Some_0 = value;
return o;
}
func CmdOption_MakeNone() -> CmdOption {
return CmdOption { tag: CmdOption_None };
}
enum KeyTypeOption {
Some(KeyType),
None,
}
func KeyTypeOption_MakeSome(value: KeyType) -> KeyTypeOption {
let o: KeyTypeOption = KeyTypeOption { tag: KeyTypeOption_Some };
o.data.Some_0 = value;
return o;
}
func KeyTypeOption_MakeNone() -> KeyTypeOption {
return KeyTypeOption { tag: KeyTypeOption_None };
}
enum KeyResult {
Ok(String),
Err(String),
}
func KeyResult_MakeOk(value: String) -> KeyResult {
let r: KeyResult = KeyResult { tag: KeyResult_Ok };
r.data.Ok_0 = value;
return r;
}
func KeyResult_MakeErr(msg: String) -> KeyResult {
let r: KeyResult = KeyResult { tag: KeyResult_Err };
r.data.Err_0 = msg;
return r;
}
enum ExitResult {
Ok(int),
Err(String),
}
func ExitResult_MakeOk(value: int) -> ExitResult {
let r: ExitResult = ExitResult { tag: ExitResult_Ok };
r.data.Ok_0 = value;
return r;
}
func ExitResult_MakeErr(msg: String) -> ExitResult {
let r: ExitResult = ExitResult { tag: ExitResult_Err };
r.data.Err_0 = msg;
return r;
}
// =============================================================================
// Commands
// =============================================================================
enum Cmd {
Sign,
Verify,
Decode,
Keygen,
Help,
}
func ParseCmd(name: String) -> CmdOption {
if String_Eq(name, "sign") { return CmdOption_MakeSome(Cmd { tag: Cmd_Sign }); }
if String_Eq(name, "verify") { return CmdOption_MakeSome(Cmd { tag: Cmd_Verify }); }
if String_Eq(name, "decode") { return CmdOption_MakeSome(Cmd { tag: Cmd_Decode }); }
if String_Eq(name, "keygen") { return CmdOption_MakeSome(Cmd { tag: Cmd_Keygen }); }
if String_Eq(name, "help") || String_Eq(name, "--help") || String_Eq(name, "-h") {
return CmdOption_MakeSome(Cmd { tag: Cmd_Help });
}
return CmdOption_MakeNone();
}
// =============================================================================
// Key type generation
// =============================================================================
enum KeyType {
Rsa,
Ecdsa,
Ed25519,
}
func ParseKeyType(name: String) -> KeyTypeOption {
if String_Eq(name, "rsa") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Rsa }); }
if String_Eq(name, "ecdsa") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Ecdsa }); }
if String_Eq(name, "ed25519") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Ed25519 }); }
return KeyTypeOption_MakeNone();
}
// =============================================================================
// Extend JwtAlg with parsing / introspection methods
// =============================================================================
extend JwtAlg {
func Name(self: JwtAlg) -> String {
match self {
JwtAlg::HS256 => "HS256",
JwtAlg::HS384 => "HS384",
JwtAlg::HS512 => "HS512",
JwtAlg::RS256 => "RS256",
JwtAlg::RS384 => "RS384",
JwtAlg::RS512 => "RS512",
JwtAlg::ES256 => "ES256",
JwtAlg::ES384 => "ES384",
JwtAlg::EdDSA => "EdDSA",
_ => "HS256",
}
}
func IsHmac(self: JwtAlg) -> bool {
return self.tag == JwtAlg_HS256 || self.tag == JwtAlg_HS384 || self.tag == JwtAlg_HS512;
}
func NeedsPemFile(self: JwtAlg) -> bool {
return self.tag == JwtAlg_RS256 || self.tag == JwtAlg_RS384 || self.tag == JwtAlg_RS512 ||
self.tag == JwtAlg_ES256 || self.tag == JwtAlg_ES384;
}
}
func ParseAlg(name: String) -> AlgOption {
if String_Eq(name, "HS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS256 }); }
if String_Eq(name, "HS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS384 }); }
if String_Eq(name, "HS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS512 }); }
if String_Eq(name, "RS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS256 }); }
if String_Eq(name, "RS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS384 }); }
if String_Eq(name, "RS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS512 }); }
if String_Eq(name, "ES256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_ES256 }); }
if String_Eq(name, "ES384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_ES384 }); }
if String_Eq(name, "EdDSA") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_EdDSA }); }
return AlgOption_MakeNone();
}
// =============================================================================
// Help / Usage
// =============================================================================
func PrintUsage() {
PrintLine("╔══════════════════════════════════════════════════════╗");
PrintLine(f"║ {AppName} — JWT CLI Tool v{Version} ║");
PrintLine("║ Sign, verify, decode JSON Web Tokens ║");
PrintLine("╚══════════════════════════════════════════════════════╝");
PrintLine("");
PrintLine("Usage:");
PrintLine(f" {AppName} sign <alg> <key> <claims>");
PrintLine(f" {AppName} verify <token> <alg> <key>");
PrintLine(f" {AppName} decode <token>");
PrintLine(f" {AppName} keygen <type>");
PrintLine("");
PrintLine("Commands:");
PrintLine(" sign Create a signed JWT from claims JSON");
PrintLine(" verify Verify a JWT signature and print payload");
PrintLine(" decode Decode a JWT without verification");
PrintLine(" keygen Generate cryptographic keys");
PrintLine("");
PrintLine("Algorithms:");
PrintLine(" HS256, HS384, HS512 — HMAC (symmetric)");
PrintLine(" RS256, RS384, RS512 — RSA PKCS#1 v1.5");
PrintLine(" ES256, ES384 — ECDSA P-256 / P-384");
PrintLine(" EdDSA — Ed25519");
PrintLine("");
PrintLine("Key formats:");
PrintLine(" HMAC: raw secret string");
PrintLine(" RSA: path to PEM private/public key file");
PrintLine(" ECDSA: path to PEM private/public key file");
PrintLine(" EdDSA: base64-encoded 32-byte raw key");
PrintLine("");
PrintLine("Key generation:");
PrintLine(f" {AppName} keygen rsa # RSA 2048-bit (PEM)");
PrintLine(f" {AppName} keygen ecdsa # ECDSA P-256 (PEM)");
PrintLine(f" {AppName} keygen ed25519 # Ed25519 (raw base64)");
PrintLine("");
PrintLine("Examples:");
Print(f" {AppName} sign HS256 'my-secret' ");
PrintLine("'{\"sub\":\"123\"}'");
PrintLine(f" {AppName} verify eyJh... HS256 'my-secret'");
PrintLine(f" {AppName} decode eyJh...");
PrintLine(f" {AppName} keygen ed25519");
}
// =============================================================================
// Key resolution — for RSA/ECDSA, read PEM file; for HMAC/EdDSA, pass through
// =============================================================================
func ResolveKey(alg: JwtAlg, keyArg: String) -> KeyResult {
if !alg.NeedsPemFile() {
return KeyResult_MakeOk(keyArg);
}
if bux_file_exists(keyArg) == 0 {
return KeyResult_MakeErr(f"ERROR: PEM file not found: {keyArg}");
}
let pem: String = bux_read_file(keyArg);
if String_Len(pem) == 0 {
return KeyResult_MakeErr(f"ERROR: could not read PEM file: {keyArg}");
}
return KeyResult_MakeOk(pem);
}
// =============================================================================
// Command: sign
// =============================================================================
func CmdSign(algName: String, keyArg: String, claimsJson: String) -> ExitResult {
let algOpt: AlgOption = ParseAlg(algName);
if algOpt.tag != AlgOption_Some {
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
}
let alg: JwtAlg = algOpt.data.Some_0;
let keyRes: KeyResult = ResolveKey(alg, keyArg);
if keyRes.tag != KeyResult_Ok {
return ExitResult_MakeErr(keyRes.data.Err_0);
}
let key: String = keyRes.data.Ok_0;
let header: String = Jwt_MakeHeader(alg);
let token: String = Jwt_Encode(header, claimsJson, alg, key);
if String_Len(token) == 0 {
return ExitResult_MakeErr("ERROR: signing failed");
}
PrintLine(token);
return ExitResult_MakeOk(0);
}
// =============================================================================
// Command: verify
// =============================================================================
func CmdVerify(token: String, algName: String, keyArg: String) -> ExitResult {
let algOpt: AlgOption = ParseAlg(algName);
if algOpt.tag != AlgOption_Some {
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
}
let alg: JwtAlg = algOpt.data.Some_0;
let keyRes: KeyResult = ResolveKey(alg, keyArg);
if keyRes.tag != KeyResult_Ok {
return ExitResult_MakeErr(keyRes.data.Err_0);
}
let key: String = keyRes.data.Ok_0;
var header: String = "";
var payload: String = "";
if !Jwt_Decode(token, alg, key, &header, &payload) {
return ExitResult_MakeErr("✗ Signature INVALID (or malformed token)");
}
PrintLine("✓ Signature valid");
PrintLine("");
PrintLine("Header:");
PrintLine(header);
PrintLine("");
PrintLine("Payload:");
PrintLine(payload);
return ExitResult_MakeOk(0);
}
// =============================================================================
// Command: decode (no verification)
// =============================================================================
func CmdDecode(token: String) -> ExitResult {
let partCount: uint = String_SplitCount(token, ".");
if partCount != 3 {
return ExitResult_MakeErr("ERROR: not a valid JWT (expected 3 parts)");
}
let headerB64: String = String_SplitPart(token, ".", 0);
let payloadB64: String = String_SplitPart(token, ".", 1);
let sigB64: String = String_SplitPart(token, ".", 2);
let headerJson: String = Base64URL_Decode(headerB64);
let payloadJson: String = Base64URL_Decode(payloadB64);
PrintLine("Decoded (no verification):");
PrintLine("");
PrintLine("Header:");
PrintLine(headerJson);
PrintLine("");
PrintLine("Payload:");
PrintLine(payloadJson);
PrintLine("");
Print("Signature (base64url): ");
PrintLine(sigB64);
return ExitResult_MakeOk(0);
}
// =============================================================================
// Command: keygen
// =============================================================================
func CmdKeygen(keyType: String) -> ExitResult {
let ktOpt: KeyTypeOption = ParseKeyType(keyType);
if ktOpt.tag != KeyTypeOption_Some {
return ExitResult_MakeErr(f"ERROR: unknown key type '{keyType}'. Use: rsa, ecdsa, ed25519");
}
let kt: KeyType = ktOpt.data.Some_0;
if kt.tag == KeyType_Ed25519 {
let pubBuf: *void = bux_alloc(32);
let priv: *void = bux_alloc(32);
if !Ed25519_Keypair(pubBuf, priv) {
return ExitResult_MakeErr("ERROR: Ed25519 key generation failed (OpenSSL 1.1.1+ required)");
}
let pubB64: String = bux_base64_encode(pubBuf as String, 32);
let privB64: String = bux_base64_encode(priv as String, 32);
PrintLine("Ed25519 keypair (base64):");
PrintLine(f" Public: {pubB64}");
PrintLine(f" Private: {privB64}");
return ExitResult_MakeOk(0);
}
if kt.tag == KeyType_Rsa {
PrintLine("RSA key generation requires OpenSSL CLI:");
PrintLine(" openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048");
PrintLine(" openssl rsa -in private.pem -pubout -out public.pem");
PrintLine("");
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
return ExitResult_MakeOk(0);
}
if kt.tag == KeyType_Ecdsa {
PrintLine("ECDSA key generation requires OpenSSL CLI:");
PrintLine(" openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem");
PrintLine(" openssl ec -in ec_private.pem -pubout -out ec_public.pem");
PrintLine("");
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
return ExitResult_MakeOk(0);
}
return ExitResult_MakeErr(f"ERROR: unknown key type '{keyType}'");
}
// =============================================================================
// Dispatch a parsed command to its handler
// =============================================================================
func RunCommand(cmd: Cmd, args: Array<String>) -> ExitResult {
if cmd.tag == Cmd_Help {
PrintUsage();
return ExitResult_MakeOk(0);
}
if cmd.tag == Cmd_Sign {
if args.len < 5 {
return ExitResult_MakeErr("ERROR: 'sign' requires: <alg> <key> <claims_json>");
}
return CmdSign(args[2], args[3], args[4]);
}
if cmd.tag == Cmd_Verify {
if args.len < 5 {
return ExitResult_MakeErr("ERROR: 'verify' requires: <token> <alg> <key>");
}
return CmdVerify(args[2], args[3], args[4]);
}
if cmd.tag == Cmd_Decode {
if args.len < 3 {
return ExitResult_MakeErr("ERROR: 'decode' requires: <token>");
}
return CmdDecode(args[2]);
}
if cmd.tag == Cmd_Keygen {
if args.len < 3 {
return ExitResult_MakeErr("ERROR: 'keygen' requires: <type>");
}
return CmdKeygen(args[2]);
}
return ExitResult_MakeErr("ERROR: unhandled command");
}
// =============================================================================
// Main Entry Point
// =============================================================================
func Main() -> int {
let argc: int = bux_argc();
// Collect CLI arguments into a generic Array<String>
let args: Array<String> = Array_New<String>(argc as uint);
for i in 0..argc {
Array_Push<String>(&args, bux_argv(i));
}
if args.len < 2 {
PrintUsage();
return 0;
}
let command: String = args[1];
let cmdOpt: CmdOption = ParseCmd(command);
if cmdOpt.tag != CmdOption_Some {
PrintLine(f"ERROR: unknown command '{command}'");
PrintLine(f"Run '{AppName} help' for usage.");
return 1;
}
let cmd: Cmd = cmdOpt.data.Some_0;
let result: ExitResult = RunCommand(cmd, args);
if result.tag == ExitResult_Err {
PrintLine(result.data.Err_0);
return 1;
}
return result.data.Ok_0;
}
} // module Main
+189
View File
@@ -0,0 +1,189 @@
# Nexus
**High-performance, multi-threaded HTTP/1.1, HTTP/2 & WebSocket server — built with the [Bux](https://github.com/bux-lang/bux) programming language.**
Nexus is a from-scratch web server that demonstrates Bux's systems-programming capabilities: raw TCP sockets, pthread-based concurrency, manual memory management, and zero-dependency C ABI interop — all from a clean, modern syntax.
---
## Features
| Area | What's Implemented |
|------|-------------------|
| **HTTP/1.1** | Full request parsing (method, path, headers, body), response building with status codes, content negotiation |
| **Multi-threaded** | Configurable worker pool using the multi-accept pattern — each worker calls `accept()` directly on the shared listen socket |
| **HTTP/2** | Connection preface detection (`PRI * HTTP/2.0`), upgrade-aware routing |
| **WebSocket** | RFC 6455 upgrade handshake detection, `Sec-WebSocket-Key` extraction |
| **Static files** | Serves from `public/` with MIME-type detection for 20+ file types, directory-traversal protection |
| **JSON API** | Built-in `/api/health` and `/api/info` endpoints |
| **Logging** | Per-request structured logging (method, path, status code) |
## Quick Start
```bash
# From the project root
cd apps/nexus
# Build with the bootstrap compiler (Nim)
../../buxc build
# Or with the self-hosted compiler
../../buxc_lir build
# Run
./nexus
```
Server starts on `http://0.0.0.0:8080`:
```
╔══════════════════════════════════════════════╗
║ Nexus HTTP Server v0.1.0 ║
║ High-performance multi-threaded HTTP/1.1 ║
║ HTTP/2 & WebSocket detection included ║
║ Built with Bux ║
╚══════════════════════════════════════════════╝
✓ Server listening on http://0.0.0.0:8080
✓ Worker threads: 4
✓ Static files: ./public/
Endpoints:
GET / — Static files (public/)
GET /api/health — Health check (JSON)
GET /api/info — Server info (JSON)
GET /ws — WebSocket upgrade
ANY /* — Static file serving
```
## Testing It
```bash
# Home page (HTML)
curl http://localhost:8080/
# Health check
curl http://localhost:8080/api/health
# → {"status":"ok","server":"Nexus","version":"0.1.0"}
# Server info
curl http://localhost:8080/api/info
# → {"name":"Nexus","language":"Bux","features":[...]}
# Static file (404 page)
curl http://localhost:8080/404.html
# Nonexistent file
curl http://localhost:8080/nope
# → 404 Not Found
# WebSocket upgrade attempt
curl -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
http://localhost:8080/ws
# → 101 Switching Protocols
```
## Architecture
### Thread Model — Multi-Accept
Nexus uses the **multi-accept** pattern rather than a traditional thread pool with a work queue:
```
Main Thread Worker 1 Worker 2 Worker 3
│ │ │ │
├─ socket()/bind()/listen() │ │ │
├─ spawn Worker() ──────────► │ │
├─ spawn Worker() ─────────────────────────► │
├─ spawn Worker() ────────────────────────────────────────►
│ │ │ │
▼ Worker() ▼ accept() ▼ accept() ▼ accept()
accept() loop │ │ │
```
Each worker thread calls `accept()` on the **same** listening socket. The Linux kernel distributes incoming connections across the blocked accept calls — the same mechanism used by nginx and Apache prefork. No mutex, no queue, no context switching between a dispatcher and workers.
### Request Lifecycle
```
Client connects
Net_Accept() → client fd
Net_Recv(fd, 8192) → raw bytes
ParseRequest()
├─ Split headers on \r\n\r\n
├─ Parse request line → method, path, version
└─ Parse header lines → key-value array
Router_Dispatch()
├─ /api/* → JSON handlers
├─ /ws → WebSocket upgrade
├─ Upgrade header → HTTP/2 or WS detection
└─ /* → Static file serving
BuildResponse() → HTTP/1.1 status line + headers + body
Net_Send(fd, response) → bytes to client
Net_Close(fd)
```
### Design Decisions
- **No keep-alive by default.** Each connection is closed after one response. This avoids blocking worker threads on idle clients (Bux doesn't yet expose `SO_RCVTIMEO`).
- **Linear header array instead of hash map.** HTTP requests typically carry 515 headers. A linear scan over a key-value array is faster than hashing for this N, and avoids the complexity of iterating over Bux's generic `StringMap`.
- **Raw extern calls for the string builder.** The stdlib `StringBuilder` wrapper adds a struct indirection. Calling `bux_sb_new` / `bux_sb_append` / `bux_sb_build` directly is simpler and equally safe.
- **WebSocket accept key is a placeholder.** A full implementation needs SHA-1 hashing and Base64 encoding. These aren't in Bux's stdlib yet; they can be added as extern C functions when needed.
## Project Structure
```
apps/nexus/
├── bux.toml # Package manifest
├── README.md # This file
├── src/
│ └── Main.bux # The entire server (~640 lines)
└── public/
├── index.html # Landing page
└── 404.html # Error page
```
Everything lives in one file (`src/Main.bux`) by design — it keeps the module graph flat and the build fast. As the server grows, the HTTP parser, router, and handlers can be split into separate modules.
## Configuration
Edit the constants at the top of `src/Main.bux`:
```bux
const SERVER_PORT: int = 8080; // Listen port
const THREAD_COUNT: int = 4; // Number of worker threads
const RECV_BUF_SIZE: int = 8192; // Receive buffer per request
const SERVER_NAME: String = "Nexus/0.1.0 (Bux)";
const PUBLIC_DIR: String = "public"; // Static files directory
```
## Roadmap
- [ ] Keep-alive with configurable socket timeout
- [ ] Full WebSocket frame read/write (requires SHA-1 + Base64)
- [ ] HTTP/2 binary framing layer (HPACK, stream multiplexing)
- [ ] SSL/TLS via OpenSSL extern bindings
- [ ] Middleware / filter chain
- [ ] Request body parsing (JSON, form-encoded, multipart)
- [ ] Virtual hosts
- [ ] Access logging to file
- [ ] Rate limiting
## License
Nexus is part of the Bux project. See the root [LICENSE](../../LICENSE) for terms.
+1
View File
@@ -0,0 +1 @@
EXIT:137
+1
View File
@@ -0,0 +1 @@
EXIT:124
+1
View File
@@ -0,0 +1 @@
EXIT:124
+1
View File
@@ -0,0 +1 @@
EXIT:124
+1
View File
@@ -0,0 +1 @@
EXIT:124
+2
View File
@@ -0,0 +1,2 @@
timeout: командата „„../../buxc““ не може да бъде изпълнена: Няма такъв файл или директория
EXIT:127
+1
View File
@@ -0,0 +1 @@
EXIT:137
+8
View File
@@ -0,0 +1,8 @@
[Package]
Name = "nexus"
Version = "0.1.0"
Type = "bin"
Description = "High-performance multi-threaded HTTP/1.1, HTTP/2 and WebSocket server"
[Build]
Output = "Bin"
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 — Not Found | Nexus</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d1117;
color: #c9d1d9;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
h1 { font-size: 4rem; color: #f85149; margin-bottom: 0.5rem; }
p { color: #8b949e; font-size: 1.2rem; }
.back { margin-top: 2rem; }
.back a { color: #58a6ff; text-decoration: none; }
.back a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>404</h1>
<p>The requested resource was not found on this server.</p>
<p class="back"><a href="/">← Back to Home</a></p>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
<html><body>Hello Nexus</body></html>
+21
View File
@@ -0,0 +1,21 @@
module Config {
pub struct ServerConfig {
bindAddr: String;
port: int;
workerCount: int;
publicDir: String;
backlog: int;
}
pub const func DefaultConfig() -> ServerConfig {
return ServerConfig {
bindAddr: "0.0.0.0",
port: 8080,
workerCount: 4,
publicDir: "public",
backlog: 128,
};
}
}
+65
View File
@@ -0,0 +1,65 @@
module Errors {
import Http::{HttpRequest};
pub enum HttpError {
BadRequest,
NotFound,
MethodNotAllowed,
InternalError(String),
}
pub enum ParseResult {
Ok(HttpRequest),
Err(HttpError),
}
pub enum FileResult {
Ok(String),
Err(HttpError),
}
pub func ParseResult_NewOk(req: HttpRequest) -> ParseResult {
let r: ParseResult = ParseResult { tag: ParseResult_Ok };
r.data.Ok_0 = req;
return r;
}
pub func ParseResult_NewErr(err: HttpError) -> ParseResult {
let r: ParseResult = ParseResult { tag: ParseResult_Err };
r.data.Err_0 = err;
return r;
}
pub func FileResult_NewOk(content: String) -> FileResult {
let r: FileResult = FileResult { tag: FileResult_Ok };
r.data.Ok_0 = content;
return r;
}
pub func FileResult_NewErr(err: HttpError) -> FileResult {
let r: FileResult = FileResult { tag: FileResult_Err };
r.data.Err_0 = err;
return r;
}
pub func FileResult_IsOk(r: FileResult) -> bool {
return r.tag == FileResult_Ok;
}
pub func FileResult_Unwrap(r: FileResult) -> String {
return r.data.Ok_0;
}
pub func FileResult_UnwrapErr(r: FileResult) -> HttpError {
return r.data.Err_0;
}
pub func HttpError_ToString(err: HttpError) -> String {
if err.tag == HttpError_BadRequest { return "Bad Request"; }
if err.tag == HttpError_NotFound { return "Not Found"; }
if err.tag == HttpError_MethodNotAllowed { return "Method Not Allowed"; }
return err.data.InternalError_0;
}
}
+94
View File
@@ -0,0 +1,94 @@
module Handlers {
import Std::String::{String_Eq, String_Contains};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse, Http_MimeType, RequestHeader_Get};
import Errors::{HttpError, FileResult, FileResult_NewOk, FileResult_NewErr, FileResult_IsOk, FileResult_Unwrap, FileResult_UnwrapErr, HttpError_ToString};
extern func bux_strlen(s: String) -> uint;
extern func bux_file_exists(path: String) -> int;
extern func bux_read_file(path: String) -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
pub func NotFoundResponse() -> HttpResponse {
return Http_NewResponse(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
}
pub func MethodNotAllowedResponse() -> HttpResponse {
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
}
pub func ReadStaticFile(requestPath: String) -> FileResult {
if String_Contains(requestPath, "..") {
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
}
var filePath: String = requestPath;
if String_Eq(filePath, "/") {
filePath = "/index.html";
}
let fullPath: String = bux_path_join("public", filePath);
if bux_file_exists(fullPath) == 0 {
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
}
let content: String = bux_read_file(fullPath);
return FileResult_NewOk(content);
}
func FileErrorResponse(err: HttpError) -> HttpResponse {
match err {
HttpError::NotFound => NotFoundResponse(),
_ => Http_NewResponse(500, "text/plain; charset=utf-8", HttpError_ToString(err)),
}
}
pub func ServeStaticFile(req: HttpRequest) -> HttpResponse {
if req.method != HttpMethod_GET && req.method != HttpMethod_HEAD {
return MethodNotAllowedResponse();
}
let result: FileResult = ReadStaticFile(req.path);
if FileResult_IsOk(result) {
let content: String = FileResult_Unwrap(result);
return Http_NewResponse(200, Http_MimeType(req.path), content);
}
return FileErrorResponse(FileResult_UnwrapErr(result));
}
pub func HandleApiHealth() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.2.0\"}");
}
pub func HandleApiInfo() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"features\":[\"HTTP/1.1\",\"thread-pool\",\"algebraic-enums\"]}");
}
pub func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
let wsKey: String = RequestHeader_Get(req, "Sec-WebSocket-Key");
var resp: HttpResponse;
resp.statusCode = 101;
resp.contentType = "";
resp.body = "";
let sb: *void = bux_sb_new(256);
bux_sb_append(sb, "Upgrade: websocket\r\n");
bux_sb_append(sb, "Connection: Upgrade\r\n");
bux_sb_append(sb, "Sec-WebSocket-Accept: ");
bux_sb_append(sb, wsKey);
bux_sb_append(sb, "\r\n");
resp.extraHeaders = bux_sb_build(sb);
bux_sb_free(sb);
return resp;
}
}
+107
View File
@@ -0,0 +1,107 @@
module Http {
import Std::Array::{Array};
import Std::String::{String_Eq, String_EndsWith, String_Contains};
pub enum HttpMethod {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
UNKNOWN,
}
pub struct HeaderEntry {
key: String;
value: String;
}
pub struct HttpRequest {
method: HttpMethod;
path: String;
version: String;
body: String;
headers: Array<HeaderEntry>;
}
pub struct HttpResponse {
statusCode: int;
contentType: String;
body: String;
extraHeaders: String;
}
pub func Http_StatusText(code: int) -> String {
if code == 200 { return "OK"; }
if code == 201 { return "Created"; }
if code == 204 { return "No Content"; }
if code == 301 { return "Moved Permanently"; }
if code == 302 { return "Found"; }
if code == 304 { return "Not Modified"; }
if code == 400 { return "Bad Request"; }
if code == 401 { return "Unauthorized"; }
if code == 403 { return "Forbidden"; }
if code == 404 { return "Not Found"; }
if code == 405 { return "Method Not Allowed"; }
if code == 413 { return "Payload Too Large"; }
if code == 414 { return "URI Too Long"; }
if code == 500 { return "Internal Server Error"; }
if code == 501 { return "Not Implemented"; }
if code == 503 { return "Service Unavailable"; }
return "Unknown";
}
pub func Http_MimeType(path: String) -> String {
if String_EndsWith(path, ".html") || String_EndsWith(path, ".htm") { return "text/html; charset=utf-8"; }
if String_EndsWith(path, ".css") { return "text/css; charset=utf-8"; }
if String_EndsWith(path, ".js") { return "application/javascript; charset=utf-8"; }
if String_EndsWith(path, ".json") { return "application/json; charset=utf-8"; }
if String_EndsWith(path, ".xml") { return "application/xml; charset=utf-8"; }
if String_EndsWith(path, ".txt") { return "text/plain; charset=utf-8"; }
if String_EndsWith(path, ".png") { return "image/png"; }
if String_EndsWith(path, ".jpg") || String_EndsWith(path, ".jpeg") { return "image/jpeg"; }
if String_EndsWith(path, ".gif") { return "image/gif"; }
if String_EndsWith(path, ".svg") { return "image/svg+xml"; }
if String_EndsWith(path, ".ico") { return "image/x-icon"; }
if String_EndsWith(path, ".webp") { return "image/webp"; }
if String_EndsWith(path, ".woff2") { return "font/woff2"; }
if String_EndsWith(path, ".woff") { return "font/woff"; }
if String_EndsWith(path, ".wasm") { return "application/wasm"; }
return "application/octet-stream";
}
pub func Http_MethodName(m: HttpMethod) -> String {
match m {
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
HttpMethod::PUT => "PUT",
HttpMethod::DELETE => "DELETE",
HttpMethod::PATCH => "PATCH",
HttpMethod::HEAD => "HEAD",
HttpMethod::OPTIONS => "OPTIONS",
HttpMethod::UNKNOWN => "UNKNOWN",
}
}
pub func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
var resp: HttpResponse;
resp.statusCode = code;
resp.contentType = contentType;
resp.body = body;
resp.extraHeaders = "";
return resp;
}
pub func RequestHeader_Get(req: HttpRequest, key: String) -> String {
for entry in req.headers {
if String_Eq(entry.key, key) {
return entry.value;
}
}
return "";
}
}
+49
View File
@@ -0,0 +1,49 @@
module Main {
import Config::{ServerConfig, DefaultConfig};
import Http::{HttpMethod};
import Router::{Handler, Route, Router};
import Server::{RunServer};
import Std::Array::{Array, Array_New, Array_Push};
func BuildRouter() -> Router {
var routes: Array<Route> = Array_New<Route>(8);
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/api/health",
handler: Handler { tag: Handler_ApiHealth },
});
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/api/info",
handler: Handler { tag: Handler_ApiInfo },
});
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/ws",
handler: Handler { tag: Handler_WsUpgrade },
});
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/",
handler: Handler { tag: Handler_StaticFile },
});
return Router {
routes: routes,
notFound: Handler { tag: Handler_NotFound },
};
}
func Main() -> int {
let config: ServerConfig = DefaultConfig();
let router: Router = BuildRouter();
return RunServer(config, router);
}
}
+130
View File
@@ -0,0 +1,130 @@
module Parser {
import Std::String::{String_Len, String_Eq, String_Trim};
import Std::Array::{Array, Array_New, Array_Push};
import Http::{HttpMethod, HttpRequest, HeaderEntry};
import Errors::{HttpError, ParseResult, ParseResult_NewOk, ParseResult_NewErr};
extern func bux_strlen(s: String) -> uint;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_offset(pos: String, base: String) -> uint;
pub func ParseMethod(s: String) -> HttpMethod {
if String_Eq(s, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
return HttpMethod { tag: HttpMethod_UNKNOWN };
}
func Slice(raw: String, start: int, len: int) -> String {
if start < 0 || len <= 0 { return ""; }
return bux_str_slice(raw, start as uint, len as uint);
}
func FindCrlf(raw: String, start: int) -> int {
let rawLen: uint = bux_strlen(raw);
if start as uint >= rawLen { return -1; }
let tail: String = bux_str_slice(raw, start as uint, rawLen - start as uint);
let hit: String = bux_strstr(tail, "\r\n");
if String_Len(hit) == 0 { return -1; }
let offset: uint = bux_str_offset(hit, tail);
return start + offset as int;
}
func ParseHeaders(raw: String, start: int, end: int) -> Array<HeaderEntry> {
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
var pos: int = start;
while pos < end {
let lineEnd: int = FindCrlf(raw, pos);
if lineEnd < 0 || lineEnd >= end { break; }
let lineLen: int = lineEnd - pos;
if lineLen > 0 {
let line: String = Slice(raw, pos, lineLen);
let colon: String = bux_strstr(line, ":");
if String_Len(colon) > 0 {
let keyLen: uint = bux_str_offset(colon, line);
let key: String = String_Trim(Slice(line, 0, keyLen as int));
let valStart: int = keyLen as int + 1;
let val: String = String_Trim(Slice(line, valStart, lineLen - valStart));
let entry: HeaderEntry = HeaderEntry { key: key, value: val };
Array_Push<HeaderEntry>(&headers, entry);
}
}
pos = lineEnd + 2;
}
return headers;
}
pub func ParseRequest(raw: String) -> ParseResult {
let rawLen: uint = bux_strlen(raw);
if rawLen == 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
// Find end of request line
let lineEnd: int = FindCrlf(raw, 0);
if lineEnd < 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
// Split request line: METHOD PATH VERSION
var methodEnd: int = -1;
var pathStart: int = -1;
var pathEnd: int = -1;
var i: int = 0;
while i < lineEnd {
if raw[i] as int == 32 { // space
if methodEnd < 0 {
methodEnd = i;
pathStart = i + 1;
} else if pathEnd < 0 {
pathEnd = i;
break;
}
}
i = i + 1;
}
if methodEnd < 0 || pathStart < 0 || pathEnd < 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
let methodStr: String = Slice(raw, 0, methodEnd);
let path: String = Slice(raw, pathStart, pathEnd - pathStart);
let version: String = Slice(raw, pathEnd + 1, lineEnd - pathEnd - 1);
// Find header/body boundary
let boundary: String = bux_strstr(raw, "\r\n\r\n");
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
var body: String = "";
if String_Len(boundary) > 0 {
let headerEnd: uint = bux_str_offset(boundary, raw);
headers = ParseHeaders(raw, lineEnd + 2, headerEnd as int);
let bodyStart: uint = headerEnd + 4;
if bodyStart < rawLen {
body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
}
} else {
headers = ParseHeaders(raw, lineEnd + 2, rawLen as int);
}
if String_Eq(path, "") {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
let req: HttpRequest = HttpRequest {
method: ParseMethod(methodStr),
path: path,
version: version,
body: body,
headers: headers,
};
return ParseResult_NewOk(req);
}
}
+46
View File
@@ -0,0 +1,46 @@
module Router {
import Std::Array::{Array};
import Std::String::{String_Eq};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse};
import Handlers::{ServeStaticFile, HandleApiHealth, HandleApiInfo, HandleWebSocketUpgrade, NotFoundResponse};
pub enum Handler {
StaticFile,
ApiHealth,
ApiInfo,
WsUpgrade,
NotFound,
}
pub struct Route {
method: HttpMethod;
path: String;
handler: Handler;
}
pub struct Router {
routes: Array<Route>;
notFound: Handler;
}
pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
match h {
Handler::StaticFile => ServeStaticFile(req),
Handler::ApiHealth => HandleApiHealth(),
Handler::ApiInfo => HandleApiInfo(),
Handler::WsUpgrade => HandleWebSocketUpgrade(req),
Handler::NotFound => NotFoundResponse(),
}
}
pub func Router_Dispatch(r: Router, req: HttpRequest) -> HttpResponse {
for route in r.routes {
if route.method == req.method && String_Eq(route.path, req.path) {
return Handler_Handle(route.handler, req);
}
}
return Handler_Handle(r.notFound, req);
}
}
+176
View File
@@ -0,0 +1,176 @@
module Server {
import Std::Io::{Print, PrintLine, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{String_Len, String_StartsWith};
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
import Config::{ServerConfig};
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse};
import Errors::{ParseResult};
import Parser::{ParseRequest};
import Router::{Router, Router_Dispatch};
extern func bux_strlen(s: String) -> uint;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
pub struct ConnectionTask {
fd: int;
}
pub func BuildResponse(resp: HttpResponse) -> String {
let sb: *void = bux_sb_new(4096);
bux_sb_append(sb, "HTTP/1.1 ");
bux_sb_append_int(sb, resp.statusCode as int64);
bux_sb_append(sb, " ");
bux_sb_append(sb, Http_StatusText(resp.statusCode));
bux_sb_append(sb, "\r\n");
bux_sb_append(sb, "Server: Nexus/0.2.0 (Bux)\r\n");
if bux_strlen(resp.extraHeaders) > 0 {
bux_sb_append(sb, resp.extraHeaders);
}
if bux_strlen(resp.contentType) > 0 {
bux_sb_append(sb, "Content-Type: ");
bux_sb_append(sb, resp.contentType);
bux_sb_append(sb, "\r\n");
}
let bodyLen: uint = bux_strlen(resp.body);
bux_sb_append(sb, "Content-Length: ");
bux_sb_append_int(sb, bodyLen as int64);
bux_sb_append(sb, "\r\n");
bux_sb_append(sb, "Connection: close\r\n");
bux_sb_append(sb, "\r\n");
if bodyLen > 0 {
bux_sb_append(sb, resp.body);
}
let result: String = bux_sb_build(sb);
bux_sb_free(sb);
return result;
}
pub func HandleConnection(fd: int, router: Router) {
let raw: String = Net_Recv(fd, 8192);
if String_Len(raw) == 0 {
return;
}
// HTTP/2 preface detection
if String_StartsWith(raw, "PRI * HTTP/2.0") {
let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
"HTTP/2 detected — full support planned for future release.\r\n");
Net_Send(fd, BuildResponse(resp));
return;
}
let parsed: ParseResult = ParseRequest(raw);
if parsed.tag == ParseResult_Ok {
let req: HttpRequest = parsed.data.Ok_0;
let resp: HttpResponse = Router_Dispatch(router, req);
Net_Send(fd, BuildResponse(resp));
} else {
let resp: HttpResponse = Http_NewResponse(400, "text/plain; charset=utf-8", "Bad Request");
Net_Send(fd, BuildResponse(resp));
}
}
pub struct WorkerCtx {
taskQueue: *Channel<ConnectionTask>;
router: Router;
}
pub func Worker(ctx: *WorkerCtx) {
while true {
let task: ConnectionTask = Channel_Recv<ConnectionTask>(ctx.taskQueue);
HandleConnection(task.fd, ctx.router);
Net_Close(task.fd);
}
}
pub struct AcceptorCtx {
serverFd: int;
taskQueue: *Channel<ConnectionTask>;
}
pub func Acceptor(ctx: *AcceptorCtx) {
while true {
let fd: int = Net_Accept(ctx.serverFd);
if fd >= 0 {
let task: ConnectionTask = ConnectionTask { fd: fd };
Channel_Send<ConnectionTask>(ctx.taskQueue, task);
}
}
}
pub func RunServer(config: ServerConfig, router: Router) -> int {
PrintLine("================================================");
PrintLine(" Nexus HTTP Server v0.2.0");
PrintLine(" Production-ready HTTP/1.1 with thread-pool");
PrintLine(" Built with Bux");
PrintLine("================================================");
PrintLine("");
let serverFd: int = Net_Create();
if serverFd < 0 {
PrintLine("FATAL: socket() failed");
return 1;
}
if !Net_SetReuse(serverFd) {
PrintLine("WARN: SO_REUSEADDR failed");
}
if !Net_Bind(serverFd, config.bindAddr, config.port) {
Print("FATAL: bind failed: ");
PrintLine(Net_LastError());
Net_Close(serverFd);
return 1;
}
if !Net_Listen(serverFd, config.backlog) {
PrintLine("FATAL: listen() failed");
Net_Close(serverFd);
return 1;
}
Print("Listening on http://");
Print(config.bindAddr);
Print(":");
PrintInt(config.port);
PrintLine("");
PrintInt(config.workerCount);
PrintLine(" worker threads | static: ./public/");
PrintLine("Endpoints: / /api/health /api/info /ws");
PrintLine("Press Ctrl+C to stop.");
PrintLine("");
let taskQueue: Channel<ConnectionTask> = Channel_New<ConnectionTask>(config.backlog as int64);
let workerCtx: WorkerCtx = WorkerCtx { taskQueue: &taskQueue, router: router };
let acceptorCtx: AcceptorCtx = AcceptorCtx { serverFd: serverFd, taskQueue: &taskQueue };
// Spawn workers (main thread will also become one)
var i: int = 0;
while i < config.workerCount - 1 {
spawn Worker(&workerCtx);
i = i + 1;
}
// Spawn acceptor
spawn Acceptor(&acceptorCtx);
// Main thread works too
Worker(&workerCtx);
return 0;
}
}
+2
View File
@@ -0,0 +1,2 @@
bux 0.1.0 (bootstrap)
EXIT:0
+77
View File
@@ -0,0 +1,77 @@
# SimpleDB
File-backed key-value database for Bux.
## Usage
```
simpledb <dbfile> set <key> <value>
simpledb <dbfile> get <key>
simpledb <dbfile> del <key>
simpledb <dbfile> has <key>
simpledb <dbfile> keys
simpledb <dbfile> count
```
## Examples
```sh
$ simpledb data.db set name BuxLang
OK name = BuxLang
$ simpledb data.db set version 0.1.0
OK version = 0.1.0
$ simpledb data.db get name
BuxLang
$ simpledb data.db has name
true name
$ simpledb data.db keys
keys: 2
name
version
$ simpledb data.db count
count: 2
$ simpledb data.db del version
DEL version
$ cat data.db
name=BuxLang
```
## Storage
Data is stored as plain text, one `key=value` per line. The file is created automatically on first write.
## Build
```sh
# workaround: disable broken JWT module
mv ../../lib/crypto/jwt.bux ../../lib/crypto/jwt.bux.bak
../../buxc build
mv ../../lib/crypto/jwt.bux.bak ../../lib/crypto/jwt.bux
```
## API
The `Database` struct and its functions are defined in `src/Main.bux` and can be imported into other Bux projects:
```bux
import Simpledb::{Database, DB_New, DB_Load, DB_Save, DB_Get, DB_Set, DB_Del, DB_Has, DB_Count, DB_Keys};
```
| Function | Signature |
|----------|-----------|
| `DB_New` | `(path: String) -> Database` |
| `DB_Load` | `(db: *Database) -> bool` |
| `DB_Save` | `(db: *Database) -> bool` |
| `DB_Get` | `(db: *Database, key: String) -> String` |
| `DB_Set` | `(db: *Database, key: String, value: String)` |
| `DB_Del` | `(db: *Database, key: String) -> bool` |
| `DB_Has` | `(db: *Database, key: String) -> bool` |
| `DB_Count` | `(db: *Database) -> uint` |
| `DB_Keys` | `(db: *Database) -> *String` |
+8
View File
@@ -0,0 +1,8 @@
[Package]
Name = "simpledb"
Version = "0.1.0"
Type = "bin"
Description = "SimpleDB — file-backed JSON key-value database for Bux"
[Build]
Output = "Bin"
+339
View File
@@ -0,0 +1,339 @@
module Main {
import Std::Io::{PrintLine, Print, PrintInt, ReadFile, WriteFile, FileExists};
import Std::Os::{Os_ArgsCount, Os_Args};
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Find, String_Slice, String_Offset};
extern func bux_strlen(s: String) -> uint;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_char(sb: *void, c: char8);
func SB_AppendChar(sb: *void, c: int) {
bux_sb_append_char(sb, c as char8);
}
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_alloc(size: uint) -> *void;
extern func bux_free(ptr: *void);
struct Database {
path: String,
sb: *void,
loaded: bool,
count: uint,
}
func DB_New(filePath: String) -> Database {
var db: Database;
db.path = filePath;
db.sb = bux_sb_new(4096);
db.loaded = false;
db.count = 0;
return db;
}
func DB_Load(self: *Database) -> bool {
if self.loaded { return true; }
if !FileExists(self.path) {
self.loaded = true;
return true;
}
let raw: String = ReadFile(self.path);
if String_Len(raw) == 0 || bux_strlen(raw) == 0 {
self.loaded = true;
return true;
}
bux_sb_append(self.sb, raw);
let lineCount: uint = bux_str_split_count(raw, "\n");
var i: uint = 0;
var n: uint = 0;
while i < lineCount {
let line: String = bux_str_split_part(raw, "\n", i);
if bux_strlen(line) > 0 {
n = n + 1;
}
i = i + 1;
}
self.count = n;
self.loaded = true;
return true;
}
func DB_Get(self: *Database, key: String) -> String {
let raw: String = bux_sb_build(self.sb);
let lineCount: uint = bux_str_split_count(raw, "\n");
var i: uint = 0;
while i < lineCount {
let line: String = bux_str_split_part(raw, "\n", i);
if String_StartsWith(line, key) {
let eqPos: String = String_Find(line, "=");
if String_Len(eqPos) > 0 {
let keyLen: uint = bux_strlen(key);
let valStart: uint = keyLen + 1;
let lineLen: uint = bux_strlen(line);
if valStart < lineLen {
return bux_str_slice(line, valStart, lineLen - valStart);
}
}
}
i = i + 1;
}
return "";
}
func DB_Set(self: *Database, key: String, value: String) {
let raw: String = bux_sb_build(self.sb);
let lineCount: uint = bux_str_split_count(raw, "\n");
var newSb: *void = bux_sb_new(4096);
var found: bool = false;
var i: uint = 0;
while i < lineCount {
let line: String = bux_str_split_part(raw, "\n", i);
if bux_strlen(line) > 0 {
if String_StartsWith(line, key) {
let eqPos: String = String_Find(line, "=");
if String_Len(eqPos) > 0 {
bux_sb_append(newSb, key);
SB_AppendChar(newSb, 61);
bux_sb_append(newSb, value);
SB_AppendChar(newSb, 10);
found = true;
} else {
bux_sb_append(newSb, line);
SB_AppendChar(newSb, 10);
}
} else {
bux_sb_append(newSb, line);
SB_AppendChar(newSb, 10);
}
}
i = i + 1;
}
if !found {
bux_sb_append(newSb, key);
SB_AppendChar(newSb, 61);
bux_sb_append(newSb, value);
SB_AppendChar(newSb, 10);
self.count = self.count + 1;
}
bux_sb_free(self.sb);
self.sb = newSb;
}
func DB_Del(self: *Database, key: String) {
let raw: String = bux_sb_build(self.sb);
let lineCount: uint = bux_str_split_count(raw, "\n");
var newSb: *void = bux_sb_new(4096);
var i: uint = 0;
var delCount: uint = 0;
while i < lineCount {
let line: String = bux_str_split_part(raw, "\n", i);
if bux_strlen(line) > 0 {
if String_StartsWith(line, key) {
let eqPos: String = String_Find(line, "=");
if String_Len(eqPos) > 0 {
delCount = delCount + 1;
} else {
bux_sb_append(newSb, line);
SB_AppendChar(newSb, 10);
}
} else {
bux_sb_append(newSb, line);
SB_AppendChar(newSb, 10);
}
}
i = i + 1;
}
if delCount > 0 {
self.count = self.count - delCount;
}
bux_sb_free(self.sb);
self.sb = newSb;
}
func DB_Has(self: *Database, key: String) -> bool {
let val: String = DB_Get(self, key);
return bux_strlen(val) > 0;
}
func DB_Count(self: *Database) -> uint {
return self.count;
}
func DB_Keys(self: *Database) -> *String {
if self.count == 0 { return null as *String; }
let raw: String = bux_sb_build(self.sb);
let lineCount: uint = bux_str_split_count(raw, "\n");
let keys: *String = bux_alloc(self.count * sizeof(String)) as *String;
var found: uint = 0;
var i: uint = 0;
while i < lineCount && found < self.count {
let line: String = bux_str_split_part(raw, "\n", i);
if bux_strlen(line) > 0 {
let eqPos: String = String_Find(line, "=");
if String_Len(eqPos) > 0 {
let keyLen: uint = String_Offset(eqPos, line);
keys[found] = bux_str_slice(line, 0, keyLen);
found = found + 1;
}
}
i = i + 1;
}
return keys;
}
func DB_Save(self: *Database) -> bool {
let content: String = bux_sb_build(self.sb);
let ok: bool = WriteFile(self.path, content);
return ok;
}
func PrintUsage() {
PrintLine("SimpleDB — file-backed key-value database");
PrintLine("Usage:");
PrintLine(" simpledb <dbfile> set <key> <value>");
PrintLine(" simpledb <dbfile> get <key>");
PrintLine(" simpledb <dbfile> del <key>");
PrintLine(" simpledb <dbfile> has <key>");
PrintLine(" simpledb <dbfile> keys");
PrintLine(" simpledb <dbfile> count");
}
func Main() -> int {
let argc: int = Os_ArgsCount();
if argc < 3 {
PrintUsage();
return 1;
}
let dbFile: String = Os_Args(1);
let cmd: String = Os_Args(2);
var db: Database = DB_New(dbFile);
if String_Eq(cmd, "get") {
DB_Load(&db);
if argc < 4 {
PrintLine("get: missing key");
return 1;
}
let key: String = Os_Args(3);
let val: String = DB_Get(&db, key);
if bux_strlen(val) > 0 {
PrintLine(val);
} else {
Print("(not found) ");
PrintLine(key);
}
return 0;
}
if String_Eq(cmd, "set") {
DB_Load(&db);
if argc < 5 {
PrintLine("set: missing key or value");
return 1;
}
let key: String = Os_Args(3);
let val: String = Os_Args(4);
DB_Set(&db, key, val);
let saved: bool = DB_Save(&db);
if saved {
Print("OK ");
Print(key);
Print(" = ");
PrintLine(val);
} else {
PrintLine("set: save failed");
}
return 0;
}
if String_Eq(cmd, "del") {
DB_Load(&db);
if argc < 4 {
PrintLine("del: missing key");
return 1;
}
let key: String = Os_Args(3);
DB_Del(&db, key);
DB_Save(&db);
Print("DEL ");
PrintLine(key);
return 0;
}
if String_Eq(cmd, "has") {
DB_Load(&db);
if argc < 4 {
PrintLine("has: missing key");
return 1;
}
let key: String = Os_Args(3);
let found: bool = DB_Has(&db, key);
if found {
Print("true ");
PrintLine(key);
} else {
Print("false ");
PrintLine(key);
}
return 0;
}
if String_Eq(cmd, "keys") {
DB_Load(&db);
let n: uint = DB_Count(&db);
Print("keys: ");
PrintInt(n as int);
PrintLine("");
if n == 0 {
return 0;
}
let list: *String = DB_Keys(&db);
var i: uint = 0;
while i < n {
Print(" ");
PrintLine(list[i]);
i = i + 1;
}
bux_free(list as *void);
return 0;
}
if String_Eq(cmd, "count") {
DB_Load(&db);
let n: uint = DB_Count(&db);
Print("count: ");
PrintInt(n as int);
PrintLine("");
return 0;
}
Print("unknown command: ");
PrintLine(cmd);
return 1;
}
}
+37
View File
@@ -33,6 +33,7 @@ type
tekDynRef ## &dyn Trait — trait object (fat pointer)
tekTuple
tekSelf
tekFunc
TypeExpr* = ref object
loc*: SourceLocation
@@ -54,6 +55,9 @@ type
tupleElements*: seq[TypeExpr]
of tekSelf:
discard
of tekFunc:
funcParams*: seq[TypeExpr]
funcRet*: TypeExpr
# ---------------------------------------------------------------------------
# Patterns
@@ -124,8 +128,11 @@ type
ekUnwrap ## expr! — unwrap or panic
ekSpawn ## spawn expr — create a new task
ekAwait ## expr.await — suspend until future resolves
ekBorrow ## borrow &mut expr — explicit borrow expression
ekBlock
ekMatch
ekStringInterp
ekClosure
MatchArm* = object
loc*: SourceLocation
@@ -172,6 +179,7 @@ type
of ekCall:
exprCallCallee*: Expr
exprCallArgs*: seq[Expr]
exprCallArgNames*: seq[string] ## empty = positional, non-empty = named arg
exprCallInferredTypeArgs*: seq[TypeExpr] ## filled by sema for inferred generic calls
of ekGenericCall:
exprGenericCallee*: string
@@ -210,11 +218,24 @@ type
exprSpawnAsync*: bool
of ekAwait:
exprAwaitOperand*: Expr
of ekBorrow:
exprBorrowOperand*: Expr
exprBorrowMutable*: bool
of ekBlock:
exprBlock*: Block
of ekMatch:
exprMatchSubject*: Expr
exprMatchArms*: seq[MatchArm]
of ekStringInterp:
exprInterpTexts*: seq[string]
exprInterpExprs*: seq[Expr]
of ekClosure:
exprClosureParams*: seq[Param]
exprClosureBody*: Block
exprClosureReturnType*: TypeExpr
captureCount*: int
captureNames*: seq[string]
captureTypeKinds*: seq[int]
# ---------------------------------------------------------------------------
# Statements
@@ -234,6 +255,8 @@ type
skStaticAssert
skComptime
skEmit
skDefer
skSwitch
skDecl
ElseIf* = object
@@ -241,6 +264,11 @@ type
cond*: Expr
blk*: Block
SwitchCase* = object
loc*: SourceLocation
caseValue*: Expr
caseBody*: Block
Block* = ref object
loc*: SourceLocation
stmts*: seq[Stmt]
@@ -294,6 +322,12 @@ type
of skEmit:
stmtEmitExpr*: Expr
stmtEmitEvaluated*: string ## filled by sema CTFE
of skDefer:
stmtDeferBody*: Expr
of skSwitch:
stmtSwitchExpr*: Expr
stmtSwitchCases*: seq[SwitchCase]
stmtSwitchDefault*: Block
of skDecl:
stmtDecl*: Decl
@@ -441,3 +475,6 @@ proc newBinaryExpr*(op: TokenKind, left, right: Expr, loc: SourceLocation): Expr
proc newUnaryExpr*(op: TokenKind, operand: Expr, loc: SourceLocation): Expr =
result = Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand)
proc newStringInterpExpr*(texts: seq[string], exprs: seq[Expr], loc: SourceLocation): Expr =
result = Expr(kind: ekStringInterp, loc: loc, exprInterpTexts: texts, exprInterpExprs: exprs)
+56
View File
@@ -8,6 +8,7 @@ type
varCounter*: int
declaredVars*: seq[string]
sliceTypeDefs*: seq[tuple[name: string, elem: string]] ## Generated Slice_<T> typedefs
deferStack*: seq[seq[string]] ## Function-level deferred statements (LIFO)
proc cEscape(s: string): string =
## Escape a string for use as a C string literal.
@@ -45,6 +46,28 @@ proc freshVar(be: var CBackend): string =
inc be.varCounter
result = &"__tmp_{be.varCounter}"
# ---------------------------------------------------------------------------
# Defer helpers
# ---------------------------------------------------------------------------
proc pushDeferScope(be: var CBackend) =
be.deferStack.add(@[])
proc popDeferScope(be: var CBackend) =
if be.deferStack.len > 0:
be.deferStack.setLen(be.deferStack.len - 1)
proc emitCurrentDefers(be: var CBackend) =
## Emit all deferred statements in current scope, LIFO order
if be.deferStack.len == 0: return
let idx = be.deferStack.len - 1
for i in countdown(be.deferStack[idx].len - 1, 0):
be.emitLine(be.deferStack[idx][i])
proc clearCurrentDefers(be: var CBackend) =
if be.deferStack.len > 0:
let idx = be.deferStack.len - 1
be.deferStack[idx].setLen(0)
# Type conversion: Bux Type → C type string
proc typeToC*(be: var CBackend, typ: Type): string =
if typ == nil: return "void"
@@ -249,6 +272,10 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
let base = be.emitExpr(node.fieldPtrBase)
return &"(&({base}.{node.fieldName}))"
of hFieldAccess:
let base = be.emitExpr(node.fieldAccessBase)
return &"({base}.{node.fieldAccessName})"
of hArrowField:
let base = be.emitExpr(node.arrowFieldBase)
return &"(&({base}->{node.arrowFieldName}))"
@@ -358,7 +385,14 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
proc emitStmt(be: var CBackend, node: HirNode) =
if node == nil: return
case node.kind
of hDefer:
let expr = be.emitExpr(node.deferBody)
if be.deferStack.len > 0:
let idx = be.deferStack.len - 1
be.deferStack[idx].add(expr & ";")
of hReturn:
be.emitCurrentDefers()
if node.returnValue != nil:
let val = be.emitExpr(node.returnValue)
be.emitLine(&"return {val};")
@@ -394,9 +428,11 @@ proc emitStmt(be: var CBackend, node: HirNode) =
be.emitLine("}")
of hBreak:
be.emitCurrentDefers()
be.emitLine("break;")
of hContinue:
be.emitCurrentDefers()
be.emitLine("continue;")
of hEmit:
@@ -444,6 +480,18 @@ proc emitStmt(be: var CBackend, node: HirNode) =
be.emitLine(&"{expr};")
proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
# Emit env struct and global instance for closures with captures
if hfunc.captureNames.len > 0 and hfunc.envStructName != "":
be.emitLine(&"struct {hfunc.envStructName} {{")
inc be.indent
for i in 0 ..< hfunc.captureNames.len:
let capName = hfunc.captureNames[i]
let capType = if i < hfunc.captureTypes.len: typeToC(be, hfunc.captureTypes[i]) else: "int"
be.emitLine(&"{capType} {capName};")
dec be.indent
be.emitLine("};")
be.emitLine(&"static struct {hfunc.envStructName} {hfunc.envInstanceName};")
be.emitLine("")
let retType = typeToC(be, hfunc.retType)
var params: seq[string] = @[]
for p in hfunc.params:
@@ -453,15 +501,23 @@ proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
let paramsStr = params.join(", ")
be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{")
inc be.indent
be.pushDeferScope()
if hfunc.body != nil:
if hfunc.body.kind == hBlock:
for stmt in hfunc.body.blockStmts:
be.emitStmt(stmt)
if hfunc.body.blockExpr != nil and hfunc.retType.kind != tkVoid:
be.emitCurrentDefers()
let val = be.emitExpr(hfunc.body.blockExpr)
be.emitLine(&"return {val};")
else:
be.emitCurrentDefers()
else:
be.emitStmt(hfunc.body)
be.emitCurrentDefers()
else:
be.emitCurrentDefers()
be.popDeferScope()
dec be.indent
be.emitLine("}")
be.emitLine("")
+14 -12
View File
@@ -1,5 +1,5 @@
import std/[os, strutils, terminal, strformat, osproc, sets]
import lexer, parser, ast, sema, manifest, hir_lower, c_backend
import lexer, parser, ast, sema, manifest, hir_lower, lir_lower, lir_c_backend
type
ColorMode* = enum
@@ -259,9 +259,9 @@ proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Dec
proc findStdlibDir(root: string): string =
let searchPaths = @[
getAppDir() / ".." / "library",
getAppDir() / "library",
root / "library",
getAppDir() / ".." / "lib",
getAppDir() / "lib",
root / "lib",
]
for path in searchPaths:
if dirExists(path):
@@ -454,14 +454,15 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
return 1
let hirMod = lowerModule(unifiedModule, semaCtx)
var cbe = initCBackend()
var allCCode = cbe.emitModule(hirMod)
let lirBuilder = lowerModuleToLir(hirMod)
var lirCbe = initLirCBackend()
var allCCode = lirCbe.emitModule(lirBuilder, hirMod)
# Write C file
let cFile = buildDir / "main.c"
writeFile(cFile, allCCode)
# Copy runtime and stdlib files
# Copy runtime files (rt/ is sibling of lib/)
let stdlibDir = pctx.stdlibDir
let runtimeDst = buildDir / "runtime.c"
let ioDst = buildDir / "io.c"
@@ -469,24 +470,25 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
printError("stdlib directory not found", useColor)
return 1
let runtimeSrc = stdlibDir / "runtime" / "runtime.c"
let baseDir = stdlibDir.parentDir()
let runtimeSrc = baseDir / "rt" / "runtime.c"
if fileExists(runtimeSrc):
copyFile(runtimeSrc, runtimeDst)
else:
printError("runtime.c not found in library/runtime/", useColor)
printError("runtime.c not found in rt/", useColor)
return 1
let ioSrc = stdlibDir / "runtime" / "io.c"
let ioSrc = baseDir / "rt" / "io.c"
if fileExists(ioSrc):
copyFile(ioSrc, ioDst)
else:
printError("io.c not found in library/runtime/", useColor)
printError("io.c not found in rt/", useColor)
return 1
# Compile with cc
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
let outputFile = buildDir / outputName
let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
let ccCmd = &"cc -O0 -g -pthread -Wl,--build-id=none -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
if opts.verbose:
printInfo(&"running: {ccCmd}", useColor)
let (output, exitCode) = execCmdEx(ccCmd)
+22
View File
@@ -17,11 +17,13 @@ type
hBreak
hContinue
hReturn
hDefer
# Memory
hAlloca
hLoad
hStore
hFieldPtr
hFieldAccess
hArrowField
hIndexPtr
hSliceIndex
@@ -84,6 +86,8 @@ type
continueLabel*: string
of hReturn:
returnValue*: HirNode
of hDefer:
deferBody*: HirNode
of hAlloca:
allocaType*: Type
allocaName*: string
@@ -95,6 +99,9 @@ type
of hFieldPtr:
fieldPtrBase*: HirNode
fieldName*: string
of hFieldAccess:
fieldAccessBase*: HirNode
fieldAccessName*: string
of hArrowField:
arrowFieldBase*: HirNode
arrowFieldName*: string
@@ -163,6 +170,11 @@ type
retType*: Type
body*: HirNode
isPublic*: bool
# Closure capture metadata
captureNames*: seq[string]
captureTypes*: seq[Type]
envStructName*: string
envInstanceName*: string
HirEnumVariant* = object
name*: string
@@ -200,15 +212,25 @@ proc hirCall*(callee: string, args: seq[HirNode], typ: Type, loc: SourceLocation
proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hReturn, returnValue: value, typ: makeVoid(), loc: loc)
proc hirDefer*(body: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hDefer, deferBody: body, typ: makeVoid(), loc: loc)
proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode =
HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope)
proc hirIf*(cond, thenBranch, elseBranch: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hIf, ifCond: cond, ifThen: thenBranch, ifElse: elseBranch, typ: makeVoid(), loc: loc)
proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc)
proc hirStore*(ptrNode, value: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hStore, storePtr: ptrNode, storeValue: value, typ: makeVoid(), loc: loc)
proc hirAssign*(target, value: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hAssign, assignOp: tkAssign, assignTarget: target, assignValue: value, typ: makeVoid(), loc: loc)
proc hirLoad*(ptrNode: HirNode, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hLoad, loadPtr: ptrNode, typ: typ, loc: loc)
+682 -43
View File
@@ -11,6 +11,7 @@ type
varCounter*: int
tryCounter*: int
pendingStmts*: seq[HirNode]
deferStmts*: seq[HirNode]
typeSubst*: Table[string, Type] # Type parameter substitution for generics
importTable*: Table[string, string] # Local name → fully qualified name for imports
genericStructs*: Table[string, Decl] # Generic struct declarations
@@ -21,6 +22,9 @@ type
generatedFuncInsts*: Table[string, bool] # Track generated function instantiations
extraFuncs*: seq[HirFunc] # Monomorphized generic methods
varTypeExprs*: Table[string, TypeExpr] # Track variable names -> type expr for generic method inference
closureDepth*: int
currentClosureExpr*: Expr
envInstanceName*: string
proc freshName(ctx: var LowerCtx): string =
inc ctx.varCounter
@@ -38,6 +42,14 @@ proc flushPending(ctx: var LowerCtx, node: HirNode): HirNode =
return hirBlock(stmts, nil, makeVoid(), node.loc)
return node
proc enumHasDataVariants(ctx: var LowerCtx, enumName: string): bool =
let sym = ctx.globalScope.lookup(enumName)
if sym != nil and sym.decl != nil and sym.decl.kind == dkEnum:
for v in sym.decl.declEnumVariants:
if v.fields.len > 0 or v.namedFields.len > 0:
return true
return false
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.
@@ -47,6 +59,13 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
# Allocate result variable
stmts.add(hirAlloca(resultName, typ, loc))
# Determine whether the matched enum has data variants (needs .tag access).
var subjectEnumName = ""
var subjectHasData = false
if subject.typ != nil and subject.typ.kind == tkNamed:
subjectEnumName = subject.typ.name
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
# Build if-else chain from arms (last arm is the outermost else)
var ifChain: HirNode = nil
@@ -62,12 +81,18 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
let variantName = path[^1]
let tagName = enumName & "_" & variantName
# condition: subject.tag == EnumName_VariantName
let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag",
typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc)
let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc)
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc)
let cond = hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc)
var cond: HirNode
if subjectHasData and enumName == subjectEnumName:
# Algebraic enum: compare subject.tag
let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag",
typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc)
let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc)
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc)
cond = hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc)
else:
# Simple enum or cross-enum match: compare subject directly
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName), loc)
cond = hirBinary(tkEq, subject, tagConst, makeBool(), loc)
# body: result = arm_body
var armStmts: seq[HirNode] = @[]
@@ -162,10 +187,14 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type])
break
if hasUnresolved: break
if not hasUnresolved:
var localSubst = subst
for j, tp in genericDecl.declStructTypeParams:
if j < te.typeArgs.len:
localSubst[tp.name] = substituteType(ctx, te.typeArgs[j], subst)
var fields: seq[tuple[name: string, typ: Type]] = @[]
var concreteArgs: seq[Type] = @[]
for f in genericDecl.declStructFields:
let resolvedType = substituteType(ctx, f.ftype, subst)
let resolvedType = substituteType(ctx, f.ftype, localSubst)
fields.add((f.name, resolvedType))
for arg in te.typeArgs:
concreteArgs.add(substituteType(ctx, arg, subst))
@@ -263,12 +292,19 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
of tekDynRef: return makeDynRef(te.dynInterface)
of tekPointer: return makePointer(ctx.resolveTypeExpr(te.pointerPointee))
of tekSlice: return makeSlice(ctx.resolveTypeExpr(te.sliceElement))
of tekFunc:
var params: seq[Type] = @[]
for p in te.funcParams:
params.add(ctx.resolveTypeExpr(p))
let ret = if te.funcRet != nil: ctx.resolveTypeExpr(te.funcRet) else: makeVoid()
return makeFunc(params, ret)
else: return makeUnknown()
# Forward declarations
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode
proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if expr == nil: return makeUnknown()
@@ -319,7 +355,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
of ekUnary:
case expr.exprUnaryOp
of tkBang: return makeBool()
of tkAmp: return makePointer(ctx.resolveExprType(expr.exprUnaryOperand))
of tkAmp: return makeMutRef(ctx.resolveExprType(expr.exprUnaryOperand))
of tkStar:
let inner = ctx.resolveExprType(expr.exprUnaryOperand)
if inner.isPointer: return inner.inner[0]
@@ -352,22 +388,87 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if objType.isPointer and objType.inner.len > 0:
objType = objType.inner[0]
if objType.kind == tkNamed:
let sym = ctx.globalScope.lookup(objType.name)
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct:
for f in sym.decl.declStructFields:
if f.name == expr.exprFieldName:
if f.ftype != nil:
case f.ftype.kind
of tekNamed:
case f.ftype.typeName
of "int", "int32", "int64": return makeInt()
of "float64": return makeFloat64()
of "float32": return makeFloat32()
of "bool": return makeBool()
else: return makeNamed(f.ftype.typeName)
of tekOwn, tekPointer:
return ctx.resolveTypeExpr(f.ftype)
else: return makeUnknown()
# Check if this is a _Data union field access
if objType.name.endsWith("_Data"):
let enumName = objType.name[0..^6]
let enumSym = ctx.globalScope.lookup(enumName)
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
for variant in enumSym.decl.declEnumVariants:
for i, f in variant.fields:
let fieldName = variant.name & "_" & $i
if fieldName == expr.exprFieldName:
return ctx.resolveTypeExpr(f)
for nf in variant.namedFields:
if nf.name == expr.exprFieldName:
return ctx.resolveTypeExpr(nf.ftype)
var sym = ctx.globalScope.lookup(objType.name)
var decl = if sym != nil: sym.decl else: nil
# If the type is a monomorphized generic struct instance, look up the base
if decl == nil and ctx.structInstMap.hasKey(objType.name):
let (baseName, typeArgs) = ctx.structInstMap[objType.name]
let baseSym = ctx.globalScope.lookup(baseName)
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkStruct:
decl = baseSym.decl
var subst = initTable[string, Type]()
for i, tp in decl.declStructTypeParams:
if i < typeArgs.len:
subst[tp.name] = typeArgs[i]
for f in decl.declStructFields:
if f.name == expr.exprFieldName:
if f.ftype != nil:
case f.ftype.kind
of tekNamed:
if f.ftype.typeArgs.len > 0:
return substituteType(ctx, f.ftype, subst)
case f.ftype.typeName
of "int", "int32", "int64": return makeInt()
of "float64": return makeFloat64()
of "float32": return makeFloat32()
of "bool": return makeBool()
else:
if subst.hasKey(f.ftype.typeName):
return subst[f.ftype.typeName]
return makeNamed(f.ftype.typeName)
of tekOwn, tekPointer:
return substituteType(ctx, f.ftype, subst)
else: return makeUnknown()
if decl != nil:
case decl.kind
of dkStruct:
for f in decl.declStructFields:
if f.name == expr.exprFieldName:
if f.ftype != nil:
case f.ftype.kind
of tekNamed:
if f.ftype.typeArgs.len > 0:
return ctx.resolveTypeExpr(f.ftype)
case f.ftype.typeName
of "int", "int32", "int64": return makeInt()
of "float64": return makeFloat64()
of "float32": return makeFloat32()
of "bool": return makeBool()
else: return makeNamed(f.ftype.typeName)
of tekOwn, tekPointer:
return ctx.resolveTypeExpr(f.ftype)
else: return makeUnknown()
of dkEnum:
# Algebraic enum fields: tag and data
var hasData = false
for v in decl.declEnumVariants:
if v.fields.len > 0 or v.namedFields.len > 0:
hasData = true
break
if not hasData and expr.exprFieldName == "tag":
return makeNamed(objType.name)
elif expr.exprFieldName == "tag":
return makeNamed(objType.name & "_Tag")
elif expr.exprFieldName == "data":
return makeNamed(objType.name & "_Data")
else:
# Enum variant field access: e.g., r.data.Ok_0
# We can't easily resolve this here; return unknown
return makeUnknown()
else: discard
return makeUnknown()
of ekStructInit:
if expr.exprStructInitTypeArgs.len > 0:
@@ -380,7 +481,15 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
return makeSlice(makeUnknown())
of ekRange:
let loType = ctx.resolveExprType(expr.exprRangeLo)
return makeRange(loType)
let hiType = ctx.resolveExprType(expr.exprRangeHi)
if loType == hiType:
return makeRange(loType)
elif loType.isAssignableTo(hiType):
return makeRange(hiType)
elif hiType.isAssignableTo(loType):
return makeRange(loType)
else:
return makeRange(loType)
of ekTuple:
var elems: seq[Type] = @[]
for e in expr.exprTupleElements:
@@ -395,12 +504,25 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
return makeInt()
of ekUnwrap:
return makeInt()
of ekIndex:
let baseType = ctx.resolveExprType(expr.exprIndexObj)
if baseType.isSlice and baseType.inner.len > 0:
return baseType.inner[0]
if baseType.isPointer and baseType.inner.len > 0:
return baseType.inner[0]
return makeUnknown()
of ekMatch:
if expr.exprMatchArms.len > 0:
return ctx.resolveExprType(expr.exprMatchArms[0].body)
return makeUnknown()
of ekBlock:
if expr.exprBlock.stmts.len > 0:
let last = expr.exprBlock.stmts[^1]
if last.kind == skExpr:
return ctx.resolveExprType(last.stmtExpr)
return makeVoid()
of ekBorrow:
return ctx.resolveExprType(expr.exprBorrowOperand)
else: return makeUnknown()
proc extractGenericStructInfo(ctx: LowerCtx, te: TypeExpr): tuple[baseName: string, typeArgs: seq[TypeExpr]] =
@@ -427,6 +549,49 @@ proc getReceiverTypeExpr(ctx: LowerCtx, expr: Expr): TypeExpr =
else: discard
return nil
proc getCollectionElementTypeExpr(ctx: var LowerCtx, expr: Expr): TypeExpr =
## Return the element TypeExpr of a collection expression (Array<T>, Iter<T>, Channel<T>).
## For identifiers we can use the declared TypeExpr directly; for other expressions we
## fall back to the resolved concrete Type.
case expr.kind
of ekIdent:
if ctx.varTypeExprs.hasKey(expr.exprIdent):
let te = ctx.varTypeExprs[expr.exprIdent]
if te.kind == tekNamed and te.typeArgs.len > 0:
return te.typeArgs[0]
if te.kind in {tekPointer, tekRef, tekMutRef} and te.pointerPointee.kind == tekNamed and te.pointerPointee.typeArgs.len > 0:
return te.pointerPointee.typeArgs[0]
of ekField:
# Try to resolve the field's declared TypeExpr directly.
let objType = ctx.resolveExprType(expr.exprFieldObj)
if objType.kind == tkNamed:
var decl = ctx.globalScope.lookup(objType.name).decl
if decl == nil and ctx.structInstMap.hasKey(objType.name):
let (baseName, _) = ctx.structInstMap[objType.name]
let baseSym = ctx.globalScope.lookup(baseName)
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkStruct:
decl = baseSym.decl
if decl != nil and decl.kind == dkStruct:
for f in decl.declStructFields:
if f.name == expr.exprFieldName and f.ftype != nil:
let fte = f.ftype
if fte.kind == tekNamed and fte.typeArgs.len > 0 and (fte.typeName == "Array" or fte.typeName == "Iter" or fte.typeName == "Channel"):
return fte.typeArgs[0]
return fte
else:
discard
let t = ctx.resolveExprType(expr)
if t.kind == tkNamed and t.inner.len > 0:
return typeToTypeExpr(t.inner[0])
if t.isPointer and t.inner.len > 0 and t.inner[0].kind == tkNamed and t.inner[0].inner.len > 0:
return typeToTypeExpr(t.inner[0].inner[0])
# Generic struct instances (e.g. Array_HeaderEntry) store their type args in structInstMap.
if t.kind == tkNamed and ctx.structInstMap.hasKey(t.name):
let (baseName, concreteArgs) = ctx.structInstMap[t.name]
if concreteArgs.len > 0 and (baseName == "Array" or baseName == "Iter" or baseName == "Channel"):
return typeToTypeExpr(concreteArgs[0])
return TypeExpr(kind: tekNamed, typeName: "unknown")
proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string
proc lowerExprWithDynRefCoerce(ctx: var LowerCtx, arg: Expr, expectedType: Type): HirNode =
@@ -462,6 +627,103 @@ proc findMethodEntry(ctx: LowerCtx, typeName: string): (string, seq[MethodInfo])
return (prefix, ctx.methodTable[prefix])
return ("", @[])
proc operatorMethodName(op: TokenKind): string =
case op
of tkPlus: "operator_add"
of tkMinus: "operator_sub"
of tkStar: "operator_mul"
of tkSlash: "operator_div"
of tkPercent: "operator_mod"
of tkEq: "operator_eq"
of tkNe: "operator_ne"
of tkLt: "operator_lt"
of tkLe: "operator_le"
of tkGt: "operator_gt"
of tkGe: "operator_ge"
of tkAmp: "operator_bitand"
of tkPipe: "operator_bitor"
of tkCaret: "operator_xor"
of tkShl: "operator_shl"
of tkShr: "operator_shr"
else: ""
proc tryLowerOperatorCall(ctx: var LowerCtx, op: TokenKind, leftExpr, rightExpr: Expr, typ: Type, loc: SourceLocation): HirNode =
## Try to lower a binary operator to a method call. Returns nil if no overload found.
let methodName = operatorMethodName(op)
if methodName == "": return nil
let receiverType = ctx.resolveExprType(leftExpr)
var receiverTypeName = ""
if receiverType.kind == tkNamed:
receiverTypeName = receiverType.name
if ctx.typeSubst.hasKey(receiverTypeName):
let substituted = ctx.typeSubst[receiverTypeName]
if substituted.kind == tkNamed:
receiverTypeName = substituted.name
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
receiverTypeName = substituted.inner[0].name
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
receiverTypeName = receiverType.toString
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
receiverTypeName = receiverType.inner[0].name
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
if typeName == "": return nil
for minfo in methods:
if minfo.name == methodName:
var calleeName = typeName & "_" & methodName
# Check generic method instantiation
let recvTypeExpr = ctx.getReceiverTypeExpr(leftExpr)
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
var args: seq[HirNode] = @[]
let loweredReceiver = ctx.lowerExpr(leftExpr)
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
else:
args.add(loweredReceiver)
args.add(ctx.lowerExpr(rightExpr))
return hirCall(calleeName, args, typ, loc)
return nil
proc tryLowerIndexCall(ctx: var LowerCtx, objExpr, idxExpr: Expr, typ: Type, loc: SourceLocation): HirNode =
## Try to lower arr[i] to operator_index_get(arr, i). Returns nil if no overload found.
let receiverType = ctx.resolveExprType(objExpr)
var receiverTypeName = ""
if receiverType.kind == tkNamed:
receiverTypeName = receiverType.name
if ctx.typeSubst.hasKey(receiverTypeName):
let substituted = ctx.typeSubst[receiverTypeName]
if substituted.kind == tkNamed:
receiverTypeName = substituted.name
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
receiverTypeName = substituted.inner[0].name
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
receiverTypeName = receiverType.toString
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
receiverTypeName = receiverType.inner[0].name
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
if typeName == "": return nil
for minfo in methods:
if minfo.name == "operator_index_get":
var calleeName = typeName & "_operator_index_get"
let recvTypeExpr = ctx.getReceiverTypeExpr(objExpr)
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
var args: seq[HirNode] = @[]
let loweredReceiver = ctx.lowerExpr(objExpr)
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
else:
args.add(loweredReceiver)
args.add(ctx.lowerExpr(idxExpr))
return hirCall(calleeName, args, typ, loc)
return nil
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
if expr == nil: return nil
let loc = expr.loc
@@ -473,6 +735,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
of ekIdent:
let name = expr.exprIdent
# Capture rewriting: if inside closure and ident is captured
if ctx.closureDepth > 0 and ctx.currentClosureExpr != nil and ctx.envInstanceName != "":
let idx = ctx.currentClosureExpr.captureNames.find(name)
if idx >= 0:
let capType = if idx < ctx.currentClosureExpr.captureTypeKinds.len: Type(kind: TypeKind(ctx.currentClosureExpr.captureTypeKinds[idx])) else: makeInt()
let base = hirVar(ctx.envInstanceName, makeNamed(""), loc)
return HirNode(kind: hFieldAccess, fieldAccessName: name, fieldAccessBase: base, typ: capType, loc: loc)
if ctx.importTable.hasKey(name):
return hirVar(ctx.importTable[name], typ, loc)
return hirVar(name, typ, loc)
@@ -491,9 +760,34 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
return hirUnary(expr.exprUnaryOp, operand, typ, loc)
of ekBinary:
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let right = ctx.lowerExpr(expr.exprBinaryRight)
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
case expr.exprBinaryOp
of tkAmpAmp:
# Short-circuit &&: use if-then-else to avoid evaluating right when left is false
let tmp = hirAlloca("__and_tmp_" & $ctx.varCounter, makeBool(), loc)
inc ctx.varCounter
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let thenBlock = hirBlock(@[hirStore(tmp, ctx.lowerExpr(expr.exprBinaryRight), loc)], nil, makeVoid(), loc)
let falseTok = Token(kind: tkBoolLiteral, text: "false", loc: loc)
let elseBlock = hirBlock(@[hirStore(tmp, hirLit(falseTok, makeBool(), loc), loc)], nil, makeVoid(), loc)
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
of tkPipePipe:
# Short-circuit ||: use if-then-else to avoid evaluating right when left is true
let tmp = hirAlloca("__or_tmp_" & $ctx.varCounter, makeBool(), loc)
inc ctx.varCounter
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let trueTok = Token(kind: tkBoolLiteral, text: "true", loc: loc)
let thenBlock = hirBlock(@[hirStore(tmp, hirLit(trueTok, makeBool(), loc), loc)], nil, makeVoid(), loc)
let elseBlock = hirBlock(@[hirStore(tmp, ctx.lowerExpr(expr.exprBinaryRight), loc)], nil, makeVoid(), loc)
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
else:
let lowered = ctx.tryLowerOperatorCall(expr.exprBinaryOp, expr.exprBinaryLeft, expr.exprBinaryRight, typ, loc)
if lowered != nil:
return lowered
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let right = ctx.lowerExpr(expr.exprBinaryRight)
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
of ekCall:
# Method call desugaring: obj.method(args) → Type_method(obj, args)
@@ -598,6 +892,17 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
of ekField:
let objType = ctx.resolveExprType(expr.exprFieldObj)
let base = ctx.lowerExpr(expr.exprFieldObj)
# Simple enum .tag is the enum value itself
if objType.kind == tkNamed and expr.exprFieldName == "tag":
let sym = ctx.globalScope.lookup(objType.name)
if sym != nil and sym.decl != nil and sym.decl.kind == dkEnum:
var hasData = false
for v in sym.decl.declEnumVariants:
if v.fields.len > 0 or v.namedFields.len > 0:
hasData = true
break
if not hasData:
return base
# Auto-dereference pointer types for field access
if objType.isPointer:
let arrowPtr = HirNode(kind: hArrowField, arrowFieldBase: base,
@@ -610,9 +915,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
of ekIndex:
let baseType = ctx.resolveExprType(expr.exprIndexObj)
if not baseType.isSlice:
let lowered = ctx.tryLowerIndexCall(expr.exprIndexObj, expr.exprIndexIdx, typ, loc)
if lowered != nil:
return lowered
let base = ctx.lowerExpr(expr.exprIndexObj)
let idx = ctx.lowerExpr(expr.exprIndexIdx)
let baseType = ctx.resolveExprType(expr.exprIndexObj)
if baseType.isSlice:
let sliceIdx = HirNode(kind: hSliceIndex, sliceIndexBase: base,
sliceIndexIndex: idx,
@@ -624,6 +933,44 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
of ekAssign:
# Check for operator_index_set overload
if expr.exprAssignTarget.kind == ekIndex:
let objExpr = expr.exprAssignTarget.exprIndexObj
let idxExpr = expr.exprAssignTarget.exprIndexIdx
let receiverType = ctx.resolveExprType(objExpr)
var receiverTypeName = ""
if receiverType.kind == tkNamed:
receiverTypeName = receiverType.name
if ctx.typeSubst.hasKey(receiverTypeName):
let substituted = ctx.typeSubst[receiverTypeName]
if substituted.kind == tkNamed:
receiverTypeName = substituted.name
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
receiverTypeName = substituted.inner[0].name
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
receiverTypeName = receiverType.toString
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
receiverTypeName = receiverType.inner[0].name
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
if typeName != "":
for minfo in methods:
if minfo.name == "operator_index_set":
var calleeName = typeName & "_operator_index_set"
let recvTypeExpr = ctx.getReceiverTypeExpr(objExpr)
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
var args: seq[HirNode] = @[]
let loweredReceiver = ctx.lowerExpr(objExpr)
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
else:
args.add(loweredReceiver)
args.add(ctx.lowerExpr(idxExpr))
args.add(ctx.lowerExpr(expr.exprAssignValue))
return hirCall(calleeName, args, makeVoid(), loc)
let target = ctx.lowerExpr(expr.exprAssignTarget)
let value = ctx.lowerExpr(expr.exprAssignValue)
return HirNode(kind: hAssign, assignOp: expr.exprAssignOp,
@@ -631,9 +978,6 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
typ: makeVoid(), loc: loc)
of ekStructInit:
var fields: seq[tuple[name: string, value: HirNode]] = @[]
for f in expr.exprStructInitFields:
fields.add((f.name, ctx.lowerExpr(f.value)))
var structName = expr.exprStructInitName
if expr.exprStructInitTypeArgs.len > 0:
var suffix = ""
@@ -642,6 +986,24 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
let argType = ctx.resolveTypeExpr(targ)
suffix.add(argType.toString)
structName = structName & "_" & suffix
# Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant
var enumDecl: Decl = nil
let enumSym = ctx.globalScope.lookup(structName)
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
enumDecl = enumSym.decl
var isSimple = false
if enumDecl != nil:
for v in enumDecl.declEnumVariants:
if v.fields.len > 0 or v.namedFields.len > 0:
isSimple = true
break
isSimple = not isSimple
if isSimple and expr.exprStructInitFields.len == 1 and expr.exprStructInitFields[0].name == "tag":
let variantExpr = ctx.lowerExpr(expr.exprStructInitFields[0].value)
return variantExpr
var fields: seq[tuple[name: string, value: HirNode]] = @[]
for f in expr.exprStructInitFields:
fields.add((f.name, ctx.lowerExpr(f.value)))
return HirNode(kind: hStructInit, structInitName: structName,
structInitFields: fields, typ: typ, loc: loc)
@@ -721,7 +1083,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
let tmpName = ctx.freshTryVar()
let tmpAlloca = hirAlloca(tmpName, operandType, loc)
let tmpVar = hirVar(tmpName, makePointer(operandType), loc)
let tmpVar = hirVar(tmpName, operandType, loc)
let tmpStore = hirStore(tmpVar, operand, loc)
let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag",
@@ -826,6 +1188,56 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
let lowered = ctx.lowerExpr(expr.exprAwaitOperand)
return hirCall("bux_async_await", @[lowered], makePointer(makeVoid()), loc)
of ekBorrow:
# borrow &mut expr — lowered to the operand directly (borrow is a no-op in HIR)
# The borrow checker validates before lowering
return ctx.lowerExpr(expr.exprBorrowOperand)
of ekStringInterp:
# Desugar string interpolation to chained String_Concat calls with conversions
var resultNode: HirNode = nil
for i in 0 ..< expr.exprInterpExprs.len:
let textPart = expr.exprInterpTexts[i]
let exprPart = expr.exprInterpExprs[i]
# Text literal
var textNode = HirNode(kind: hLit,
litToken: Token(kind: tkStringLiteral, text: "\"" & textPart & "\"", loc: loc),
typ: makeStr(), loc: loc)
if resultNode == nil:
resultNode = textNode
else:
resultNode = hirCall("String_Concat", @[resultNode, textNode], makeStr(), loc)
# Expression part with conversion if needed
let loweredExpr = ctx.lowerExpr(exprPart)
let exprType = ctx.resolveExprType(exprPart)
var convertedExpr = loweredExpr
if exprType.kind == tkInt or exprType.kind == tkInt8 or exprType.kind == tkInt16 or
exprType.kind == tkInt32 or exprType.kind == tkInt64 or
exprType.kind == tkUInt or exprType.kind == tkUInt8 or exprType.kind == tkUInt16 or
exprType.kind == tkUInt32 or exprType.kind == tkUInt64:
convertedExpr = hirCall("String_FromInt", @[loweredExpr], makeStr(), loc)
elif exprType.kind == tkFloat32 or exprType.kind == tkFloat64:
convertedExpr = hirCall("String_FromFloat", @[loweredExpr], makeStr(), loc)
elif exprType.kind == tkBool:
convertedExpr = hirCall("String_FromBool", @[loweredExpr], makeStr(), loc)
elif exprType.kind == tkStr:
discard # already a string
resultNode = hirCall("String_Concat", @[resultNode, convertedExpr], makeStr(), loc)
# Add final text part
let lastText = expr.exprInterpTexts[^1]
var lastTextNode = HirNode(kind: hLit,
litToken: Token(kind: tkStringLiteral, text: "\"" & lastText & "\"", loc: loc),
typ: makeStr(), loc: loc)
if resultNode == nil:
resultNode = lastTextNode
else:
resultNode = hirCall("String_Concat", @[resultNode, lastTextNode], makeStr(), loc)
return resultNode
of ekClosure:
let f = ctx.lowerClosureFunc(expr)
return hirUnary(tkAmp, hirVar(f.name, makeFunc(@[], makeVoid()), loc), typ, loc)
else:
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
typ: makeVoid(), loc: loc)
@@ -854,6 +1266,12 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
of tekSlice:
let elemType = ctx.resolveTypeExpr(stmt.stmtLetType.sliceElement)
makeSlice(elemType)
of tekFunc:
var params: seq[Type] = @[]
for p in stmt.stmtLetType.funcParams:
params.add(ctx.resolveTypeExpr(p))
let ret = if stmt.stmtLetType.funcRet != nil: ctx.resolveTypeExpr(stmt.stmtLetType.funcRet) else: makeVoid()
makeFunc(params, ret)
else: makeUnknown()
elif stmt.stmtLetInit != nil:
ctx.resolveExprType(stmt.stmtLetInit)
@@ -878,11 +1296,33 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
if initHir != nil:
let store = hirStore(varNode, initHir, loc)
stmts.add(store)
# If init is a closure with captures, emit capture assignments
if stmt.stmtLetInit != nil and stmt.stmtLetInit.kind == ekClosure and stmt.stmtLetInit.captureCount > 0:
let closureIdx = ctx.varCounter - 1
let envInst = "__closure_env_instance_" & $closureIdx
var capStmts: seq[HirNode] = @[]
for i in 0 ..< stmt.stmtLetInit.captureCount:
let capName = stmt.stmtLetInit.captureNames[i]
let capType = if i < stmt.stmtLetInit.captureTypeKinds.len: Type(kind: TypeKind(stmt.stmtLetInit.captureTypeKinds[i])) else: makeInt()
let base = hirVar(envInst, makeNamed(""), loc)
let field = HirNode(kind: hFieldAccess, fieldAccessName: capName, fieldAccessBase: base, typ: capType, loc: loc)
let val = hirVar(capName, capType, loc)
capStmts.add(hirAssign(field, val, loc))
# Prepend capture assignments before the let
var allStmts = capStmts
allStmts.add(stmts)
return hirBlock(allStmts, nil, makeVoid(), loc)
return hirBlock(stmts, nil, makeVoid(), loc)
of skReturn:
let value = if stmt.stmtReturnValue != nil: ctx.lowerExpr(stmt.stmtReturnValue) else: nil
return ctx.flushPending(hirReturn(value, loc))
var stmts = ctx.pendingStmts
ctx.pendingStmts = @[]
# Add defers in reverse order (LIFO)
for i in countdown(ctx.deferStmts.len - 1, 0):
stmts.add(ctx.deferStmts[i])
stmts.add(hirReturn(value, loc))
return hirBlock(stmts, nil, makeVoid(), loc)
of skIf:
let cond = ctx.lowerExpr(stmt.stmtIfCond)
@@ -945,7 +1385,8 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
let inclusive = iterExpr.exprRangeInclusive
# Determine loop variable type from range bounds
let varType = ctx.resolveExprType(iterExpr.exprRangeLo)
let rangeType = ctx.resolveExprType(iterExpr)
let varType = if rangeType.inner.len > 0: rangeType.inner[0] else: ctx.resolveExprType(iterExpr.exprRangeLo)
# Create: var i = lo; while i < hi { body; i = i + 1; }
let initStmt = hirAlloca(varName, varType, loc)
@@ -976,10 +1417,130 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
let forBlock = hirBlock(@[initStmt, initStore, whileNode], nil, makeVoid(), loc, isScope = true)
return ctx.flushPending(forBlock)
# Generic iterator for loop (simplified - just infinite loop for now)
let loweredIter = ctx.lowerExpr(iterExpr)
# Collection-based for: for x in collection { body }
let collType = ctx.resolveExprType(iterExpr)
let elemTypeExpr = ctx.getCollectionElementTypeExpr(iterExpr)
let elemType = ctx.resolveTypeExpr(elemTypeExpr)
# Resolve the collection type to its mangled struct instance (e.g. Array<int> -> Array_int).
let collTypeMangled = substituteType(ctx, typeToTypeExpr(collType), ctx.typeSubst)
let isChannel = collType.kind == tkNamed and collType.name.startsWith("Channel")
if isChannel:
# Channel lowering:
# alloca x
# while (true) {
# if (!Channel_Recv_Ok_T(&ch, &x)) break;
# body
# }
let recvOkName = ctx.generateMethodInstance("Channel_Recv_Ok", @[elemTypeExpr])
let xAlloca = hirAlloca(varName, elemType, loc)
let xVar = hirVar(varName, elemType, loc)
ctx.varTypeExprs[varName] = elemTypeExpr
let chAddr = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: ctx.lowerExpr(iterExpr),
typ: makePointer(collType), loc: loc)
let xAddr = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: xVar,
typ: makePointer(elemType), loc: loc)
let recvOkCall = hirCall(recvOkName, @[chAddr, xAddr], makeBool(), loc)
let notRecvOk = HirNode(kind: hUnary, unaryOp: tkBang, unaryOperand: recvOkCall,
typ: makeBool(), loc: loc)
let breakNode = HirNode(kind: hBreak, loc: loc)
let ifNode = HirNode(kind: hIf, ifCond: notRecvOk, ifThen: breakNode, ifElse: nil,
typ: makeVoid(), loc: loc)
let loweredBody = ctx.lowerBlock(body)
var whileBodyStmts: seq[HirNode] = @[]
whileBodyStmts.add(xAlloca)
whileBodyStmts.add(ifNode)
if loweredBody != nil:
whileBodyStmts.add(loweredBody)
let whileBody = hirBlock(whileBodyStmts, nil, makeVoid(), loc)
let trueLit = hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc)
let whileNode = HirNode(kind: hWhile, whileCond: trueLit, whileBody: whileBody,
typ: makeVoid(), loc: loc)
let forBlock = hirBlock(@[whileNode], nil, makeVoid(), loc, isScope = true)
return ctx.flushPending(forBlock)
# Array / Iter lowering:
# alloca __iter
# __iter = Array_Iter_T(&collection);
# while (Iter_HasNext_T(&__iter)) {
# alloca x
# x = Iter_Next_T(&__iter);
# body
# }
let iterFuncName = ctx.generateMethodInstance("Array_Iter", @[elemTypeExpr])
let hasNextFuncName = ctx.generateMethodInstance("Iter_HasNext", @[elemTypeExpr])
let nextFuncName = ctx.generateMethodInstance("Iter_Next", @[elemTypeExpr])
# Ensure Iter<T> struct instance exists and resolve its mangled name.
let iterType = substituteType(ctx, TypeExpr(kind: tekNamed, typeName: "Iter", typeArgs: @[elemTypeExpr]), ctx.typeSubst)
let iterVarName = "__iter_" & varName & "_" & $ctx.varCounter
inc ctx.varCounter
# Build collection pointer. If the collection is not a simple identifier, spill to a temp.
var preStmts: seq[HirNode] = @[]
var collPtr: HirNode = nil
if iterExpr.kind == ekIdent:
let collVar = hirVar(iterExpr.exprIdent, collType, loc)
collPtr = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: collVar,
typ: makePointer(collType), loc: loc)
else:
let collAllocaName = ctx.freshName()
let collAlloca = hirAlloca(collAllocaName, collTypeMangled, loc)
let collVarPtr = hirVar(collAllocaName, makePointer(collTypeMangled), loc)
let collValue = ctx.lowerExpr(iterExpr)
let collStore = hirStore(collVarPtr, collValue, loc)
preStmts.add(collAlloca)
preStmts.add(collStore)
collPtr = HirNode(kind: hUnary, unaryOp: tkAmp,
unaryOperand: hirVar(collAllocaName, collTypeMangled, loc),
typ: makePointer(collTypeMangled), loc: loc)
let iterAlloca = hirAlloca(iterVarName, iterType, loc)
let iterVarPtr = hirVar(iterVarName, makePointer(iterType), loc)
let iterInitCall = hirCall(iterFuncName, @[collPtr], iterType, loc)
let iterStore = hirStore(iterVarPtr, iterInitCall, loc)
preStmts.add(iterAlloca)
preStmts.add(iterStore)
# while condition: Iter_HasNext_T(&__iter)
let iterAddr = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: hirVar(iterVarName, iterType, loc),
typ: makePointer(iterType), loc: loc)
let condCall = hirCall(hasNextFuncName, @[iterAddr], makeBool(), loc)
# loop body: alloca x; x = Iter_Next_T(&__iter); body
let xAlloca = hirAlloca(varName, elemType, loc)
let xVarPtr = hirVar(varName, makePointer(elemType), loc)
let iterAddr2 = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: hirVar(iterVarName, iterType, loc),
typ: makePointer(iterType), loc: loc)
let nextCall = hirCall(nextFuncName, @[iterAddr2], elemType, loc)
let xStore = hirStore(xVarPtr, nextCall, loc)
ctx.varTypeExprs[varName] = elemTypeExpr
let loweredBody = ctx.lowerBlock(body)
return ctx.flushPending(HirNode(kind: hLoop, loopBody: loweredBody, typ: makeVoid(), loc: loc))
var bodyStmts: seq[HirNode] = @[]
bodyStmts.add(xAlloca)
bodyStmts.add(xStore)
if loweredBody != nil:
bodyStmts.add(loweredBody)
let whileBody = hirBlock(bodyStmts, nil, makeVoid(), loc)
let whileNode = HirNode(kind: hWhile, whileCond: condCall, whileBody: whileBody,
typ: makeVoid(), loc: loc)
var blockStmts = preStmts
blockStmts.add(whileNode)
let forBlock = hirBlock(blockStmts, nil, makeVoid(), loc, isScope = true)
return ctx.flushPending(forBlock)
of skDoWhile:
let body = ctx.lowerBlock(stmt.stmtDoWhileBody)
@@ -997,6 +1558,29 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
return ctx.flushPending(HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
typ: makeVoid(), loc: loc))
of skSwitch:
let subject = ctx.lowerExpr(stmt.stmtSwitchExpr)
var current: HirNode = nil
# Build if-else chain from bottom up (default first)
if stmt.stmtSwitchDefault != nil:
current = ctx.lowerBlock(stmt.stmtSwitchDefault)
# Cases in reverse order
for i in countdown(stmt.stmtSwitchCases.len - 1, 0):
let caseBranch = stmt.stmtSwitchCases[i]
let caseVal = ctx.lowerExpr(caseBranch.caseValue)
let caseBody = ctx.lowerBlock(caseBranch.caseBody)
let cond = HirNode(kind: hBinary, binaryOp: tkEq,
binaryLeft: subject, binaryRight: caseVal,
typ: makeBool(), loc: caseBranch.loc)
current = HirNode(kind: hIf, ifCond: cond, ifThen: caseBody, ifElse: current,
typ: makeVoid(), loc: caseBranch.loc)
return ctx.flushPending(current)
of skDefer:
let body = ctx.lowerExpr(stmt.stmtDeferBody)
ctx.deferStmts.add(body)
return nil
of skDecl:
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
typ: makeVoid(), loc: loc)
@@ -1020,8 +1604,7 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
# but for hVar/hLit/hCall etc. we could treat them as block expr.
# For now, leave as-is to avoid breaking control-flow statements.
discard
return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr,
typ: if expr != nil: expr.typ else: makeVoid(), loc: blk.loc)
return hirBlock(stmts, expr, if expr != nil: expr.typ else: makeVoid(), blk.loc, isScope = true)
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
# Set up type substitution for generic functions
@@ -1053,8 +1636,6 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
if p.ptype != nil:
pType = substituteType(ctx, p.ptype, ctx.typeSubst)
params.add((p.name, pType))
if p.ptype != nil:
ctx.varTypeExprs[p.name] = p.ptype
var retType = makeVoid()
if funcReturnType != nil:
@@ -1066,7 +1647,28 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
ctx.currentFuncRetType = retType
ctx.currentFuncDecl = decl
ctx.varTypeExprs = initTable[string, TypeExpr]() # Clear local vars for new function
let body = if funcBody != nil: ctx.lowerBlock(funcBody) else: nil
# Add parameters to varTypeExprs after clearing so they are visible in the body.
for p in funcParams:
if p.ptype != nil:
ctx.varTypeExprs[p.name] = p.ptype
var body = if funcBody != nil: ctx.lowerBlock(funcBody) else: nil
# Inject remaining defers at end of function (for implicit return)
if ctx.deferStmts.len > 0 and body != nil and body.kind == hBlock:
# Only add if last statement is not already a return (defers already injected there)
var hasReturn = false
if body.blockStmts.len > 0 and body.blockStmts[^1].kind == hReturn:
hasReturn = true
elif body.blockStmts.len > 0 and body.blockStmts[^1].kind == hBlock:
# Check nested block's last statement
let last = body.blockStmts[^1]
if last.blockStmts.len > 0 and last.blockStmts[^1].kind == hReturn:
hasReturn = true
if not hasReturn:
for i in countdown(ctx.deferStmts.len - 1, 0):
body.blockStmts.add(ctx.deferStmts[i])
ctx.deferStmts = @[]
ctx.currentFuncDecl = oldFuncDecl
ctx.currentFuncRetType = oldFuncRetType
ctx.varTypeExprs = oldVarTypeExprs
@@ -1117,6 +1719,40 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs:
ctx.generatedFuncInsts[mangledName] = true
return mangledName
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
let name = "__closure_" & $ctx.varCounter
inc ctx.varCounter
var f = HirFunc(name: name, isPublic: false)
# Copy capture metadata
if expr.captureCount > 0:
f.captureNames = expr.captureNames
for tk in expr.captureTypeKinds:
f.captureTypes.add(Type(kind: TypeKind(tk)))
f.envStructName = "__closure_env_" & $(ctx.varCounter - 1)
f.envInstanceName = "__closure_env_instance_" & $(ctx.varCounter - 1)
# Params
for p in expr.exprClosureParams:
f.params.add((name: p.name, typ: if p.ptype != nil: ctx.resolveTypeExpr(p.ptype) else: makeUnknown()))
# Return type
if expr.exprClosureReturnType != nil:
f.retType = ctx.resolveTypeExpr(expr.exprClosureReturnType)
else:
f.retType = makeVoid()
# Body with closure rewriting
let savedDepth = ctx.closureDepth
let savedExpr = ctx.currentClosureExpr
let savedEnv = ctx.envInstanceName
ctx.closureDepth = ctx.closureDepth + 1
ctx.currentClosureExpr = expr
ctx.envInstanceName = f.envInstanceName
if expr.exprClosureBody != nil:
f.body = ctx.lowerBlock(expr.exprClosureBody)
ctx.closureDepth = savedDepth
ctx.currentClosureExpr = savedExpr
ctx.envInstanceName = savedEnv
ctx.extraFuncs.add(f)
return f
proc lowerModule*(module: Module, sema: Sema): HirModule =
var ctx = initLowerCtx(module, sema)
var funcs: seq[HirFunc] = @[]
@@ -1287,6 +1923,9 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
var found = false
for avail in methods:
if avail.name == req.declFuncName:
# Skip generic methods — they have no concrete C function to put in vtable
if avail.decl.declFuncTypeParams.len > 0:
break
found = true
methodNames.add(req.declFuncName)
break
+4 -23
View File
@@ -61,12 +61,6 @@ proc advance(lex: var Lexer): char =
else:
inc lex.col
proc match(lex: var Lexer, expected: char): bool =
if lex.isAtEnd(): return false
if lex.peek() != expected: return false
discard lex.advance()
return true
proc matchStr(lex: var Lexer, s: string): bool =
for i, c in s:
if lex.peek(i) != c:
@@ -81,9 +75,6 @@ proc currentLocation(lex: Lexer): SourceLocation =
proc emitError(lex: var Lexer, loc: SourceLocation, message: string) =
lex.diagnostics.add(LexerDiagnostic(severity: ldsError, loc: loc, message: message))
proc emitWarning(lex: var Lexer, loc: SourceLocation, message: string) =
lex.diagnostics.add(LexerDiagnostic(severity: ldsWarning, loc: loc, message: message))
proc makeToken(lex: Lexer, kind: TokenKind, startLoc: SourceLocation, startPos: int): Token =
let text = lex.source[startPos ..< lex.pos]
result = Token(kind: kind, text: text, loc: startLoc)
@@ -337,19 +328,6 @@ proc scanSymbol(lex: var Lexer, startLoc: SourceLocation): Token =
else:
return lex.makeToken(kind1, startLoc, startPos)
template check3(c2: char, kind2: TokenKind, c3: char, kind3: TokenKind, kind1: TokenKind) =
if lex.peek() == c2:
discard lex.advance()
if lex.peek() == c3:
discard lex.advance()
return lex.makeToken(kind3, startLoc, startPos)
return lex.makeToken(kind2, startLoc, startPos)
else:
return lex.makeToken(kind1, startLoc, startPos)
template checkEq(c2: char, kind2: TokenKind, kind1: TokenKind) =
check2(c2, kind2, kind1)
case c1
of '(': return lex.makeToken(tkLParen, startLoc, startPos)
of ')': return lex.makeToken(tkRParen, startLoc, startPos)
@@ -527,7 +505,10 @@ proc nextToken(lex: var Lexer): Token =
discard lex.advance()
return Token(kind: tkNewLine, text: "\n", loc: startLoc)
# String prefixes: c8" c16" c32" — must come before ident check
# String prefixes: f" c8" c16" c32" — must come before ident check
if c == 'f' and lex.peek(1) == '"':
discard lex.advance() # f
return lex.scanString(startLoc, 1)
if c == 'c' and lex.peek(1) in {'8', '1', '3'}:
let d = lex.peek(1)
if d == '8' and lex.peek(2) == '"':
+312
View File
@@ -0,0 +1,312 @@
## LIR — Low-level Intermediate Representation
## Linear 3-address code IR, designed for straightforward C emission.
## Each HIR construct lowers to 5-30 LIR instructions.
type
LirKind* = enum
# ── Data movement ──
lirMov ## dst = src
lirLoad ## dst = *(base + offset) [type: base elem type]
lirStore ## *(base + offset) = src
lirLoadGlobal ## dst = global_name
# ── Arithmetic (3-address, signed) ──
lirAdd
lirSub
lirMul
lirDiv
lirMod
# ── Bitwise ──
lirAnd
lirOr
lirXor
lirShl
lirShr
# ── Unary ──
lirNeg ## dst = -src
lirNot ## dst = !src (logical)
lirBNot ## dst = ~src (bitwise)
# ── Comparison (dst = 0 or 1) ──
lirCmpEq
lirCmpNe
lirCmpLt
lirCmpLe
lirCmpGt
lirCmpGe
# ── Control flow ──
lirLabel ## label_name:
lirJmp ## goto target
lirJz ## if (!cond) goto target
lirJnz ## if (cond) goto target
# ── Calls ──
lirCall ## dst = callee(args...)
lirCallVoid ## callee(args...) [no return]
lirCallIndirect ## dst = (*fn_ptr)(args...)
# ── Return ──
lirRet ## return [val]
# ── Stack allocation ──
lirAlloca ## type dst; (declare a C local)
# ── Pointers / addressing ──
lirAddrOf ## dst = &source_var
lirFieldPtr ## dst = &(base.field_name)
lirArrowFieldPtr ## dst = &(base->field_name)
lirIndexPtr ## dst = &base[idx]
lirPtrAdd ## dst = base + offset_bytes (raw pointer arithmetic)
# ── Type conversion ──
lirCast ## dst = (target_type)src
# ── Composite literals ──
lirStructInit ## dst = (StructType){.f1=v1, .f2=v2, ...}
lirSliceInit ## dst = (SliceType){.data=arr, .len=n}
# ── Ternary (convenience; lowered from if-expr) ──
lirSelect ## dst = cond ? a : b
# ── Inline C (for runtime calls that C handles natively) ──
lirRawC ## emit raw C line
# ── Source annotation ──
lirComment ## /* text */
LirValueKind* = enum
lvkTemp ## Virtual register / temp (e.g. "%42")
lvkVar ## Named variable / parameter
lvkInt ## Integer literal
lvkFloat ## Float literal
lvkString ## String literal (already C-escaped)
lvkGlobal ## Global variable / constant name
lvkLabel ## Label reference
lvkField ## Field name (for struct operations)
lvkType ## C type name (for casts, alloca, struct init)
lvkVoid ## No value
LirValue* = object
case kind*: LirValueKind
of lvkVoid: discard
of lvkTemp, lvkVar, lvkGlobal, lvkLabel, lvkField, lvkType, lvkString:
strVal*: string
of lvkInt:
intVal*: int64
of lvkFloat:
floatVal*: float64
LirInstr* = object
kind*: LirKind
dst*: LirValue ## Destination (temp or void)
src*: LirValue ## Source operand
src2*: LirValue ## Second source operand (for binary ops)
extra*: seq[LirValue] ## Extra operands (call args, struct fields, etc.)
locLine*: int ## Source line for debug comments (0 = none)
locFile*: string ## Source file for debug comments ("" = none)
# ── Constructor helpers ──
proc lirTemp*(name: string): LirValue =
LirValue(kind: lvkTemp, strVal: name)
proc lirVar*(name: string): LirValue =
LirValue(kind: lvkVar, strVal: name)
proc lirInt*(v: int64): LirValue =
LirValue(kind: lvkInt, intVal: v)
proc lirFloatLit*(v: float64): LirValue =
LirValue(kind: lvkFloat, floatVal: v)
proc lirStr*(v: string): LirValue =
## Already C-escaped and quoted string
LirValue(kind: lvkString, strVal: v)
proc lirGlobal*(name: string): LirValue =
LirValue(kind: lvkGlobal, strVal: name)
proc lirLabel*(name: string): LirValue =
LirValue(kind: lvkLabel, strVal: name)
proc lirField*(name: string): LirValue =
LirValue(kind: lvkField, strVal: name)
proc lirType*(name: string): LirValue =
LirValue(kind: lvkType, strVal: name)
proc lirVoid*(): LirValue =
LirValue(kind: lvkVoid)
proc `$`*(v: LirValue): string =
case v.kind
of lvkVoid: "void"
of lvkTemp: "%" & v.strVal
of lvkVar: v.strVal
of lvkInt: $v.intVal
of lvkFloat: $v.floatVal
of lvkString: v.strVal
of lvkGlobal: "@" & v.strVal
of lvkLabel: ":" & v.strVal
of lvkField: "." & v.strVal
of lvkType: "<" & v.strVal & ">"
# ── LIR function ──
type
LirFunc* = object
name*: string
params*: seq[tuple[name: string, cType: string]]
retType*: string ## C return type ("int", "void", etc.)
instrs*: seq[LirInstr]
isPublic*: bool
LirModule* = object
funcs*: seq[LirFunc]
globals*: seq[tuple[name: string, cType: string, initVal: string]]
structDefs*: seq[string] ## Raw C struct typedef strings
enumDefs*: seq[string] ## Raw C enum typedef strings
externs*: seq[string] ## Raw C extern declarations
includes*: seq[string] ## #include <...>
preamble*: string ## Raw C code at top of file
# ── Builder context (temp counter, label counter) ──
type
LirBuilder* = object
funcs*: seq[LirFunc]
tempCounter*: int
labelCounter*: int
commentLine*: int
commentFile*: string
## Current function being built
curFunc*: LirFunc
curFuncActive*: bool
proc initLirBuilder*(): LirBuilder =
result = LirBuilder()
result.tempCounter = 0
result.labelCounter = 0
proc freshTemp*(b: var LirBuilder): LirValue =
inc b.tempCounter
result = lirTemp("_t" & $b.tempCounter)
proc freshLabel*(b: var LirBuilder, prefix: string = "L"): LirValue =
inc b.labelCounter
result = lirLabel(prefix & $b.labelCounter)
# ── Instruction emitters ──
proc emit*(b: var LirBuilder, instr: LirInstr) =
var i = instr
if b.commentLine > 0:
i.locLine = b.commentLine
i.locFile = b.commentFile
b.curFunc.instrs.add(i)
proc emitMov*(b: var LirBuilder, dst, src: LirValue) =
b.emit(LirInstr(kind: lirMov, dst: dst, src: src))
proc emitLoad*(b: var LirBuilder, dst, base: LirValue, offset: int = 0) =
b.emit(LirInstr(kind: lirLoad, dst: dst, src: base, src2: lirInt(offset)))
proc emitStore*(b: var LirBuilder, base: LirValue, src: LirValue, offset: int = 0) =
b.emit(LirInstr(kind: lirStore, src: src, src2: base, dst: lirInt(offset)))
proc emitBinOp*(b: var LirBuilder, op: LirKind, dst, a, bl: LirValue) =
b.emit(LirInstr(kind: op, dst: dst, src: a, src2: bl))
proc emitUnary*(b: var LirBuilder, op: LirKind, dst, src: LirValue) =
b.emit(LirInstr(kind: op, dst: dst, src: src))
proc emitCmp*(b: var LirBuilder, op: LirKind, dst, a, bl: LirValue) =
b.emit(LirInstr(kind: op, dst: dst, src: a, src2: bl))
proc emitLabel*(b: var LirBuilder, label: LirValue) =
b.emit(LirInstr(kind: lirLabel, src: label))
proc emitJmp*(b: var LirBuilder, target: LirValue) =
b.emit(LirInstr(kind: lirJmp, src: target))
proc emitJz*(b: var LirBuilder, target, cond: LirValue) =
b.emit(LirInstr(kind: lirJz, src: target, src2: cond))
proc emitJnz*(b: var LirBuilder, target, cond: LirValue) =
b.emit(LirInstr(kind: lirJnz, src: target, src2: cond))
proc emitCall*(b: var LirBuilder, dst: LirValue, callee: string, args: seq[LirValue]) =
b.emit(LirInstr(kind: lirCall, dst: dst, src: lirGlobal(callee), extra: args))
proc emitCallVoid*(b: var LirBuilder, callee: string, args: seq[LirValue]) =
b.emit(LirInstr(kind: lirCallVoid, dst: lirVoid(), src: lirGlobal(callee), extra: args))
proc emitRet*(b: var LirBuilder, val: LirValue = lirVoid()) =
b.emit(LirInstr(kind: lirRet, src: val))
proc emitAlloca*(b: var LirBuilder, name: string, cType: string) =
b.emit(LirInstr(kind: lirAlloca, dst: lirVar(name), src: lirType(cType)))
proc emitAddrOf*(b: var LirBuilder, dst, src: LirValue) =
b.emit(LirInstr(kind: lirAddrOf, dst: dst, src: src))
proc emitFieldPtr*(b: var LirBuilder, dst, base: LirValue, field: string) =
b.emit(LirInstr(kind: lirFieldPtr, dst: dst, src: base, src2: lirField(field)))
proc emitArrowFieldPtr*(b: var LirBuilder, dst, base: LirValue, field: string) =
b.emit(LirInstr(kind: lirArrowFieldPtr, dst: dst, src: base, src2: lirField(field)))
proc emitIndexPtr*(b: var LirBuilder, dst, base, idx: LirValue) =
b.emit(LirInstr(kind: lirIndexPtr, dst: dst, src: base, src2: idx))
proc emitPtrAdd*(b: var LirBuilder, dst, base, offset: LirValue) =
b.emit(LirInstr(kind: lirPtrAdd, dst: dst, src: base, src2: offset))
proc emitCast*(b: var LirBuilder, dst, src: LirValue, targetType: string) =
b.emit(LirInstr(kind: lirCast, dst: dst, src: src, src2: lirType(targetType)))
proc emitStructInit*(b: var LirBuilder, dst: LirValue, structType: string,
fields: seq[tuple[name: string, val: LirValue]]) =
var extras: seq[LirValue] = @[lirType(structType)]
for f in fields:
extras.add(lirField(f.name))
extras.add(f.val)
b.emit(LirInstr(kind: lirStructInit, dst: dst, extra: extras))
proc emitSliceInit*(b: var LirBuilder, dst: LirValue, elemType: string,
dataPtr: LirValue, length: LirValue) =
b.emit(LirInstr(kind: lirSliceInit, dst: dst, src: dataPtr, src2: length,
extra: @[lirType(elemType)]))
proc emitSelect*(b: var LirBuilder, dst, cond, thenVal, elseVal: LirValue) =
b.emit(LirInstr(kind: lirSelect, dst: dst, src: cond, src2: thenVal,
extra: @[elseVal]))
proc emitRawC*(b: var LirBuilder, code: string) =
b.emit(LirInstr(kind: lirRawC, src: lirStr(code)))
proc emitComment*(b: var LirBuilder, text: string) =
b.emit(LirInstr(kind: lirComment, src: lirStr(text)))
# ── Function management ──
proc beginFunc*(b: var LirBuilder, name: string, params: seq[tuple[name: string, cType: string]],
retType: string, isPublic: bool = true) =
if b.curFuncActive:
b.funcs.add(b.curFunc)
b.curFunc = LirFunc(name: name, params: params, retType: retType,
isPublic: isPublic)
b.curFuncActive = true
proc endFunc*(b: var LirBuilder) =
if b.curFuncActive:
b.funcs.add(b.curFunc)
b.curFuncActive = false
b.curFunc = LirFunc()
proc setSourceLoc*(b: var LirBuilder, line: int, file: string) =
b.commentLine = line
b.commentFile = file
+744
View File
@@ -0,0 +1,744 @@
## LIR → C Backend
## Emits clean, well-structured C code from LIR instructions.
## Since LIR is already linear and low-level, C emission is straightforward.
import std/[strutils, strformat, tables, sequtils, sets]
import lir, hir, types, token
type
LirCBackend* = object
output*: string
indent*: int
tempTypes*: Table[string, string] ## Track C types of temp variables
proc initLirCBackend*(): LirCBackend =
result = LirCBackend(
indent: 0,
tempTypes: initTable[string, string](),
)
proc emitIndent(be: var LirCBackend) =
for i in 0 ..< be.indent:
be.output.add(" ")
proc emitLine(be: var LirCBackend, s: string) =
be.emitIndent()
be.output.add(s)
be.output.add("\n")
proc valToC(be: var LirCBackend, v: LirValue): string =
## Convert a LirValue to its C representation.
case v.kind
of lvkVoid: ""
of lvkTemp: v.strVal
of lvkVar: v.strVal
of lvkInt: $v.intVal
of lvkFloat: $v.floatVal
of lvkString: v.strVal
of lvkGlobal: v.strVal
of lvkLabel: v.strVal
of lvkField: v.strVal
of lvkType: v.strVal
proc cParamDecl(cType, name: string): string =
## Emit a C parameter declaration, handling function-pointer syntax.
if cType.contains("(*)"):
return cType.replace("(*)", "(*" & name & ")")
else:
return cType & " " & name
# ── Per-instruction emission ──
proc emitInstr(be: var LirCBackend, instr: LirInstr) =
template v(x: LirValue): string = valToC(be, x)
case instr.kind
# ── Data movement ──
of lirMov:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)};")
of lirLoad:
# dst = *(base + offset) or dst = base->src2 (if src2 is a field name)
if instr.src2.kind == lvkField:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)}.{v(instr.src2)};")
elif instr.src2.kind == lvkInt and instr.src2.intVal == 0:
be.emitLine(&"{v(instr.dst)} = *{v(instr.src)};")
elif instr.src2.kind == lvkTemp or instr.src2.kind == lvkVar:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)}[{v(instr.src2)}];")
else:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)}[{v(instr.src2)}];")
of lirStore:
# *(base + offset) = src
if instr.src2.kind == lvkField:
be.emitLine(&"{v(instr.src2)}.{v(instr.dst)} = {v(instr.src)};")
elif instr.dst.kind == lvkInt and instr.dst.intVal == 0:
be.emitLine(&"*{v(instr.src2)} = {v(instr.src)};")
elif instr.src2.kind == lvkTemp or instr.src2.kind == lvkVar:
be.emitLine(&"{v(instr.src2)}[{v(instr.dst)}] = {v(instr.src)};")
else:
be.emitLine(&"*({v(instr.src2)} + {v(instr.dst)}) = {v(instr.src)};")
of lirLoadGlobal:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)};")
# ── Arithmetic ──
of lirAdd, lirSub, lirMul, lirDiv, lirMod,
lirAnd, lirOr, lirXor, lirShl, lirShr:
let op = case instr.kind
of lirAdd: "+"
of lirSub: "-"
of lirMul: "*"
of lirDiv: "/"
of lirMod: "%"
of lirAnd: "&"
of lirOr: "|"
of lirXor: "^"
of lirShl: "<<"
of lirShr: ">>"
else: "?"
be.emitLine(&"{v(instr.dst)} = {v(instr.src)} {op} {v(instr.src2)};")
of lirNeg:
be.emitLine(&"{v(instr.dst)} = -{v(instr.src)};")
of lirNot:
be.emitLine(&"{v(instr.dst)} = !{v(instr.src)};")
of lirBNot:
be.emitLine(&"{v(instr.dst)} = ~{v(instr.src)};")
# ── Comparison ──
of lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe:
let op = case instr.kind
of lirCmpEq: "=="
of lirCmpNe: "!="
of lirCmpLt: "<"
of lirCmpLe: "<="
of lirCmpGt: ">"
of lirCmpGe: ">="
else: "=="
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)} {op} {v(instr.src2)});")
# ── Control flow ──
of lirLabel:
be.emitLine(&"{v(instr.src)}:;") # C requires statement after label
# Add a null statement to avoid "label at end of compound statement" warnings
# Handled by the next instruction naturally
of lirJmp:
be.emitLine(&"goto {v(instr.src)};")
of lirJz:
be.emitLine(&"if (!{v(instr.src2)}) goto {v(instr.src)};")
of lirJnz:
be.emitLine(&"if ({v(instr.src2)}) goto {v(instr.src)};")
# ── Calls ──
of lirCall:
var argsStr = ""
for i, arg in instr.extra:
if i > 0: argsStr.add(", ")
argsStr.add(v(arg))
be.emitLine(&"{v(instr.dst)} = {v(instr.src)}({argsStr});")
of lirCallVoid:
var argsStr = ""
for i, arg in instr.extra:
if i > 0: argsStr.add(", ")
argsStr.add(v(arg))
be.emitLine(&"{v(instr.src)}({argsStr});")
of lirCallIndirect:
var argsStr = ""
for i, arg in instr.extra:
if i > 0: argsStr.add(", ")
argsStr.add(v(arg))
if instr.dst.kind != lvkVoid:
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)})({argsStr});")
else:
be.emitLine(&"({v(instr.src)})({argsStr});")
# ── Return ──
of lirRet:
if instr.src.kind != lvkVoid:
be.emitLine(&"return {v(instr.src)};")
else:
be.emitLine("return;")
# ── Alloca ──
of lirAlloca:
var ct = v(instr.src)
if instr.dst.strVal.len > 0 and be.tempTypes.hasKey(instr.dst.strVal):
let inferred = be.tempTypes[instr.dst.strVal]
if inferred != "" and inferred != ct:
ct = inferred
be.emitLine(cParamDecl(ct, v(instr.dst)) & ";")
# ── Pointers ──
of lirAddrOf:
be.emitLine(&"{v(instr.dst)} = &{v(instr.src)};")
of lirFieldPtr:
be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}.{v(instr.src2)});")
of lirArrowFieldPtr:
be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}->{v(instr.src2)});")
of lirIndexPtr:
be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}[{v(instr.src2)}]);")
of lirPtrAdd:
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)} + {v(instr.src2)});")
# ── Cast ──
of lirCast:
be.emitLine(&"{v(instr.dst)} = ({v(instr.src2)}){v(instr.src)};")
# ── StructInit ──
of lirStructInit:
let structType = v(instr.extra[0])
var fieldPairs = ""
var i = 1
while i < instr.extra.len:
let fieldName = v(instr.extra[i]) # e.g. "width"
let fieldVal = v(instr.extra[i + 1]) # e.g. "10"
if i > 1: fieldPairs.add(", ")
fieldPairs.add(&".{fieldName} = {fieldVal}")
i += 2
be.emitLine(&"{v(instr.dst)} = ({structType}){{{fieldPairs}}};")
# ── SliceInit ──
of lirSliceInit:
let elemType = v(instr.extra[0])
be.emitLine(&"{v(instr.dst)} = (Slice_{elemType}){{.data = ({elemType}*){v(instr.src)}, .len = {v(instr.src2)}}};")
# ── Select (ternary) ──
of lirSelect:
let elseVal = if instr.extra.len > 0: v(instr.extra[0]) else: "0"
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)}) ? {v(instr.src2)} : {elseVal};")
# ── Raw C ──
of lirRawC:
let code = v(instr.src)
if code.len > 0:
be.emitLine(code)
# ── Comment ──
of lirComment:
let text = v(instr.src)
be.emitLine(&"/* {text} */")
# ── Function emission ──
proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, string], funcPtrTypes: Table[string, string]) =
var paramsStr = ""
for i, p in f.params:
if i > 0: paramsStr.add(", ")
paramsStr.add(cParamDecl(p.cType, p.name))
if f.params.len == 0:
paramsStr = "void"
be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{")
be.indent += 1
# ── Pass 1: collect types from allocas, params, and instructions ──
var varTypes = initTable[string, string]()
var tempsSet: seq[string] = @[]
for p in f.params:
varTypes[p.name] = p.cType
be.tempTypes[p.name] = p.cType
for instr in f.instrs:
if instr.kind == lirAlloca and instr.dst.kind == lvkVar and instr.src.kind == lvkType:
varTypes[instr.dst.strVal] = instr.src.strVal
be.tempTypes[instr.dst.strVal] = instr.src.strVal
if instr.dst.strVal notin tempsSet:
tempsSet.add(instr.dst.strVal)
# ── Pass 2: iterative type inference for temps ──
var changed = true
while changed:
changed = false
for instr in f.instrs:
if instr.dst.kind != lvkTemp or instr.dst.strVal.len == 0:
continue
let name = instr.dst.strVal
let oldType = if be.tempTypes.hasKey(name): be.tempTypes[name] else: ""
var newType = oldType
case instr.kind
of lirStructInit:
if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
newType = instr.extra[0].strVal
of lirSliceInit:
if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
newType = "Slice_" & instr.extra[0].strVal
of lirCast:
if instr.src2.kind == lvkType:
newType = instr.src2.strVal
of lirCall:
if instr.src.kind == lvkGlobal and funcRetTypes.hasKey(instr.src.strVal):
newType = funcRetTypes[instr.src.strVal]
of lirCallIndirect:
# Conservative; try to infer from dst usage in later passes
discard
of lirMov:
if instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
newType = be.tempTypes[instr.src.strVal]
elif instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
newType = varTypes[instr.src.strVal]
of lirLoad, lirLoadGlobal:
# Try to deduce pointee type from pointer vars/temps
if instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
let srcType = varTypes[instr.src.strVal]
if srcType.endsWith("*"):
newType = srcType[0 ..< srcType.len - 1]
elif srcType.startsWith("Slice_"):
newType = srcType[6 ..< srcType.len]
elif instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
let srcType = be.tempTypes[instr.src.strVal]
if srcType.endsWith("*"):
newType = srcType[0 ..< srcType.len - 1]
elif srcType.startsWith("Slice_"):
newType = srcType[6 ..< srcType.len]
of lirSelect:
if instr.src2.kind == lvkTemp and be.tempTypes.hasKey(instr.src2.strVal):
newType = be.tempTypes[instr.src2.strVal]
elif instr.extra.len > 0 and instr.extra[0].kind == lvkTemp and be.tempTypes.hasKey(instr.extra[0].strVal):
newType = be.tempTypes[instr.extra[0].strVal]
elif instr.src2.kind == lvkVar and varTypes.hasKey(instr.src2.strVal):
newType = varTypes[instr.src2.strVal]
of lirAddrOf:
if funcPtrTypes.hasKey(instr.src.strVal):
newType = funcPtrTypes[instr.src.strVal]
else:
newType = "void*";
of lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
newType = "void*"
of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg,
lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe,
lirAnd, lirOr, lirXor, lirShl, lirShr, lirNot, lirBNot:
newType = "int"
else:
discard
if newType != "" and newType != oldType:
be.tempTypes[name] = newType
changed = true
# ── Pass 3: declare temps that were inferred ──
var declared: seq[string] = @[]
for instr in f.instrs:
if instr.kind == lirAlloca and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
declared.add(instr.dst.strVal)
continue
if instr.dst.kind == lvkTemp and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
if be.tempTypes.hasKey(instr.dst.strVal):
let ct = be.tempTypes[instr.dst.strVal]
if ct != "":
declared.add(instr.dst.strVal)
be.emitLine(cParamDecl(ct, instr.dst.strVal) & ";")
# ── Pass 4: emit instructions ──
for instr in f.instrs:
be.emitInstr(instr)
be.indent -= 1
be.emitLine("}")
be.emitLine("")
# ── Struct/Enum emission (from HIR module) ──
proc typeToCStr(typ: Type): string =
## Duplicate from lir_lower for self-containedness
if typ == nil: return "int"
case typ.kind
of tkVoid: return "void"
of tkBool, tkBool8, tkBool16, tkBool32: return "bool"
of tkChar8: return "char"
of tkChar16: return "char16_t"
of tkChar32: return "char32_t"
of tkStr: return "const char*"
of tkInt8: return "int8_t"
of tkInt16: return "int16_t"
of tkInt32: return "int32_t"
of tkInt64: return "int64_t"
of tkInt: return "int"
of tkUInt8: return "uint8_t"
of tkUInt16: return "uint16_t"
of tkUInt32: return "uint32_t"
of tkUInt64: return "uint64_t"
of tkUInt: return "unsigned int"
of tkFloat32: return "float"
of tkFloat64: return "double"
of tkPointer, tkRef, tkMutRef:
if typ.inner.len > 0:
return typeToCStr(typ.inner[0]) & "*"
return "void*"
of tkDynRef:
return typ.name & "_FatPtr"
of tkSlice:
let elem = if typ.inner.len > 0: typeToCStr(typ.inner[0]) else: "void"
return "Slice_" & elem.replace(" ", "_").replace("*", "Ptr")
of tkNamed:
case typ.name
of "String", "str": return "const char*"
of "int": return "int"
of "int8": return "int8_t"
of "int16": return "int16_t"
of "int32": return "int32_t"
of "int64": return "int64_t"
of "uint": return "unsigned int"
of "uint8": return "uint8_t"
of "uint16": return "uint16_t"
of "uint32": return "uint32_t"
of "uint64": return "uint64_t"
of "float32": return "float"
of "float64": return "double"
of "bool": return "bool"
else: return typ.name
of tkFunc:
if typ.inner.len == 0: return "void (*)(void)"
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
let ret = typeToCStr(typ.inner[^1])
return ret & " (*)(" & params & ")"
else: return "int"
proc emitStructDef(be: var LirCBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) =
be.emitLine(&"typedef struct {name} {{")
be.indent += 1
for f in fields:
be.emitLine(&"{typeToCStr(f.typ)} {f.name};")
be.indent -= 1
be.emitLine(&"}} {name};")
be.emitLine("")
proc emitEnumDef(be: var LirCBackend, name: string, variants: seq[HirEnumVariant]) =
var hasData = false
for v in variants:
if v.fields.len > 0 or v.namedFields.len > 0:
hasData = true
break
if not hasData:
# Simple enum
be.emitLine(&"typedef enum {{")
be.indent += 1
for i, v in variants:
if i < variants.len - 1:
be.emitLine(&"{name}_{v.name},")
else:
be.emitLine(&"{name}_{v.name}")
be.indent -= 1
be.emitLine(&"}} {name};")
be.emitLine("")
else:
# Tagged union
be.emitLine(&"typedef enum {{")
be.indent += 1
for i, v in variants:
if i < variants.len - 1:
be.emitLine(&"{name}_{v.name},")
else:
be.emitLine(&"{name}_{v.name}")
be.indent -= 1
be.emitLine(&"}} {name}_Tag;")
be.emitLine("")
be.emitLine(&"typedef union {{")
be.indent += 1
for v in variants:
if v.fields.len > 0:
for i, f in v.fields:
be.emitLine(&"{typeToCStr(f)} {v.name}_{i};")
elif v.namedFields.len > 0:
be.emitLine(&"struct {{")
be.indent += 1
for nf in v.namedFields:
be.emitLine(&"{typeToCStr(nf.typ)} {nf.name};")
be.indent -= 1
be.emitLine(&"}} {v.name};")
be.indent -= 1
be.emitLine(&"}} {name}_Data;")
be.emitLine("")
be.emitLine(&"typedef struct {{")
be.indent += 1
be.emitLine(&"{name}_Tag tag;")
be.emitLine(&"{name}_Data data;")
be.indent -= 1
be.emitLine(&"}} {name};")
be.emitLine("")
# ── Type dependency ordering ──
proc collectValueDeps(typ: Type): seq[string] =
## Return type names that must be fully defined before a value of `typ`
## can be declared. Pointers/refs only need a forward declaration, so
## they do not introduce a dependency.
if typ == nil: return @[]
case typ.kind
of tkNamed:
return @[typ.name]
of tkSlice:
return @[typeToCStr(typ)]
of tkPointer, tkRef, tkMutRef, tkTuple, tkFunc:
return @[]
else:
return @[]
proc emitSliceTypeDef(be: var LirCBackend, name: string, elem: string) =
be.emitLine(&"typedef struct {{ {elem}* data; size_t len; }} {name};")
# ── Module emission ──
proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): string =
## Emit full C source from LIR builder + HIR module metadata.
be.output = ""
# Build function return type lookup table
var funcRetTypes = initTable[string, string]()
for f in module.funcs:
funcRetTypes[f.name] = typeToCStr(f.retType)
for f in module.externFuncs:
funcRetTypes[f.name] = typeToCStr(f.retType)
# Build function-pointer type lookup table (for address-of)
var funcPtrTypes = initTable[string, string]()
for f in module.funcs:
let params = f.params.mapIt(typeToCStr(it.typ)).join(", ")
let ret = typeToCStr(f.retType)
funcPtrTypes[f.name] = ret & " (*)(" & params & ")"
for f in module.externFuncs:
let params = f.params.mapIt(typeToCStr(it.typ)).join(", ")
let ret = typeToCStr(f.retType)
funcPtrTypes[f.name] = ret & " (*)(" & params & ")"
# Header
be.emitLine("/* Generated by Bux Compiler (LIR backend) */")
be.emitLine("#include <stdio.h>")
be.emitLine("#include <stdlib.h>")
be.emitLine("#include <stdint.h>")
be.emitLine("#include <stdbool.h>")
be.emitLine("#include <string.h>")
be.emitLine("")
# Forward struct declarations
for s in module.structs:
be.emitLine(&"typedef struct {s.name} {s.name};")
if module.structs.len > 0:
be.emitLine("")
# Forward trait object declarations
for iface in module.interfaces:
if not iface.hasAssocTypes:
be.emitLine(&"typedef struct {iface.name}_FatPtr {iface.name}_FatPtr;")
if module.interfaces.len > 0:
be.emitLine("")
# Extern declarations
if module.externFuncs.len > 0:
be.emitLine("/* Extern function declarations */")
for ef in module.externFuncs:
let rt = typeToCStr(ef.retType)
var params: seq[string] = @[]
for p in ef.params:
params.add(cParamDecl(typeToCStr(p.typ), p.name))
if params.len == 0: params.add("void")
be.emitLine(&"extern {rt} {ef.name}({params.join(\", \")});")
be.emitLine("")
# Constants as #define
if module.consts.len > 0:
be.emitLine("/* Constants */")
for c in module.consts:
if c.value != nil and c.value.kind == hLit:
case c.value.litToken.kind
of tkIntLiteral: be.emitLine(&"#define {c.name} {c.value.litToken.text}")
of tkStringLiteral: be.emitLine(&"#define {c.name} \"{c.value.litToken.text}\"")
of tkBoolLiteral: be.emitLine(&"#define {c.name} {c.value.litToken.text}")
else: discard
be.emitLine("")
# Collect local type names (structs and enums defined in this module).
var localTypeNames: HashSet[string]
for s in module.structs:
localTypeNames.incl(s.name)
for e in module.enums:
localTypeNames.incl(e.name)
# Collect slice types used in struct fields and enum payloads.
var sliceTypes: seq[tuple[name: string, elem: string]] = @[]
var sliceNames: HashSet[string]
proc registerSlice(t: Type) =
if t == nil or t.kind != tkSlice: return
let name = typeToCStr(t)
if sliceNames.contains(name): return
sliceNames.incl(name)
let elem = if t.inner.len > 0: typeToCStr(t.inner[0]) else: "void"
sliceTypes.add((name, elem))
for s in module.structs:
for f in s.fields:
registerSlice(f.typ)
for e in module.enums:
for v in e.variants:
for ft in v.fields:
registerSlice(ft)
for nf in v.namedFields:
registerSlice(nf.typ)
# Build dependency graph among structs, enums, and slice types.
# Edge A -> B means "A depends on B, so B must be emitted before A".
var deps: Table[string, seq[string]]
for s in module.structs:
deps[s.name] = @[]
for e in module.enums:
deps[e.name] = @[]
for st in sliceTypes:
deps[st.name] = @[]
proc addDeps(node: string, t: Type) =
for dep in collectValueDeps(t):
if dep == node: continue
if localTypeNames.contains(dep) or sliceNames.contains(dep):
if dep notin deps[node]:
deps[node].add(dep)
for s in module.structs:
for f in s.fields:
addDeps(s.name, f.typ)
for e in module.enums:
for v in e.variants:
for ft in v.fields:
addDeps(e.name, ft)
for nf in v.namedFields:
addDeps(e.name, nf.typ)
# Topological sort (Kahn's algorithm).
var inDegree: Table[string, int]
var dependents: Table[string, seq[string]]
for node in deps.keys:
inDegree[node] = 0
for node, nodeDeps in deps:
for d in nodeDeps:
if not inDegree.hasKey(d): inDegree[d] = 0
inDegree[node] += 1
dependents.mgetOrPut(d, @[]).add(node)
var queue: seq[string] = @[]
for node, deg in inDegree:
if deg == 0:
queue.add(node)
var sorted: seq[string] = @[]
while queue.len > 0:
let node = queue.pop()
sorted.add(node)
for depNode in dependents.getOrDefault(node):
inDegree[depNode] -= 1
if inDegree[depNode] == 0:
queue.add(depNode)
if sorted.len < deps.len:
# Cycle detected; fall back to a safe deterministic order.
sorted = @[]
for s in module.structs: sorted.add(s.name)
for e in module.enums: sorted.add(e.name)
for st in sliceTypes: sorted.add(st.name)
# Map type names back to their definitions.
var structMap: Table[string, seq[tuple[name: string, typ: Type]]]
for s in module.structs: structMap[s.name] = s.fields
var enumMap: Table[string, seq[HirEnumVariant]]
for e in module.enums: enumMap[e.name] = e.variants
var sliceMap: Table[string, string]
for st in sliceTypes: sliceMap[st.name] = st.elem
# Emit type definitions in dependency order.
for name in sorted:
if structMap.hasKey(name):
be.emitStructDef(name, structMap[name])
elif enumMap.hasKey(name):
be.emitEnumDef(name, enumMap[name])
elif sliceMap.hasKey(name):
be.emitSliceTypeDef(name, sliceMap[name])
# Forward function declarations
for f in module.funcs:
let rt = typeToCStr(f.retType)
var params: seq[string] = @[]
for p in f.params:
params.add(cParamDecl(typeToCStr(p.typ), p.name))
if params.len == 0: params.add("void")
be.emitLine(&"{rt} {f.name}({params.join(\", \")});")
be.emitLine("")
# VTable and fat pointer structs
for iface in module.interfaces:
if iface.hasAssocTypes: continue
let iname = iface.name
be.emitLine(&"typedef struct {iname}_VTable {{")
be.indent += 1
for m in iface.methods:
var paramCTypes: seq[string] = @["void* self"]
for i in 1 ..< m.params.len:
paramCTypes.add(cParamDecl(typeToCStr(m.params[i]), "param"))
let rt = typeToCStr(m.ret)
be.emitLine(&"{rt} (*{m.name})({paramCTypes.join(\", \")});")
be.indent -= 1
be.emitLine(&"}} {iname}_VTable;")
be.emitLine(&"typedef struct {iname}_FatPtr {{")
be.indent += 1
be.emitLine("void* data;")
be.emitLine(&"{iname}_VTable* vtable;")
be.indent -= 1
be.emitLine(&"}} {iname}_FatPtr;")
be.emitLine("")
# VTable instances
for vt in module.vtables:
if vt.hasAssocTypes: continue
let varName = vt.concreteType & "_" & vt.interfaceName & "_VTable"
be.emitLine(&"{vt.interfaceName}_VTable {varName} = {{")
be.indent += 1
for m in vt.methodNames:
be.emitLine(&".{m} = (void*){vt.concreteType}_{m},")
be.indent -= 1
be.emitLine("};")
be.emitLine("")
# Emit env structs for closures with captures
for f in module.funcs:
if f.captureNames.len > 0 and f.envStructName != "":
be.emitLine(&"struct {f.envStructName} {{")
be.indent += 1
for i in 0 ..< f.captureNames.len:
let capName = f.captureNames[i]
let capType = if i < f.captureTypes.len: typeToCStr(f.captureTypes[i]) else: "int"
be.emitLine(&"{capType} {capName};")
be.indent -= 1
be.emitLine("};")
be.emitLine(&"static struct {f.envStructName} {f.envInstanceName};")
be.emitLine("")
# Emit all LIR functions
for f in builder.funcs:
be.emitFunc(f, funcRetTypes, funcPtrTypes)
# C main wrapper
var hasMain = false
for f in module.funcs:
if f.name == "Main":
hasMain = true
break
if hasMain:
be.emitLine("/* C entry point wrapper */")
be.emitLine("extern int g_argc;")
be.emitLine("extern char** g_argv;")
be.emitLine("int main(int argc, char** argv) {")
be.emitLine(" g_argc = argc;")
be.emitLine(" g_argv = argv;")
be.emitLine(" return Main();")
be.emitLine("}")
return be.output
+795
View File
@@ -0,0 +1,795 @@
## LIR Lowering — HIR → LIR
## Converts the high-level HIR tree into flat, linear LIR instructions.
## Each HIR node kind lowers to 1-20 LIR instructions.
import std/[strutils, strformat, tables, sequtils]
import types, token, hir, lir
## Convert LirValue to C expression string (no % prefix)
proc lirValToC(v: LirValue): string =
case v.kind
of lvkTemp: v.strVal
of lvkVar: v.strVal
of lvkInt: $v.intVal
of lvkFloat: $v.floatVal
of lvkString: v.strVal
of lvkLabel: v.strVal
of lvkGlobal: v.strVal
of lvkField: v.strVal
of lvkVoid: ""
of lvkType: v.strVal
type
LowerToLirCtx* = object
builder*: LirBuilder
## Map HIR var names -> C type names (for alloca/load/store type info)
varTypes*: Table[string, string]
## Map HIR var names -> LirValue kind (lvkVar or lvkTemp)
varLirValues*: Table[string, LirValue]
## C types for function params / returns
funcRetType*: string
## Current source location for debug
currentFile*: string
## Loop end labels for break/continue
loopEndLabels*: seq[string]
loopStartLabels*: seq[string]
proc initLowerToLirCtx*(): LowerToLirCtx =
result = LowerToLirCtx(
builder: initLirBuilder(),
varTypes: initTable[string, string](),
varLirValues: initTable[string, LirValue](),
loopEndLabels: @[],
loopStartLabels: @[],
)
# ── Helpers ──
proc cEscape(s: string): string =
result = ""
for c in s:
case c
of '\\': result.add("\\\\")
of '"': result.add("\\\"")
of '\n': result.add("\\n")
of '\r': result.add("\\r")
of '\t': result.add("\\t")
of '\0': result.add("\\0")
else: result.add(c)
proc typeToCStr(typ: Type): string =
## Convert a Bux Type to a C type string.
if typ == nil: return "int"
case typ.kind
of tkVoid: return "void"
of tkBool, tkBool8, tkBool16, tkBool32: return "bool"
of tkChar8: return "char"
of tkChar16: return "char16_t"
of tkChar32: return "char32_t"
of tkStr: return "const char*"
of tkInt8: return "int8_t"
of tkInt16: return "int16_t"
of tkInt32: return "int32_t"
of tkInt64: return "int64_t"
of tkInt: return "int"
of tkUInt8: return "uint8_t"
of tkUInt16: return "uint16_t"
of tkUInt32: return "uint32_t"
of tkUInt64: return "uint64_t"
of tkUInt: return "unsigned int"
of tkFloat32: return "float"
of tkFloat64: return "double"
of tkPointer, tkRef, tkMutRef:
if typ.inner.len > 0:
return typeToCStr(typ.inner[0]) & "*"
return "void*"
of tkDynRef:
return typ.name & "_FatPtr"
of tkSlice:
let elem = if typ.inner.len > 0: typeToCStr(typ.inner[0]) else: "void"
return "Slice_" & elem.replace(" ", "_").replace("*", "Ptr")
of tkNamed:
case typ.name
of "String", "str": return "const char*"
of "int": return "int"
of "int8": return "int8_t"
of "int16": return "int16_t"
of "int32": return "int32_t"
of "int64": return "int64_t"
of "uint": return "unsigned int"
of "uint8": return "uint8_t"
of "uint16": return "uint16_t"
of "uint32": return "uint32_t"
of "uint64": return "uint64_t"
of "float32": return "float"
of "float64": return "double"
of "bool": return "bool"
else: return typ.name
of tkFunc:
if typ.inner.len == 0: return "void (*)(void)"
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
let ret = typeToCStr(typ.inner[^1])
return ret & " (*)(" & params & ")"
else: return "int"
proc hirTypeToC(ctx: var LowerToLirCtx, node: HirNode): string =
if node == nil: return "int"
result = typeToCStr(node.typ)
proc binOpToLir(op: TokenKind): LirKind =
case op
of tkPlus: lirAdd
of tkMinus: lirSub
of tkStar: lirMul
of tkSlash: lirDiv
of tkPercent: lirMod
of tkAmp: lirAnd
of tkPipe: lirOr
of tkCaret: lirXor
of tkShl: lirShl
of tkShr: lirShr
else: lirAdd
proc cmpOpToLir(op: TokenKind): LirKind =
case op
of tkEq: lirCmpEq
of tkNe: lirCmpNe
of tkLt: lirCmpLt
of tkLe: lirCmpLe
of tkGt: lirCmpGt
of tkGe: lirCmpGe
else: lirCmpEq
# ── Forward declarations ──
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode)
# ── Lowering: Expressions → LirValue ──
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
if node == nil: return lirInt(0)
template b: var LirBuilder = ctx.builder
case node.kind
# ── Literals ──
of hLit:
case node.litToken.kind
of tkBoolLiteral:
if node.litToken.text == "true": return lirInt(1)
else: return lirInt(0)
of tkStringLiteral:
var text = node.litToken.text
# Handle backtick strings
if text.len >= 2 and text[0] == '`' and text[text.len-1] == '`':
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
elif text.len >= 2 and text[0] == '"' and text[text.len-1] == '"':
# Strip c8" c16" c32" prefixes
if text.startsWith("c32\""):
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
elif text.startsWith("c16\""):
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
elif text.startsWith("c8\""):
text = "\"" & cEscape(text[3 ..< text.len-1]) & "\""
else:
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
elif text.len >= 2 and text[0] == '"':
text = "\"" & cEscape(text[1 ..< text.len]) & "\""
else:
text = "\"" & cEscape(text) & "\""
return lirStr(text)
of tkNull:
return lirInt(0)
else:
# Integer/float literal
return lirVar(node.litToken.text)
# ── Variable reference ──
of hVar:
let name = node.varName
if ctx.varLirValues.hasKey(name):
return ctx.varLirValues[name]
return lirVar(name)
# ── Alloca (address of local) ──
of hAlloca:
let cType = typeToCStr(node.allocaType)
let name = node.allocaName
if not ctx.varLirValues.hasKey(name):
ctx.varTypes[name] = cType
ctx.varLirValues[name] = lirVar(name)
b.emitAlloca(name, cType)
return lirVar("&" & name)
# ── Self ──
of hSelf:
return lirVar("self")
# ── Unary ──
of hUnary:
let operand = lowerExpr(ctx, node.unaryOperand)
case node.unaryOp
of tkMinus:
let t = b.freshTemp()
b.emitUnary(lirNeg, t, operand)
return t
of tkBang:
let t = b.freshTemp()
b.emitUnary(lirNot, t, operand)
return t
of tkTilde:
let t = b.freshTemp()
b.emitUnary(lirBNot, t, operand)
return t
of tkStar:
# Dereference: *ptr → load
let t = b.freshTemp()
b.emitLoad(t, operand)
return t
of tkAmp:
# Address of: &expr
# Optimize: &struct.field → fieldPtr (no temp copy)
# &array[i] → indexPtr (no temp copy)
if node.unaryOperand.kind == hLoad and node.unaryOperand.loadPtr != nil:
let ptrNode = node.unaryOperand.loadPtr
case ptrNode.kind
of hFieldPtr:
let base = lowerExpr(ctx, ptrNode.fieldPtrBase)
let baseTyp = ptrNode.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
if isPtr:
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}->{ptrNode.fieldName});")
else:
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}.{ptrNode.fieldName});")
return t
of hArrowField:
let base = lowerExpr(ctx, ptrNode.arrowFieldBase)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}->{ptrNode.arrowFieldName});")
return t
of hIndexPtr:
let base = lowerExpr(ctx, ptrNode.indexPtrBase)
let idx = lowerExpr(ctx, ptrNode.indexPtrIndex)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}[{lirValToC(idx)}]);")
return t
else: discard
let t = b.freshTemp()
b.emitAddrOf(t, operand)
return t
else:
return operand
# ── Binary ──
of hBinary:
let left = lowerExpr(ctx, node.binaryLeft)
let right = lowerExpr(ctx, node.binaryRight)
case node.binaryOp
of tkEq, tkNe, tkLt, tkLe, tkGt, tkGe:
let t = b.freshTemp()
b.emitCmp(cmpOpToLir(node.binaryOp), t, left, right)
return t
of tkAmpAmp, tkPipePipe:
# Logical and/or: lowered to branches for short-circuit evaluation
let t = b.freshTemp()
b.emitAlloca(t.strVal, "int")
let falseLbl = b.freshLabel("and_false")
let trueLbl = b.freshLabel("and_true")
let endLbl = b.freshLabel("and_end")
if node.binaryOp == tkAmpAmp:
# left && right: if !left goto false; if !right goto false; t=1; goto end; false: t=0; end:
b.emitJz(falseLbl, left)
b.emitJz(falseLbl, right)
b.emitMov(t, lirInt(1))
b.emitJmp(endLbl)
b.emitLabel(falseLbl)
b.emitMov(t, lirInt(0))
b.emitLabel(endLbl)
else:
# left || right: if left goto true; if right goto true; t=0; goto end; true: t=1; end:
b.emitJnz(trueLbl, left)
b.emitJnz(trueLbl, right)
b.emitMov(t, lirInt(0))
b.emitJmp(endLbl)
b.emitLabel(trueLbl)
b.emitMov(t, lirInt(1))
b.emitLabel(endLbl)
return t
else:
let t = b.freshTemp()
b.emitBinOp(binOpToLir(node.binaryOp), t, left, right)
return t
# ── Call ──
of hCall:
var args: seq[LirValue] = @[]
for arg in node.callArgs:
args.add(lowerExpr(ctx, arg))
let callee = node.callCallee
let t = b.freshTemp()
let cType = hirTypeToC(ctx, node)
if cType != "void" and cType != "":
b.emitAlloca(t.strVal, cType)
b.emitCall(t, callee, args)
return t
# ── CallIndirect ──
of hCallIndirect:
let callee = lowerExpr(ctx, node.callIndirectCallee)
var args: seq[LirValue] = @[callee]
for arg in node.callIndirectArgs:
args.add(lowerExpr(ctx, arg))
let t = b.freshTemp()
let cType = hirTypeToC(ctx, node)
if cType != "void" and cType != "":
b.emitAlloca(t.strVal, cType)
# Use lirCallIndirect: dst = (*fn_ptr)(args...)
b.emit(LirInstr(kind: lirCallIndirect, dst: t, src: callee, extra: args[1..^1]))
return t
# ── Field pointer expressions (return address) ──
# These return a typed pointer (void* for now, cast before deref)
of hFieldPtr:
let base = lowerExpr(ctx, node.fieldPtrBase)
let baseTyp = node.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
if isPtr:
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}->{node.fieldName});")
else:
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}.{node.fieldName});")
return t
of hFieldAccess:
let base = lowerExpr(ctx, node.fieldAccessBase)
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = {lirValToC(base)}.{node.fieldAccessName};")
return t
of hArrowField:
let base = lowerExpr(ctx, node.arrowFieldBase)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}->{node.arrowFieldName});")
return t
of hIndexPtr:
let base = lowerExpr(ctx, node.indexPtrBase)
let idx = lowerExpr(ctx, node.indexPtrIndex)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}[{lirValToC(idx)}]);")
return t
# ── Load ──
of hLoad:
# Load through a pointer or field access
# Optimize common patterns: load(field_ptr) → direct field access
if node.loadPtr != nil and node.loadPtr.kind == hArrowField:
let base = lowerExpr(ctx, node.loadPtr.arrowFieldBase)
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = {lirValToC(base)}->{node.loadPtr.arrowFieldName};")
return t
if node.loadPtr != nil and node.loadPtr.kind == hFieldPtr:
let base = lowerExpr(ctx, node.loadPtr.fieldPtrBase)
let baseTyp = node.loadPtr.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
if isPtr:
b.emitRawC(&"{t.strVal} = {lirValToC(base)}->{node.loadPtr.fieldName};")
else:
b.emitRawC(&"{t.strVal} = {lirValToC(base)}.{node.loadPtr.fieldName};")
return t
if node.loadPtr != nil and node.loadPtr.kind == hIndexPtr:
let base = lowerExpr(ctx, node.loadPtr.indexPtrBase)
let idx = lowerExpr(ctx, node.loadPtr.indexPtrIndex)
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = {lirValToC(base)}[{lirValToC(idx)}];")
return t
# Generic: dereference pointer
let ptrVal = lowerExpr(ctx, node.loadPtr)
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = *({cType}*){lirValToC(ptrVal)};")
return t
# ── Slice Index ──
of hSliceIndex:
let base = lowerExpr(ctx, node.sliceIndexBase)
let idx = lowerExpr(ctx, node.sliceIndexIndex)
let t = b.freshTemp()
# Emit: base.data[idx] (with optional bounds check)
if node.sliceIndexBoundsCheck:
b.emitRawC(&"bux_bounds_check((size_t)({lirValToC(idx)}), ({lirValToC(base)}).len)")
b.emit(LirInstr(kind: lirLoad, dst: t, src: base, src2: idx))
return t
# ── Cast ──
of hCast:
let operand = lowerExpr(ctx, node.castOperand)
let targetCType = typeToCStr(node.castType)
let t = b.freshTemp()
b.emitCast(t, operand, targetCType)
return t
# ── SizeOf ──
of hSizeOf:
let ctype = typeToCStr(node.sizeOfType)
b.emit(LirInstr(kind: lirRawC, src: lirStr(&"/* sizeof({ctype}) */")))
return lirVar(&"sizeof({ctype})")
# ── Spawn ──
of hSpawn:
if node.spawnAsync:
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitCall(t, "bux_async_spawn", @[lirGlobal(node.spawnCallee)])
return t
else:
var args: seq[LirValue] = @[]
if node.spawnArgs.len > 0:
args.add(lowerExpr(ctx, node.spawnArgs[0]))
else:
args.add(lirInt(0))
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitCall(t, "bux_task_spawn", @[lirGlobal(node.spawnCallee)] & args)
return t
# ── DynRef (trait object) ──
of hDynRef:
let data = lowerExpr(ctx, node.dynRefData)
let t = b.freshTemp()
let fatPtrType = node.dynRefInterface & "_FatPtr"
b.emitRawC(&"{fatPtrType} {t.strVal};")
b.emitMov(lirVar(t.strVal & ".data"), data)
b.emitMov(lirVar(t.strVal & ".vtable"), lirGlobal(node.dynRefConcreteType & "_" & node.dynRefInterface & "_VTable"))
return t
# ── DynCall ──
of hDynCall:
let receiver = lowerExpr(ctx, node.dynCallReceiver)
var args: seq[LirValue] = @[receiver]
for i in 1 ..< node.dynCallArgs.len:
args.add(lowerExpr(ctx, node.dynCallArgs[i]))
let t = b.freshTemp()
b.emitRawC(&"{t.strVal} = {lirValToC(receiver)}.vtable->{node.dynCallMethod}({args.mapIt($it).join(\", \")});")
return t
# ── StructInit ──
of hStructInit:
var fields: seq[tuple[name: string, val: LirValue]] = @[]
for f in node.structInitFields:
fields.add((f.name, lowerExpr(ctx, f.value)))
let t = b.freshTemp()
b.emitStructInit(t, node.structInitName, fields)
return t
# ── SliceInit ──
of hSliceInit:
let t = b.freshTemp()
let elemType = if node.typ.inner.len > 0: typeToCStr(node.typ.inner[0]) else: "void"
var elems: seq[LirValue] = @[]
for e in node.sliceInitElements:
elems.add(lowerExpr(ctx, e))
# Create a temporary array, then wrap in slice
let arrTmp = b.freshTemp()
b.emitRawC(&"{elemType} {arrTmp.strVal}[] = {{{elems.mapIt($it).join(\", \")}}};")
b.emitSliceInit(t, elemType, arrTmp, lirInt(node.sliceInitLen))
return t
# ── TupleInit ──
of hTupleInit:
var elems: seq[LirValue] = @[]
for e in node.tupleInitElements:
elems.add(lowerExpr(ctx, e))
let t = b.freshTemp()
b.emitRawC(&"/* tuple */ {t.strVal} = {{{elems.mapIt($it).join(\", \")}}};")
return t
# ── If expression (ternary) ──
of hIf:
if node.ifThen.kind != hBlock and node.ifElse != nil:
# Simple ternary
let cond = lowerExpr(ctx, node.ifCond)
let thenVal = lowerExpr(ctx, node.ifThen)
let elseVal = lowerExpr(ctx, node.ifElse)
let t = b.freshTemp()
b.emitSelect(t, cond, thenVal, elseVal)
return t
else:
# Complex if — fallback to block lowering
# This shouldn't happen if lowering is done right, but handle gracefully
return lirInt(0)
# ── Block expression (returns last expr) ──
of hBlock:
for stmt in node.blockStmts:
lowerStmt(ctx, stmt)
if node.blockExpr != nil:
return lowerExpr(ctx, node.blockExpr)
return lirVoid()
# ── Match (lowered by hir_lower already, but handle if present) ──
of hMatch:
# Should have been lowered by hir_lower.nim already
return lirInt(0)
else:
# Fallback for unhandled expression kinds
b.emitComment(&"unhandled expr kind: {node.kind}")
return lirInt(0)
# ── Build C lvalue string for direct field/index assignment ──
proc buildLval(ctx: var LowerToLirCtx, n: HirNode): string =
case n.kind
of hLoad:
if n.loadPtr != nil:
return buildLval(ctx, n.loadPtr)
else:
let v = lowerExpr(ctx, n)
return lirValToC(v)
of hVar:
return n.varName
of hSelf:
return "self"
of hFieldPtr:
let baseStr = buildLval(ctx, n.fieldPtrBase)
let baseTyp = n.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let sep = if isPtr: "->" else: "."
return baseStr & sep & n.fieldName
of hArrowField:
let baseStr = buildLval(ctx, n.arrowFieldBase)
return baseStr & "->" & n.arrowFieldName
of hFieldAccess:
let baseStr = buildLval(ctx, n.fieldAccessBase)
return baseStr & "." & n.fieldAccessName
of hIndexPtr:
let baseStr = buildLval(ctx, n.indexPtrBase)
let idx = lowerExpr(ctx, n.indexPtrIndex)
return baseStr & "[" & lirValToC(idx) & "]"
else:
let v = lowerExpr(ctx, n)
return lirValToC(v)
# ── Lowering: Statements → void ──
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
if node == nil: return
template b: var LirBuilder = ctx.builder
case node.kind
# ── Return ──
of hReturn:
if node.returnValue != nil:
let val = lowerExpr(ctx, node.returnValue)
b.emitRet(val)
else:
b.emitRet()
# ── If statement ──
of hIf:
# Lower to: cond = lower(ifCond); jz else_label, cond
# lower(ifThen); jmp end_label
# else_label: lower(ifElse); end_label:
let cond = lowerExpr(ctx, node.ifCond)
let elseLbl = b.freshLabel("else")
let endLbl = b.freshLabel("endif")
if node.ifElse != nil:
b.emitJz(elseLbl, cond)
lowerStmt(ctx, node.ifThen)
b.emitJmp(endLbl)
b.emitLabel(elseLbl)
lowerStmt(ctx, node.ifElse)
b.emitLabel(endLbl)
else:
b.emitJz(endLbl, cond)
lowerStmt(ctx, node.ifThen)
b.emitLabel(endLbl)
# ── While statement ──
of hWhile:
let startLbl = b.freshLabel("while")
let endLbl = b.freshLabel("wend")
ctx.loopStartLabels.add(startLbl.strVal)
ctx.loopEndLabels.add(endLbl.strVal)
b.emitLabel(startLbl)
let cond = lowerExpr(ctx, node.whileCond)
b.emitJz(endLbl, cond)
lowerStmt(ctx, node.whileBody)
b.emitJmp(startLbl)
b.emitLabel(endLbl)
discard ctx.loopStartLabels.pop()
discard ctx.loopEndLabels.pop()
# ── Loop (infinite) ──
of hLoop:
let startLbl = b.freshLabel("loop")
let endLbl = b.freshLabel("lend")
ctx.loopStartLabels.add(startLbl.strVal)
ctx.loopEndLabels.add(endLbl.strVal)
b.emitLabel(startLbl)
lowerStmt(ctx, node.loopBody)
b.emitJmp(startLbl)
b.emitLabel(endLbl)
discard ctx.loopStartLabels.pop()
discard ctx.loopEndLabels.pop()
# ── Break ──
of hBreak:
if ctx.loopEndLabels.len > 0:
b.emitJmp(lirLabel(ctx.loopEndLabels[^1]))
else:
b.emitRawC("break;")
# ── Continue ──
of hContinue:
if ctx.loopStartLabels.len > 0:
b.emitJmp(lirLabel(ctx.loopStartLabels[^1]))
else:
b.emitRawC("continue;")
# ── Alloca ──
of hAlloca:
let cType = typeToCStr(node.allocaType)
let name = node.allocaName
ctx.varTypes[name] = cType
ctx.varLirValues[name] = lirVar(name)
b.emitAlloca(name, cType)
# ── Store ──
of hStore:
# If storing to a simple variable, use mov (direct assignment)
if node.storePtr.kind == hVar:
let val = lowerExpr(ctx, node.storeValue)
b.emitMov(lirVar(node.storePtr.varName), val)
else:
let ptrVal = lowerExpr(ctx, node.storePtr)
let val = lowerExpr(ctx, node.storeValue)
# ptrVal is a void* address; cast and store
let valCType = hirTypeToC(ctx, node.storeValue)
b.emitRawC(&"*({valCType}*){lirValToC(ptrVal)} = {lirValToC(val)};")
# ── Assign ──
of hAssign:
let value = lowerExpr(ctx, node.assignValue)
case node.assignOp
of tkAssign:
case node.assignTarget.kind
of hFieldPtr:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hFieldAccess:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hArrowField:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hIndexPtr:
let base = lowerExpr(ctx, node.assignTarget.indexPtrBase)
let idx = lowerExpr(ctx, node.assignTarget.indexPtrIndex)
b.emit(LirInstr(kind: lirStore, src: value, src2: base, dst: idx))
of hLoad:
if node.assignTarget.loadPtr != nil:
let ptrNode = node.assignTarget.loadPtr
case ptrNode.kind
of hIndexPtr:
let base = lowerExpr(ctx, ptrNode.indexPtrBase)
let idx = lowerExpr(ctx, ptrNode.indexPtrIndex)
b.emit(LirInstr(kind: lirStore, src: value, src2: base, dst: idx))
of hFieldPtr:
let lval = buildLval(ctx, ptrNode)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hArrowField:
let lval = buildLval(ctx, ptrNode)
b.emitRawC(&"{lval} = {lirValToC(value)};")
else:
let ptrVal = lowerExpr(ctx, ptrNode)
let valCType = hirTypeToC(ctx, node.assignValue)
b.emitRawC(&"*({valCType}*){lirValToC(ptrVal)} = {lirValToC(value)};")
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
of tkPlusAssign:
let target = lowerExpr(ctx, node.assignTarget)
let t = b.freshTemp()
b.emitBinOp(lirAdd, t, target, value)
b.emit(LirInstr(kind: lirStore, src: t, src2: target))
of tkMinusAssign:
let target = lowerExpr(ctx, node.assignTarget)
let t = b.freshTemp()
b.emitBinOp(lirSub, t, target, value)
b.emit(LirInstr(kind: lirStore, src: t, src2: target))
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
# ── Call statement (void return) ──
of hCall:
var args: seq[LirValue] = @[]
for arg in node.callArgs:
args.add(lowerExpr(ctx, arg))
b.emitCallVoid(node.callCallee, args)
# ── CallIndirect statement ──
of hCallIndirect:
let callee = lowerExpr(ctx, node.callIndirectCallee)
var args: seq[LirValue] = @[]
for arg in node.callIndirectArgs:
args.add(lowerExpr(ctx, arg))
b.emit(LirInstr(kind: lirCallIndirect, src: callee, extra: args))
# ── Block ──
of hBlock:
if node.isScope:
b.emitRawC("{")
for stmt in node.blockStmts:
lowerStmt(ctx, stmt)
if node.blockExpr != nil:
# If block is an expression, result is unused at statement level
discard lowerExpr(ctx, node.blockExpr)
if node.isScope:
b.emitRawC("}")
# ── Emit (inline C) ──
of hEmit:
b.emitRawC(node.emitCode)
# ── Expression statement ──
else:
# Expression evaluated for side effects; temp is unused
discard lowerExpr(ctx, node)
# ── Module-level lowering ──
proc lowerModuleToLir*(hirMod: HirModule): LirBuilder =
## Convert a full HIR module into LIR functions.
var ctx = initLowerToLirCtx()
for f in hirMod.funcs:
var params: seq[tuple[name: string, cType: string]] = @[]
for p in f.params:
let ct = typeToCStr(p.typ)
params.add((p.name, ct))
ctx.varTypes[p.name] = ct
ctx.varLirValues[p.name] = lirVar(p.name)
let retCT = if f.retType != nil: typeToCStr(f.retType) else: "void"
ctx.funcRetType = retCT
ctx.builder.beginFunc(f.name, params, retCT, f.isPublic)
if f.body != nil:
if f.body.kind == hBlock:
for stmt in f.body.blockStmts:
lowerStmt(ctx, stmt)
if f.body.blockExpr != nil and f.retType != nil and f.retType.kind != tkVoid:
let val = lowerExpr(ctx, f.body.blockExpr)
ctx.builder.emitRet(val)
else:
lowerStmt(ctx, f.body)
ctx.builder.endFunc()
return ctx.builder
-1
View File
@@ -90,7 +90,6 @@ proc parseInlineTable(s: string): OrderedTableRef[string, TomlValue] =
if content[i] == '{': inc braceCount
elif content[i] == '}': dec braceCount
inc i
let val = content[valStart ..< i]
result[key] = TomlValue(kind: tvkInlineTable, inlineVal: newOrderedTable[string, TomlValue]())
else:
while i < content.len and content[i] notin {',', ' ', '\t'}:
+191 -11
View File
@@ -1,5 +1,5 @@
import std/strutils
import token, source_location, ast
import token, source_location, ast, lexer
type
ParserDiagnosticSeverity* = enum
@@ -48,6 +48,12 @@ proc advance(p: var Parser): Token =
if p.pos < p.tokens.len:
inc p.pos
proc hasErrors*(p: Parser): bool =
for d in p.diagnostics:
if d.severity == pdsError:
return true
return false
proc check(p: Parser, kind: TokenKind): bool =
p.peek() == kind
@@ -56,12 +62,6 @@ proc checkAny(p: Parser, kinds: openArray[TokenKind]): bool =
if p.peek() == k: return true
return false
proc match(p: var Parser, kind: TokenKind): bool =
if p.check(kind):
discard p.advance()
return true
return false
proc isTypeArgListAhead(p: Parser): bool =
## Lookahead to determine if '<' starts a type argument list.
## Returns true if we can find a matching '>' before EOF, '{', or ';'.
@@ -169,6 +169,7 @@ type
callConv*: CallingConvention
targetOs*: string
checked*: bool ## @[Checked] — enable borrow checking
shared*: bool ## @[Shared] — mark function as thread-safe
proc parseAttrs(p: var Parser): ParsedAttrs =
while p.check(tkAt):
@@ -177,6 +178,8 @@ proc parseAttrs(p: var Parser): ParsedAttrs =
let name = p.expect(tkIdent, "expected attribute name").text
if name == "Checked":
result.checked = true
elif name == "Shared":
result.shared = true
elif name == "Import":
discard p.expect(tkLParen, "expected '('")
let key = p.expect(tkIdent, "expected attribute key").text
@@ -201,6 +204,10 @@ proc parseBaseType(p: var Parser): TypeExpr =
var typeArgs: seq[TypeExpr] = @[]
discard p.advance()
while not p.check(tkGt) and not p.isAtEnd:
while p.check(tkNewLine):
discard p.advance()
if p.check(tkGt) or p.isAtEnd:
break
typeArgs.add(p.parseType())
if p.check(tkComma):
discard p.advance()
@@ -235,6 +242,20 @@ proc parseBaseType(p: var Parser): TypeExpr =
discard p.advance()
discard p.expect(tkRParen, "expected ')' to close tuple type")
return TypeExpr(kind: tekTuple, loc: loc, tupleElements: elems)
of tkFunc:
discard p.advance() # func
discard p.expect(tkLParen, "expected '(' after 'func'")
var params: seq[TypeExpr] = @[]
while not p.check(tkRParen) and not p.isAtEnd:
params.add(p.parseType())
if p.check(tkComma):
discard p.advance()
discard p.expect(tkRParen, "expected ')' to close func param types")
var ret: TypeExpr = nil
if p.check(tkArrow):
discard p.advance()
ret = p.parseType()
return TypeExpr(kind: tekFunc, loc: loc, funcParams: params, funcRet: ret)
else:
p.emitError(loc, "expected type expression")
return TypeExpr(kind: tekNamed, loc: loc, typeName: "")
@@ -307,6 +328,10 @@ proc parsePrimaryPattern(p: var Parser): Pattern =
discard p.advance()
var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
while not p.check(tkRBrace) and not p.isAtEnd:
while p.check(tkNewLine):
discard p.advance()
if p.check(tkRBrace) or p.isAtEnd:
break
let fieldName = p.expect(tkIdent, "expected field name in struct pattern").text
discard p.expect(tkColon, "expected ':' after field name in pattern")
fields.add((fieldName, p.parsePattern()))
@@ -380,11 +405,77 @@ proc parseAssign(p: var Parser): Expr
proc parseExpr(p: var Parser): Expr =
p.parseAssign()
proc parseStringInterpolation(p: var Parser, tok: Token): Expr =
## Parse a string literal that contains {expr} interpolations.
let text = tok.text
# Only f"..." strings support interpolation; plain "...", backtick, and c8/c16/c32 are raw.
if text.len < 3 or text[0] != 'f' or text[1] != '"' or text[^1] != '"':
return newLiteralExpr(tok)
let inner = text[2 ..< ^1] # strip f" ... "
var texts: seq[string] = @[]
var exprs: seq[Expr] = @[]
var i = 0
var currentText = ""
while i < inner.len:
if inner[i] == '\\' and i + 1 < inner.len:
if inner[i + 1] == '{':
currentText.add('{')
i += 2
continue
elif inner[i + 1] == '}':
currentText.add('}')
i += 2
continue
elif inner[i] == '{':
texts.add(currentText)
currentText = ""
var j = i + 1
var depth = 1
while j < inner.len and depth > 0:
if inner[j] == '{': depth += 1
elif inner[j] == '}': depth -= 1
j += 1
if depth != 0:
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
message: "unmatched '{' in string interpolation"))
return newLiteralExpr(tok)
let exprStr = inner[i + 1 ..< j - 1]
if exprStr.len == 0:
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
message: "empty interpolation {}"))
return newLiteralExpr(tok)
let lexRes = tokenize(exprStr, p.sourceName & "<interp>")
if lexRes.hasErrors:
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
message: "invalid expression in string interpolation: " & exprStr))
return newLiteralExpr(tok)
var subParser = initParser(lexRes.tokens, p.sourceName & "<interp>")
let expr = subParser.parseExpr()
if subParser.hasErrors:
for d in subParser.diagnostics:
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
message: "interpolation parse error: " & d.message))
return newLiteralExpr(tok)
exprs.add(expr)
i = j
continue
currentText.add(inner[i])
i += 1
texts.add(currentText)
if exprs.len == 0:
return newLiteralExpr(tok)
return newStringInterpExpr(texts, exprs, tok.loc)
proc parsePrimary(p: var Parser): Expr =
while p.check(tkNewLine):
discard p.advance()
let loc = p.currentLoc
case p.peek()
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
return newLiteralExpr(p.advance())
let tok = p.advance()
if tok.kind == tkStringLiteral:
return parseStringInterpolation(p, tok)
return newLiteralExpr(tok)
of tkSelf:
discard p.advance()
return Expr(kind: ekSelf, loc: loc)
@@ -490,6 +581,26 @@ proc parsePrimary(p: var Parser): Expr =
of tkNull:
discard p.advance()
return newLiteralExpr(Token(kind: tkNull, text: "null", loc: loc))
of tkPipe:
# Closure: |params| -> Ret { body }
discard p.advance() # |
var params: seq[Param] = @[]
while not p.check(tkPipe) and not p.isAtEnd:
let nameTok = p.expect(tkIdent, "expected parameter name in closure")
discard p.expect(tkColon, "expected ':' in closure parameter")
let ptype = p.parseType()
params.add(Param(name: nameTok.text, ptype: ptype))
if p.check(tkComma):
discard p.advance()
else:
break
discard p.expect(tkPipe, "expected '|' to close closure params")
var retType: TypeExpr = nil
if p.check(tkArrow):
discard p.advance() # ->
retType = p.parseType()
let body = p.parseBlock()
return Expr(kind: ekClosure, loc: loc, exprClosureParams: params, exprClosureBody: body, exprClosureReturnType: retType)
else:
p.emitError(loc, "expected expression")
discard p.advance()
@@ -498,23 +609,35 @@ proc parsePrimary(p: var Parser): Expr =
proc parsePostfix(p: var Parser): Expr =
var left = p.parsePrimary()
while true:
while p.check(tkNewLine):
discard p.advance()
let loc = p.currentLoc
case p.peek()
of tkLParen:
# Call expression
discard p.advance()
var args: seq[Expr] = @[]
var argNames: seq[string] = @[]
while not p.check(tkRParen) and not p.isAtEnd:
if p.check(tkDotDotDot):
discard p.advance()
let operand = p.parseExpr()
args.add(Expr(kind: ekSpread, loc: operand.loc, exprSpreadOperand: operand))
argNames.add("")
elif p.peek() == tkIdent and p.peek(1) == tkColon:
# Named argument: name: value
let nameTok = p.advance()
discard p.advance() # :
let value = p.parseExpr()
args.add(value)
argNames.add(nameTok.text)
else:
args.add(p.parseExpr())
argNames.add("")
if p.check(tkComma):
discard p.advance()
discard p.expect(tkRParen, "expected ')' to close call")
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args)
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args, exprCallArgNames: argNames)
of tkLt:
# Generic type arguments: Max<int>(10, 20)
# Only treat '<' as generic args if lookahead confirms a matching '>'
@@ -594,8 +717,18 @@ proc parsePostfix(p: var Parser): Expr =
return left
proc parseUnary(p: var Parser): Expr =
while p.check(tkNewLine):
discard p.advance()
let loc = p.currentLoc
case p.peek()
of tkBorrow:
discard p.advance() # borrow
discard p.expect(tkAmp, "expected '&' after 'borrow'")
var isMut = p.check(tkMut)
if isMut:
discard p.advance() # mut
let operand = p.parseUnary() # parse the moved value
return Expr(kind: ekBorrow, loc: loc, exprBorrowOperand: operand, exprBorrowMutable: isMut)
of tkBang, tkMinus, tkTilde, tkStar, tkAmp:
let op = p.advance().kind
if op == tkAmp and p.check(tkMut):
@@ -649,7 +782,6 @@ proc parseShift(p: var Parser): Expr =
return left
proc parseCast(p: var Parser): Expr =
let loc = p.currentLoc
var left = p.parseShift()
# 'as' and 'is' are handled in postfix for chaining
return left
@@ -876,6 +1008,37 @@ proc parseStmt(p: var Parser): Stmt =
discard p.advance()
discard p.expect(tkRBrace, "expected '}' to close match")
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms))
of tkSwitch:
discard p.advance()
p.structInitAllowed = false
let subject = p.parseExpr()
p.structInitAllowed = true
while p.check(tkNewLine):
discard p.advance()
discard p.expect(tkLBrace, "expected '{' to start switch body")
var cases: seq[SwitchCase] = @[]
var defaultBlock: Block = nil
while not p.check(tkRBrace) and not p.isAtEnd:
while p.check(tkNewLine):
discard p.advance()
if p.check(tkRBrace) or p.isAtEnd:
break
if p.check(tkDefault):
discard p.advance()
discard p.expect(tkColon, "expected ':' after default")
defaultBlock = Block(loc: p.currentLoc, stmts: @[p.parseStmt()])
continue
if p.check(tkCase):
discard p.advance()
let caseVal = p.parseExpr()
discard p.expect(tkColon, "expected ':' after case value")
let caseBody = Block(loc: p.currentLoc, stmts: @[p.parseStmt()])
cases.add(SwitchCase(loc: caseVal.loc, caseValue: caseVal, caseBody: caseBody))
continue
# Unknown token in switch body — skip
discard p.advance()
discard p.expect(tkRBrace, "expected '}' to close switch")
return Stmt(kind: skSwitch, loc: loc, stmtSwitchExpr: subject, stmtSwitchCases: cases, stmtSwitchDefault: defaultBlock)
of tkReturn:
discard p.advance()
var val: Expr = nil
@@ -934,6 +1097,12 @@ proc parseStmt(p: var Parser): Stmt =
# No-op: emit literal 0 as expression statement
let zeroTok = Token(kind: tkIntLiteral, text: "0", loc: loc)
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekLiteral, loc: loc, exprLit: zeroTok))
of tkDefer:
discard p.advance()
let expr = p.parseExpr()
if p.check(tkSemicolon):
discard p.advance()
return Stmt(kind: skDefer, loc: loc, stmtDeferBody: expr)
of tkFunc, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule,
tkImport, tkConst, tkType, tkExtern, tkPub:
# Local declaration
@@ -954,6 +1123,10 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] =
if p.check(tkLt):
discard p.advance()
while not p.check(tkGt) and not p.isAtEnd:
while p.check(tkNewLine):
discard p.advance()
if p.check(tkGt) or p.isAtEnd:
break
var name = ""
var isLifetime = false
if p.check(tkIdent):
@@ -986,8 +1159,11 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] =
proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] =
discard p.expect(tkLParen, "expected '('")
while not p.check(tkRParen) and not p.isAtEnd:
while p.check(tkNewLine):
discard p.advance()
if p.check(tkRParen) or p.isAtEnd:
break
let loc = p.currentLoc
var isVar = false
if allowVariadic and p.check(tkDotDotDot):
discard p.advance()
let nameTok = p.at
@@ -1250,6 +1426,10 @@ proc parseUseDecl(p: var Parser, attrs: ParsedAttrs): Decl =
if p.check(tkLBrace):
discard p.advance()
while not p.check(tkRBrace) and not p.isAtEnd:
while p.check(tkNewLine):
discard p.advance()
if p.check(tkRBrace) or p.isAtEnd:
break
names.add(p.expect(tkIdent, "expected name in multi-import").text)
if p.check(tkComma):
discard p.advance()
+10
View File
@@ -44,3 +44,13 @@ proc lookupLocal*(scope: Scope, name: string): Symbol =
if scope.table.hasKey(name):
return scope.table[name]
return nil
proc lookupUpTo*(scope: Scope, name: string, limit: Scope): Symbol =
var cur = scope
while cur != nil:
if cur.table.hasKey(name):
return cur.table[name]
if cur == limit:
break
cur = cur.parent
return nil
+319 -51
View File
@@ -44,6 +44,10 @@ type
checkedFunc*: bool ## true inside @[Checked] function
currentFuncIsAsync*: bool ## true inside async func
movedVars*: seq[string] ## variables moved in current checked function
currentRetType*: Type ## return type of the function being checked
closureDepth*: int ## nesting depth inside closures
currentClosureExpr*: Expr ## current closure being analyzed
closureScope*: Scope ## scope at which the current closure was entered
# ---------------------------------------------------------------------------
# Helpers
@@ -91,9 +95,6 @@ proc unescapeStringLiteral*(s: string): string =
proc emitError(sema: var Sema, loc: SourceLocation, message: string) =
sema.diagnostics.add(SemaDiagnostic(severity: sdsError, loc: loc, message: message))
proc emitWarning(sema: var Sema, loc: SourceLocation, message: string) =
sema.diagnostics.add(SemaDiagnostic(severity: sdsWarning, loc: loc, message: message))
proc hasErrors*(res: SemaResult): bool =
for d in res.diagnostics:
if d.severity == sdsError:
@@ -126,10 +127,14 @@ proc typeExprReferencesTypeParam(te: TypeExpr, name: string): bool =
of tekTuple:
for elem in te.tupleElements:
if typeExprReferencesTypeParam(elem, name): return true
of tekFunc:
for p in te.funcParams:
if typeExprReferencesTypeParam(p, name): return true
return typeExprReferencesTypeParam(te.funcRet, name)
of tekSelf:
return false
proc typeToTypeExpr(t: Type): TypeExpr =
proc typeToTypeExpr*(t: Type): TypeExpr =
## Convert a resolved Type back to a TypeExpr for storage in inferred type args.
case t.kind
of tkInt: TypeExpr(kind: tekNamed, typeName: "int")
@@ -146,7 +151,11 @@ proc typeToTypeExpr(t: Type): TypeExpr =
of tkFloat64: TypeExpr(kind: tekNamed, typeName: "float64")
of tkBool: TypeExpr(kind: tekNamed, typeName: "bool")
of tkStr: TypeExpr(kind: tekNamed, typeName: "String")
of tkNamed: TypeExpr(kind: tekNamed, typeName: t.name)
of tkNamed:
var args: seq[TypeExpr] = @[]
for a in t.inner:
args.add(typeToTypeExpr(a))
return TypeExpr(kind: tekNamed, typeName: t.name, typeArgs: args)
of tkPointer:
if t.inner.len > 0:
TypeExpr(kind: tekPointer, refLifetime: "", pointerPointee: typeToTypeExpr(t.inner[0]))
@@ -165,6 +174,45 @@ proc typeToTypeExpr(t: Type): TypeExpr =
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
else: TypeExpr(kind: tekNamed, typeName: t.toString)
proc substituteTypeInType(sema: var Sema, t: Type, subst: Table[string, Type]): Type =
## Recursively substitute type parameters in a resolved Type.
if t == nil:
return makeUnknown()
case t.kind
of tkTypeParam:
if subst.hasKey(t.name):
return subst[t.name]
return t
of tkPointer, tkRef, tkMutRef:
if t.inner.len > 0:
return Type(kind: t.kind, inner: @[sema.substituteTypeInType(t.inner[0], subst)])
return t
of tkSlice, tkRange:
if t.inner.len > 0:
return Type(kind: t.kind, inner: @[sema.substituteTypeInType(t.inner[0], subst)])
return t
of tkTuple:
var elems: seq[Type] = @[]
for e in t.inner:
elems.add(sema.substituteTypeInType(e, subst))
return makeTuple(elems)
of tkFunc:
var inner: seq[Type] = @[]
for it in t.inner:
inner.add(sema.substituteTypeInType(it, subst))
return Type(kind: tkFunc, inner: inner)
of tkNamed:
if subst.hasKey(t.name):
return subst[t.name]
if t.inner.len > 0:
var args: seq[Type] = @[]
for a in t.inner:
args.add(sema.substituteTypeInType(a, subst))
return Type(kind: tkNamed, name: t.name, inner: args)
return t
else:
return t
proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
loc: SourceLocation): seq[TypeExpr] =
## Infer type arguments from argument types for a generic function call.
@@ -252,6 +300,11 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
of "float64": return makeFloat64()
of "float": return makeFloat64()
else:
if te.typeArgs.len > 0:
var args: seq[Type] = @[]
for arg in te.typeArgs:
args.add(sema.resolveType(arg))
return Type(kind: tkNamed, name: name, inner: args)
if sema.typeTable.hasKey(name):
return sema.typeTable[name]
return makeNamed(name)
@@ -278,6 +331,12 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
return makeTuple(elems)
of tekSelf:
return makeNamed("self")
of tekFunc:
var params: seq[Type] = @[]
for p in te.funcParams:
params.add(sema.resolveType(p))
let ret = if te.funcRet != nil: sema.resolveType(te.funcRet) else: makeVoid()
return makeFunc(params, ret)
# ---------------------------------------------------------------------------
# First pass: collect global symbols
@@ -535,10 +594,17 @@ proc collectGlobals*(sema: var Sema) =
if not sema.globalScope.define(sym):
sema.emitError(decl.loc, &"duplicate symbol '{decl.declEnumName}'")
sema.typeTable[decl.declEnumName] = t
# Check if algebraic or simple enum
var hasData = false
for variant in decl.declEnumVariants:
if variant.fields.len > 0 or variant.namedFields.len > 0:
hasData = true
break
# For algebraic enums, add variant constants with _Tag type
# For simple enums, variant constants have the enum type itself
for variant in decl.declEnumVariants:
let variantName = decl.declEnumName & "_" & variant.name
let variantType = makeNamed(decl.declEnumName & "_Tag")
let variantType = if hasData: makeNamed(decl.declEnumName & "_Tag") else: makeNamed(decl.declEnumName)
let variantSym = Symbol(kind: skConst, name: variantName, typ: variantType,
decl: decl, isPublic: decl.isPublic)
discard sema.globalScope.define(variantSym)
@@ -563,24 +629,8 @@ proc collectGlobals*(sema: var Sema) =
sema.emitError(decl.loc, &"duplicate symbol '{decl.declAliasName}'")
sema.typeTable[decl.declAliasName] = t
of dkUse:
# Imports: register imported names into scope
if decl.declUsePath.len > 0:
case decl.declUseKind
of ukMulti:
for name in decl.declUseNames:
if sema.globalScope.lookup(name) == nil:
let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true)
discard sema.globalScope.define(sym)
of ukGlob:
let name = decl.declUsePath[^1]
if sema.globalScope.lookup(name) == nil:
let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true)
discard sema.globalScope.define(sym)
of ukSingle:
let name = decl.declUsePath[^1]
if sema.globalScope.lookup(name) == nil:
let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true)
discard sema.globalScope.define(sym)
# Imports handled in second pass after all declarations are registered
discard
of dkInterface:
# Register interface for conformance checking
sema.interfaceTable[decl.declInterfaceName] = decl
@@ -641,6 +691,26 @@ proc collectGlobals*(sema: var Sema) =
for decl in sema.module.items:
if decl.kind == dkConst:
discard sema.constFoldConstDecl(decl)
# Third pass: register imports after all real declarations are known
for decl in sema.module.items:
if decl.kind == dkUse:
if decl.declUsePath.len > 0:
case decl.declUseKind
of ukMulti:
for name in decl.declUseNames:
if sema.globalScope.lookup(name) == nil:
let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true)
discard sema.globalScope.define(sym)
of ukGlob:
let name = decl.declUsePath[^1]
if sema.globalScope.lookup(name) == nil:
let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true)
discard sema.globalScope.define(sym)
of ukSingle:
let name = decl.declUsePath[^1]
if sema.globalScope.lookup(name) == nil:
let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true)
discard sema.globalScope.define(sym)
# ---------------------------------------------------------------------------
# Expression type checking
@@ -714,6 +784,63 @@ proc checkExprList(sema: var Sema, exprs: seq[Expr], scope: Scope): seq[Type] =
for e in exprs:
result.add(sema.checkExpr(e, scope))
proc resolveCallArgs(sema: var Sema, expr: Expr, calleeDecl: Decl, scope: Scope) =
## Reorder named args and inject defaults for missing positional args.
if expr.kind != ekCall or calleeDecl == nil or calleeDecl.kind != dkFunc:
return
let params = calleeDecl.declFuncParams
let providedArgs = expr.exprCallArgs
let providedNames = expr.exprCallArgNames
if providedNames.len == 0:
# All positional — just inject defaults for trailing missing args
if providedArgs.len < params.len:
var newArgs = providedArgs
var newNames = providedNames
for i in providedArgs.len ..< params.len:
if params[i].defaultValue != nil:
newArgs.add(params[i].defaultValue)
newNames.add("")
else:
sema.emitError(expr.loc, &"missing argument for parameter '{params[i].name}'")
break
expr.exprCallArgs = newArgs
expr.exprCallArgNames = newNames
return
# Named args present
var newArgs: seq[Expr] = @[]
var newNames: seq[string] = @[]
var usedNamed = false
var namedArgMap: Table[string, Expr]
# Collect named args and validate ordering
for i in 0 ..< providedArgs.len:
if providedNames[i] != "":
usedNamed = true
if providedNames[i] in namedArgMap:
sema.emitError(expr.loc, &"duplicate named argument '{providedNames[i]}'")
return
namedArgMap[providedNames[i]] = providedArgs[i]
else:
if usedNamed:
sema.emitError(expr.loc, "positional argument after named argument")
return
# Build final arg list in param order
for i in 0 ..< params.len:
if i < providedArgs.len and providedNames[i] == "":
# Positional arg at expected position
newArgs.add(providedArgs[i])
newNames.add("")
elif params[i].name in namedArgMap:
newArgs.add(namedArgMap[params[i].name])
newNames.add("")
elif params[i].defaultValue != nil:
newArgs.add(params[i].defaultValue)
newNames.add("")
else:
sema.emitError(expr.loc, &"missing argument for parameter '{params[i].name}'")
break
expr.exprCallArgs = newArgs
expr.exprCallArgNames = newNames
proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
if expr == nil:
return makeUnknown()
@@ -737,6 +864,14 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
return makeUnknown()
if sym.typ == nil:
return makeUnknown()
# Capture tracking
if sema.closureDepth > 0 and sema.currentClosureExpr != nil and sema.closureScope != nil:
let localSym = scope.lookupUpTo(expr.exprIdent, sema.closureScope)
if localSym == nil and sym.kind == skVar:
if expr.exprIdent notin sema.currentClosureExpr.captureNames:
sema.currentClosureExpr.captureNames.add(expr.exprIdent)
sema.currentClosureExpr.captureTypeKinds.add(sym.typ.kind.int)
sema.currentClosureExpr.captureCount = sema.currentClosureExpr.captureNames.len
return sym.typ
of ekSelf:
let sym = scope.lookup("self")
@@ -786,6 +921,33 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
of ekBinary:
let left = sema.checkExpr(expr.exprBinaryLeft, scope)
let right = sema.checkExpr(expr.exprBinaryRight, scope)
# Operator overloading: check method table before builtin rules
let opMethodName = case expr.exprBinaryOp
of tkPlus: "operator_add"
of tkMinus: "operator_sub"
of tkStar: "operator_mul"
of tkSlash: "operator_div"
of tkPercent: "operator_mod"
of tkEq: "operator_eq"
of tkNe: "operator_ne"
of tkLt: "operator_lt"
of tkLe: "operator_le"
of tkGt: "operator_gt"
of tkGe: "operator_ge"
of tkAmp: "operator_bitand"
of tkPipe: "operator_bitor"
of tkCaret: "operator_xor"
of tkShl: "operator_shl"
of tkShr: "operator_shr"
else: ""
if opMethodName != "" and left.kind == tkNamed and sema.methodTable.hasKey(left.name):
for minfo in sema.methodTable[left.name]:
if minfo.name == opMethodName:
# Validate argument count (self + other)
if minfo.params.len == 2:
let otherType = minfo.params[1]
if right.isAssignableTo(otherType) or otherType.isAssignableTo(right) or right.kind == tkUnknown:
return minfo.retType
case expr.exprBinaryOp
of tkPlus, tkMinus, tkStar, tkSlash, tkPercent, tkStarStar:
if not left.isNumeric or not right.isNumeric:
@@ -845,9 +1007,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
of ekRange:
let lo = sema.checkExpr(expr.exprRangeLo, scope)
let hi = sema.checkExpr(expr.exprRangeHi, scope)
if lo != hi:
var rangeType: Type = lo
if lo == hi:
rangeType = lo
elif lo.isAssignableTo(hi):
rangeType = hi
elif hi.isAssignableTo(lo):
rangeType = lo
else:
sema.emitError(expr.loc, "range bounds must have same type")
return makeRange(lo)
return makeRange(rangeType)
of ekCall:
if expr.exprCallCallee == nil:
sema.emitError(expr.loc, "internal error: nil callee in call expression")
@@ -860,19 +1029,22 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
sema.emitError(expr.loc, &"undeclared identifier '{expr.exprCallCallee.exprGenericCallee}'")
return makeUnknown()
if sym.typ != nil and sym.typ.kind == tkFunc:
# Get the return type and substitute type parameters
let retType = sym.typ.inner[^1]
if retType.kind == tkNamed:
# Check if this is a type parameter
let sym2 = sema.globalScope.lookup(expr.exprCallCallee.exprGenericCallee)
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc:
let typeParams = sym2.decl.declFuncTypeParams
for i, tp in typeParams:
if retType.name == tp.name and i < expr.exprCallCallee.exprGenericTypeArgs.len:
# Substitute with concrete type
let concreteType = expr.exprCallCallee.exprGenericTypeArgs[i]
if concreteType.kind == tekNamed:
return sema.resolveType(concreteType)
let sym2 = sema.globalScope.lookup(expr.exprCallCallee.exprGenericCallee)
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc and
sym2.decl.declFuncTypeParams.len > 0 and
sym2.decl.declFuncReturnType != nil:
let typeParams = sym2.decl.declFuncTypeParams
var added: seq[string] = @[]
for i, tp in typeParams:
if i < expr.exprCallCallee.exprGenericTypeArgs.len:
let concrete = sema.resolveType(expr.exprCallCallee.exprGenericTypeArgs[i])
sema.typeTable[tp.name] = concrete
added.add(tp.name)
let resolvedRet = sema.resolveType(sym2.decl.declFuncReturnType)
for tp in added:
sema.typeTable.del(tp)
return resolvedRet
return retType
return makeUnknown()
@@ -945,8 +1117,7 @@ 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)
# Look up callee declaration early (needed for borrow checking and defaults)
var calleeDecl: Decl = nil
case expr.exprCallCallee.kind
of ekIdent:
@@ -957,13 +1128,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
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
# Resolve named args and inject defaults before type-checking args
sema.resolveCallArgs(expr, calleeDecl, scope)
var argTypes = sema.checkExprList(expr.exprCallArgs, scope)
let isGenericFunc = calleeDecl != nil and calleeDecl.kind == dkFunc and calleeDecl.declFuncTypeParams.len > 0
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:
elif not isGenericFunc:
# Generic function arg checks are deferred until after type inference/monomorphization.
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}")
@@ -1038,6 +1212,26 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
let idx = sema.checkExpr(expr.exprIndexIdx, scope)
if not idx.isInteger:
sema.emitError(expr.loc, "index must be integer")
# Try method-table operator_index_get on a named receiver (possibly behind a pointer).
var receiverNamed: Type = nil
if obj.kind == tkNamed:
receiverNamed = obj
elif obj.isPointer and obj.inner.len > 0 and obj.inner[0].kind == tkNamed:
receiverNamed = obj.inner[0]
if receiverNamed != nil and sema.methodTable.hasKey(receiverNamed.name):
for minfo in sema.methodTable[receiverNamed.name]:
if minfo.name == "operator_index_get" and minfo.params.len == 2:
var subst = initTable[string, Type]()
if minfo.decl.declFuncTypeParams.len > 0 and receiverNamed.inner.len > 0:
for i, tp in minfo.decl.declFuncTypeParams:
if i < receiverNamed.inner.len:
subst[tp.name] = receiverNamed.inner[i]
let idxType = sema.substituteTypeInType(minfo.params[1], subst)
if idx.isAssignableTo(idxType) or idxType.isAssignableTo(idx) or idx.kind == tkUnknown:
return sema.substituteTypeInType(minfo.retType, subst)
if obj.isSlice:
if sema.checkedFunc:
expr.exprIndexBoundsCheck = true
@@ -1079,13 +1273,24 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
let sym = sema.globalScope.lookup(objType.name)
if sym != nil and sym.decl != nil:
if sym.decl.kind == dkStruct:
var subst = initTable[string, Type]()
for i, tp in sym.decl.declStructTypeParams:
if i < objType.inner.len:
subst[tp.name] = objType.inner[i]
for f in sym.decl.declStructFields:
if f.name == expr.exprFieldName:
return sema.resolveType(f.ftype)
return sema.substituteTypeInType(sema.resolveType(f.ftype), subst)
sema.emitError(expr.loc, &"struct '{objType.name}' has no field '{expr.exprFieldName}'")
elif sym.decl.kind == dkEnum:
# Algebraic enum fields
if expr.exprFieldName == "tag":
var hasData = false
for v in sym.decl.declEnumVariants:
if v.fields.len > 0 or v.namedFields.len > 0:
hasData = true
break
if not hasData and expr.exprFieldName == "tag":
return makeNamed(objType.name)
elif expr.exprFieldName == "tag":
return makeNamed(objType.name & "_Tag")
elif expr.exprFieldName == "data":
return makeNamed(objType.name & "_Data")
@@ -1147,12 +1352,12 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
discard sema.checkExpr(expr.exprIsOperand, scope)
return makeBool()
of ekTry:
let operandType = sema.checkExpr(expr.exprTryOperand, scope)
discard sema.checkExpr(expr.exprTryOperand, scope)
# For now, assume Result<int, String> -> int
# TODO: check operand is Result/Option and current function returns same type
return makeInt()
of ekUnwrap:
let operandType = sema.checkExpr(expr.exprUnwrapOperand, scope)
discard sema.checkExpr(expr.exprUnwrapOperand, scope)
# Unwrap: extract Ok value or panic on Err
return makeInt()
of ekBlock:
@@ -1162,7 +1367,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
lastType = sema.checkStmt(stmt, blockScope)
return lastType
of ekMatch:
let subjectType = sema.checkExpr(expr.exprMatchSubject, scope)
discard sema.checkExpr(expr.exprMatchSubject, scope)
var resultType = makeUnknown()
for arm in expr.exprMatchArms:
var armScope = newScope(scope)
@@ -1197,11 +1402,55 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
expr.exprSpawnAsync = true
return makePointer(makeVoid())
of ekAwait:
let operand = sema.checkExpr(expr.exprAwaitOperand, scope)
discard sema.checkExpr(expr.exprAwaitOperand, scope)
# await on a task handle returns *void (result pointer)
return makePointer(makeVoid())
of ekBorrow:
let operand = sema.checkExpr(expr.exprBorrowOperand, scope)
# borrow &mut expr returns the same type as the original (reference)
# The borrow is tracked in the borrow checker
if sema.checkedFunc and expr.exprBorrowMutable:
# Track: variable "operand" is mutably borrowed here
# For now, just validate the type
discard
return operand
of ekSpread:
return sema.checkExpr(expr.exprSpreadOperand, scope)
of ekStringInterp:
for e in expr.exprInterpExprs:
discard sema.checkExpr(e, scope)
return makeStr()
of ekClosure:
let savedRetType = sema.currentRetType
let savedClosureDepth = sema.closureDepth
let savedClosureExpr = sema.currentClosureExpr
let savedClosureScope = sema.closureScope
let childScope = Scope(parent: scope)
sema.closureDepth = sema.closureDepth + 1
sema.currentClosureExpr = expr
sema.closureScope = childScope
expr.captureCount = 0
expr.captureNames = @[]
expr.captureTypeKinds = @[]
sema.currentRetType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeUnknown()
# Register params
for p in expr.exprClosureParams:
let ptype = if p.ptype != nil: sema.resolveType(p.ptype) else: makeUnknown()
discard childScope.define(Symbol(kind: skVar, name: p.name, typ: ptype))
# Check body
if expr.exprClosureBody != nil:
for stmt in expr.exprClosureBody.stmts:
discard sema.checkStmt(stmt, childScope)
sema.currentRetType = savedRetType
sema.closureDepth = savedClosureDepth
sema.currentClosureExpr = savedClosureExpr
sema.closureScope = savedClosureScope
# Build function type
var params: seq[Type] = @[]
for p in expr.exprClosureParams:
params.add(if p.ptype != nil: sema.resolveType(p.ptype) else: makeUnknown())
let retType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeVoid()
return makeFunc(params, retType)
# ---------------------------------------------------------------------------
# Statement type checking
@@ -1262,9 +1511,17 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtLoopBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtLoopBody.loc, exprBlock: stmt.stmtLoopBody)), scope)
return makeVoid()
of skFor:
discard sema.checkExpr(stmt.stmtForIter, scope)
let iterExpr = stmt.stmtForIter
let collType = sema.checkExpr(iterExpr, scope)
var forScope = newScope(scope)
let iterSym = Symbol(kind: skVar, name: stmt.stmtForVar, typ: makeUnknown(), isMutable: true)
var iterTyp = makeUnknown()
if iterExpr.kind == ekRange:
iterTyp = sema.checkExpr(iterExpr.exprRangeLo, scope)
elif collType.kind == tkNamed and collType.inner.len > 0:
iterTyp = collType.inner[0]
elif collType.isPointer and collType.inner.len > 0 and collType.inner[0].kind == tkNamed and collType.inner[0].inner.len > 0:
iterTyp = collType.inner[0].inner[0]
let iterSym = Symbol(kind: skVar, name: stmt.stmtForVar, typ: iterTyp, isMutable: true)
discard forScope.define(iterSym)
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtForBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtForBody.loc, exprBlock: stmt.stmtForBody)), forScope)
return makeVoid()
@@ -1308,6 +1565,17 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
elif not exprType.isUnknown and exprType.kind != tkStr:
sema.emitError(stmt.loc, "#emit requires a string expression")
return makeVoid()
of skDefer:
discard sema.checkExpr(stmt.stmtDeferBody, scope)
return makeVoid()
of skSwitch:
discard sema.checkExpr(stmt.stmtSwitchExpr, scope)
for caseBranch in stmt.stmtSwitchCases:
discard sema.checkExpr(caseBranch.caseValue, scope)
discard sema.checkStmt(Stmt(kind: skExpr, loc: caseBranch.caseBody.loc, stmtExpr: Expr(kind: ekBlock, loc: caseBranch.caseBody.loc, exprBlock: caseBranch.caseBody)), scope)
if stmt.stmtSwitchDefault != nil:
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtSwitchDefault.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtSwitchDefault.loc, exprBlock: stmt.stmtSwitchDefault)), scope)
return makeVoid()
of skDecl:
# Local declaration inside block
case stmt.stmtDecl.kind
+17 -2
View File
@@ -25,6 +25,9 @@ type
tkContinue # continue
tkReturn # return
tkMatch # match
tkSwitch # switch
tkCase # case
tkDefault # default
##Declaration keywords
tkFunc # func
@@ -51,6 +54,7 @@ type
tkSizeOf # sizeof
tkOwn # own (gradual ownership transfer)
tkMut # mut (mutable reference)
tkBorrow # borrow (explicit borrow expression)
tkDiscard # discard (evaluate and throw away)
tkAsync # async
tkAwait # await
@@ -58,6 +62,7 @@ type
tkStaticAssert # static_assert
tkComptime # comptime
tkDyn # dyn
tkDefer # defer
tkLifetime # 'a (lifetime parameter)
##Punctuation
@@ -148,10 +153,10 @@ type
proc isKeyword*(kind: TokenKind): bool =
case kind
of tkIf, tkElse, tkWhile, tkDo, tkLoop, tkFor, tkIn,
tkBreak, tkContinue, tkReturn, tkMatch,
tkBreak, tkContinue, tkReturn, tkMatch, tkSwitch, tkCase, tkDefault,
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn:
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkBorrow, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn, tkDefer:
true
else:
false
@@ -199,6 +204,9 @@ proc keywordKind*(text: string): TokenKind =
of "continue": tkContinue
of "return": tkReturn
of "match": tkMatch
of "switch": tkSwitch
of "case": tkCase
of "default": tkDefault
of "as": tkAs
of "is": tkIs
of "null": tkNull
@@ -207,6 +215,7 @@ proc keywordKind*(text: string): TokenKind =
of "sizeof": tkSizeOf
of "own": tkOwn
of "mut": tkMut
of "borrow": tkBorrow
of "discard": tkDiscard
of "async": tkAsync
of "await": tkAwait
@@ -214,6 +223,7 @@ proc keywordKind*(text: string): TokenKind =
of "static_assert": tkStaticAssert
of "comptime": tkComptime
of "dyn": tkDyn
of "defer": tkDefer
of "true", "false": tkBoolLiteral
else: tkIdent
@@ -238,6 +248,9 @@ proc tokenKindName*(kind: TokenKind): string =
of tkContinue: "'continue'"
of tkReturn: "'return'"
of tkMatch: "'match'"
of tkSwitch: "'switch'"
of tkCase: "'case'"
of tkDefault: "'default'"
of tkFunc: "'func'"
of tkLet: "'let'"
of tkVar: "'var'"
@@ -259,6 +272,7 @@ proc tokenKindName*(kind: TokenKind): string =
of tkSuper: "'super'"
of tkOwn: "'own'"
of tkMut: "'mut'"
of tkBorrow: "'borrow'"
of tkDiscard: "'discard'"
of tkAsync: "'async'"
of tkAwait: "'await'"
@@ -266,6 +280,7 @@ proc tokenKindName*(kind: TokenKind): string =
of tkStaticAssert: "'static_assert'"
of tkComptime: "'comptime'"
of tkDyn: "'dyn'"
of tkDefer: "'defer'"
of tkLifetime: "lifetime"
of tkLParen: "'('"
of tkRParen: "')'"
+4
View File
@@ -183,6 +183,10 @@ proc isAssignableTo*(a, b: Type): bool =
return true
if b.isDynRef and a.isMutRef:
return true
# &mut func(...) -> func(...) (function pointer decay)
if a.isMutRef and b.kind == tkFunc:
if a.inner.len > 0 and a.inner[0].kind == tkFunc:
return true
return false
# String representation
+1 -1
View File
@@ -1,6 +1,6 @@
[Package]
Name = "buxc"
Version = "0.1.0"
Version = "0.3.0"
Type = "bin"
[Build]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More