Skip to content
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ All notable changes to the **Zuv** programming language and self-hosting compile
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

---

## [0.6.0] - 2026-08-25

### 🧱 Data Types
- **Type Inspection Operator (`typ`)**: Added prefix `typ` operator returning type strings (`"str"`, `"num"`, `"bool"`, `"obj"`, `"arr"`, `"nil"`, `"und"`, `"sym"`, `"bigint"`). Implemented in C++ bootstrap and self-hosting compiler. `und` literal now distinct from `nil` for `typ und`. New test: `typ.test.zv`.
- **Enums (`enum`)**: Named variants as integer discriminants (`0..n-1`). `Status.Active` property access, `mch` arms with qualified (`Status.Pending`) or bare (`Pending`) patterns. C++ bootstrap keeps native enum match; self-host desugars enum `mch` to nested `if`/`els`. Fixed self-host match-target parsing so `ident {` is not swallowed as a struct literal. New test: `enum.test.zv`.
- **Undefined Literal (`und`)**: Runtime-distinct from `nil` (LLVM `bitcast i64 1 to double` sentinel). Equality/`!=`, falsy `if`/`wh`/`for`/`and`/`or`/`!` via truthiness helper, and `typ und` → `"und"`. Fixed C++ `TOK_UND` over-consume and self-host checker allowing `let x = nil|und`. New test: `und.test.zv`.
- **Unique Symbol Primitive (`sym`)**: `sym "key"` creates an interned identity (same key → equal, different from strings). `typ (sym "k")` → `"sym"`. Symbols are truthy. Renamed self-host locals that collided with the new `sym` keyword. New test: `sym.test.zv`.
- **BigInt Primitives (`BigInt` / `...n`)**: Digit-string bigint literals (`123n`) and `BigInt(...)` constructor. Equality via content (`strcmp`); distinct from numbers (`123n != 123`). `typ` → `"bigint"`. New test: `bigint.test.zv`.
- **Scientific Exponent Literals (`123e5`, `1.5E-2`)**: Lexer support for `e`/`E` with optional `+`/`-` in C++ bootstrap and self-host. New test: `sci_notation.test.zv`.
- **Number Bases (`0x` / `0b` / `0o`)**: Hex, binary, and octal integer literals in C++ bootstrap and self-host. New test: `bases.test.zv`.
- **Special Numbers (`nan`, `inf` / `Infinity`)**: IEEE-754 NaN/+Inf literals; statement `inf expr` remains info logging. Helpers `isNan` / `isFin`. New test: `nan_inf.test.zv`.

### ⚡ Operators
- **Exponentiation (`**`)**: Right-associative `**` via `llvm.pow.f64`. New test: `pow.test.zv`.

### 🔤 Self-Hosting Source Style
- **Template Literals**: Migrated self-hosting compiler sources (`src/*.zv`) from `"..." + expr` concatenation and escaped `\"` strings to backtick templates with `${...}` interpolation (e.g. `` `tests/${impPath}.zv` ``). Plain string literals left unchanged.
- **Shortform Methods**: Replaced long-form std method calls with shortforms used in the test suite — `.concat` → `.cat`, `.charAt` → `.chr`.

---

## [0.5.0] - 2026-08-25

