diff --git a/CHANGELOG.md b/CHANGELOG.md index cba80a5..68a3396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.10.0] - 2026-08-29 + +### 🌍 Cross-Compilation & Target Triples (`--target`) +- Added `--target ` flag to `zuv.exe` and `zuv_selfhost.exe` for cross-compiling to any LLVM target: + - `windows-x64` → `x86_64-pc-windows-msvc` (default, full link) + - `linux-x64` → `x86_64-unknown-linux-gnu` (ELF `.o`) + - `linux-arm64` → `aarch64-unknown-linux-gnu` (ELF AArch64 `.o`) + - `macos-arm64` → `arm64-apple-darwin` (Mach-O `.o`) + - `macos-x64` → `x86_64-apple-darwin` (Mach-O `.o`) + - Raw LLVM triple passthrough supported. +- Initialized AArch64 LLVM backend alongside X86 in both compilers. +- Dynamic `target triple` injected into IR before emission. +- Non-Windows targets emit `.o` only (cross-link skipped with a clear message). +- Added `tests/cross_target.test.zv` in both `tests/` dirs. +- `--help` updated with `--target` aliases. + +### 🏷️ AST Attribute Declarations & Metaprogramming Annotations +- Added `@name` / `@name(...)` attribute syntax on functions and `obj` declarations. +- `@inline` / `@inline(always)` → LLVM `alwaysinline`; `@noinline` → `noinline`. +- Supports `@test`, `@derive(...)`, `@deprecated`, and custom attribute args. +- Added `tests/attributes.test.zv`. + +### 🔗 Multi-line `extern` & Block Declarations +- Added `extern "lib" { ... }` and `pub extern "C" { ... }` block syntax for grouping multiple FFI declarations in one block. +- `pub extern "C" { ... }` auto-emits `dllexport` on all functions inside. +- Removed manual `extern "msvcrt.dll" system` from `cli.zv`; uses builtin `sh` instead. +- Updated tests: `cdylib_export.test.zv`, `c_ffi_lib.test.zv`, `call_cdylib.test.zv`. + ## [0.9.0] - 2026-08-27 ### 🔌 JS Interop & Shared C Dynamic Library Output (`--cdylib` / `--lib`) diff --git a/README.md b/README.md index f6dc1e1..31c634e 100644 --- a/README.md +++ b/README.md @@ -179,36 +179,112 @@ zuv --help ## 🛠️ Building & Running the Self-Hosting Compiler -### 1. Compile the Self-Hosting Compiler -Compile the pure Zuv compiler sources: +### Prerequisites +- **Windows** with [LLVM](https://llvm.org/releases/) installed (e.g. `D:\LLVM`) +- **lld-link.exe** — comes with LLVM, used by `zuv.exe` for native linking + +--- + +### 1. Build the Self-Hosting Compiler (`zuv_selfhost.exe`) + ```powershell -zuv build src/main.zv +# From the repo root +.\zuv.exe build sub_projects/zuv/src/main.zv -o sub_projects/zuv/zuv_selfhost.exe ``` -To install the built compiler to your default binary folder: + +To install it as your system `zuv`: ```powershell -Copy-Item output.exe "C:\Users\$env:USERNAME\zuv\bin\zuv.exe" -Force +Copy-Item sub_projects\zuv\zuv_selfhost.exe "$HOME\zuv\bin\zuv.exe" -Force ``` -### 2. Run the Native AOT Test Runner -Execute all 27 unit test suites natively using `zuv`: +--- + +### 2. Run the Test Suite (All 4 Runners) + ```powershell -zuv test +# Bootstrap compiler — from repo root +.\zuv.exe test # compile & run all tests/ +.\zuv.exe checkall # parse & type-check all tests/ (no LLVM emit) + +# Self-hosting compiler — from sub_projects/zuv/ +.\zuv_selfhost.exe test +.\zuv_selfhost.exe checkall ``` -### 3. Run Static Type & Borrow Checker -Verify syntax and borrow safety across all test suites without emitting binaries: +Expected results: + +| Runner | Passing | +|--------|---------| +| `zuv.exe test` | ✅ | +| `zuv.exe checkall` | ✅ | +| `zuv_selfhost.exe test` | ✅ | +| `zuv_selfhost.exe checkall` | ✅ | + +> The 2 intentional failures (`redecl_error.test.zv`, `semantic_errors.test.zv`) are negative tests — they are expected to fail. + +--- + +### 3. Build & Run a Single Zuv Program + ```powershell -zuv checkall +# Build to output.exe (default) +.\zuv.exe build tests/functions.test.zv + +# Build with custom output name +.\zuv.exe build tests/functions.test.zv -o my_app.exe + +# Build & run in one step +.\zuv.exe run tests/functions.test.zv + +# Release build (-O3) +.\zuv.exe build tests/functions.test.zv --release -o my_app.exe ``` -### 4. Build and Run a Zuv Program +--- + +### 4. Cross-Compile to Another Platform + +> Emits a native object file (`.o`) for the target. Cross-linking requires a target sysroot. + +```powershell +# Linux x64 (ELF) +.\zuv.exe build src/main.zv --target linux-x64 -o output_linux.o + +# Linux ARM64 (ELF AArch64) +.\zuv.exe build src/main.zv --target linux-arm64 -o output_arm64.o + +# macOS ARM64 (Mach-O) +.\zuv.exe build src/main.zv --target macos-arm64 -o output_macos.o + +# Raw LLVM triple +.\zuv.exe build src/main.zv --target x86_64-unknown-linux-musl -o output.o +``` + +Also works with `zuv_selfhost.exe`: +```powershell +.\zuv_selfhost.exe build tests/func_simple.test.zv --target linux-x64 -o out.o +``` + +--- + +### 5. Shared C Library (`.dll` / `.so`) + +```powershell +.\zuv.exe build math.zv --cdylib -o math.dll +``` + +--- + +### 6. Emit LLVM IR + ```powershell -zuv build tests/functions.test.zv -zuv run tests/functions.test.zv +.\zuv.exe build src/main.zv --emit-llvm +# Produces output.ll and output_debug.ll in the current directory ``` --- + ## 🤝 Contributing We welcome contributions from developers worldwide! Please review our [Contributing Guide](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) before submitting pull requests. diff --git a/src/cli.zv b/src/cli.zv index 6d0bab2..4ed9a67 100644 --- a/src/cli.zv +++ b/src/cli.zv @@ -11,7 +11,14 @@ extern "LLVMCore.lib" LLVMInitializeX86TargetMC -> void extern "LLVMCore.lib" LLVMInitializeX86AsmPrinter -> void extern "LLVMCore.lib" LLVMInitializeX86AsmParser -> void -extern "LLVMCore.lib" LLVMGetGlobalContext -> ptr +extern "LLVMCore.lib" LLVMInitializeAArch64TargetInfo -> void +extern "LLVMCore.lib" LLVMInitializeAArch64Target -> void +extern "LLVMCore.lib" LLVMInitializeAArch64TargetMC -> void +extern "LLVMCore.lib" LLVMInitializeAArch64AsmPrinter -> void +extern "LLVMCore.lib" LLVMInitializeAArch64AsmParser -> void + +extern "LLVMCore.lib" LLVMContextCreate -> ptr +extern "LLVMCore.lib" LLVMContextDispose ctx: ptr -> void extern "LLVMCore.lib" LLVMCreateMemoryBufferWithMemoryRange data: ptr, len: num, name: str, reqNull: num -> ptr extern "LLVMCore.lib" LLVMParseIRInContext ctx: ptr, memBuf: ptr, outMod: ptr, outMsg: ptr -> num extern "LLVMTarget.lib" LLVMGetTargetFromTriple triple: str, target: ptr, errMsg: ptr -> num @@ -20,17 +27,27 @@ extern "LLVMTarget.lib" LLVMTargetMachineEmitToFile tm: ptr, mod: ptr, filename: extern "LLVMTarget.lib" LLVMDisposeTargetMachine tm: ptr -> void extern "LLVMCore.lib" LLVMDisposeModule mod: ptr -> void -extern "msvcrt.dll" system cmd: str -> num - -emitDirectObjectFile ir, outputObjPath, isRelease { +emitDirectObjectFile ir, outputObjPath, isRelease, targetTriple { LLVMInitializeX86TargetInfo LLVMInitializeX86Target LLVMInitializeX86TargetMC LLVMInitializeX86AsmPrinter LLVMInitializeX86AsmParser - fullIr = `target triple = "x86_64-pc-windows-msvc"\n${ir}` - ctx = LLVMGetGlobalContext + LLVMInitializeAArch64TargetInfo + LLVMInitializeAArch64Target + LLVMInitializeAArch64TargetMC + LLVMInitializeAArch64AsmPrinter + LLVMInitializeAArch64AsmParser + + // Resolve the LLVM triple + mut resolvedTriple = "x86_64-pc-windows-msvc" + if targetTriple != "" { + resolvedTriple = targetTriple + } + + fullIr = `target triple = "${resolvedTriple}"\n${ir}` + ctx = LLVMContextCreate memBuf = LLVMCreateMemoryBufferWithMemoryRange fullIr, fullIr.ln, "zuv_ir", 0 let outModPtr = malloc 8 @@ -39,17 +56,23 @@ emitDirectObjectFile ir, outputObjPath, isRelease { if res != 0 { errMsg = zuv_ptr_deref outMsgPtr prnt (`[LLVM Error] IR Parse Failed: ${errMsg}`) + LLVMContextDispose ctx + free outModPtr + free outMsgPtr -> 1 } mod = zuv_ptr_deref outModPtr - triple = "x86_64-pc-windows-msvc" let targetPtr = malloc 8 - tRes = LLVMGetTargetFromTriple triple, targetPtr, outMsgPtr + tRes = LLVMGetTargetFromTriple resolvedTriple, targetPtr, outMsgPtr if tRes != 0 { errMsg2 = zuv_ptr_deref outMsgPtr - prnt (`[LLVM Error] Target lookup failed: ${errMsg2}`) + prnt (`[LLVM Error] Target lookup failed for '${resolvedTriple}': ${errMsg2}`) LLVMDisposeModule mod + LLVMContextDispose ctx + free outModPtr + free outMsgPtr + free targetPtr -> 1 } target = zuv_ptr_deref targetPtr @@ -59,18 +82,26 @@ emitDirectObjectFile ir, outputObjPath, isRelease { optLevel = 3 } - tm = LLVMCreateTargetMachine target, triple, "generic", "", optLevel, 0, 0 + tm = LLVMCreateTargetMachine target, resolvedTriple, "generic", "", optLevel, 0, 0 emitRes = LLVMTargetMachineEmitToFile tm, mod, outputObjPath, 1, outMsgPtr if emitRes != 0 { errMsg3 = zuv_ptr_deref outMsgPtr prnt (`[LLVM Error] Object code emission failed: ${errMsg3}`) LLVMDisposeTargetMachine tm LLVMDisposeModule mod + LLVMContextDispose ctx + free outModPtr + free outMsgPtr + free targetPtr -> 1 } LLVMDisposeTargetMachine tm LLVMDisposeModule mod + LLVMContextDispose ctx + free outModPtr + free outMsgPtr + free targetPtr -> 0 } @@ -101,15 +132,21 @@ linkDirectObjectFile objPath, outExePath, isCdylib, customLibs { mut dllFlags = "-stack:33554432" if isDll == 1 { - dllFlags = "-dll -noentry -defaultlib:ucrt -defaultlib:vcruntime" + mut outLibPath = outExePath + if (strEndsWith outLibPath, ".dll") == 1 || (strEndsWith outLibPath, ".DLL") == 1 { + outLibPath = `${outLibPath.slc 0, (outLibPath.ln - 4)}.lib` + } els { + outLibPath = `${outLibPath}.lib` + } + dllFlags = `-dll -implib:"${outLibPath}" -noentry -defaultlib:ucrt -defaultlib:vcruntime` } cmd = `${lldBin} "${objPath}" -out:"${outExePath}" ${dllFlags} -defaultlib:libcmt ${customLibs} -defaultlib:legacy_stdio_definitions -defaultlib:oldnames -defaultlib:user32 -defaultlib:kernel32 -nologo` - res = system cmd + res = sh cmd -> res } -handleBuild targetFile, isRelease, emitLlvm, outExe, isCdylib { +handleBuild targetFile, isRelease, emitLlvm, outExe, isCdylib, targetTriple { if fE targetFile { code = rF targetFile prnt "===========================================" @@ -194,19 +231,43 @@ handleBuild targetFile, isRelease, emitLlvm, outExe, isCdylib { prnt "[zuv build] Successfully generated output_selfhost.ll via self-hosting compiler!" -> 1 } els { - objPath = `${targetExe}.obj` - emitRes = emitDirectObjectFile ir, objPath, isRelease + // Determine if cross-compiling to a non-Windows target + // A triple is non-Windows if it ends with a known Linux/macOS/WASM suffix + mut isCross = 0 + if targetTriple != "" { + isLinuxGnu = strEndsWith targetTriple, "linux-gnu" + isLinuxMusl = strEndsWith targetTriple, "linux-musl" + isDarwin = strEndsWith targetTriple, "apple-darwin" + isWasm = strEndsWith targetTriple, "wasm32" + if isLinuxGnu == 1 || isLinuxMusl == 1 || isDarwin == 1 || isWasm == 1 { + isCross = 1 + } + } + + // For cross-compile targets, emit .o directly (no Windows linker) + mut objOutPath = `${targetExe}.obj` + if isCross == 1 { + objOutPath = targetExe + } + + emitRes = emitDirectObjectFile ir, objOutPath, isRelease, targetTriple if emitRes != 0 { prnt "[zuv build] Direct in-process LLVM object code generation failed." -> 0 } + if isCross == 1 { + prnt (`[zuv build] Cross-compile: object file emitted: ${objOutPath}`) + prnt (`[zuv build] Note: cross-linking to ${targetTriple} requires a target sysroot.`) + -> 1 + } + mut customLibs = "" let li = 0 wh li < prog.statements.ln { s = prog.statements[li] if s.kind == "ExternDecl" { - if s.libName != "" && (strEq s.libName, "c") == 0 && (strEq s.libName, "libc") == 0 { + if s.libName != "" && (strEq s.libName, "c") == 0 && (strEq s.libName, "C") == 0 && (strEq s.libName, "libc") == 0 && (strEq s.libName, "LIBC") == 0 { mut cleanLib = s.libName if (strEndsWith cleanLib, ".dll") == 1 || (strEndsWith cleanLib, ".DLL") == 1 { cleanLib = cleanLib.slc 0, (cleanLib.ln - 4) @@ -217,10 +278,30 @@ handleBuild targetFile, isRelease, emitLlvm, outExe, isCdylib { customLibs = `${customLibs} -defaultlib:${cleanLib}` } } + } els if s.kind == "StmtSequence" { + let subLi = 0 + wh subLi < s.statements.ln { + subS = s.statements[subLi] + if subS.kind == "ExternDecl" { + if subS.libName != "" && (strEq subS.libName, "c") == 0 && (strEq subS.libName, "C") == 0 && (strEq subS.libName, "libc") == 0 && (strEq subS.libName, "LIBC") == 0 { + mut cleanSubLib = subS.libName + if (strEndsWith cleanSubLib, ".dll") == 1 || (strEndsWith cleanSubLib, ".DLL") == 1 { + cleanSubLib = cleanSubLib.slc 0, (cleanSubLib.ln - 4) + } + if (strEndsWith cleanSubLib, ".lib") == 1 || (strEndsWith cleanSubLib, ".LIB") == 1 { + customLibs = `${customLibs} "${cleanSubLib}"` + } els { + customLibs = `${customLibs} -defaultlib:${cleanSubLib}` + } + } + } + subLi = subLi + 1 + } } li = li + 1 } + let objPath = objOutPath linkRes = linkDirectObjectFile objPath, targetExe, isCdylib, customLibs if fE objPath { rmF objPath @@ -258,6 +339,7 @@ handleBuild targetFile, isRelease, emitLlvm, outExe, isCdylib { } } + handleCheck targetFile { if fE targetFile { code = rF targetFile @@ -295,7 +377,7 @@ handleCheck targetFile { } handleRun targetFile { - res = handleBuild targetFile, 0, 0, "output.exe" + res = handleBuild targetFile, 0, 0, "output.exe", 0, "" if res == 1 { prnt (`[zuv run] Executing output.exe for ${targetFile}`) sh ".\\output.exe" @@ -303,6 +385,7 @@ handleRun targetFile { -> res } + handleFmt targetFile { if fE targetFile { code = rF targetFile @@ -364,7 +447,7 @@ handleTest { tFile = tests[ti] tStart = nw tempExe = `test_run_temp_${ti}.exe` - buildOk = handleBuild tFile, 0, 0, tempExe, 0 + buildOk = handleBuild tFile, 0, 0, tempExe, 0, "" if buildOk == 0 { tElapsed = nw - tStart @@ -405,9 +488,10 @@ handleTest { } runCompiler targetFile { - handleBuild targetFile, 0, 0, "output.exe", 0 + handleBuild targetFile, 0, 0, "output.exe", 0, "" } runCompilerOpt targetFile, isRelease, emitLlvm, outExe { - handleBuild targetFile, isRelease, emitLlvm, outExe, 0 + handleBuild targetFile, isRelease, emitLlvm, outExe, 0, "" } + diff --git a/src/codegen.zv b/src/codegen.zv index 46fde1c..70da6ab 100644 --- a/src/codegen.zv +++ b/src/codegen.zv @@ -3225,6 +3225,10 @@ codegenStmt cg, stmt, outBuf { afterThr = newLbl cg, "after_thr_exit" outBuf.code = outBuf.code.cat (`\n${afterThr}:\n`) } + } els if stmt.kind == "FunctionDecl" { + codegenFunctionDecl cg, stmt + } els if stmt.kind == "ExternDecl" { + addCgExtern cg, stmt } els if stmt.kind == "EnumDecl" { registerEnum cg, stmt } els if stmt.kind == "UnsafeBlock" { @@ -3275,7 +3279,25 @@ codegenFunctionDecl cg, funcStmt { exportAttr = "dllexport " } - let fnBuf = { code: (`define ${exportAttr}double @${fnName}(${paramsStr}) {\nentry:\n`) } + mut inlineAttr = "" + if funcStmt.attributes.ln > 0 { + let ai = 0 + wh ai < funcStmt.attributes.ln { + attr = funcStmt.attributes[ai] + if attr.name == "inline" { + if attr.args.ln == 0 || attr.args[0] == "always" { + inlineAttr = "alwaysinline " + } els if attr.args[0] == "never" { + inlineAttr = "noinline " + } + } els if attr.name == "noinline" { + inlineAttr = "noinline " + } + ai = ai + 1 + } + } + + let fnBuf = { code: (`define ${exportAttr}double @${fnName}(${paramsStr}) ${inlineAttr}{\nentry:\n`) } let j = 0 wh j < funcStmt.params.ln { @@ -3391,7 +3413,7 @@ generateLLVMFull program { header = header.cat "@glb = global i64 0\n" addCgSymbol cg, "glb", "@glb" - // Pass 1: Declare top-level variables as global variables + // Pass 1: Declare top-level variables as global variables and register externs let gi = 0 wh gi < program.statements.ln { stmt = program.statements[gi] @@ -3411,48 +3433,6 @@ generateLLVMFull program { addCgSymbol cg, stmt.init.name, gReg } } - } els if stmt.kind == "ExternDecl" { - addCgExtern cg, stmt - } - gi = gi + 1 - } - - // Pass: register rest-param indices before generating any function bodies - let ri = 0 - wh ri < program.statements.ln { - rst = program.statements[ri] - if rst.kind == "FunctionDecl" { - if rst.hasRest == 1 { - if rst.params.ln > 0 { - cg.restNames.psh rst.name - rIdx = rst.params.ln - 1 - cg.restIdxs.psh rIdx - } - } - } - ri = ri + 1 - } - - // Collect and generate function definitions & top-level statements - let i = 0 - wh i < program.statements.ln { - stmt = program.statements[i] - if stmt.kind == "FunctionDecl" { - cg.isMain = 0 - codegenFunctionDecl cg, stmt - } els if stmt.kind == "StmtSequence" { - let seqi = 0 - wh seqi < stmt.statements.ln { - subS = stmt.statements[seqi] - if subS.kind == "FunctionDecl" { - cg.isMain = 0 - codegenFunctionDecl cg, subS - } els { - cg.isMain = 1 - codegenStmt cg, subS, mainBuf - } - seqi = seqi + 1 - } } els if stmt.kind == "ExternDecl" { let extParams = "" let pi = 0 @@ -3472,7 +3452,6 @@ generateLLVMFull program { extParams = extParams + pt pi = pi + 1 } - // Default to i32 (Win32 / C int); only use void when explicitly declared let retType = "i32" if stmt.typeName == "void" { retType = "void" @@ -3490,11 +3469,121 @@ generateLLVMFull program { header = header.cat (`declare ${retType} @${stmt.name}(${extParams})\n`) } addCgExtern cg, stmt - } els { - cg.isMain = 1 - codegenStmt cg, stmt, mainBuf + } els if stmt.kind == "StmtSequence" { + let seqg = 0 + wh seqg < stmt.statements.ln { + subG = stmt.statements[seqg] + if subG.kind == "ExternDecl" { + let extParams2 = "" + let pi2 = 0 + wh pi2 < subG.params.ln { + p2 = subG.params[pi2] + let pt2 = "i32" + if p2.typeName == "str" || p2.typeName == "ptr" { + pt2 = "ptr" + } els if p2.typeName == "double" || p2.typeName == "num" { + pt2 = "double" + } els if p2.typeName == "i64" { + pt2 = "i64" + } + if pi2 > 0 { + extParams2 = `${extParams2}, ` + } + extParams2 = extParams2 + pt2 + pi2 = pi2 + 1 + } + let retType2 = "i32" + if subG.typeName == "void" { + retType2 = "void" + } els if subG.typeName == "ptr" || subG.typeName == "str" { + retType2 = "ptr" + } els if subG.typeName == "double" { + retType2 = "double" + } els if subG.typeName == "i64" { + retType2 = "i64" + } els if subG.typeName == "num" || subG.typeName == "i32" { + retType2 = "i32" + } + declSig2 = `@${subG.name}(` + if (header.cnt declSig2) == 0 { + header = header.cat (`declare ${retType2} @${subG.name}(${extParams2})\n`) + } + addCgExtern cg, subG + } + seqg = seqg + 1 + } } - i = i + 1 + gi = gi + 1 + } + + // Pass: register rest-param indices before generating any function bodies + let ri = 0 + wh ri < program.statements.ln { + rst = program.statements[ri] + if rst.kind == "FunctionDecl" { + if rst.hasRest == 1 { + if rst.params.ln > 0 { + cg.restNames.psh rst.name + rIdx = rst.params.ln - 1 + cg.restIdxs.psh rIdx + } + } + } els if rst.kind == "StmtSequence" { + let seqr = 0 + wh seqr < rst.statements.ln { + subR = rst.statements[seqr] + if subR.kind == "FunctionDecl" && subR.hasRest == 1 && subR.params.ln > 0 { + cg.restNames.psh subR.name + rIdx = subR.params.ln - 1 + cg.restIdxs.psh rIdx + } + seqr = seqr + 1 + } + } + ri = ri + 1 + } + + // Pass 2a: Generate all function definitions + cg.isMain = 0 + let fi = 0 + wh fi < program.statements.ln { + stmt = program.statements[fi] + if stmt.kind == "FunctionDecl" { + codegenFunctionDecl cg, stmt + } els if stmt.kind == "StmtSequence" { + let seqi = 0 + wh seqi < stmt.statements.ln { + subS = stmt.statements[seqi] + if subS.kind == "FunctionDecl" { + codegenFunctionDecl cg, subS + } + seqi = seqi + 1 + } + } + fi = fi + 1 + } + + // Pass 2b: Generate top-level statements into main + cg.isMain = 1 + clearCgLocals cg + let ti = 0 + wh ti < program.statements.ln { + stmt = program.statements[ti] + if stmt.kind != "FunctionDecl" && stmt.kind != "ExternDecl" && stmt.kind != "ImportStmt" { + if stmt.kind == "StmtSequence" { + let seqti = 0 + wh seqti < stmt.statements.ln { + subS = stmt.statements[seqti] + if subS.kind != "FunctionDecl" && subS.kind != "ExternDecl" && subS.kind != "ImportStmt" { + codegenStmt cg, subS, mainBuf + } + seqti = seqti + 1 + } + } els { + codegenStmt cg, stmt, mainBuf + } + } + ti = ti + 1 } if (cg.funcs.cnt "@main_user(") > 0 { diff --git a/src/lexer.zv b/src/lexer.zv index e1c55e2..79c494b 100644 --- a/src/lexer.zv +++ b/src/lexer.zv @@ -539,7 +539,7 @@ nextLexerToken lexer { } if lexer.ch == "~" || lexer.ch == "(" || lexer.ch == ")" || lexer.ch == "{" || lexer.ch == "}" || - lexer.ch == "[" || lexer.ch == "]" || lexer.ch == "," || lexer.ch == ";" { + lexer.ch == "[" || lexer.ch == "]" || lexer.ch == "," || lexer.ch == ";" || lexer.ch == "@" { c = lexer.ch readLexerChar lexer -> createToken "SYM", c, startLine, startCol diff --git a/src/main.zv b/src/main.zv index 8ea496b..a412c69 100644 --- a/src/main.zv +++ b/src/main.zv @@ -19,6 +19,8 @@ main cmd, fileArg, arg3, arg4, arg5, arg6 { let emitLlvm = 0 mut isCdylib = 0 mut outExe = "output.exe" + mut targetAlias = "" + mut targetTriple = "" let args = [arg3, arg4, arg5, arg6] let ai = 0 @@ -37,23 +39,45 @@ main cmd, fileArg, arg3, arg4, arg5, arg6 { ai = ai + 1 outExe = args[ai] } + if (a == "--target" || a == "-t") && ai + 1 < args.ln { + ai = ai + 1 + targetAlias = args[ai] + } ai = ai + 1 } + // Resolve friendly alias -> LLVM triple + if targetAlias == "windows-x64" || targetAlias == "win64" { + targetTriple = "x86_64-pc-windows-msvc" + } els if targetAlias == "linux-x64" || targetAlias == "linux-amd64" { + targetTriple = "x86_64-unknown-linux-gnu" + } els if targetAlias == "linux-arm64" || targetAlias == "linux-aarch64" { + targetTriple = "aarch64-unknown-linux-gnu" + } els if targetAlias == "macos-arm64" || targetAlias == "darwin-arm64" { + targetTriple = "arm64-apple-darwin" + } els if targetAlias == "macos-x64" || targetAlias == "darwin-x64" { + targetTriple = "x86_64-apple-darwin" + } els if targetAlias != "" { + // Raw LLVM triple passed directly + targetTriple = targetAlias + } + if isCdylib == 1 && outExe == "output.exe" { outExe = "output.dll" } if cmd == "checkall" || cmd == "check-all" { handleCheckAll - } els if cmd == "test" || cmd == "" { + } els if cmd == "test" { handleTest + } els if cmd == "" { + handleCheckAll } els { if cmd == "check" { handleCheck targetFile } els { if cmd == "build" { - handleBuild targetFile, isRelease, emitLlvm, outExe, isCdylib + handleBuild targetFile, isRelease, emitLlvm, outExe, isCdylib, targetTriple } els { if cmd == "run" { handleRun targetFile @@ -73,3 +97,4 @@ main cmd, fileArg, arg3, arg4, arg5, arg6 { } } + diff --git a/src/parser.zv b/src/parser.zv index d95d598..33dd4bb 100644 --- a/src/parser.zv +++ b/src/parser.zv @@ -345,7 +345,8 @@ createForNode initS, cond, incS, body { } createFuncNode name, params, body, isAsync, hasRest { - res = { kind: "FunctionDecl", name: name, params: params, body: body, isAsync: isAsync, hasRest: hasRest } + let emptyAttrs = [] + res = { kind: "FunctionDecl", name: name, params: params, body: body, isAsync: isAsync, hasRest: hasRest, attributes: emptyAttrs } -> res } @@ -1287,9 +1288,62 @@ parseBlock p { -> createBlockNode stmts } +parseAttributes p { + let attrs = [] + wh (curTok p).val == "@" { + advanceParser p // consume '@' + if (curTok p).kind != "IDENT" { + p.errors.psh "Expected attribute name after '@'" + brk + } + attrName = (curTok p).val + let attrArgs = [] + if (peekTok p).val == "(" { + advanceParser p // consume '(' + advanceParser p // into arguments or ')' + wh (curTok p).val != ")" && (curTok p).kind != "EOF" { + if (curTok p).kind == "IDENT" || (curTok p).kind == "STR" || (curTok p).kind == "STRING" || (curTok p).kind == "NUM" || (curTok p).kind == "NUMBER" { + attrArgs.psh (curTok p).val + if (peekTok p).val == "," { + advanceParser p // consume ',' + } + } + advanceParser p + } + } + let attrObj = { name: attrName, args: attrArgs } + attrs.psh attrObj + advanceParser p // move past attribute / ')' + } + -> attrs +} + parseStatement p { t = curTok p + if t.val == "@" { + let attrs = parseAttributes p + if (curTok p).kind == "EOF" { + -> { kind: "" } + } + stmt = parseStatement p + if stmt.kind == "FunctionDecl" { + stmt.attributes = attrs + } els if stmt.kind == "ObjDecl" { + stmt.attributes = attrs + } els if stmt.kind == "StmtSequence" { + let ssi = 0 + wh ssi < stmt.statements.ln { + subS = stmt.statements[ssi] + if subS.kind == "FunctionDecl" || subS.kind == "ObjDecl" { + subS.attributes = attrs + } + ssi = ssi + 1 + } + } + -> stmt + } + if t.kind == "KEYWORD" && t.val == "imp" { advanceParser p let items = [(curTok p).val] @@ -1312,12 +1366,54 @@ parseStatement p { -> createImportNode items, emptySyms, "" } } els if (t.kind == "KEYWORD" || t.kind == "IDENT") && (t.val == "extern" || t.val == "ffi") { - let libName = "" + mut libName = "c" advanceParser p if (curTok p).kind == "STR" || (curTok p).kind == "STRING" { libName = (curTok p).val advanceParser p } + if (curTok p).val == "{" { + advanceParser p // consume '{' + let extStmts = [] + wh (curTok p).val != "}" && (curTok p).kind != "EOF" { + if (curTok p).kind == "IDENT" { + eFnName = (curTok p).val + let eParams = [] + let eLine = (curTok p).line + wh (peekTok p).line == eLine && (peekTok p).val != "->" && (peekTok p).kind != "ARROW" && (peekTok p).val != "}" && (peekTok p).kind != "EOF" { + advanceParser p + epName = (curTok p).val + mut epType = "num" + if (peekTok p).val == ":" { + advanceParser p // consume ':' + advanceParser p // type name + epType = (curTok p).val + } + epParamObj = { name: epName, typeName: epType } + eParams.psh epParamObj + if (peekTok p).val == "," { + advanceParser p + } + } + mut eRetType = "void" + if (peekTok p).val == "->" || (peekTok p).kind == "ARROW" { + advanceParser p // consume '->' + advanceParser p // ret type + eRetType = (curTok p).val + } + extNode = { + kind: "ExternDecl", + name: eFnName, + libName: libName, + params: eParams, + typeName: eRetType + } + extStmts.psh extNode + } + advanceParser p + } + -> createStmtSequenceNode extStmts + } fnName = (curTok p).val let params = [] wh (peekTok p).line == t.line && (peekTok p).val != "->" && (peekTok p).kind != "ARROW" && (peekTok p).kind != "EOF" { @@ -1913,6 +2009,86 @@ parseStatement p { advanceParser p // consume 'C' } } + if (curTok p).val == "{" { + advanceParser p // consume '{' + let pubStmts = [] + wh (curTok p).val != "}" && (curTok p).kind != "EOF" { + if (curTok p).kind == "IDENT" { + pFnName = (curTok p).val + advanceParser p + mut pHasRest = 0 + let pParams = [] + if (curTok p).val == "(" { + advanceParser p // consume '(' + if (curTok p).val != ")" { + pValFirst = (curTok p).val + if (peekTok p).val == ":" { + advanceParser p // consume ':' + advanceParser p // consume type + } + pParams.psh pValFirst + wh (peekTok p).val == "," { + advanceParser p // consume ',' + advanceParser p + pVal = (curTok p).val + if (peekTok p).val == ":" { + advanceParser p // consume ':' + advanceParser p // consume type + } + pParams.psh pVal + } + } + if (peekTok p).val == ")" { + advanceParser p + } + } els if (curTok p).val != "{" && (curTok p).val != "->" { + let moreParams = 1 + wh moreParams == 1 { + if (curTok p).val == "{" || (curTok p).val == "->" { + moreParams = 0 + } els if (curTok p).val == "..." { + advanceParser p + pRest = (curTok p).val + if (peekTok p).val == ":" { + advanceParser p + advanceParser p + } + pParams.psh pRest + pHasRest = 1 + } els if (curTok p).kind == "IDENT" { + pVal = (curTok p).val + if (peekTok p).val == ":" { + advanceParser p + advanceParser p + } + pParams.psh pVal + } + if moreParams == 1 { + if (peekTok p).val == "," { + advanceParser p // consume ',' + advanceParser p // move to next param + } els { + moreParams = 0 + } + } + } + } + if (peekTok p).val == "->" { + advanceParser p // consume '->' + advanceParser p // consume return type + } els if (curTok p).val == "->" { + advanceParser p // consume '->' + advanceParser p // consume return type + } + body = parseBlock p + let emptyAttrs1 = [] + pFnNode = { kind: "FunctionDecl", name: pFnName, params: pParams, body: body, isAsync: 0, hasRest: pHasRest, isExported: 1, isExternC: isExternC, attributes: emptyAttrs1 } + pubStmts.psh pFnNode + } + advanceParser p // unconditional advance (matches C++ nextToken at line 269) + } + -> createStmtSequenceNode pubStmts + } if (curTok p).kind == "IDENT" { fnName = (curTok p).val advanceParser p @@ -1941,34 +2117,34 @@ parseStatement p { if (peekTok p).val == ")" { advanceParser p } - } els if (curTok p).kind == "IDENT" || (curTok p).val == "..." { - if (curTok p).val == "..." { - advanceParser p - pRest = (curTok p).val - params.psh pRest - hasRest = 1 - } els { - pValFirst = (curTok p).val - if (peekTok p).val == ":" { - advanceParser p // consume ':' - advanceParser p // consume type - } - params.psh pValFirst - wh (peekTok p).val == "," { - advanceParser p // consume ',' + } els if (curTok p).val != "{" && (curTok p).val != "->" { + let moreParams = 1 + wh moreParams == 1 { + if (curTok p).val == "{" || (curTok p).val == "->" { + moreParams = 0 + } els if (curTok p).val == "..." { advanceParser p - if (curTok p).val == "..." { + pRest = (curTok p).val + if (peekTok p).val == ":" { advanceParser p - pRest2 = (curTok p).val - params.psh pRest2 - hasRest = 1 + advanceParser p + } + params.psh pRest + hasRest = 1 + } els if (curTok p).kind == "IDENT" { + pVal = (curTok p).val + if (peekTok p).val == ":" { + advanceParser p + advanceParser p + } + params.psh pVal + } + if moreParams == 1 { + if (peekTok p).val == "," { + advanceParser p // consume ',' + advanceParser p // move to next param } els { - pVal = (curTok p).val - if (peekTok p).val == ":" { - advanceParser p // consume ':' - advanceParser p // consume type - } - params.psh pVal + moreParams = 0 } } } @@ -1976,9 +2152,13 @@ parseStatement p { if (peekTok p).val == "->" { advanceParser p // consume '->' advanceParser p // consume return type + } els if (curTok p).val == "->" { + advanceParser p // consume '->' + advanceParser p // consume return type } body = parseBlock p - fnNode = { kind: "FunctionDecl", name: fnName, params: params, body: body, isAsync: 0, hasRest: hasRest, isExported: 1, isExternC: isExternC } + let emptyAttrs2 = [] + fnNode = { kind: "FunctionDecl", name: fnName, params: params, body: body, isAsync: 0, hasRest: hasRest, isExported: 1, isExternC: isExternC, attributes: emptyAttrs2 } -> fnNode } -> { kind: "" } @@ -2233,20 +2413,37 @@ parseStatement p { parseProgramFull tokens, filePath, sourceText { p = createParser tokens, filePath, sourceText - let statements = [] + let rawStatements = [] wh (curTok p).kind != "EOF" { stmt = parseStatement p if stmt.kind != "" { - if stmt.kind == "ExpressionStmt" { - if stmt.expr.kind != "NilExpr" && stmt.expr.kind != "" { - statements.psh stmt + rawStatements.psh stmt + } + advanceParser p + } + + let statements = [] + let ri = 0 + wh ri < rawStatements.ln { + s = rawStatements[ri] + if s.kind == "StmtSequence" { + let ssi = 0 + wh ssi < s.statements.ln { + subStmt = s.statements[ssi] + if subStmt.kind != "" { + statements.psh subStmt } - } els { - statements.psh stmt + ssi = ssi + 1 } + } els if s.kind == "ExpressionStmt" { + if s.expr.kind != "NilExpr" && s.expr.kind != "" { + statements.psh s + } + } els { + statements.psh s } - advanceParser p + ri = ri + 1 } res = { statements: statements, errors: p.errors } diff --git a/tests/attributes.test.zv b/tests/attributes.test.zv new file mode 100644 index 0000000..7237685 --- /dev/null +++ b/tests/attributes.test.zv @@ -0,0 +1,56 @@ +// Attributes & Metaprogramming Annotations Test (tests/attributes.test.zv) + +@inline +fastAdd a, b { + -> a + b +} + +@inline(always) +fastMul a, b { + -> a * b +} + +@noinline +slowCalc x { + -> x * 2 +} + +@test +testMath { + r1 = fastAdd 10, 20 + prnt r1 + r2 = fastMul 5, 6 + prnt r2 + r3 = slowCalc 21 + prnt r3 +} + +@test +@inline +multiAttrFunc a { + -> a + 100 +} + +@derive(Debug, Clone) +obj Point { + x: num, + y: num +} + +@custom(42, "metadata") +obj Config { + mode: str +} + +testMath +res = multiAttrFunc 50 +prnt res + +p = Point{ x: 1, y: 2 } +prnt p.x +prnt p.y + +c = Config{ mode: "production" } +prnt c.mode + +prnt "attributes test complete" diff --git a/tests/c_ffi_lib.test.zv b/tests/c_ffi_lib.test.zv index 3db1648..c7e32c1 100644 --- a/tests/c_ffi_lib.test.zv +++ b/tests/c_ffi_lib.test.zv @@ -1,7 +1,9 @@ // C Foreign Function Interface (FFI) with .lib Test Suite extern "msvcrt.lib" puts s: str -> num -extern "kernel32.lib" GetCurrentProcessId -> num -extern "kernel32.lib" Sleep ms: num -> void +extern "kernel32.lib" { + GetCurrentProcessId -> num + Sleep ms: num -> void +} extern "user32.lib" GetSystemMetrics nIndex: num -> num // Call C runtime function directly via .lib diff --git a/tests/call_cdylib.test.zv b/tests/call_cdylib.test.zv index 8dbea58..bcd170e 100644 --- a/tests/call_cdylib.test.zv +++ b/tests/call_cdylib.test.zv @@ -1,9 +1,11 @@ // Call Zuv-generated DLL via C FFI (tests/call_cdylib.test.zv) -extern "test_math.dll" add a: num, b: num -> num -extern "test_math.dll" multiply a: num, b: num -> num -extern "test_math.dll" calculateTax price: num, rate: num -> num +extern "test_math.dll" { + add a: num, b: num -> num + multiply a: num, b: num -> num +} +extern "test_math.dll" calculateTax price: num, rate: num -> num sum = add 10, 25 if sum == 35 { prnt "add ok: 35" diff --git a/tests/cdylib_export.test.zv b/tests/cdylib_export.test.zv index ef4a1e6..be8f754 100644 --- a/tests/cdylib_export.test.zv +++ b/tests/cdylib_export.test.zv @@ -1,13 +1,16 @@ // Shared C Dynamic Library (cdylib) Export Test (tests/cdylib_export.test.zv) -pub extern "C" add a: num, b: num -> num { - -> a + b -} +pub extern "C" { + add a: num, b: num -> num { + -> a + b + } -pub extern "C" multiply a: num, b: num -> num { - -> a * b + multiply a: num, b: num -> num { + -> a * b + } } pub extern "C" calculateTax price: num, rate: num -> num { -> price * rate } +