Compare commits

..

178 Commits

Author SHA1 Message Date
dimgigov a23860be3e release: Bux v1.0.0 language freeze
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
Bump compiler banners and package version to 1.0.0, activate SEMVER policy,
and add RELEASE_v1.0.0 notes. Fix closure auto-Drop leaking outer Array
drops into nested capture bodies (iter_hof). Fmt-clean examples/src for CI.
2026-07-27 21:49:12 +03:00
dimgigov d60ce2bc3f feat: stdlib daily APIs, macro tt/type paste, riscv64 cross smoke
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
Sessions 83–87: grow Array/Map/String/Test ergonomics; delimiter-balanced
and juxta :tt macros plus $t:type fragments; expression-level $(…),* in
templates; selfhost slice lits; riscv64/aarch64 cross smoke helper and
freestanding docs. Null-safe CBE type names and String_StartsWith.
2026-07-27 21:40:11 +03:00
dimgigov a785747c37 feat: Linux/cloud platform stack (TLS, registry, static/cross, selfhost PM)
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
Ship the QUALITY_PLAN platform focus: thin/minimal runtime, --static/--target,
Nexus HTTPS/mTLS with graceful stop, lock checksums + install --locked,
selfhost registry (search/add/HTTP), containers, and CI smokes for cloud path.
2026-07-23 23:00:55 +03:00
dimgigov a939f74b1b feat: field-move Drop (partial/nested/ptr), stmt/pat macros, Windows runtime
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
Sessions 70–74: partialMovedPaths + ptrAliases in bootstrap/selfhost CBE,
remaining drops after field moves, macro stmt/pat fragments, runtime_win.c
and MinGW hello CI, examples + drop-move smoke coverage, QUALITY_PLAN update.
2026-07-21 12:29:53 +03:00
dimgigov fe3b1e8b6a feat: macros (multi-rep, hygiene), Drop field-move, lean multi-OS CI
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
Sessions 56–69: declarative macro! with rep/zip/literal/block and
unhygienic var $name binders; partial field-move skip Drop; @[Release]
polish; LSP type hierarchy; CI Nim cache + lean macOS + Windows smoke.
2026-07-20 17:19:46 +03:00
dimgigov 6f2a3b1d88 feat(selfhost): quote/graft hygiene API for macros and mono
ci / make test (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
Add Ast_Graft/Clone/Quote helpers, HIR Lcx_GraftSourceFile, and force
definition-site paths on monomorphized bodies. Smoke checks Array mono
#line stays in lib while Main stays isolated.
2026-07-20 00:31:23 +03:00
dimgigov 1f20c683a1 feat(lsp): type hierarchy for interfaces and implementors (0.15)
prepareTypeHierarchy plus supertypes/subtypes via extend Type for Iface.
Smoke covers Drawable ↔ Circle/Square.
2026-07-20 00:24:38 +03:00
dimgigov 60b7b7522f ci: run make test on every PR and push to main
Add .github/workflows/ci.yml (Nim + gcc → make test). Keep selfhost-loop
as an optional/weekly job. Document CI layout; reformat hir_lower for fmt-check.
2026-07-20 00:23:04 +03:00
dimgigov e63dacfdbf fix(selfhost): green fixed-point buxc2→buxc3→buxc4
Raise scope/HIR buffer caps, parenthesize unary operands, fix fat-func
typedef and param access so selfhost-built compilers recompile src
identically (path-normalized C + stripped ELF).
2026-07-20 00:21:39 +03:00
dimgigov cb6df5443a feat(selfhost): Expr/Stmt/Block sourceFile for multi-file and macros
Stamp AST trees from Decl path; lower prefers node sourceFile over
func-level path so grafted macro nodes keep their origin for #line maps.
2026-07-20 00:12:18 +03:00
dimgigov b332e98ab3 feat(lsp): workspace-wide import path index (0.14)
Index import paths from scanWorkspace so path classification works without
open documents. Smoke renames Std::Io with Util.bux closed on disk.
2026-07-20 00:09:51 +03:00
dimgigov a296f1695d feat(lsp): textDocument/implementation for interfaces (0.13)
Go-to-implementation resolves interface types to implementing types and
interface methods to extend Type for Iface method decls (Circle+Square).
2026-07-19 23:46:17 +03:00
dimgigov fe1da8b42e ci: optional selfhost-loop workflow + determinism script
Add tools/selfhost_loop.sh with path-normalized C and stripped ELF
checks (bootstrap ×2). Wire GitHub Actions (manual/weekly/main paths)
outside default make test. Raise selfhost lexer maxTokens for large files.
2026-07-19 23:43:51 +03:00
dimgigov c82757d51c feat(selfhost): HirNode.sourceFile for mid-function #line maps
Stamp each HIR node with Decl source path; C backend tracks lastDebugFile
so #line re-emits when file or line changes. Closures inherit the
enclosing function path. Smoke checks Util_Double body maps to Util.bux.
2026-07-19 23:40:10 +03:00
dimgigov 200adf4d68 feat(lsp): module-path segment rename for Std::Io imports (0.12)
Index import A::B path segments and rename with left-prefix match so
Std::Io does not clobber Foo::Io, bare idents, or enum Color::Red.
2026-07-19 23:35:54 +03:00
dimgigov 244a3be8f3 feat(lsp): interface dispatch in call hierarchy (0.11)
Index interface methods and extend Type for Trait relations so call
hierarchy can show implementors as outgoing targets and .Method call
sites as incoming.

- interface I { func M } + extend T for I tracking
- prepare/outgoing/incoming with data Iface#Method
- smoke_lsp_iface_hierarchy (Drawable.Draw ↔ Circle / Render)
2026-07-19 23:30:52 +03:00
dimgigov 00195e2d98 feat(lsp): method, type, and self-receiver rename (0.10)
Add rtkMethod so .Method( call sites rename with the method decl, improve
type renames across extend/annotations, and clip self to the enclosing
method body.

- Len→Length includes self.Len(); Point→Vec2 hits extend + types
- self→this stays within one method (textual func bounds)
- smoke_lsp_rename_method + test-lsp wiring
2026-07-19 23:27:21 +03:00
dimgigov 8f645ad68b feat(lsp): method call hierarchy for extend Type / .Method (0.9)
Index methods inside extend/impl blocks and include .Method( call sites
in the call graph so IDEs can navigate receiver methods.

- analyzeFile: track extend/impl brace scope; kind=method + container
- Call hierarchy treats methods as callables; display Type.Method
- smoke_lsp_method_hierarchy (Scale→Len, Main→Scale)
2026-07-19 23:22:13 +03:00
dimgigov 5de8d8912d ci: selfhost smoke for move_field and multi-file #line
Add tools/smoke_selfhost.sh and make test-selfhost-smoke (via make selfhost)
to the default make test suite, covering field-move ownership and per-file
#line paths under buxc2.
2026-07-19 23:14:40 +03:00
dimgigov 70321075a6 feat(selfhost): multi-file #line paths from Decl.sourceFile
Stamp each merged .bux path onto decls, propagate to HirFunc, and emit
#line N \"path\" per function without requiring BUX_DEBUG_FILE.

- Decl.sourceFile + Cli_StampSourceFile on merge/project parse
- HirFunc.sourceFile; CBE switches currentFile per function
- Stdlib and multi-file user packages get distinct paths automatically
2026-07-19 23:13:02 +03:00
dimgigov ed07cf8921 feat(lsp): call hierarchy prepare / incoming / outgoing (0.8)
Expose LSP call hierarchy over a lightweight textual call graph of
known functions and Name( call sites across the workspace.

- prepareCallHierarchy on func decl or call site
- incomingCalls / outgoingCalls with fromRanges
- smoke_lsp_call_hierarchy + test-lsp wiring
2026-07-19 23:11:10 +03:00
dimgigov 42f9453e18 feat(lsp): deeper rename for fields, variants, qualified paths
bux-lsp 0.7 indexes struct fields and enum variants and renames only the
right binding: .member / ::Variant / field-init, without clobbering locals.

- Member scan in analyzeFile; access kinds bare/dot/colon/field-init
- classifyRenameTarget: local | member | global
- smoke_lsp_rename_deep + test-lsp wiring
2026-07-19 23:09:51 +03:00
dimgigov 94cc94f638 feat(selfhost): field-move skip Drop, #line maps, Array field mono
Bring selfhost C backend closer to bootstrap ownership and debug quality.

- Mark locals moved into struct fields / returns (no double Drop)
- Mangle Array/Set/Map/Channel field types so user structs fully emit
- Emit #line N from HIR line; BUX_DEBUG_FILE / BUX_NO_LINE controls
2026-07-19 23:07:01 +03:00
dimgigov cfb89dd72f fix(compiler): skip auto-Drop after field/let/return moves
Track locals moved by value into struct fields, let bindings, or return
values so Array/Drop types are not freed while still owned by the target.

- hir_lower: movedOutLocals + markMovedOutFromAst + shouldSkipDrop
- Nexus: drop zeroing workaround; free headers after each request
- examples/move_field.bux regression for Box { items: arr }
2026-07-19 23:03:05 +03:00
dimgigov adc5c743a6 fix: Array move ownership, selfhost -g flags, LSP workspace/symbol
Session 36: prevent UAF when Array is moved into a struct field (Nexus
headers), align selfhost cc flags with bootstrap debug/release, and add
workspace/symbol search to bux-lsp 0.6.

- Parser: zero headers after embedding so auto-drop is a no-op
- Request_WantsKeepAlive uses RequestHeader_Get again safely
- Selfhost: default -O0 -g; --release -O2 -DNDEBUG; BUX_CFLAGS
- LSP: workspace/symbol + smoke; version 0.6.0
2026-07-19 22:59:02 +03:00
dimgigov e30d8a5eb9 feat(nexus): HTTP/1.1 keep-alive (~2× throughput)
Reuse TCP connections for multiple requests, honor Connection close,
and raise wrk health RPS from ~45k to ~80k+ on the same machine.

- Loop HandleConnection up to 1000 reqs/fd with Connection keep-alive
- RawRequest_WantsKeepAlive (HTTP/1.1 default; HTTP/1.0 needs keep-alive)
- Nexus 0.3.0 banner; benches note before/after RPS
2026-07-19 22:53:59 +03:00
dimgigov abb2b54dce docs: record session 34 (LSP 0.5 refs/rename + CI wiring)
Complete QUALITY_PLAN notes for references/rename and expanded make test.
2026-07-19 22:50:20 +03:00
dimgigov cd7a82822c feat: LSP references/rename (0.5) and expand CI make test
Add workspace-aware Find All References and Rename for scoped locals and
globals, plus registry/apps/dwarf smokes in the default test target.

- bux-lsp: textDocument/references, prepareRename, rename
- make test-lsp runs smoke_lsp_rename; make test includes registry/dwarf/apps
2026-07-19 22:49:45 +03:00
dimgigov eb81856565 feat: HTTP registry, app/bench harnesses, and DWARF #line maps
Ship QUALITY_PLAN sessions 31–33: fetchable package index URLs, showcase
app and micro/nexus benchmarks, and debugger-friendly C codegen.

- Registry: BUX_REGISTRY accepts http(s) URLs (curl/wget → ~/.bux/cache)
- E.2: make test-apps smoke for nexus/boko/simpledb/jwt-pitbul
- E.5: benches/micro + C/Nim/Zig twins; make bench-nexus (wrk)
- E.4: HIR locs → #line .bux; default -O0 -g; --release -O2; make test-dwarf
2026-07-19 22:46:45 +03:00
dimgigov 53b43b0f79 feat: lifetime elision, tooling CI, registry, and LSP locals
Ship the QUALITY_PLAN stretch from ownership through ecosystem: C.1
lifetime elision (bootstrap + selfhost), bux fmt/test/doc CI hooks,
stdlib goldens, package registry (bux search/add), and LSP 0.4
position-sensitive locals with inferred let types. Full-tree format
pass plus Map/Set remove double-free fix.
2026-07-19 16:35:08 +03:00
dimgigov 3eb1ad3a82 feat: match guards, HOF inference, ownership C.2/C.3, LSP sema hover
Sessions 18–23 quality work:

- B.3c match arm guards + sequential found-flag lower (bootstrap + selfhost)
- Generic HOF type inference (Array/Iter map/filter/fold without type args)
- Pattern binding shadowing via unique C locals (__pN_src)
- Ownership C.2 exclusive &mut data-flow + C.4 goldens; *p= store-through fix
- Ownership C.3 auto-drop on early return/branches: scoped defers, move-on-return,
  Drop monomorphization, materialize return before Drop
- LSP 0.3.0: hover from real sema types
- Examples and QUALITY_PLAN session log; selfhost-loop identical
2026-07-18 21:52:14 +03:00
dimgigov 66f11d1869 feat: nested multi-field enum patterns (bootstrap + selfhost)
Multi-field variants use Enum_Variant_Payload nested types (avoids tag name clash).
Sema resolves data.Variant.Variant_i; enum field types fully resolve tuples.
Selfhost parses full type exprs in enum payloads; emit structs/tuples before enums.
Example: examples/nested_patterns.bux (Pair::Two, Box::Val((a,c)), Shape::Dot).
2026-07-18 01:33:36 +03:00
dimgigov f9185c96b2 feat: multi-stmt match arms and block-as-expression
Blocks can yield a value: last expression statement is the result.
Match arms accept block bodies, so multi-statement arms work:

  match n {
    1 => { let a = 10; a + 1 },
    _ => 0
  }

Bootstrap: lowerBlock(asExpr) lifts the trailing skExpr. Selfhost: parse
{...} as ekBlock, emit __blk_N yield temps (retTypeKind -2), and treat
only non-empty strValue as match/block yield. Nested enum/struct pattern
bindings recurse in both compilers.

Example: match_block.bux. Selfhost-loop remains binary-identical.
2026-07-18 01:24:48 +03:00
dimgigov e3ca724bfa feat: struct and tuple patterns in match (bootstrap + selfhost)
Support destructuring in match arms:
- Tuple: (a, b) binds subject._0 / _1
- Struct: Point { x: px, y: py } and shorthand Point { x, y }

Bootstrap: matchPatternBindings for pkTuple/pkStruct; register local
tuple typedefs from function bodies. Selfhost: parse, Sema_BindPattern,
Lcx_PatternBindings with scope defines. Fix operator-overload path that
crashed when typeName was null after pattern binds.

Example: examples/struct_tuple_pat.bux. Selfhost-loop IDENTICAL.
2026-07-18 01:15:44 +03:00
dimgigov eac78f28c1 feat(stdlib): generic Iter_Map/Filter/Fold with func monomorphization
Add Iter_Map<T,U>, Filter, Fold, Any, All, ForEach over fat function
pointers; keep Iter_MapInt and friends as thin aliases.

Compiler fixes required for non-int returns and capturing closures:
- bootstrap: resolve fat-func call return type under mono typeSubst
- bootstrap: type-check args of Foo<T>(...) so closures capture correctly
- selfhost: substitute type params inside tekFunc (BuxFn_U_T → concrete)
- selfhost: emit cstr fat typedefs with #ifndef redefinition guards

Example: examples/iter_generic.bux (int↔String map, fold, closures).
Selfhost-loop remains binary-identical.
2026-07-18 01:07:41 +03:00
dimgigov 26631252c0 feat(lsp): hover signatures, go-to-def, document outline
Upgrade bux-lsp to 0.2.0 with a richer lightweight symbol index:
func signatures, typed let/var/const, struct/enum/interface/type/module.
Skip comments and strings while scanning. Hover shows markdown Bux
code fences; definition resolves in-file and across workspace .bux files;
documentSymbol provides outline. Fix responses writing to stdout (stdin
was a broken pipe). didChange refreshes symbols; diagnostics still run
via buxc on open/save.
2026-07-18 01:01:22 +03:00
dimgigov db41ba4d84 feat: pattern bindings, empty closures, match-as-expr, string interp
Sessions 10–12 from QUALITY_PLAN:

- Pattern payload bindings (Some(value) => value) in bootstrap and selfhost
- Empty-param closures via || (tkPipePipe) with loop/return bodies
- Expression-form match: let x = match …; newline before arms
- f"…" string interpolation desugared to String_Concat + conversions
- Lexer preserves \{ \} for literal braces in f-strings
- Bootstrap fix: f"plain" strips the f prefix after escape processing
- Examples: pattern_matching, closure_control, match_let, string_interp

Selfhost-loop remains binary-identical; all examples and error goldens pass.
2026-07-18 00:58:44 +03:00
dimgigov f619316470 feat: full match expressions in bootstrap and selfhost
Lower match to if-else for literals, ranges, enum tags, and wildcards.
Fix bootstrap literal arms that always matched; port real arm AST/parser
and Lcx_LowerMatch to selfhost with last-expression return. Expand
pattern_matching example and add parse/use-after-move/double-mut golden
diagnostics. Selfhost-loop remains binary-identical.
2026-07-16 16:19:43 +03:00
dimgigov 2cbbccc508 docs: complete QUALITY_PLAN session log and next-day backlog
Record Iter HOF session and proposed follow-ups for the next work day.
2026-07-15 16:17:04 +03:00
dimgigov e805af6ef6 docs: sync README and plans with v0.5.x features
Document multi-instance closures, tuples, Iter HOF, diagnostics, and
updated test targets. Refresh QUALITY_PLAN diagnosis and next-day backlog.
2026-07-15 16:16:54 +03:00
dimgigov f1896183f5 feat(stdlib): Iter map/filter/fold with closures
Add int-specialized higher-order Iter helpers that accept fat function
pointers, plus an example using named funcs and capturing closures.
Fix selfhost C backend pointer field access and Array Push indexing.
2026-07-15 16:07:47 +03:00
dimgigov 9efba57b4c feat(selfhost): full tuple type support with .0/.1 field access
Parse (T, U) types and (a, b) expressions, lower to Tuple_* C structs,
and emit common tuple typedefs so buxc2 matches bootstrap tuple codegen.
2026-07-15 16:04:01 +03:00
dimgigov 61ac06ab5f feat: multi-instance closures, richer stdlib, and Rust-style diagnostics
Introduce fat function pointers (BuxFn {code, env}) so capturing closures
are heap-allocated per value in both bootstrap and selfhost. Expand
Array/Map/Set/String/Test/Result APIs, add proper tuple codegen and
error snippets with multi-char underlines, golden diagnostic tests, and
LSP diagnostics via buxc check.
2026-07-15 16:00:21 +03:00
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
400 changed files with 113189 additions and 14567 deletions
+458
View File
@@ -0,0 +1,458 @@
# Default PR / main CI: split Linux jobs + lean macOS + Windows bootstrap smoke.
# Full sequential suite locally: `make test`.
# Selfhost fixed-point: see selfhost-loop.yml (not on every PR).
name: ci
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# Pin patch for stable toolchain cache keys (was 2.0.x).
NIM_VERSION: "2.0.8"
# setup-nim-action install dir (relative to workspace)
NIM_INSTALL_DIR: ".nim_runtime"
jobs:
# ── Shared bootstrap build (Linux) ──────────────────────────────────────
build:
name: build (ubuntu)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache Nim toolchain
id: cache-nim
uses: actions/cache@v4
with:
path: ${{ env.NIM_INSTALL_DIR }}
key: ${{ runner.os }}-nim-${{ env.NIM_VERSION }}-v1
- name: Install Nim
if: steps.cache-nim.outputs.cache-hit != 'true'
uses: jiro4989/setup-nim-action@v2
with:
nim-version: ${{ env.NIM_VERSION }}
nim-install-directory: ${{ env.NIM_INSTALL_DIR }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: PATH for cached Nim
if: steps.cache-nim.outputs.cache-hit == 'true'
run: |
echo "$PWD/${{ env.NIM_INSTALL_DIR }}/bin" >> "$GITHUB_PATH"
echo "$HOME/.nimble/bin" >> "$GITHUB_PATH"
- name: Cache nimcache (bootstrap)
uses: actions/cache@v4
with:
path: nimcache
key: ${{ runner.os }}-nimcache-build-${{ hashFiles('bootstrap/**/*.nim') }}-v1
restore-keys: |
${{ runner.os }}-nimcache-build-
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc make binutils libssl-dev python3
- name: Build buxc
run: |
nim -v
make build
- name: Upload buxc
uses: actions/upload-artifact@v4
with:
name: buxc-linux
path: buxc
retention-days: 3
# ── Parallel Linux suites (reuse prebuilt buxc) ─────────────────────────
unit:
name: unit + fmt
needs: build
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- name: Cache Nim toolchain
id: cache-nim
uses: actions/cache@v4
with:
path: ${{ env.NIM_INSTALL_DIR }}
key: ${{ runner.os }}-nim-${{ env.NIM_VERSION }}-v1
- name: Install Nim
if: steps.cache-nim.outputs.cache-hit != 'true'
uses: jiro4989/setup-nim-action@v2
with:
nim-version: ${{ env.NIM_VERSION }}
nim-install-directory: ${{ env.NIM_INSTALL_DIR }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: PATH for cached Nim
if: steps.cache-nim.outputs.cache-hit == 'true'
run: |
echo "$PWD/${{ env.NIM_INSTALL_DIR }}/bin" >> "$GITHUB_PATH"
echo "$HOME/.nimble/bin" >> "$GITHUB_PATH"
- name: Cache nimcache (unit tests)
uses: actions/cache@v4
with:
path: nimcache
key: ${{ runner.os }}-nimcache-unit-${{ hashFiles('bootstrap/**/*.nim', 'tests/**/*.nim') }}-v1
restore-keys: |
${{ runner.os }}-nimcache-unit-
${{ runner.os }}-nimcache-build-
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc make libssl-dev
- name: Download buxc
uses: actions/download-artifact@v4
with:
name: buxc-linux
- name: Prepare buxc
run: chmod +x buxc && ./buxc --version
- name: fmt-check + unit tests
env:
BUX_SKIP_BUILD: "1"
run: |
unset BUX_DEBUG_FILE || true
make fmt-check BUX_SKIP_BUILD=1
make test-unit BUX_SKIP_BUILD=1
examples:
name: examples
needs: build
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc make libssl-dev
- name: Download buxc
uses: actions/download-artifact@v4
with:
name: buxc-linux
- name: Prepare buxc
run: chmod +x buxc && ./buxc --version
- name: test-examples
env:
BUX_SKIP_BUILD: "1"
run: |
unset BUX_DEBUG_FILE || true
make test-examples BUX_SKIP_BUILD=1
goldens:
name: goldens + tools
needs: build
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc make binutils libssl-dev python3 gdb \
gcc-aarch64-linux-gnu
- name: Download buxc
uses: actions/download-artifact@v4
with:
name: buxc-linux
- name: Prepare buxc
run: chmod +x buxc && ./buxc --version
- name: errors + stdlib + registry + dwarf + linux-targets
env:
BUX_SKIP_BUILD: "1"
run: |
unset BUX_DEBUG_FILE || true
make test-errors BUX_SKIP_BUILD=1
make test-stdlib BUX_SKIP_BUILD=1
make test-registry BUX_SKIP_BUILD=1
make test-dwarf BUX_SKIP_BUILD=1
make test-drop-move BUX_SKIP_BUILD=1
make test-linux-targets BUX_SKIP_BUILD=1
apps:
name: apps
needs: build
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc make libssl-dev openssl curl
- name: Download buxc
uses: actions/download-artifact@v4
with:
name: buxc-linux
- name: Prepare buxc
run: chmod +x buxc && ./buxc --version
- name: test-apps + nexus TLS/mTLS
env:
BUX_SKIP_BUILD: "1"
run: |
unset BUX_DEBUG_FILE || true
make test-apps BUX_SKIP_BUILD=1
make test-nexus-tls BUX_SKIP_BUILD=1
make test-nexus-mtls BUX_SKIP_BUILD=1
selfhost:
name: selfhost smoke
needs: build
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc make libssl-dev openssl curl
- name: Download buxc
uses: actions/download-artifact@v4
with:
name: buxc-linux
- name: Prepare buxc
run: chmod +x buxc && ./buxc --version
- name: test-selfhost-smoke
env:
BUX_SKIP_BUILD: "1"
run: |
unset BUX_DEBUG_FILE || true
unset BUX_SELFHOST_FIXED_POINT || true
make test-selfhost-smoke BUX_SKIP_BUILD=1
- name: selfhost install + registry
env:
BUX_SKIP_BUILD: "1"
run: |
unset BUX_DEBUG_FILE || true
make test-selfhost-install BUX_SKIP_BUILD=1
make test-selfhost-registry BUX_SKIP_BUILD=1
- name: Upload selfhost artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: ci-failure-selfhost
path: |
build/selfhost/build/main.c
_test_tmp_pkg/**
if-no-files-found: ignore
# ── macOS smoke (platform matrix) — lean: cache Nim + smoke examples ───
# Full EXAMPLES / goldens / selfhost stay on Linux. macOS still builds
# bootstrap + runs unit tests + a representative example subset.
macos:
name: macos smoke
runs-on: macos-14
timeout-minutes: 35
steps:
- uses: actions/checkout@v4
- name: Cache Nim toolchain
id: cache-nim
uses: actions/cache@v4
with:
path: ${{ env.NIM_INSTALL_DIR }}
# macOS builds Nim from source — cache is the main time win
key: ${{ runner.os }}-nim-${{ env.NIM_VERSION }}-v1
- name: Install Nim
if: steps.cache-nim.outputs.cache-hit != 'true'
uses: jiro4989/setup-nim-action@v2
with:
nim-version: ${{ env.NIM_VERSION }}
nim-install-directory: ${{ env.NIM_INSTALL_DIR }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: PATH for cached Nim
if: steps.cache-nim.outputs.cache-hit == 'true'
run: |
echo "$PWD/${{ env.NIM_INSTALL_DIR }}/bin" >> "$GITHUB_PATH"
echo "$HOME/.nimble/bin" >> "$GITHUB_PATH"
- name: Cache nimcache (bootstrap + unit)
uses: actions/cache@v4
with:
path: nimcache
key: ${{ runner.os }}-nimcache-macos-${{ hashFiles('bootstrap/**/*.nim', 'tests/**/*.nim') }}-v1
restore-keys: |
${{ runner.os }}-nimcache-macos-
- name: OpenSSL (Homebrew)
run: |
# Prefer already-installed openssl@3 (common on GHA images)
if ! brew list openssl@3 &>/dev/null; then
brew install openssl@3
fi
OPENSSL_PREFIX="$(brew --prefix openssl@3)"
echo "OPENSSL_PREFIX=$OPENSSL_PREFIX" >> "$GITHUB_ENV"
# buxc passes BUX_CFLAGS through to cc (needed for Homebrew libcrypto)
echo "BUX_CFLAGS=-I${OPENSSL_PREFIX}/include -L${OPENSSL_PREFIX}/lib" >> "$GITHUB_ENV"
- name: Build buxc
run: |
nim -v
make build
- name: unit + smoke examples
run: |
unset BUX_DEBUG_FILE || true
# fmt-check is Linux-only in split CI (unit job); skip here to save time
make test-unit
make test-examples-smoke
# ── Windows smoke (bootstrap + unit + MinGW hello) ──────────────────────
# Uses rt/runtime_win.c (no pthread/OpenSSL). Full POSIX runtime is Unix-only.
windows:
name: windows smoke
runs-on: windows-latest
timeout-minutes: 35
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Cache Nim toolchain
id: cache-nim
uses: actions/cache@v4
with:
path: ${{ env.NIM_INSTALL_DIR }}
key: ${{ runner.os }}-nim-${{ env.NIM_VERSION }}-v1
- name: Install Nim
if: steps.cache-nim.outputs.cache-hit != 'true'
uses: jiro4989/setup-nim-action@v2
with:
nim-version: ${{ env.NIM_VERSION }}
nim-install-directory: ${{ env.NIM_INSTALL_DIR }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: PATH for cached Nim
if: steps.cache-nim.outputs.cache-hit == 'true'
run: |
echo "$PWD/${{ env.NIM_INSTALL_DIR }}/bin" >> "$GITHUB_PATH"
echo "$HOME/.nimble/bin" >> "$GITHUB_PATH"
- name: Install MinGW (gcc)
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: false
install: mingw-w64-x86_64-gcc
path-type: inherit
- name: Add MinGW64 to PATH
run: |
# setup-msys2 installs under C:/msys64 by default
for d in /c/msys64/mingw64/bin /mingw64/bin "$HOME/msys64/mingw64/bin"; do
if [[ -x "$d/gcc.exe" || -x "$d/gcc" ]]; then
echo "$d" >> "$GITHUB_PATH"
export PATH="$d:$PATH"
break
fi
done
gcc --version | head -1
- name: Cache nimcache (bootstrap + unit)
uses: actions/cache@v4
with:
path: nimcache
key: ${{ runner.os }}-nimcache-win-${{ hashFiles('bootstrap/**/*.nim', 'tests/**/*.nim') }}-v1
restore-keys: |
${{ runner.os }}-nimcache-win-
- name: Build buxc
run: |
set -e
nim -v
# MinGW gcc on PATH (from setup-msys2)
gcc --version | head -1
# Windows produces buxc.exe; keep name predictable for the smoke steps
nim c --nimcache:nimcache -o:buxc.exe -d:release --opt:size bootstrap/main.nim
./buxc.exe --version
- name: Pure Nim unit tests + CLI smoke
run: |
set -e
unset BUX_DEBUG_FILE || true
export NIMFLAGS=--nimcache:nimcache
echo "Running lexer tests..."
nim c $NIMFLAGS -r tests/lexer_test.nim
echo "Running parser tests..."
nim c $NIMFLAGS -r tests/parser_test.nim
echo "Running sema tests..."
nim c $NIMFLAGS -r tests/sema_test.nim
echo "Running HIR tests..."
nim c $NIMFLAGS -r tests/hir_test.nim
echo "Running borrow checker tests..."
nim c $NIMFLAGS -r tests/borrow_test.nim
echo "CLI smoke..."
rm -rf _test_tmp_pkg
./buxc.exe new _test_tmp_pkg
./buxc.exe --version
echo "unit+CLI: PASS"
- name: hello smoke (MinGW + runtime_win)
run: |
set -e
unset BUX_DEBUG_FILE || true
export BUX_STDLIB="$PWD/lib"
# bootstrap on Windows always picks runtime_win.c
chmod +x tools/smoke_windows_hello.sh
tools/smoke_windows_hello.sh
echo "windows smoke: PASS (bootstrap + unit + CLI + hello)"
# ── Single required status for branch protection ────────────────────────
ci-gate:
name: CI gate
if: always()
needs: [unit, examples, goldens, apps, selfhost, macos, windows]
runs-on: ubuntu-latest
steps:
- name: All jobs green?
run: |
set -e
echo "unit=${{ needs.unit.result }}"
echo "examples=${{ needs.examples.result }}"
echo "goldens=${{ needs.goldens.result }}"
echo "apps=${{ needs.apps.result }}"
echo "selfhost=${{ needs.selfhost.result }}"
echo "macos=${{ needs.macos.result }}"
echo "windows=${{ needs.windows.result }}"
for r in \
"${{ needs.unit.result }}" \
"${{ needs.examples.result }}" \
"${{ needs.goldens.result }}" \
"${{ needs.apps.result }}" \
"${{ needs.selfhost.result }}" \
"${{ needs.macos.result }}" \
"${{ needs.windows.result }}"; do
if [ "$r" != "success" ]; then
echo "CI gate failed: a required job is $r"
exit 1
fi
done
echo "CI gate: all required jobs passed"
+95
View File
@@ -0,0 +1,95 @@
# Optional / not on every PR: bootstrap selfhost determinism (make selfhost-loop).
# Default PR CI is .github/workflows/ci.yml (`make test`).
# Fixed-point gen2 vs gen3: BUX_SELFHOST_FIXED_POINT=1 (workflow_dispatch input).
name: selfhost-loop
on:
workflow_dispatch:
inputs:
fixed_point:
description: "Also run fixed-point buxc2→buxc3→buxc4 (gen2 vs gen3)"
type: boolean
default: false
schedule:
# Sundays 06:00 UTC
- cron: "0 6 * * 0"
push:
branches: [main]
paths:
- "src/**"
- "lib/**"
- "rt/**"
- "bootstrap/**"
- "tools/selfhost_loop.sh"
- "Makefile"
- ".github/workflows/selfhost-loop.yml"
concurrency:
group: selfhost-loop-${{ github.ref }}
cancel-in-progress: true
env:
NIM_VERSION: "2.0.8"
NIM_INSTALL_DIR: ".nim_runtime"
jobs:
selfhost-loop:
name: bootstrap determinism
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache Nim toolchain
id: cache-nim
uses: actions/cache@v4
with:
path: ${{ env.NIM_INSTALL_DIR }}
key: ${{ runner.os }}-nim-${{ env.NIM_VERSION }}-v1
- name: Install Nim
if: steps.cache-nim.outputs.cache-hit != 'true'
uses: jiro4989/setup-nim-action@v2
with:
nim-version: ${{ env.NIM_VERSION }}
nim-install-directory: ${{ env.NIM_INSTALL_DIR }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: PATH for cached Nim
if: steps.cache-nim.outputs.cache-hit == 'true'
run: |
echo "$PWD/${{ env.NIM_INSTALL_DIR }}/bin" >> "$GITHUB_PATH"
echo "$HOME/.nimble/bin" >> "$GITHUB_PATH"
- name: Cache nimcache
uses: actions/cache@v4
with:
path: nimcache
key: ${{ runner.os }}-nimcache-loop-${{ hashFiles('bootstrap/**/*.nim', 'src/**/*.bux') }}-v1
restore-keys: |
${{ runner.os }}-nimcache-loop-
${{ runner.os }}-nimcache-build-
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc make binutils libssl-dev
- name: Selfhost loop
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.fixed_point }}" = "true" ]; then
export BUX_SELFHOST_FIXED_POINT=1
echo "Fixed-point mode enabled (experimental)"
fi
make selfhost-loop
- name: Upload C on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: selfhost-loop-c
path: |
build/selfhost-loop-a/build/main.c
build/selfhost-loop-b/build/main.c
if-no-files-found: ignore
+12
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,13 @@ build/
_test_tmp_pkg/
test_pkg/
examples_pkg/
# Temporary test directories
_test_*/
# Backup files
*.bak
# Log files
*.log
.nim_runtime/
+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.
+272 -24
View File
@@ -1,42 +1,64 @@
NIM := nim
SRC := compiler/bootstrap/main.nim
SRC := bootstrap/main.nim
OUT := buxc
BUILD_DIR := build
# Project-local nimcache so CI can cache compiles (default is ~/.cache/nim).
NIMFLAGS ?= --nimcache:nimcache
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership 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 ownership_checked ownership_release drop_early_return lifetime_elision ctfe ctfe_crc async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic generic_infer_hof struct_tuple_pat match_block nested_patterns match_guards pattern_shadow move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw macro_type collections_extra
.PHONY: all build dev debug test clean clean-all test-examples selfhost
# Platform smoke (macOS CI): full EXAMPLES still runs on Linux.
EXAMPLES_SMOKE := hello ownership ownership_release strings map move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw ctfe_crc
.PHONY: all build dev debug test clean clean-all test-examples test-examples-smoke selfhost test-golden test-errors test-stdlib selfhost-loop lsp fmt-check docs bench test-apps test-dwarf test-selfhost-smoke test-unit test-linux-targets ensure-buxc
all: build
build:
$(NIM) c -o:$(OUT) -d:release --opt:size $(SRC)
strip $(OUT)
# Rebuild only when bootstrap sources change (CI can set BUX_SKIP_BUILD=1
# after downloading a prebuilt buxc artifact).
$(OUT): $(wildcard bootstrap/*.nim)
$(NIM) c $(NIMFLAGS) -o:$(OUT) -d:release --opt:size $(SRC)
# strip $(OUT)
build: $(OUT)
# CI parallel jobs download buxc and set BUX_SKIP_BUILD=1 to avoid rebuild.
ensure-buxc:
ifeq ($(BUX_SKIP_BUILD),1)
@test -x ./$(OUT) || (echo "error: ./$(OUT) missing (BUX_SKIP_BUILD=1)"; exit 1)
else
@$(MAKE) $(OUT)
endif
dev:
$(NIM) c -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC)
$(NIM) c $(NIMFLAGS) -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC)
debug: dev
@echo "Debug binary: buxc_debug"
test: build test-examples
# Full local / sequential suite (same coverage as split CI jobs combined).
test: build fmt-check test-examples test-errors test-stdlib test-registry test-dwarf test-drop-move test-linux-targets test-apps test-selfhost-smoke test-unit
# Nim unit tests + tiny CLI smoke (needs Nim + buxc).
test-unit: ensure-buxc
@echo "Running lexer tests..."
$(NIM) c -r compiler/tests/lexer_test.nim
$(NIM) c $(NIMFLAGS) -r tests/lexer_test.nim
@echo "Running parser tests..."
$(NIM) c -r compiler/tests/parser_test.nim
$(NIM) c $(NIMFLAGS) -r tests/parser_test.nim
@echo "Running sema tests..."
$(NIM) c -r compiler/tests/sema_test.nim
$(NIM) c $(NIMFLAGS) -r tests/sema_test.nim
@echo "Running HIR tests..."
$(NIM) c -r compiler/tests/hir_test.nim
$(NIM) c $(NIMFLAGS) -r tests/hir_test.nim
@echo "Running borrow checker tests..."
$(NIM) c -r compiler/tests/borrow_test.nim
$(NIM) c $(NIMFLAGS) -r tests/borrow_test.nim
@echo "Running integration tests..."
rm -rf _test_tmp_pkg
./$(OUT) new _test_tmp_pkg
./$(OUT) --version
test-examples: build
@for ex in $(EXAMPLES); do \
# Shared loop body for full + smoke example runners.
define run-examples
@for ex in $(1); do \
echo "=== Testing example: $$ex ==="; \
mkdir -p examples_pkg/$$ex/src; \
cp examples/$$ex.bux examples_pkg/$$ex/src/Main.bux; \
@@ -49,10 +71,23 @@ test-examples: build
echo '[Build]' >> examples_pkg/$$ex/bux.toml; \
echo 'Output = "Bin"' >> examples_pkg/$$ex/bux.toml; \
fi; \
(cd examples_pkg/$$ex && timeout 10 ../../$(OUT) run) || exit 1; \
if command -v timeout >/dev/null 2>&1; then \
(cd examples_pkg/$$ex && timeout 30 ../../$(OUT) run) || exit 1; \
else \
(cd examples_pkg/$$ex && ../../$(OUT) run) || exit 1; \
fi; \
done
endef
test-examples: ensure-buxc
$(call run-examples,$(EXAMPLES))
@echo "All examples passed!"
# Subset for macOS / quick platform smoke (Linux CI runs full EXAMPLES).
test-examples-smoke: ensure-buxc
$(call run-examples,$(EXAMPLES_SMOKE))
@echo "Smoke examples passed!"
clean:
rm -f $(OUT) buxc_debug
rm -rf $(BUILD_DIR)
@@ -62,14 +97,227 @@ 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 build/selfhost-loop-c
rm -rf tests/golden/*/build
selfhost: build
selfhost: ensure-buxc
@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: ensure-buxc
@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
test-errors: ensure-buxc
@echo "=== Error diagnostic golden tests ==="
@chmod +x tests/error_golden/run.sh
@tests/error_golden/run.sh ./$(OUT)
test-stdlib: ensure-buxc
@echo "=== Stdlib golden tests ==="
@chmod +x tests/stdlib_golden/run.sh
@tests/stdlib_golden/run.sh ./$(OUT)
# Generate stdlib API docs from /// comments → docs/api/stdlib.md
docs: ensure-buxc
@mkdir -p docs/api
@./$(OUT) doc --out docs/api/stdlib.md lib/
@echo "docs/api/stdlib.md updated"
# CI: full-tree format check (lib / examples / src / tests / apps) + dirty-path smoke.
fmt-check: ensure-buxc
@echo "=== fmt --check (full tree) ==="
@./$(OUT) fmt --check lib/
@./$(OUT) fmt --check examples/
@./$(OUT) fmt --check src/
@./$(OUT) fmt --check tests/
@./$(OUT) fmt --check apps/
@echo "=== fmt --check dirty-path smoke ==="
@mkdir -p /tmp/bux_fmt_smoke
@printf 'func Main() -> int {\nreturn 0;\n}\n' > /tmp/bux_fmt_smoke/bad.bux
@if ./$(OUT) fmt --check /tmp/bux_fmt_smoke/bad.bux >/dev/null 2>&1; then \
echo "error: expected --check to fail on dirty file"; exit 1; \
fi
@echo "fmt --check passed (tree clean + dirty exits 1)"
# One-shot reformat of the same trees (run before committing style-only fixes)
.PHONY: fmt
fmt: ensure-buxc
@./$(OUT) fmt lib/
@./$(OUT) fmt examples/
@./$(OUT) fmt src/
@./$(OUT) fmt tests/
@./$(OUT) fmt apps/
@echo "Formatted lib/ examples/ src/ tests/ apps/"
# Fixed-point: bootstrap buxc → buxc2 → buxc3 (path-normalized C + stripped ELF).
# Slow; not part of default `make test`. Optional CI: .github/workflows/selfhost-loop.yml
selfhost-loop: ensure-buxc
@chmod +x tools/selfhost_loop.sh
@tools/selfhost_loop.sh
lsp: tools/bux-lsp
@echo "LSP server ready at tools/bux-lsp"
tools/bux-lsp: tools/lsp_server.nim bootstrap/*.nim
cd tools && $(NIM) c -d:release --opt:size --path:../bootstrap -o:bux-lsp lsp_server.nim
.PHONY: test-lsp
test-lsp: lsp
@echo "=== LSP unit (locals / inference) ==="
$(NIM) r --path:bootstrap tools/test_lsp_locals.nim
@echo "=== LSP hover smoke ==="
@chmod +x tools/smoke_lsp_hover.sh
@tools/smoke_lsp_hover.sh
@echo "=== LSP references / rename smoke ==="
@chmod +x tools/smoke_lsp_rename.sh
@tools/smoke_lsp_rename.sh
@echo "=== LSP workspace/symbol smoke ==="
@chmod +x tools/smoke_lsp_workspace.sh
@tools/smoke_lsp_workspace.sh
@echo "=== LSP deeper rename smoke ==="
@chmod +x tools/smoke_lsp_rename_deep.sh
@tools/smoke_lsp_rename_deep.sh
@echo "=== LSP call hierarchy smoke ==="
@chmod +x tools/smoke_lsp_call_hierarchy.sh
@tools/smoke_lsp_call_hierarchy.sh
@echo "=== LSP method call hierarchy smoke ==="
@chmod +x tools/smoke_lsp_method_hierarchy.sh
@tools/smoke_lsp_method_hierarchy.sh
@echo "=== LSP method/type/receiver rename smoke ==="
@chmod +x tools/smoke_lsp_rename_method.sh
@tools/smoke_lsp_rename_method.sh
@echo "=== LSP interface dispatch hierarchy smoke ==="
@chmod +x tools/smoke_lsp_iface_hierarchy.sh
@tools/smoke_lsp_iface_hierarchy.sh
@echo "==> LSP path rename smoke"
@chmod +x tools/smoke_lsp_rename_path.sh
@tools/smoke_lsp_rename_path.sh
@echo "==> LSP implementation smoke"
@chmod +x tools/smoke_lsp_implementation.sh
@tools/smoke_lsp_implementation.sh
@echo "==> LSP workspace import index smoke"
@chmod +x tools/smoke_lsp_workspace_imports.sh
@tools/smoke_lsp_workspace_imports.sh
@echo "==> LSP type hierarchy smoke"
@chmod +x tools/smoke_lsp_type_hierarchy.sh
@tools/smoke_lsp_type_hierarchy.sh
@echo "==> LSP type hierarchy workspace (closed multi-file)"
@chmod +x tools/smoke_lsp_type_hierarchy_ws.sh
@tools/smoke_lsp_type_hierarchy_ws.sh
.PHONY: test-registry
test-registry: ensure-buxc
@echo "=== Registry smoke (E.1 + HTTP) ==="
@chmod +x tools/smoke_registry.sh
@tools/smoke_registry.sh
# E.2 — build showcase apps + simpledb/jwt CLI smoke
.PHONY: test-apps
test-apps: ensure-buxc
@echo "=== Apps smoke (E.2) ==="
@chmod +x tools/smoke_apps.sh
@tools/smoke_apps.sh
# E.5 — micro-benchmarks (Bux + C/Nim/Zig twins)
.PHONY: bench
bench: ensure-buxc
@chmod +x tools/bench.sh
@tools/bench.sh
# E.5 — Nexus HTTP throughput (wrk); optional via BENCH_NEXUS=1 make bench
.PHONY: bench-nexus
bench-nexus: ensure-buxc
@chmod +x tools/bench_nexus.sh
@tools/bench_nexus.sh
# E.4 — DWARF / #line debugger smoke
.PHONY: test-dwarf
test-dwarf: ensure-buxc
@echo "=== DWARF / #line smoke (E.4) ==="
@chmod +x tools/smoke_dwarf.sh
@tools/smoke_dwarf.sh
# Session 75/85 — Linux / cloud / embedded: minimal, static, aarch64+riscv64 cross, CTFE CRC
.PHONY: test-linux-targets
test-linux-targets: ensure-buxc
@echo "=== Linux targets smoke (minimal / static / aarch64+riscv64 cross) ==="
@chmod +x tools/smoke_linux_targets.sh
@tools/smoke_linux_targets.sh
# Session 78 — Nexus HTTPS (self-signed) smoke
.PHONY: test-nexus-tls
test-nexus-tls: ensure-buxc
@echo "=== Nexus TLS smoke ==="
@chmod +x tools/smoke_nexus_tls.sh
@tools/smoke_nexus_tls.sh
# Session 79 — musl static (SKIP if no musl-gcc/zig)
.PHONY: test-musl-static
test-musl-static: ensure-buxc
@echo "=== musl static smoke ==="
@chmod +x tools/smoke_musl_static.sh
@tools/smoke_musl_static.sh
# Session 80 — selfhost install --locked + Nexus mTLS
.PHONY: test-selfhost-install test-nexus-mtls test-selfhost-registry
test-selfhost-install: ensure-buxc selfhost
@echo "=== Selfhost install --locked ==="
@chmod +x tools/smoke_selfhost_install.sh
@tools/smoke_selfhost_install.sh
test-nexus-mtls: ensure-buxc
@echo "=== Nexus mTLS smoke ==="
@chmod +x tools/smoke_nexus_mtls.sh
@tools/smoke_nexus_mtls.sh
# Session 81 — selfhost full registry (search / add / HTTP)
test-selfhost-registry: ensure-buxc selfhost
@echo "=== Selfhost registry ==="
@chmod +x tools/smoke_selfhost_registry.sh
@tools/smoke_selfhost_registry.sh
# Drop / field-move goldens (whole + partial field move; early-return counts)
.PHONY: test-drop-move
test-drop-move: ensure-buxc
@echo "=== Drop / field-move smoke ==="
@chmod +x tools/smoke_drop_move.sh
@tools/smoke_drop_move.sh
# Selfhost (buxc2): move_field ownership + multi-file #line (session 41/42)
# When BUX_SKIP_BUILD=1, reuse prebuilt buxc; still builds buxc2 via selfhost.
.PHONY: test-selfhost-smoke
test-selfhost-smoke: ensure-buxc selfhost
@echo "=== Selfhost smoke (move_field + multi-file #line) ==="
@chmod +x tools/smoke_selfhost.sh
@tools/smoke_selfhost.sh
@echo "=== Graft / quote hygiene smoke ==="
@chmod +x tools/smoke_graft_hygiene.sh
@tools/smoke_graft_hygiene.sh
+276 -63
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 ✅
```
@@ -428,7 +432,7 @@ func ReadFile(path: String) -> Result<String, IoError> {
| `8.2.1` `own` keyword | ✅ | `own T` parsed and resolves to `T`; ready for borrow checker integration |
| `8.2.2` `borrow` / `&` | ✅ | `&T` shared reference type checked and enforced |
| `8.2.3` `mut` references | ✅ | `&mut T` mutable reference type checked and enforced |
| `8.2.4` Lifetime elision | | Simple rules for common cases; explicit `'a` for complex |
| `8.2.4` Lifetime elision | | Single-input elision + dangling return; explicit `'a` for multi-input |
| `8.2.5` Opt-in checker | ✅ | `@[Checked]` attribute enables borrow checking: writes through `&T` are rejected |
```bux
@@ -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) | ✅ | Single-input elision; multi-input requires `'a` |
| `10.2.7` Explicit lifetimes `'a` | ✅ | Parsed + checked; multi-input + mismatch |
### 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 |
---
+160 -32
View File
@@ -2,10 +2,16 @@
![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:** **v1.0.0** — language freeze. Bootstrap (`buxc`, Nim) and self-hosted (`buxc2`, Bux) both compile `.bux` → C → native binary.
> **Selfhost loop:** deterministic C codegen + ELF verified.
> **Gradual Ownership:** `@[Checked]` borrow checker, `@[Release]` zero-cost mode, `borrow &mut` expressions.
> **Closures:** multi-instance capturing closures via fat function pointers (`BuxFn { code, env }`) in both compilers.
> **Tuples:** `(T, U)` types and `.0`/`.1` field access (bootstrap + selfhost).
> **Green Threads:** M:N scheduler with channels (Go-style goroutines without GC).
> **Examples:** 40+ programs pass (`make test-examples`). Apps: `boko-framework`, `jwt-pitbul`, `nexus`, `simpledb`.
> **Semver:** after 1.0, breaking changes require MAJOR (`docs/SEMVER.md`).
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), multi-instance closures, async/await, generics, algebraic enums, and a package manager.
---
@@ -21,6 +27,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 +140,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 +170,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;
}
```
@@ -176,29 +188,66 @@ func Main() -> int {
}
```
### Multi-instance closures
```bux
func MakeAdder(base: int) -> func(int) -> int {
return |a: int| -> int { return a + base; };
}
func Main() -> int {
let a10 = MakeAdder(10);
let a20 = MakeAdder(20);
// Independent capture environments
PrintInt(a10(1)); // 11
PrintInt(a20(1)); // 21
return 0;
}
```
### Iter map / filter / fold
```bux
import Std::Iter::{Array_Iter, Iter_MapInt, Iter_FilterInt, Iter_FoldInt};
func Main() -> int {
var nums: Array<int> = Array_New<int>(4);
Array_Push<int>(&nums, 1);
Array_Push<int>(&nums, 2);
Array_Push<int>(&nums, 3);
let it = Array_Iter<int>(&nums);
let doubled = Iter_MapInt(&it, |x: int| -> int { return x * 2; });
return 0;
}
```
---
## Features
| Feature | Status |
|---------|--------|
| **Types** | Primitives, pointers, slices, tuples, structs, enums, unions |
| **Types** | Primitives, pointers, slices, tuples `(T,U)` + `.0`/`.1`, structs, enums, unions |
| **Generics** | Generic functions (monomorphization) |
| **Algebraic Enums** | Enums with data (`Result`, `Option`) |
| **Pattern Matching** | `match` with guards |
| **Methods** | `extend` blocks for struct methods |
| **Interfaces** | `interface` + `extend` for trait-like behavior |
| **Error Handling** | `Result<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) |
| **Strings** | Raw multi-line backtick strings (`...`), C-string interop |
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
| **Error Handling** | `Result`/`Option`, `?`, `Expect`/`UnwrapOr`/`Or` helpers |
| **Closures** | Capture-less + capturing; **multi-instance** fat pointers (`BuxFn`) |
| **Function pointers** | `func(T) -> R` as fat values; named funcs via adapters |
| **Standard Library** | `Io`, `Array`, `String`, `Map`, `Set`, `Iter` (map/filter/fold), `Fs`, `Mem`, `Path`, `Math`, `Task`, `Channel`, `Sync`, `Os`, `Time`, `Process`, `Test`, … |
| **Backend** | LIR → C transpiler (clean 3-address code, then gcc/clang) |
| **Strings** | Raw multi-line backticks, `f"..."` interp (bootstrap), `ReplaceAll` / `IsBlank` / `Repeat` |
| **Gradual Ownership** | `@[Checked]` + `@[Release]` + `@[Shared]` + `borrow &mut` / `borrow &` |
| **Drop / RAII** | Auto-drop (`@[Drop]` / `extend … for Drop`); **field-move skips Drop** (no double-free) |
| **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 |
| **Tooling** | `bux new`, `bux build`, `bux run`, `bux test`, `bux check` |
| **Cross-Compilation** | `--target <triple>` via clang (e.g. `aarch64-linux-gnu`) |
| **Diagnostics** | Rust-style snippets, multi-char underlines, `= help:` hints |
| **Tooling** | `bux new/build/run/test/check/fmt/doc`, LSP 0.4.0 (locals + inferred lets) |
---
@@ -206,43 +255,108 @@ 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
```
---
## Documentation
| Doc | Description |
|-----|-------------|
| [`docs/LanguageRef.md`](docs/LanguageRef.md) | Language reference |
| [`docs/Stdlib.md`](docs/Stdlib.md) | Standard library API |
| [`docs/BuildAndTest.md`](docs/BuildAndTest.md) | Build, test, and tooling |
| [`docs/QUALITY_PLAN.md`](docs/QUALITY_PLAN.md) | Roadmap toward a “good” v1.0 |
| [`docs/Packages.md`](docs/Packages.md) | Package manager + registry |
| [`docs/SEMVER.md`](docs/SEMVER.md) | Versioning policy |
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Feature status (constructs) |
| [`PLAN.md`](PLAN.md) | Long-form phase plan |
---
## Build & Test
```bash
# Build bootstrap compiler (Nim → C)
make build
# Run all example programs
make test-examples
# Golden diagnostic tests (Rust-style error format)
make test-errors
# Full unit + example suite (local; CI splits the same coverage across jobs)
make test
# Individual suites (also used by .github/workflows/ci.yml in parallel):
# make test-unit / test-examples / test-errors / test-stdlib /
# test-registry / test-dwarf / test-apps / test-selfhost-smoke
# Full-tree format check (lib/ examples/ src/ tests/ apps/)
make fmt-check
# Reformat those trees
make fmt
# Stdlib behavioral goldens (Array / String / collections)
make test-stdlib
# Generate stdlib API docs from /// comments
make docs
# Build self-hosted compiler (Bux → C → native)
make selfhost
# Run all tests
make test
# Package tests (filter + summary table)
./buxc test --filter first _test_runner
# Run example programs
make test-examples
# Format / CI format check
./buxc fmt path/to/file.bux
./buxc fmt --check path/
# API docs
./buxc doc --out docs/api/stdlib.md lib/
# Package registry
./buxc search greet
make test-registry # add greet → install → build temp app
# Selfhost determinism (optional CI job; not in default `make test`)
make selfhost-loop
# Full fixed-point: buxc2 → buxc3 → buxc4 (gen2 vs gen3 C+ELF identical)
# BUX_SELFHOST_FIXED_POINT=1 make selfhost-loop
# Clean build artifacts
make clean
@@ -252,6 +366,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
+218
View File
@@ -0,0 +1,218 @@
# 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, **keep-alive** (reuse TCP), status codes, content negotiation |
| **Multi-threaded** | Worker pool + channel task queue; configurable via `NEXUS_WORKERS` |
| **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** | Access log: `METHOD path status duration_ms` (`NEXUS_ACCESS_LOG=0` to disable) |
| **Limits** | `NEXUS_MAX_BODY` (default 1 MiB) → HTTP 413 when exceeded |
| **Graceful stop** | SIGINT/SIGTERM: close listen fd, poison workers, exit 0 |
| **TLS / HTTPS** | OpenSSL server mode via `NEXUS_TLS=1` + PEM cert/key |
| **mTLS** | `NEXUS_TLS_CLIENT_CA` PEM → require client certificates |
## 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
# Optional env (also used by `make bench-nexus`)
# NEXUS_PORT=18080 NEXUS_BIND=127.0.0.1 NEXUS_WORKERS=4 \
# NEXUS_MAX_BODY=1048576 NEXUS_ACCESS_LOG=1 ./build/nexus
# HTTPS (self-signed example)
# openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365 -subj /CN=localhost
# NEXUS_TLS=1 NEXUS_TLS_CERT=cert.pem NEXUS_TLS_KEY=key.pem NEXUS_PORT=8443 ./build/nexus
# curl -k https://127.0.0.1:8443/api/health
# mTLS (require client cert signed by CA)
# NEXUS_TLS_CLIENT_CA=ca.pem NEXUS_TLS=1 NEXUS_TLS_CERT=server.pem NEXUS_TLS_KEY=server.key …
# curl --cert client.pem --key client.key --cacert ca.pem https://…
```
Server starts on `http://0.0.0.0:8080` (or `https://` when TLS is enabled).
Stop with **Ctrl+C** or `kill -TERM` (graceful: workers drained via poison pills).
Smoke: `make test-nexus-tls` (self-signed cert + curl -k).
### Docker
```bash
# Full Nexus (needs libssl3)
../../buxc --release build # from apps/nexus
docker build -f ../../examples/docker/Dockerfile.nexus -t bux-nexus ../..
docker run --rm -p 8080:8080 bux-nexus
```
```
╔══════════════════════════════════════════════╗
║ 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>
+37
View File
@@ -0,0 +1,37 @@
module Config {
pub struct ServerConfig {
bindAddr: String;
port: int;
workerCount: int;
publicDir: String;
backlog: int;
/// Max raw request bytes (recv buffer / body safety). Default 1 MiB.
maxBodyBytes: int;
/// Access log to stdout (method path status ms). Default true.
accessLog: bool;
/// Enable HTTPS (OpenSSL). Requires tlsCertPath + tlsKeyPath.
tlsEnabled: bool;
tlsCertPath: String;
tlsKeyPath: String;
/// Optional client CA PEM → mTLS (require client certificate).
tlsClientCaPath: String;
}
pub const func DefaultConfig() -> ServerConfig {
return ServerConfig {
bindAddr: "0.0.0.0",
port: 8080,
workerCount: 4,
publicDir: "public",
backlog: 128,
maxBodyBytes: 1048576,
accessLog: true,
tlsEnabled: false,
tlsCertPath: "",
tlsKeyPath: "",
tlsClientCaPath: "",
};
}
}
+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;
}
}
+95
View File
@@ -0,0 +1,95 @@
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.6.0\"}");
}
pub func HandleApiInfo() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"features\":[\"HTTP/1.1\",\"TLS\",\"mTLS\",\"keep-alive\",\"thread-pool\",\"graceful-stop\",\"access-log\",\"max-body\"]}");
}
pub func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
var reqMut: HttpRequest = req;
let wsKey: String = RequestHeader_Get(&reqMut, "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;
}
}
+170
View File
@@ -0,0 +1,170 @@
module Http {
import Std::Array::{Array, Array_Len, Array_Get};
import Std::String::{String_Eq, String_EndsWith, String_Contains, String_Len, String_StartsWith};
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;
}
func CharLower(c: int) -> int {
if c >= 65 && c <= 90 {
return c + 32;
}
return c;
}
/// Case-insensitive string equality (HTTP header names/values).
pub func String_EqIgnoreCase(a: String, b: String) -> bool {
let la: uint = String_Len(a);
let lb: uint = String_Len(b);
if la != lb {
return false;
}
var i: uint = 0;
while i < la {
if CharLower(a[i] as int) != CharLower(b[i] as int) {
return false;
}
i = i + 1;
}
return true;
}
pub func RequestHeader_Get(req: *HttpRequest, key: String) -> String {
// Index-based walk — for-in over Array<struct{String}> can corrupt string fields.
let n: uint = Array_Len<HeaderEntry>(&req.headers);
var i: uint = 0;
while i < n {
let entry: HeaderEntry = Array_Get<HeaderEntry>(&req.headers, i);
if String_EqIgnoreCase(entry.key, key) {
return entry.value;
}
i = i + 1;
}
return "";
}
/// HTTP/1.1 defaults to keep-alive; Connection: close forces close;
/// HTTP/1.0 needs explicit keep-alive. Uses parsed headers (safe after
/// Parser field-move ownership handoff).
pub func Request_WantsKeepAlive(req: *HttpRequest) -> bool {
let conn: String = RequestHeader_Get(req, "Connection");
if String_EqIgnoreCase(conn, "close") {
return false;
}
if String_EqIgnoreCase(conn, "keep-alive") {
return true;
}
if String_StartsWith(req.version, "HTTP/1.0") {
return false;
}
return true;
}
/// Fallback when only raw bytes are available.
pub func RawRequest_WantsKeepAlive(raw: String) -> bool {
if String_Contains(raw, "Connection: close") || String_Contains(raw, "connection: close") ||
String_Contains(raw, "CONNECTION: CLOSE") {
return false;
}
if String_Contains(raw, "HTTP/1.0") {
if String_Contains(raw, "Connection: keep-alive") ||
String_Contains(raw, "Connection: Keep-Alive") ||
String_Contains(raw, "connection: keep-alive") {
return true;
}
return false;
}
return true;
}
}
+119
View File
@@ -0,0 +1,119 @@
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};
import Std::Os::{Os_GetEnv};
import Std::String::{String_Len, String_ToInt, String_Eq};
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 },
};
}
/// Apply optional env overrides for benches / ops:
/// NEXUS_PORT, NEXUS_WORKERS, NEXUS_BIND, NEXUS_PUBLIC,
/// NEXUS_MAX_BODY (bytes), NEXUS_ACCESS_LOG (0/1/false/true),
/// NEXUS_TLS=1 + NEXUS_TLS_CERT + NEXUS_TLS_KEY (PEM paths)
/// NEXUS_TLS_CLIENT_CA (optional PEM → mTLS)
func ApplyEnvConfig(config: *ServerConfig) {
let portEnv: String = Os_GetEnv("NEXUS_PORT");
if String_Len(portEnv) > 0 {
let p: int64 = String_ToInt(portEnv);
if p > 0 && p < 65536 {
config.port = p as int;
}
}
let workersEnv: String = Os_GetEnv("NEXUS_WORKERS");
if String_Len(workersEnv) > 0 {
let w: int64 = String_ToInt(workersEnv);
if w > 0 && w <= 256 {
config.workerCount = w as int;
}
}
let bindEnv: String = Os_GetEnv("NEXUS_BIND");
if String_Len(bindEnv) > 0 {
config.bindAddr = bindEnv;
}
let pubEnv: String = Os_GetEnv("NEXUS_PUBLIC");
if String_Len(pubEnv) > 0 {
config.publicDir = pubEnv;
}
let bodyEnv: String = Os_GetEnv("NEXUS_MAX_BODY");
if String_Len(bodyEnv) > 0 {
let b: int64 = String_ToInt(bodyEnv);
if b >= 1024 && b <= 67108864 {
config.maxBodyBytes = b as int;
}
}
let logEnv: String = Os_GetEnv("NEXUS_ACCESS_LOG");
if String_Len(logEnv) > 0 {
if String_Eq(logEnv, "0") || String_Eq(logEnv, "false") || String_Eq(logEnv, "off") {
config.accessLog = false;
} else {
config.accessLog = true;
}
}
let tlsEnv: String = Os_GetEnv("NEXUS_TLS");
if String_Len(tlsEnv) > 0 {
if String_Eq(tlsEnv, "1") || String_Eq(tlsEnv, "true") || String_Eq(tlsEnv, "on") ||
String_Eq(tlsEnv, "https") {
config.tlsEnabled = true;
}
}
let certEnv: String = Os_GetEnv("NEXUS_TLS_CERT");
if String_Len(certEnv) > 0 {
config.tlsCertPath = certEnv;
config.tlsEnabled = true;
}
let keyEnv: String = Os_GetEnv("NEXUS_TLS_KEY");
if String_Len(keyEnv) > 0 {
config.tlsKeyPath = keyEnv;
config.tlsEnabled = true;
}
let caEnv: String = Os_GetEnv("NEXUS_TLS_CLIENT_CA");
if String_Len(caEnv) > 0 {
config.tlsClientCaPath = caEnv;
config.tlsEnabled = true;
}
}
func Main() -> int {
var config: ServerConfig = DefaultConfig();
ApplyEnvConfig(&config);
let router: Router = BuildRouter();
return RunServer(config, router);
}
}
+133
View File
@@ -0,0 +1,133 @@
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");
// Single assignment path (no Array_New then overwrite). Field-move of
// `headers` into HttpRequest skips auto-Drop (compiler movedOutLocals).
var headers: Array<HeaderEntry>;
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, "") {
// auto-drop of `headers` runs on error return
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);
}
}
+348
View File
@@ -0,0 +1,348 @@
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,
Tls_ServerCtx, Tls_ServerCtxMtls, Tls_CtxFree, Tls_Accept, Tls_Send, Tls_Recv, Tls_Close, Tls_LastError
};
import Std::String::{String_Len, String_StartsWith, String_Eq};
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
import Std::Array::{Array_Drop};
import Std::Os::{Os_InstallStopHandlers, Os_ShouldStop, Os_SetStopListenFd};
import Std::Time::{Time_NowMs};
import Config::{ServerConfig};
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse, Http_MethodName, Request_WantsKeepAlive, HeaderEntry};
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);
/// Cap requests per TCP connection (safety + fair scheduling).
const MAX_KEEPALIVE_REQUESTS: int = 1000;
/// fd < 0 is a poison pill: worker exits cleanly.
/// tls is null for plain HTTP; non-null SSL* for HTTPS (session 78).
pub struct ConnectionTask {
fd: int;
tls: *void;
}
pub func BuildResponse(resp: HttpResponse, keepAlive: bool) -> 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.6.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");
if keepAlive {
bux_sb_append(sb, "Connection: keep-alive\r\n");
bux_sb_append(sb, "Keep-Alive: timeout=5, max=1000\r\n");
} else {
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;
}
func ConnRecv(fd: int, tls: *void, maxLen: int) -> String {
if tls != null as *void {
return Tls_Recv(tls, maxLen);
}
return Net_Recv(fd, maxLen);
}
func ConnSend(fd: int, tls: *void, data: String) -> int {
if tls != null as *void {
return Tls_Send(tls, data);
}
return Net_Send(fd, data);
}
/// Structured access log: method path status duration_ms
func AccessLog(method: String, path: String, status: int, ms: int64) {
Print(method);
Print(" ");
Print(path);
Print(" ");
PrintInt(status);
Print(" ");
PrintInt(ms as int);
PrintLine("ms");
}
/// Serve one TCP (or TLS) client: zero or more HTTP/1.1 keep-alive requests.
pub func HandleConnection(fd: int, tls: *void, router: Router, config: ServerConfig) {
var reqCount: int = 0;
let maxRecv: int = config.maxBodyBytes;
while reqCount < MAX_KEEPALIVE_REQUESTS {
if Os_ShouldStop() {
return;
}
let raw: String = ConnRecv(fd, tls, maxRecv);
if String_Len(raw) == 0 {
return;
}
if String_Len(raw) as int >= maxRecv {
let resp: HttpResponse = Http_NewResponse(413, "text/plain; charset=utf-8", "Payload Too Large");
ConnSend(fd, tls, BuildResponse(resp, false));
return;
}
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");
ConnSend(fd, tls, BuildResponse(resp, false));
return;
}
let t0: int64 = Time_NowMs();
let parsed: ParseResult = ParseRequest(raw);
var keepAlive: bool = false;
var statusOut: int = 400;
var methodName: String = "?";
var pathOut: String = "-";
if parsed.tag == ParseResult_Ok {
var req: HttpRequest = parsed.data.Ok_0;
methodName = Http_MethodName(req.method);
pathOut = req.path;
if String_Len(req.body) as int > maxRecv {
let resp: HttpResponse = Http_NewResponse(413, "text/plain; charset=utf-8", "Payload Too Large");
ConnSend(fd, tls, BuildResponse(resp, false));
Array_Drop<HeaderEntry>(&req.headers);
return;
}
keepAlive = Request_WantsKeepAlive(&req);
if reqCount + 1 >= MAX_KEEPALIVE_REQUESTS {
keepAlive = false;
}
let resp: HttpResponse = Router_Dispatch(router, req);
statusOut = resp.statusCode;
ConnSend(fd, tls, BuildResponse(resp, keepAlive));
Array_Drop<HeaderEntry>(&req.headers);
} else {
let resp: HttpResponse = Http_NewResponse(400, "text/plain; charset=utf-8", "Bad Request");
statusOut = 400;
ConnSend(fd, tls, BuildResponse(resp, false));
if config.accessLog {
let dt: int64 = Time_NowMs() - t0;
AccessLog("?", "-", 400, dt);
}
return;
}
if config.accessLog {
let dt: int64 = Time_NowMs() - t0;
AccessLog(methodName, pathOut, statusOut, dt);
}
reqCount = reqCount + 1;
if !keepAlive {
return;
}
}
}
pub struct WorkerCtx {
taskQueue: *Channel<ConnectionTask>;
router: Router;
config: ServerConfig;
}
pub func Worker(ctx: *WorkerCtx) {
while true {
let task: ConnectionTask = Channel_Recv<ConnectionTask>(ctx.taskQueue);
if task.fd < 0 {
return;
}
HandleConnection(task.fd, task.tls, ctx.router, ctx.config);
if task.tls != null as *void {
Tls_Close(task.tls);
}
Net_Close(task.fd);
}
}
pub struct AcceptorCtx {
serverFd: int;
taskQueue: *Channel<ConnectionTask>;
workerCount: int;
tlsCtx: *void;
}
pub func Acceptor(ctx: *AcceptorCtx) {
while !Os_ShouldStop() {
let fd: int = Net_Accept(ctx.serverFd);
if fd >= 0 {
var tls: *void = null as *void;
if ctx.tlsCtx != null as *void {
tls = Tls_Accept(ctx.tlsCtx, fd);
if tls == null as *void {
Print("WARN: TLS handshake failed: ");
PrintLine(Tls_LastError());
Net_Close(fd);
continue;
}
}
let task: ConnectionTask = ConnectionTask { fd: fd, tls: tls };
Channel_Send<ConnectionTask>(ctx.taskQueue, task);
} else {
if Os_ShouldStop() {
break;
}
}
}
var i: int = 0;
while i < ctx.workerCount {
let poison: ConnectionTask = ConnectionTask { fd: -1, tls: null as *void };
Channel_Send<ConnectionTask>(ctx.taskQueue, poison);
i = i + 1;
}
PrintLine("acceptor: stop — workers poisoned");
}
pub func RunServer(config: ServerConfig, router: Router) -> int {
PrintLine("================================================");
PrintLine(" Nexus HTTP Server v0.6.0");
PrintLine(" HTTP/1.1 + TLS/mTLS + keep-alive + graceful stop");
PrintLine(" Built with Bux");
PrintLine("================================================");
PrintLine("");
Os_InstallStopHandlers();
var tlsCtx: *void = null as *void;
if config.tlsEnabled {
if String_Eq(config.tlsCertPath, "") || String_Eq(config.tlsKeyPath, "") {
PrintLine("FATAL: TLS enabled but NEXUS_TLS_CERT / NEXUS_TLS_KEY not set");
return 1;
}
if !String_Eq(config.tlsClientCaPath, "") {
tlsCtx = Tls_ServerCtxMtls(config.tlsCertPath, config.tlsKeyPath, config.tlsClientCaPath);
PrintLine("mTLS: client certificates required");
Print("Client CA: ");
PrintLine(config.tlsClientCaPath);
} else {
tlsCtx = Tls_ServerCtx(config.tlsCertPath, config.tlsKeyPath);
}
if tlsCtx == null as *void {
Print("FATAL: TLS context failed: ");
PrintLine(Tls_LastError());
return 1;
}
Print("TLS cert: ");
PrintLine(config.tlsCertPath);
}
let serverFd: int = Net_Create();
if serverFd < 0 {
PrintLine("FATAL: socket() failed");
if tlsCtx != null as *void { Tls_CtxFree(tlsCtx); }
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);
if tlsCtx != null as *void { Tls_CtxFree(tlsCtx); }
return 1;
}
if !Net_Listen(serverFd, config.backlog) {
PrintLine("FATAL: listen() failed");
Net_Close(serverFd);
if tlsCtx != null as *void { Tls_CtxFree(tlsCtx); }
return 1;
}
Os_SetStopListenFd(serverFd);
if config.tlsEnabled {
Print("Listening on https://");
} else {
Print("Listening on http://");
}
Print(config.bindAddr);
Print(":");
PrintInt(config.port);
PrintLine("");
PrintInt(config.workerCount);
Print(" worker threads | keep-alive: on | max-body: ");
PrintInt(config.maxBodyBytes);
PrintLine(" B");
PrintLine("Endpoints: / /api/health /api/info /ws");
PrintLine("Stop: Ctrl+C / SIGTERM (graceful).");
PrintLine("");
let taskQueue: Channel<ConnectionTask> = Channel_New<ConnectionTask>(config.backlog as int64);
let workerCtx: WorkerCtx = WorkerCtx {
taskQueue: &taskQueue,
router: router,
config: config
};
let acceptorCtx: AcceptorCtx = AcceptorCtx {
serverFd: serverFd,
taskQueue: &taskQueue,
workerCount: config.workerCount,
tlsCtx: tlsCtx
};
var i: int = 0;
while i < config.workerCount {
spawn Worker(&workerCtx);
i = i + 1;
}
Acceptor(&acceptorCtx);
Os_SetStopListenFd(-1);
if serverFd >= 0 {
Net_Close(serverFd);
}
if tlsCtx != null as *void {
Tls_CtxFree(tlsCtx);
}
PrintLine("nexus: acceptor stopped — exit");
return 0;
}
}
+2
View File
@@ -0,0 +1,2 @@
bux 0.1.0 (bootstrap)
EXIT:0
+76
View File
@@ -0,0 +1,76 @@
# 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
cd apps/simpledb
../../buxc build
./build/simpledb data.db set hello world
```
## 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;
}
}
+58
View File
@@ -0,0 +1,58 @@
# Bux benchmarks (E.5)
Micro-kernels + optional Nexus HTTP throughput for regression and language comparison.
## Quick run
```bash
# From repo root
make bench # Bux micro + C + Nim (+ Zig if installed)
make bench-nexus # wrk vs apps/nexus /api/health
BENCH_NEXUS=1 make bench
```
## Micro suites
| Suite | Kernels | Build |
|-------|---------|-------|
| `micro/` | Bux: `int_loop`, `fib30`, `string_concat`, `array_push` | `buxc build` |
| `c/` | C: `fib30`, `int_loop` | `gcc -O2` |
| `nim/` | Nim twins | `nim c -d:release --opt:speed` |
| `zig/` | Zig twins (optional) | `zig build-exe -OReleaseFast` |
Output lines:
```
BENCH <name> iters=N total_us=T us_per_op=P
```
## Nexus throughput
`tools/bench_nexus.sh` / `make bench-nexus`:
1. Builds `apps/nexus`
2. Starts on `NEXUS_PORT` (default **18080**), bind `127.0.0.1`
3. `wrk -t4 -c64 -d5s` against `/api/health`
4. Prints `BENCH nexus_health rps=…`
Env knobs:
| Variable | Default | Meaning |
|----------|---------|---------|
| `NEXUS_PORT` | `18080` | Listen port (also used by the server binary) |
| `NEXUS_WORKERS` | `4` | Worker threads |
| `NEXUS_BENCH_DURATION` | `5s` | wrk `-d` |
| `NEXUS_BENCH_C` | `64` | wrk connections |
| `NEXUS_BENCH_T` | `4` | wrk threads |
Server-side (any nexus run):
- `NEXUS_PORT`, `NEXUS_WORKERS`, `NEXUS_BIND`, `NEXUS_PUBLIC`
## Notes
- Numbers vary by machine; use them relatively (same host, same day).
- Nexus **v0.3** uses HTTP/1.1 keep-alive (default). Sample RPS on one machine:
- close-only era: ~45k req/s
- keep-alive: ~80k+ req/s (`wrk -t4 -c64 -d5s /api/health`)
- Requires `wrk` for `bench-nexus` (`apt install wrk` on Debian/Ubuntu).
+26
View File
@@ -0,0 +1,26 @@
/* C reference: recursive fib(30) — compare with benches/micro fib30 */
#include <stdio.h>
#include <stdint.h>
#include <time.h>
static int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
static int64_t now_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000000LL + (int64_t)ts.tv_nsec / 1000LL;
}
int main(void) {
(void)fib(20); /* warmup */
int64_t t0 = now_us();
int r = fib(30);
int64_t t1 = now_us();
if (r < 0) return 1;
printf("BENCH fib30 iters=1 total_us=%lld us_per_op=%lld\n",
(long long)(t1 - t0), (long long)(t1 - t0));
return 0;
}
+24
View File
@@ -0,0 +1,24 @@
/* C reference: 1e6 integer arithmetic loop */
#include <stdio.h>
#include <stdint.h>
#include <time.h>
static int64_t now_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000000LL + (int64_t)ts.tv_nsec / 1000LL;
}
int main(void) {
const int iters = 1000000;
int acc = 0;
int64_t t0 = now_us();
for (int i = 0; i < iters; i++) {
acc = acc + i * 3 - 1;
}
int64_t t1 = now_us();
if (acc == 0 && iters > 0) return 1;
printf("BENCH int_loop iters=%d total_us=%lld us_per_op=%lld\n",
iters, (long long)(t1 - t0), (long long)((t1 - t0) / iters));
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
[Package]
Name = "micro"
Version = "0.1.0"
Type = "bin"
Description = "Micro-benchmarks for Bux (E.5)"
[Build]
Output = "Bin"
+104
View File
@@ -0,0 +1,104 @@
// Micro-benchmarks (E.5) — recursive fib, string concat, array push.
// Prints lines: BENCH name iters total_us us_per_op
import Std::Io::{PrintLine, Print};
import Std::Time::{Time_NowUs};
import Std::String::{String_FromInt, String_Concat, String_Len};
import Std::Array::{Array, Array_New, Array_Push, Array_Len, Array_Get};
func Fib(n: int) -> int {
if n <= 1 {
return n;
}
return Fib(n - 1) + Fib(n - 2);
}
func Report(name: String, iters: int, usTotal: int64) {
var perOpUs: int64 = 0;
if iters > 0 {
perOpUs = usTotal / (iters as int64);
}
Print("BENCH ");
Print(name);
Print(" iters=");
Print(String_FromInt(iters as int64));
Print(" total_us=");
Print(String_FromInt(usTotal));
Print(" us_per_op=");
PrintLine(String_FromInt(perOpUs));
}
func BenchIntLoop() {
let iters: int = 1000000;
var acc: int = 0;
var i: int = 0;
let t0: int64 = Time_NowUs();
while i < iters {
acc = acc + i * 3 - 1;
i = i + 1;
}
let t1: int64 = Time_NowUs();
if acc == 0 && iters > 0 {
PrintLine("unreachable");
}
Report("int_loop", iters, t1 - t0);
}
func BenchFib() {
discard Fib(20);
let n: int = 30;
let t0: int64 = Time_NowUs();
let r: int = Fib(n);
let t1: int64 = Time_NowUs();
if r < 0 {
PrintLine("unreachable");
}
Report("fib30", 1, t1 - t0);
}
func BenchStringConcat() {
let iters: int = 50000;
var i: int = 0;
let t0: int64 = Time_NowUs();
var s: String = "";
while i < iters {
s = String_Concat(s, "x");
if String_Len(s) > 256 {
s = "x";
}
i = i + 1;
}
let t1: int64 = Time_NowUs();
if String_Len(s) < 0 {
PrintLine("unreachable");
}
Report("string_concat", iters, t1 - t0);
}
func BenchArrayPush() {
let iters: int = 100000;
var arr: Array<int> = Array_New<int>(16);
var i: int = 0;
let t0: int64 = Time_NowUs();
while i < iters {
Array_Push<int>(&arr, i);
i = i + 1;
}
let t1: int64 = Time_NowUs();
let n: uint = Array_Len<int>(&arr);
if n as int != iters {
Print("array len mismatch: ");
PrintLine(String_FromInt(n as int64));
}
discard Array_Get<int>(&arr, (iters - 1) as uint);
Report("array_push", iters, t1 - t0);
}
func Main() -> int {
PrintLine("=== Bux micro-benchmarks (E.5) ===");
BenchIntLoop();
BenchFib();
BenchStringConcat();
BenchArrayPush();
PrintLine("=== done ===");
return 0;
}
+16
View File
@@ -0,0 +1,16 @@
## Nim twin of benches/c/fib.c — recursive fib(30)
import std/[times, strformat]
proc fib(n: int): int =
if n <= 1: return n
fib(n - 1) + fib(n - 2)
proc nowUs(): int64 =
int64(epochTime() * 1_000_000.0)
discard fib(20)
let t0 = nowUs()
let r = fib(30)
let t1 = nowUs()
if r < 0: quit(1)
echo &"BENCH fib30 iters=1 total_us={t1 - t0} us_per_op={t1 - t0}"
+15
View File
@@ -0,0 +1,15 @@
## Nim twin of benches/c/int_loop.c — 1e6 integer arithmetic loop
import std/[times, strformat]
proc nowUs(): int64 =
int64(epochTime() * 1_000_000.0)
const iters = 1_000_000
var acc = 0
let t0 = nowUs()
for i in 0 ..< iters:
acc = acc + i * 3 - 1
let t1 = nowUs()
if acc == 0 and iters > 0: quit(1)
let total = t1 - t0
echo &"BENCH int_loop iters={iters} total_us={total} us_per_op={total div iters}"
+22
View File
@@ -0,0 +1,22 @@
// Zig twin of benches/c/fib.c — recursive fib(30)
// Build: zig build-exe -OReleaseFast -femit-bin=build/fib fib.zig
const std = @import("std");
fn fib(n: i32) i32 {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
fn nowUs() i64 {
return @divTrunc(@as(i64, @intCast(std.time.nanoTimestamp())), 1000);
}
pub fn main() !void {
_ = fib(20);
const t0 = nowUs();
const r = fib(30);
const t1 = nowUs();
if (r < 0) return error.Unreachable;
const out = std.io.getStdOut().writer();
try out.print("BENCH fib30 iters=1 total_us={d} us_per_op={d}\n", .{ t1 - t0, t1 - t0 });
}
+22
View File
@@ -0,0 +1,22 @@
// Zig twin of benches/c/int_loop.c — 1e6 integer arithmetic loop
// Build: zig build-exe -OReleaseFast -femit-bin=build/int_loop int_loop.zig
const std = @import("std");
fn nowUs() i64 {
return @divTrunc(@as(i64, @intCast(std.time.nanoTimestamp())), 1000);
}
pub fn main() !void {
const iters: i32 = 1_000_000;
var acc: i32 = 0;
const t0 = nowUs();
var i: i32 = 0;
while (i < iters) : (i += 1) {
acc = acc + i * 3 - 1;
}
const t1 = nowUs();
if (acc == 0 and iters > 0) return error.Unreachable;
const total = t1 - t0;
const out = std.io.getStdOut().writer();
try out.print("BENCH int_loop iters={d} total_us={d} us_per_op={d}\n", .{ iters, total, @divTrunc(total, iters) });
}
+98
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,17 @@ 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
ekMacroCall ## name!(args) — expanded before sema
ekMacroStmt ## `$s:stmt` arg wrapper (expand only)
ekMacroPat ## `$p:pat` arg wrapper (expand only)
ekMacroTt ## `$x:tt` bound fragment (expand only; may flatten groups)
ekMacroRep ## `$( expr ),*` expression-level rep in templates (expand only)
ekMacroType ## `$t:type` bound type (expand only)
MatchArm* = object
loc*: SourceLocation
@@ -172,6 +185,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 +224,48 @@ 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]
of ekMacroCall:
exprMacroName*: string
exprMacroArgs*: seq[Expr]
## Group lengths for multi-rep: `m!(1,2; 3,4)` → @[2, 2].
## Empty means a single group of all args.
exprMacroGroupLens*: seq[int]
of ekMacroStmt:
## Statement fragment argument (`$s:stmt`) — only during expand
exprMacroStmt*: Stmt
of ekMacroPat:
## Pattern fragment argument (`$p:pat`) — only during expand
exprMacroPat*: Pattern
of ekMacroTt:
## Bound `:tt` fragment. `exprMacroTtGroup` is true for parenthesized
## multi-element groups (parsed as tuples) — flattened when spliced as
## the sole argument of a call (`$f($args)` → `f(a, b)`).
exprMacroTtInner*: Expr
exprMacroTtGroup*: bool
of ekMacroRep:
## Expression-level `$( body ),*` / `$( body )*` in macro templates.
exprMacroRepBody*: Expr
of ekMacroType:
## Bound `:type` fragment.
exprMacroType*: TypeExpr
# ---------------------------------------------------------------------------
# Statements
@@ -234,13 +285,21 @@ type
skStaticAssert
skComptime
skEmit
skDefer
skSwitch
skDecl
skMacroRep ## $( … )* template repetition (macro body only)
ElseIf* = object
loc*: SourceLocation
cond*: Expr
blk*: Block
SwitchCase* = object
loc*: SourceLocation
caseValue*: Expr
caseBody*: Block
Block* = ref object
loc*: SourceLocation
stmts*: seq[Stmt]
@@ -294,8 +353,16 @@ 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
of skMacroRep: ## $( stmts… )* in macro templates
stmtMacroRepBody*: Block
# ---------------------------------------------------------------------------
# Type Parameters (for generics with trait bounds)
@@ -322,6 +389,31 @@ type
dkExternFunc
dkExternVar
dkExternBlock
dkMacro ## macro! name { ($x:expr) => { … } }
## One declarative macro arm: ($a:expr, $($x:expr),*) => { template }
MacroFragKind* = enum
mfkExpr ## any expression
mfkIdent ## bare identifier (after expand must be ekIdent)
mfkTt ## token-tree (MVP: same as expr)
mfkLiteral ## int/float/string/char/bool literal only
mfkBlock ## block expression `{ … }`
mfkStmt ## one statement (let/if/… or expression-stmt)
mfkPat ## match/let pattern
mfkType ## type expression (`int`, `*int`, `String`, …)
MacroFragment* = object
name*: string ## primary / first name (compat)
kind*: MacroFragKind ## primary kind (compat)
names*: seq[string] ## one or more $names (compound rep: $a,$b)
kinds*: seq[MacroFragKind] ## parallel to names
isRep*: bool ## true for $( … ),* or $( … )*
repSep*: string ## "," if separator was present before *, else ""
MacroRule* = object
loc*: SourceLocation
frags*: seq[MacroFragment]
body*: Block ## template (substituted, then used as ekBlock)
Param* = object
loc*: SourceLocation
@@ -414,6 +506,9 @@ type
declExtBlockDll*: string
declExtBlockCallConv*: CallingConvention
declExtBlockItems*: seq[Decl]
of dkMacro:
declMacroName*: string
declMacroRules*: seq[MacroRule]
# ---------------------------------------------------------------------------
# Module (AST root)
@@ -441,3 +536,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)
+112 -22
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"
@@ -105,8 +128,31 @@ proc typeToC*(be: var CBackend, typ: Type): string =
of "float64": return "double"
of "bool": return "bool"
else: return typ.name
of tkTuple: return "void*" # TODO: proper tuple struct
of tkFunc: return "void*" # TODO: function pointer
of tkTuple:
if typ.inner.len == 0:
return "Tuple_Empty"
var parts: seq[string] = @[]
for e in typ.inner:
var p = typeToC(be, e)
p = p.replace("const char*", "cstr").replace("unsigned int", "uint")
p = p.replace(" ", "_").replace("*", "Ptr").replace("(", "").replace(")", "").replace(",", "_")
parts.add(p)
let tname = "Tuple_" & parts.join("_")
# Ensure typedef is collected alongside slices
var already = false
for d in be.sliceTypeDefs:
if d.name == tname:
already = true
break
if not already:
# Reuse sliceTypeDefs as a generic "extra typedef" bag: elem holds field list markup
be.sliceTypeDefs.add((name: tname, elem: "/*tuple*/"))
return tname
of tkFunc:
if typ.inner.len == 0: return "void (*)(void)"
let params = typ.inner[0..^2].mapIt(typeToC(be, it)).join(", ")
let ret = typeToC(be, typ.inner[^1])
return ret & " (*)(" & params & ")"
else:
when defined(release):
return "void*"
@@ -249,6 +295,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}))"
@@ -284,10 +334,11 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
return &"({base}).data[{idx}]"
of hTupleInit:
var elems: seq[string] = @[]
for e in node.tupleInitElements:
elems.add(be.emitExpr(e))
return &"{{{elems.join(\", \")}}}"
let typeName = typeToC(be, node.typ)
var fields: seq[string] = @[]
for i, e in node.tupleInitElements:
fields.add(&"._{i} = {be.emitExpr(e)}")
return &"(({typeName}){{{fields.join(\", \")}}})"
of hCast:
let operand = be.emitExpr(node.castOperand)
@@ -350,7 +401,8 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
return "0"
of hMatch:
return "0" # TODO: match expression lowering
# Match should be desugared in hir_lower to if-else. Fallback: 0.
return "0"
else:
return "0"
@@ -358,7 +410,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 +453,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 +505,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 +526,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("")
@@ -514,20 +595,17 @@ proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) =
be.emitLine(&"typedef union {{")
inc be.indent
for v in variants:
if v.fields.len > 0:
# Positional fields
for i, f in v.fields:
let typ = typeToC(be, f)
be.emitLine(&"{typ} {v.name}_{i};")
if v.fields.len == 1:
# Single positional field — flat union member (compat: data.Variant_0)
let typ = typeToC(be, v.fields[0])
be.emitLine(&"{typ} {v.name}_0;")
elif v.fields.len > 1:
# Multi positional — named nested typedef Enum_Variant_Payload
let nestedName = name & "_" & v.name & "_Payload"
be.emitLine(&"{nestedName} {v.name};")
elif v.namedFields.len > 0:
# Named fields - generate as struct
be.emitLine(&"struct {{")
inc be.indent
for nf in v.namedFields:
let typ = typeToC(be, nf.typ)
be.emitLine(&"{typ} {nf.name};")
dec be.indent
be.emitLine(&"}} {v.name};")
let nestedName = name & "_" & v.name & "_Payload"
be.emitLine(&"{nestedName} {v.name};")
dec be.indent
be.emitLine(&"}} {name}_Data;")
be.emitLine("")
@@ -627,14 +705,26 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
if module.structs.len > 0:
be.emitLine("")
# Enum definitions (must come before structs that reference them)
# Nested multi-field enum payloads (Enum_Variant_Payload) must be fully
# defined before the algebraic enum union that embeds them by value.
var payloadStructNames: seq[string] = @[]
for e in module.enums:
for v in e.variants:
if v.fields.len > 1 or v.namedFields.len > 0:
payloadStructNames.add(e.name & "_" & v.name & "_Payload")
for s in module.structs:
if s.name in payloadStructNames:
be.emitStruct(s.name, s.fields)
# Enum definitions (after payload structs; before user structs that may use them)
for e in module.enums:
be.emitEnum(e.name, e.variants)
if module.enums.len > 0:
be.emitLine("")
# Struct definitions
# Remaining struct definitions (skip payloads already emitted)
for s in module.structs:
if s.name notin payloadStructNames:
be.emitStruct(s.name, s.fields)
# Slice fat-pointer typedefs
+829 -46
View File
File diff suppressed because it is too large Load Diff
+202
View File
@@ -0,0 +1,202 @@
## docgen.nim — Extract `///` (and adjacent `/* */`) docs into Markdown.
## Used by `bux doc [path...]`.
import std/[os, strutils, strformat, algorithm]
type
DocItem* = object
kind*: string ## module | func | struct | enum | interface | extern
name*: string
signature*: string ## first declaration line (trimmed)
docs*: string
file*: string
line*: int
proc isDeclStart(line: string): bool =
let s = line.strip()
if s.len == 0: return false
# Leading attributes @[Checked] etc. — not a decl by themselves
if s.startsWith("@["): return false
if s.startsWith("func ") or s.startsWith("pub func ") or
s.startsWith("extern func ") or s.startsWith("const func ") or
s.startsWith("async func "):
return true
if s.startsWith("struct ") or s.startsWith("pub struct ") or
s.startsWith("enum ") or s.startsWith("pub enum ") or
s.startsWith("union ") or s.startsWith("interface ") or
s.startsWith("module ") or s.startsWith("type "):
return true
return false
proc declKindAndName(line: string): tuple[kind, name: string] =
var s = line.strip()
# Strip leading pub/extern/const/async
for prefix in ["pub ", "extern ", "const ", "async "]:
if s.startsWith(prefix):
s = s[prefix.len .. ^1].strip()
var kind = "item"
if s.startsWith("func "):
kind = "func"
s = s["func ".len .. ^1]
elif s.startsWith("struct "):
kind = "struct"
s = s["struct ".len .. ^1]
elif s.startsWith("enum "):
kind = "enum"
s = s["enum ".len .. ^1]
elif s.startsWith("union "):
kind = "union"
s = s["union ".len .. ^1]
elif s.startsWith("interface "):
kind = "interface"
s = s["interface ".len .. ^1]
elif s.startsWith("module "):
kind = "module"
s = s["module ".len .. ^1]
elif s.startsWith("type "):
kind = "type"
s = s["type ".len .. ^1]
# Name: until `<` `(` `{` `:` space
var name = ""
for ch in s:
if ch in {' ', '<', '(', '{', ':', ';'}:
break
name.add(ch)
if name.len == 0:
name = s
# extern funcs already stripped "extern "
if kind == "func" and line.strip().startsWith("extern"):
kind = "extern"
return (kind, name)
proc extractDocsFromSource*(source, path: string): seq[DocItem] =
result = @[]
var pending: seq[string] = @[]
var inBlockComment = false
var blockDoc: seq[string] = @[]
var lineNo = 0
for rawLine in source.splitLines():
inc lineNo
var line = rawLine
let stripped = line.strip()
# Block comment handling (/* ... */ used in stdlib today)
if inBlockComment:
let endIdx = stripped.find("*/")
if endIdx >= 0:
let before = stripped[0 ..< endIdx].strip()
if before.len > 0:
blockDoc.add(before)
inBlockComment = false
# Treat completed block as pending doc if non-empty
if blockDoc.len > 0:
pending = blockDoc
blockDoc = @[]
continue
else:
blockDoc.add(stripped)
continue
if stripped.startsWith("/*") and not stripped.startsWith("/***"):
let rest = stripped["/*".len .. ^1]
let endIdx = rest.find("*/")
if endIdx >= 0:
let body = rest[0 ..< endIdx].strip()
if body.len > 0:
pending = @[body]
else:
inBlockComment = true
blockDoc = @[]
let body = rest.strip()
if body.len > 0:
blockDoc.add(body)
continue
# Triple-slash doc comments
if stripped.startsWith("///"):
var body = stripped["///".len .. ^1]
if body.startsWith(" "):
body = body[1 .. ^1]
pending.add(body)
continue
# Empty line: keep pending docs (allow blank lines inside doc blocks)
if stripped.len == 0:
continue
# Attributes immediately before decl: keep pending
if stripped.startsWith("@["):
continue
if isDeclStart(line):
if pending.len > 0:
let (kind, name) = declKindAndName(line)
result.add(DocItem(
kind: kind,
name: name,
signature: stripped,
docs: pending.join("\n"),
file: path,
line: lineNo
))
pending = @[]
continue
# Other code clears pending (except plain // comments)
if stripped.startsWith("//"):
continue
pending = @[]
proc collectBuxFilesForDoc*(root: string): seq[string] =
result = @[]
if fileExists(root) and root.endsWith(".bux"):
result.add(root)
return
if not dirExists(root):
return
for path in walkDirRec(root):
if path.endsWith(".bux"):
result.add(path)
result.sort(system.cmp)
proc renderMarkdown*(items: seq[DocItem], title: string = "API Reference"): string =
var sb: string
sb.add(&"# {title}\n\n")
sb.add("Generated by `bux doc` from `///` and `/* */` documentation comments.\n\n")
if items.len == 0:
sb.add("_No documented items found._\n")
return sb
# Group by file
var byFile: seq[string] = @[]
for it in items:
if it.file notin byFile:
byFile.add(it.file)
byFile.sort(system.cmp)
for f in byFile:
let base = splitFile(f).name
sb.add(&"## `{base}`\n\n")
sb.add(&"_Source: `{f}`_\n\n")
for it in items:
if it.file != f:
continue
sb.add(&"### `{it.name}` _{it.kind}_\n\n")
sb.add("```bux\n")
sb.add(it.signature)
sb.add("\n```\n\n")
if it.docs.len > 0:
sb.add(it.docs)
sb.add("\n\n")
return sb
proc generateDocs*(paths: seq[string]): seq[DocItem] =
result = @[]
var files: seq[string] = @[]
for p in paths:
for f in collectBuxFilesForDoc(p):
if f notin files:
files.add(f)
files.sort(system.cmp)
for f in files:
let src = readFile(f)
result.add(extractDocsFromSource(src, f))
+106
View File
@@ -0,0 +1,106 @@
## fmt.nim — Indentation-based Bux source formatter (bootstrap).
## Mirrors selfhost `src/fmt.bux`: re-indent by brace depth, preserve content.
import std/[strutils, os, algorithm]
proc isInStringOrComment(line: string, pos: int): bool =
## Simplified: track `//`, `"..."`, and `'...'` up to `pos`.
var inString = false
var inChar = false
var inComment = false
var i = 0
while i < pos and i < line.len:
let c = line[i]
let n = if i + 1 < line.len: line[i + 1] else: '\0'
if inComment:
inc i
continue
if c == '/' and n == '/':
inComment = true
inc i
continue
if c == '"' and not inChar:
inString = not inString
if c == '\'' and not inString:
inChar = not inChar
inc i
return inString or inChar or inComment
proc countBraceDelta(line: string): int =
var delta = 0
for i in 0 ..< line.len:
if isInStringOrComment(line, i):
continue
let c = line[i]
if c == '{':
inc delta
elif c == '}':
dec delta
return delta
proc formatSource*(source: string): string =
## Re-indent each non-empty line to 4 spaces × brace depth.
## Idempotent: formatting a clean file is a no-op.
var sb: string
var indent = 0
# Nim's splitLines leaves a trailing "" when the source ends with '\n'.
# Drop that artifact so we don't accumulate blank lines on re-format.
var lines = source.splitLines(keepEol = false)
if source.len > 0 and source.endsWith('\n') and lines.len > 0 and lines[^1].len == 0:
lines.setLen(lines.len - 1)
for line in lines:
let trimmed = line.strip(leading = true, trailing = false)
if trimmed.len == 0:
sb.add('\n')
continue
let delta = countBraceDelta(trimmed)
let firstChar = trimmed[0]
if firstChar == '}':
dec indent
if indent < 0:
indent = 0
for _ in 0 ..< indent:
sb.add(" ")
sb.add(trimmed)
sb.add('\n')
if firstChar != '}':
indent = indent + delta
else:
# Net delta after the initial decrease for a leading `}`
indent = indent + delta + 1
if indent < 0:
indent = 0
return sb
proc formatFile*(path: string, checkOnly: bool): tuple[ok: bool, changed: bool, msg: string] =
## Format `path` in place, or only check if reformatting would change it.
if not fileExists(path):
return (false, false, "file not found: " & path)
let source = readFile(path)
let formatted = formatSource(source)
if formatted == source:
return (true, false, "")
if checkOnly:
return (true, true, "would reformat")
try:
writeFile(path, formatted)
return (true, true, "formatted")
except CatchableError as e:
return (false, false, e.msg)
proc collectBuxFiles*(root: string): seq[string] =
## Collect `.bux` files: single file, or recursive directory walk.
result = @[]
if fileExists(root) and root.endsWith(".bux"):
result.add(root)
return
if not dirExists(root):
return
for path in walkDirRec(root):
if path.endsWith(".bux"):
result.add(path)
result.sort(system.cmp)
+27
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
@@ -156,6 +163,7 @@ type
HirMatchArm* = object
pattern*: Pattern
body*: HirNode
guard*: HirNode ## optional: lowered `if cond` from `p if cond => body`
HirFunc* = object
name*: string
@@ -163,6 +171,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
@@ -177,6 +190,10 @@ type
consts*: seq[tuple[name: string, typ: Type, value: HirNode]]
interfaces*: seq[tuple[name: string, hasAssocTypes: bool, methods: seq[tuple[name: string, params: seq[Type], ret: Type]]]]
vtables*: seq[tuple[interfaceName: string, concreteType: string, methodNames: seq[string], hasAssocTypes: bool]]
## Named functions used as values → need __adapt_ wrappers for fat-func ABI
funcAdapters*: seq[tuple[name: string, typ: Type]]
## Extra func types seen in locals/closures that need BuxFn_* typedefs
seenFatTypes*: seq[Type]
# Constructor helpers
proc hirLit*(tok: Token, typ: Type, loc: SourceLocation): HirNode =
@@ -200,15 +217,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)
+1792 -138
View File
File diff suppressed because it is too large Load Diff
+20 -24
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:
@@ -76,14 +70,12 @@ proc matchStr(lex: var Lexer, s: string): bool =
return true
proc currentLocation(lex: Lexer): SourceLocation =
result = SourceLocation(line: lex.line, column: lex.col, offset: uint32(lex.pos))
result = SourceLocation(line: lex.line, column: lex.col, offset: uint32(lex.pos),
file: lex.sourceName)
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)
@@ -236,6 +228,9 @@ proc scanEscapeSequence(lex: var Lexer): string =
of 'r': result = "\r"
of 't': result = "\t"
of '0': result = "\0"
of '{', '}':
# Preserve \{ and \} so f"..." interpolation can treat them as literal braces
result = "\\" & $c
of 'x':
var hexVal = ""
for _ in 0..<2:
@@ -337,19 +332,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)
@@ -482,6 +464,17 @@ proc scanSymbol(lex: var Lexer, startLoc: SourceLocation): Token =
return lex.makeToken(tkCaretAssign, startLoc, startPos)
else:
return lex.makeToken(tkCaret, startLoc, startPos)
of '$':
# $name fragment, or bare $ for macro repetition $( ... )*
if isIdentStart(lex.peek()):
discard lex.advance() # first ident char ( $ already consumed as c1)
while not lex.isAtEnd() and isIdentChar(lex.peek()):
discard lex.advance()
# text includes leading '$'
return lex.makeToken(tkIdent, startLoc, startPos)
else:
# bare $ (c1 already consumed)
return lex.makeToken(tkDollar, startLoc, startPos)
of '#':
# Check for intrinsics: #line, #column, #file, #function, #date, #time, #module
let afterHash = lex.peek()
@@ -527,7 +520,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

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