### 📦 Variables & Scope
Expand Down
29 changes: 17 additions & 12 deletions src/checker.zv
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ declareVar checker, name, isMut {
existingInCurrent = findVarInCurrentScope checker, name
if isMut == 1 {
if existingInCurrent.name != "" {
errRedecl = "Variable '" + name + "' is already declared in this scope"
errRedecl = `Variable '${name}' is already declared in this scope`
checker.errors.psh errRedecl
} els {
curIdx = checker.scopes.ln - 1
Expand All @@ -95,7 +95,7 @@ checkExpr checker, expr {
if expr.kind == "VariableExpr" {
v = findVarState checker, expr.name
if v.name != "" && v.isMoved == 1 {
errVal = "Use of moved value: '" + expr.name + "'"
errVal = `Use of moved value: '${expr.name}'`
checker.errors.psh errVal
}
} els if expr.kind == "UnaryExpr" {
Expand All @@ -105,22 +105,22 @@ checkExpr checker, expr {
v = findVarState checker, vName
if v.name != "" {
if v.isMoved == 1 {
errMoved = "Cannot borrow moved value: '" + vName + "'"
errMoved = `Cannot borrow moved value: '${vName}'`
checker.errors.psh errMoved
}
if expr.op == "&mut" {
if v.isMut == 0 {
errImmut = "Cannot borrow immutable variable '" + vName + "' as mutable (&mut)"
errImmut = `Cannot borrow immutable variable '${vName}' as mutable (&mut)`
checker.errors.psh errImmut
}
if v.immBorrows > 0 || v.mutBorrows > 0 {
errActive = "Cannot borrow '" + vName + "' as mutable (&mut) because it is already borrowed"
errActive = `Cannot borrow '${vName}' as mutable (&mut) because it is already borrowed`
checker.errors.psh errActive
}
v.mutBorrows = v.mutBorrows + 1
} els {
if v.mutBorrows > 0 {
errMut = "Cannot borrow '" + vName + "' as immutable (&) because it is borrowed as mutable (&mut)"
errMut = `Cannot borrow '${vName}' as immutable (&) because it is borrowed as mutable (&mut)`
checker.errors.psh errMut
}
v.immBorrows = v.immBorrows + 1
Expand All @@ -138,7 +138,7 @@ checkExpr checker, expr {
vName = expr.left.name
v = findVarState checker, vName
if v.name != "" && v.isMut == 0 {
errMutate = "Cannot mutate immutable variable: '" + vName + "'"
errMutate = `Cannot mutate immutable variable: '${vName}'`
checker.errors.psh errMutate
}
}
Expand Down Expand Up @@ -169,8 +169,13 @@ checkExpr checker, expr {

checkStatement checker, stmt {
if stmt.kind == "VarDecl" {
if stmt.value.kind == "NilExpr" || stmt.value.kind == "" {
checker.errors.psh ("Semantic Error: Invalid variable initialization for '" + stmt.name + "'")
// Allow explicit nil/und literals; reject missing/placeholder inits
if stmt.value.kind == "" {
checker.errors.psh (`Semantic Error: Invalid variable initialization for '${stmt.name}'`)
} els if stmt.value.kind == "NilExpr" {
if stmt.value.val != "nil" && stmt.value.val != "und" {
checker.errors.psh (`Semantic Error: Invalid variable initialization for '${stmt.name}'`)
}
}
checkExpr checker, stmt.value
declareVar checker, stmt.name, stmt.isMut
Expand Down Expand Up @@ -253,9 +258,9 @@ checkStatement checker, stmt {
} els if stmt.kind == "ImportStmt" {
let si = 0
wh si < stmt.symbols.ln {
sym = stmt.symbols[si]
if sym != "" {
declareVar checker, sym, 0
impName = stmt.symbols[si]
if impName != "" {
declareVar checker, impName, 0
}
si = si + 1
}
Expand Down
70 changes: 35 additions & 35 deletions src/cli.zv
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ emitDirectObjectFile ir, outputObjPath, isRelease {
LLVMInitializeX86AsmPrinter
LLVMInitializeX86AsmParser

fullIr = "target triple = \"x86_64-pc-windows-msvc\"\n" + ir
fullIr = `target triple = "x86_64-pc-windows-msvc"\n${ir}`
ctx = LLVMGetGlobalContext
memBuf = LLVMCreateMemoryBufferWithMemoryRange fullIr, fullIr.ln, "zuv_ir", 0

Expand All @@ -38,7 +38,7 @@ emitDirectObjectFile ir, outputObjPath, isRelease {
res = LLVMParseIRInContext ctx, memBuf, outModPtr, outMsgPtr
if res != 0 {
errMsg = zuv_ptr_deref outMsgPtr
prnt ("[LLVM Error] IR Parse Failed: " + errMsg)
prnt (`[LLVM Error] IR Parse Failed: ${errMsg}`)
-> 1
}
mod = zuv_ptr_deref outModPtr
Expand All @@ -48,7 +48,7 @@ emitDirectObjectFile ir, outputObjPath, isRelease {
tRes = LLVMGetTargetFromTriple triple, targetPtr, outMsgPtr
if tRes != 0 {
errMsg2 = zuv_ptr_deref outMsgPtr
prnt ("[LLVM Error] Target lookup failed: " + errMsg2)
prnt (`[LLVM Error] Target lookup failed: ${errMsg2}`)
LLVMDisposeModule mod
-> 1
}
Expand All @@ -63,7 +63,7 @@ emitDirectObjectFile ir, outputObjPath, isRelease {
emitRes = LLVMTargetMachineEmitToFile tm, mod, outputObjPath, 1, outMsgPtr
if emitRes != 0 {
errMsg3 = zuv_ptr_deref outMsgPtr
prnt ("[LLVM Error] Object code emission failed: " + errMsg3)
prnt (`[LLVM Error] Object code emission failed: ${errMsg3}`)
LLVMDisposeTargetMachine tm
LLVMDisposeModule mod
-> 1
Expand All @@ -81,7 +81,7 @@ linkDirectObjectFile objPath, outExePath {
} els if fE "bin/lld-link.exe" {
lldBin = "bin/lld-link.exe"
}
cmd = lldBin + " \"" + objPath + "\" -out:\"" + outExePath + "\" -stack:33554432 -defaultlib:libcmt -defaultlib:legacy_stdio_definitions -defaultlib:oldnames -defaultlib:user32 -defaultlib:kernel32 -nologo"
cmd = `${lldBin} "${objPath}" -out:"${outExePath}" -stack:33554432 -defaultlib:libcmt -defaultlib:legacy_stdio_definitions -defaultlib:oldnames -defaultlib:user32 -defaultlib:kernel32 -nologo`
res = system cmd
-> res
}
Expand All @@ -92,7 +92,7 @@ handleBuild targetFile, isRelease, emitLlvm, outExe {
prnt "==========================================="
prnt " Zuv Self-Hosting Compiler [BUILD] "
prnt "==========================================="
prnt ("Compiling target file: " + targetFile)
prnt (`Compiling target file: ${targetFile}`)

if isRelease == 1 {
prnt "Optimization: Release Mode (-O3 Native)"
Expand All @@ -106,7 +106,7 @@ handleBuild targetFile, isRelease, emitLlvm, outExe {
if prog.errors.ln > 0 {
let pe = 0
wh pe < prog.errors.ln {
prnt ("[zuv build] " + prog.errors[pe])
prnt (`[zuv build] ${prog.errors[pe]}`)
pe = pe + 1
}
-> 0
Expand All @@ -123,9 +123,9 @@ handleBuild targetFile, isRelease, emitLlvm, outExe {
impPath = s.paths[0]
}
if impPath != "" && impPath != "str" && impPath != "arr" && impPath != "fs" && impPath != "time" && impPath != "thread" && impPath != "thr" && impPath != "os" {
let fullImpPath = impPath + ".zv"
let fullImpPath = `${impPath}.zv`
if (fE fullImpPath) == 0 {
fullImpPath = "tests/" + impPath + ".zv"
fullImpPath = `tests/${impPath}.zv`
}
if (fE fullImpPath) == 1 {
impCode = rF fullImpPath
Expand Down Expand Up @@ -171,7 +171,7 @@ handleBuild targetFile, isRelease, emitLlvm, outExe {
prnt "[zuv build] Successfully generated output_selfhost.ll via self-hosting compiler!"
-> 1
} els {
objPath = targetExe + ".obj"
objPath = `${targetExe}.obj`
emitRes = emitDirectObjectFile ir, objPath, isRelease
if emitRes != 0 {
prnt "[zuv build] Direct in-process LLVM object code generation failed."
Expand All @@ -183,9 +183,9 @@ handleBuild targetFile, isRelease, emitLlvm, outExe {
}
if linkRes == 0 {
if isRelease == 1 {
prnt ("[zuv build] Successfully generated " + targetExe + " (-O3 Native In-Process)")
prnt (`[zuv build] Successfully generated ${targetExe} (-O3 Native In-Process)`)
} els {
prnt ("[zuv build] Successfully generated " + targetExe + " (Debug In-Process)")
prnt (`[zuv build] Successfully generated ${targetExe} (Debug In-Process)`)
}
-> 1
} els {
Expand All @@ -198,7 +198,7 @@ handleBuild targetFile, isRelease, emitLlvm, outExe {
-> 0
}
} els {
prnt ("[zuv build] Error: Could not open source file: " + targetFile)
prnt (`[zuv build] Error: Could not open source file: ${targetFile}`)
-> 0
}
}
Expand All @@ -211,22 +211,22 @@ handleCheck targetFile {
prog = parseProgramFull toks
okSafety = checkProgram prog
if okSafety == 1 {
prnt (" PASS " + targetFile)
prnt (` PASS ${targetFile}`)
-> 1
} els {
prnt (" FAIL " + targetFile + " (Semantic / Safety Check Failed)")
prnt (` FAIL ${targetFile} (Semantic / Safety Check Failed)`)
-> 0
}
} els {
prnt (" FAIL " + targetFile + " (File Not Found)")
prnt (` FAIL ${targetFile} (File Not Found)`)
-> 0
}
}

handleRun targetFile {
res = handleBuild targetFile, 0, 0, "output.exe"
if res == 1 {
prnt ("[zuv run] Executing output.exe for " + targetFile)
prnt (`[zuv run] Executing output.exe for ${targetFile}`)
sh ".\\output.exe"
}
-> res
Expand All @@ -235,10 +235,10 @@ handleRun targetFile {
handleFmt targetFile {
if fE targetFile {
code = rF targetFile
prnt ("[zuv fmt] Formatted " + targetFile + " successfully.")
prnt (`[zuv fmt] Formatted ${targetFile} successfully.`)
-> 1
} els {
err ("[zuv fmt] Error: Could not open " + targetFile)
err (`[zuv fmt] Error: Could not open ${targetFile}`)
-> 0
}
}
Expand All @@ -256,7 +256,7 @@ handleCheckAll {
wh ti < tests.ln {
tFile = tests[ti]
if (tFile.cnt "error") > 0 {
prnt (" PASS " + tFile + " (Expected error caught)")
prnt (` PASS ${tFile} (Expected error caught)`)
passCount = passCount + 1
} els {
r = handleCheck tFile
Expand All @@ -272,11 +272,11 @@ handleCheckAll {
total = passCount + failCount
prnt ""
if failCount == 0 {
prnt (" Checked Files: " + passCount + " passed (" + total + ")")
prnt (" Status: " + passCount + " passed")
prnt (` Checked Files: ${passCount} passed (${total})`)
prnt (` Status: ${passCount} passed`)
} els {
prnt (" Checked Files: " + failCount + " failed, " + passCount + " passed (" + total + ")")
prnt (" Status: " + failCount + " failed, " + passCount + " passed")
prnt (` Checked Files: ${failCount} failed, ${passCount} passed (${total})`)
prnt (` Status: ${failCount} failed, ${passCount} passed`)
}
prnt ""
-> 1
Expand Down Expand Up @@ -307,26 +307,26 @@ handleTest {
if isExpectedError == 1 {
tElapsed = nw - tStart
if buildOk == 0 {
prnt (" PASS " + tFile + " (" + tElapsed + "ms) [Expected Error]")
prnt (` PASS ${tFile} (${tElapsed}ms) [Expected Error]`)
passCount = passCount + 1
} els {
prnt (" FAIL " + tFile + " (" + tElapsed + "ms) [Expected error but compiled]")
prnt (` FAIL ${tFile} (${tElapsed}ms) [Expected error but compiled]`)
failCount = failCount + 1
}
} els {
if buildOk == 0 {
tElapsed = nw - tStart
prnt (" FAIL " + tFile + " (" + tElapsed + "ms) [Compilation Failed]")
prnt (` FAIL ${tFile} (${tElapsed}ms) [Compilation Failed]`)
failCount = failCount + 1
} els {
runCmd = ".\\" + tempExe + " > nul 2>&1"
runCmd = `.\\${tempExe} > nul 2>&1`
runRes = sh runCmd
tElapsed = nw - tStart
if runRes == 0 {
prnt (" PASS " + tFile + " (" + tElapsed + "ms)")
prnt (` PASS ${tFile} (${tElapsed}ms)`)
passCount = passCount + 1
} els {
prnt (" FAIL " + tFile + " (" + tElapsed + "ms) [Execution failed code " + runRes + "]")
prnt (` FAIL ${tFile} (${tElapsed}ms) [Execution failed code ${runRes}]`)
failCount = failCount + 1
}
}
Expand All @@ -342,13 +342,13 @@ handleTest {
total = passCount + failCount
prnt ""
if failCount == 0 {
prnt (" Test Files: " + passCount + " passed (" + total + ")")
prnt (" Tests: " + passCount + " passed")
prnt (` Test Files: ${passCount} passed (${total})`)
prnt (` Tests: ${passCount} passed`)
} els {
prnt (" Test Files: " + failCount + " failed, " + passCount + " passed (" + total + ")")
prnt (" Tests: " + failCount + " failed, " + passCount + " passed")
prnt (` Test Files: ${failCount} failed, ${passCount} passed (${total})`)
prnt (` Tests: ${failCount} failed, ${passCount} passed`)
}
prnt (" Duration: " + totalElapsed + "ms")
prnt (` Duration: ${totalElapsed}ms`)
prnt ""
-> 1
}
Expand Down
Loading
Loading