Repository navigation
Releases
Torvik v1.5.4
A security release, and the end of a family
of issues.
Update if you run torvc from a script, a CI job, or anywhere HOME is set from
configuration rather than by logging in. If you only ever type torvc yourself at
a prompt you already control, this is not urgent — but it is small and there is no
reason to wait.
Security — TV-2026-003, shell injection via paths
Every path the compiler put on a command line — the output path from -o, the run
directory derived from HOME, temporary objects, link scripts — went through one
quoting helper that wrapped it in double quotes.
Double quotes do not stop command substitution. $(...) and backticks expand
inside them. So a path containing shell syntax executed with the privileges of
whoever ran torvc.
Two vectors were confirmed:
torvc x.tv -o '$(command)'
HOME='/tmp/x$(command)' torvc x.tv runThe second matters more than it looks. Any context where HOME comes from
configuration — CI runners, container entrypoints, service accounts, wrapper
scripts — was exposed without the caller passing anything unusual.
The fix. The quoting helper now refuses a path containing shell syntax
instead of quoting it. The check lives in that one function, so all seventeen call
sites are covered at once. Ordinary paths are unaffected.
Why this is the seventh, and what changed because of that
TV-2026-003 is the seventh command-injection issue found in Torvik, and the last of
a family. They shared one cause: the compiler builds command lines for clang and
the linker, and values reached those command lines without being checked.
Each earlier fix was correct and incomplete — it closed the site that had been
found and left the others open. Manifest fields were fixed in 1.5.2, torvc run
arguments in 1.5.3, and this release fixes the helper all of them ultimately passed
through.
Two things changed beyond the fix itself:
A rule. No path or user-supplied string reaches a command line without passing
a validator, and the validators refuse rather than escape. Escaping has to be
correct on two different shells and stays correct only until either changes.
A gate. The test suite now reads the compiler's own source and fails if a value
reaches a command line without a validator. A runtime check cannot stop a mistake
made while writing the compiler; this can. It was tested by deliberately
reintroducing the vulnerability and confirming the gate catches it — an earlier
version of the gate passed the clean tree and missed the reintroduced bug, which
is worse than having no gate at all.
If you write tooling around torvc, this is worth a look at your own wrappers. A
script that builds a torvc command line from a filename, a branch name, or any
value it did not choose has the same problem, and updating Torvik does not fix your
script.
Upgrading
rune updateNothing in this release changes the language or the standard library. No source
changes are required.
Torvik v1.5.3
A security fix and eight correctness bugs, no new
features and no changes to anything that already worked.*
This is a maintenance release. Every entry below either replaces a wrong answer
with a correct one, turns a program that would not compile into one that does, or
adds a warning about a bug the compiler could already see. Nothing here changes
the behavior of a program that was already correct. Upgrading from v1.5.2
should be uneventful.
Security
Command injection through torvc run arguments.
Arguments forwarded to a program run with torvc run were passed through a shell
without adequate quoting, so an argument containing shell metacharacters could
execute arbitrary commands. This is the sixth issue in this family; the previous
five were fixed in v1.5.1 and v1.5.2.
Anyone who runs torvc run with arguments from an untrusted source — a CI job, a
script, anything reading a filename it did not choose — should upgrade.
A Security Advisory accompanies this release (see SECURITY.md).
Correctness
fmt printed a memory address for i128 and u128 (issue #23). A 128-bit
value is a heap box, and fmt handed the box pointer to the integer stringifier;
echo printed the value correctly, which is how the difference went unnoticed.
The same family of bugs affects u64 maximum (printed as -1) and lists (printed
as an address). Those are not fixed here: reaching the argument's real type
needs plumbing that does not exist on the 1.x line, and adding it means
restructuring the expression compiler — the last thing a security patch should
touch. Both are fixed in 2.0, where fmt and echo share one stringifier.
Hex escapes dropped bytes outside printable ASCII. "\x1b[2J" compiled
cleanly and produced [2J — the escape character was silently discarded, along
with DEL and every byte from 0x80 up. So the most common use of a hex escape, an
ANSI terminal sequence, was the case that did not work. Any byte written as
\x00–\xFF now decodes to that byte.
list<f64> could not be updated. xs[0] = 9.0 on a list of floats was
rejected by the backend. Reading an element worked, so a list of floats could be
built and read but never modified.
readint and readfloat read freed memory (issue #22). Both validated the
input by checking a pointer into the string after releasing that string — a
use-after-free that usually appeared to work, because the freed block still held
the old bytes. That is exactly what makes this class of bug survive testing. The
check now happens before the release.
(x >> n) & mask failed with an internal compiler error (issue #18). A
parenthesized expression used as the left operand of a further expression was
always converted as though it were a pointer, but a folded arithmetic chain is a
real integer — so the compiler emitted invalid code and clang rejected it. This is
why 255 & (x >> 4) and (x >> 4) on its own both worked while (x >> 4) & 255
did not. Reported against shifts; it affected any parenthesized arithmetic in that
position.
unwrap on a result of any integer width except i64 (issue #19). fixed v: u8 = unwrap(r); reported "can't assign a value of type str to 'v'" — the code
tested for i64 exactly, so u8, i8, i16, i32, u16, u32 and u64 all
fell through to the string path. All widths now work. (Bind the result to a
variable before unwrapping; unwrapping a call directly has no declared type to
read, as was already the case for i64.)
An integer assigned to an f64 produced a meaningless value (issue #17).
set n: i64 = 5; fixed f: f64 = n; compiled and printed 2.47033e-323 — the
integer's bits read as a floating-point number. The same assignment from a literal
or a call was caught correctly; only the variable form slipped through. It is now
a clean type error pointing at to_f64(...).
Three builtins denied their own existence. fs_mkdir, cwd and list_pop
called with the wrong number of arguments reported call to undefined function —
about builtins that exist and work. cwd(1) was the worst: it takes no arguments,
so passing one made the builtin vanish. All three now report the argument count
and the correct form.
Diagnostics
Narrowing conversions now warn (new). On the 1.x line a declared integer width
is not a storage constraint — every integer occupies a full-width slot, and u8
affects only literal range-checking and unsigned printing. Two things follow, both
silent:
fixed x: u8 = b;wherebholds 70000 leavesxholding 70000. The
declared type is not enforced.- The same conversion from a function call truncates without any diagnostic.
Real data loss, no warning.
Torvik v2.0 refuses both at compile time, but that fix cannot be backported: it
would turn working 1.x programs into ones that do not build, which a patch release
must never do. So 1.5.3 warns instead. Your program builds and behaves exactly
as it did; you simply find out where the narrowing happens.
Each site the warning flags is also a site that will need an explicit
to_u8(...) / try_to_u8(...) on the 2.0 line, so clearing these warnings is
also the migration.
The warning fires where a value is narrowed from a call, which is the case that
silently truncates. Narrowing from a variable cannot be judged at that point in
the 1.x compiler — that case leaves the annotation unenforced but does not lose the
value, and issue #20 stays open for the 2.0 fix.
Silence it with !@ALLOW[narrowing]; per file, or --no-warn per build.
Link failures explain themselves. The message shown when a link fails claimed
that an undefined symbol "usually means a signature does not match the C header."
That is the one thing it cannot mean — C does not encode types in symbol names, so
a mismatched signature links cleanly and goes wrong at run time instead. The
message now lists the real causes, including the one that catches people out: a C
function that exists only as a macro or static inline in its header emits no
symbol at all, and no declaration however correct can bind it.
Upgrading
rune updateor reinstall from the install script. Nothing in this release changes the language
or the standard library surface, so no source changes are required.
v1.5.2 — Security: command injection via build configuration
Update promptly
This release fixes a critical security issue. Versions 1.5.0 and 1.5.1 could
execute arbitrary commands while building a project — cloning a repository and
running rune build was enough. The compiled program never had to be run, and
nothing indicated anything unusual had happened.
Update rune to 1.5.1 as well. Both tools construct command lines, and both
were affected. Updating only one leaves the path open.
What was wrong
torvc interpolated the --arch target triple into the clang command line
without validating it, so a value containing shell syntax ran at build time. That
value can be set from a project's torvik.rune manifest, which is what made it
reachable through ordinary use rather than only through a hand-typed flag.
--entry was affected the same way. It is written directly into generated LLVM
IR, so a crafted value could inject arbitrary IR into the compiled program.
What changed
Both values are validated against an allowlist. A target triple may contain
letters, digits and _ . -; an entry symbol follows C identifier rules. Anything
else is refused with a message naming the field and explaining the limit.
Quoting was considered and rejected as insufficient: shell double-quoting stops
word-splitting but not command substitution — "$(...)" still expands — so
every value reaching a shell is validated instead.
If you have already built an untrusted project
Treat that build as having run arbitrary code with your user's privileges.
Install:
Linux
curl -fsSL https://raw.githubusercontent.com/torvik-lang/torvik/main/linux/install.sh | sh
Windows (PowerShell)
iwr -useb https://raw.githubusercontent.com/torvik-lang/torvik/main/windows/install.ps1| iex
Already have Torvik?
rune update
No source changes are required and nothing about the language changed.
Full details: SECURITY.md, advisory TV-2026-001.
v1.5.1 — Freestanding fixes: strings, division, and refcounting under --bare
A patch release fixing three bugs that made --bare builds fail to link. All three are freestanding-only — hosted programs are unaffected, and no behavior changes for existing code.
Fixed:
len() on a str under --bare referenced an undefined @strlen. The freestanding strlen/strcmp implementations existed but were emitted only alongside the on_alloc/on_free shims, so a bare program that used a string without defining an allocator failed at link time.
Integer division and modulo under --bare referenced an undefined torvik_div/torvik_mod. A freestanding program could not divide at all — which also meant it could not format a number for display. torvc now emits freestanding implementations carrying the same divide-by-zero and INT64_MIN / -1 checks the runtime performs; with no stderr to report through, a violation parks the CPU.
A str local under --bare referenced an undefined torvik_str_retain/torvik_str_release. String locals are reference-counted on scope entry and exit even when the string is a literal, so any bare program that named a string hit this. String literals carry an immortal refcount, so with no allocator present both operations are correctly no-ops.
Install:
Linux
curl -fsSL https://raw.githubusercontent.com/torvik-lang/torvik/main/linux/install.sh | sh
Windows (PowerShell)
iwr -useb https://raw.githubusercontent.com/torvik-lang/torvik/main/windows/install.ps1 | iex
Already have Torvik?
rune update
Torvik v1.5.0 — "The Forge"
Systems and OS development.
Torvik can now produce a freestanding image that boots on bare hardware — no operating
system, no C library, and no runtime beneath it. The reference kernel in this release writes
to VGA text memory, drives hardware I/O ports, and parks the CPU, and every line of it is
Torvik apart from a sixty-line boot stub.
Everything new here is opt-in and unsafe-gated. Torvik has no unsafe mode; it has
unsafe statements, and you write the word each time. If you never write it, nothing in this
release can reach an address it shouldn't — buf[i] in the function next door is still
bounds-checked, and reference counting still cleans up after your strings.
The Forge
Raw pointers — varda<T>
from_addr, as_addr, ptr_add (element-scaled), ptr_byte_offset (raw bytes), load,
store. Plus addr_of, null_addr and is_null, which are safe — they work with something
you already own rather than conjuring an address from a number.
Fixed arrays — [T; N]
Inline storage, array_zero(), array literals with a checked element count, and
bounds-checked indexing that catches negatives too. The safe surface stays safe.
Value structs — shape
Fields read and write, take part in arithmetic and comparison, and addr_of(p.field) gives a
typed pointer into one. Omitted fields are zero, not stack garbage. A packed shape maps
byte-for-byte onto a C struct or a hardware descriptor:
packed shape GdtEntry {
limit_low: u16, base_low: u16, base_mid: u8,
access: u8, flags: u8, base_high: u8
}
// size_of(GdtEntry) == 8, exactly as the manual says
size_of(T) / align_of(T)
Compile-time layout facts for any scalar or shape. Layout comes from LLVM, so packed
reports what the hardware expects rather than what a table someone maintains says.
Volatile memory — load_vol / store_vol
Never elided, merged or reordered. A store to a hardware register that nothing reads back is
dead code to an optimiser; volatile is what makes the write actually happen.
Inline assembly — galdr
unsafe galdr { "cli"; "hlt"; }
One instruction per string, emitted so the optimiser leaves it exactly where you put it.
Common operations have named intrinsics instead — cli, sti, hlt, pause_cpu,
outb/outw/outl, inb/inw/inl — so the register constraints live in the compiler,
written once and tested, rather than in every kernel.
Float widths — f16, f32, f128
Alongside f64, with true storage layout: shape Vec3 { x: f32, y: f32, z: f32 } is twelve
bytes, which is what a GPU buffer or a C struct expects.
Hex, binary and underscored integer literals — 0xB8000, 0b1010_0110, 1_000_000.
Freestanding builds
torvc kernel.tv --bare -o kernel.elfNo libc, no runtime unless you ask for one. --no-std, --entry, --arch,
--link-script and --link-with come with it.
The allocator hook. Heap types need memory, and bare there is no malloc. Define
on_alloc / on_free and str, list and the rest work in a freestanding image; leave them
out and the runtime isn't linked at all.
OS builtins are refused at compile time, with an error that says what to use instead —
rather than an undefined-symbol dump from the linker after you've already got it wrong.
The output is decided by the target, not the host. A kernel built on Windows is the same
ELF as one built on Linux.
examples/kernel/ ships a complete reference kernel: VGA text output, port I/O for the
hardware cursor, an allocator, a long-mode boot stub, a linker script, and a
Forge your first kernel tutorial.
Tooling
Run mode. torvc file.tv compiles and runs a file, leaving no binary
behind. Arguments after the filename reach the program through args_get:
torvc scan.tv report.csv data.csvrune run file.tv [args...] does the same; a bare rune run still runs the project.
The [build] manifest section. Declare target = bare, an entry symbol, an
architecture, a link script, a boot stub and a runner once, and rune build / rune run
build and launch a kernel. A bare project builds to .elf, and rune run hands it to the
runner rather than trying to execute something the host can't.
Major-version gating. rune update now keeps you on your current major and tells you
about a new one instead of installing it — while still pulling the newest release within the
major you're on. Opt in with rune update v2 --yes. --silence-major quiets the notice
until the next major ships, and refuses if there's nothing to silence.
Changed
&& and || now short-circuit. They previously evaluated both operands, which meant a
guard like i < len(xs) && xs[i] == 1 could read out of bounds. This is a correctness fix.
Builtin results fold into surrounding expressions. byte_at(s, 0) * 2 and args() >= 2
used to fail with a confusing three-error cascade; only a handful of builtins chained
correctly. All of them do now.
The standard library moved to its own repository
with its own version line, so a project can hold std steady while the toolchain moves on.
Pin it with std = "1.3.0" in your manifest; rune update --std-major --yes opts into a new
major and writes the pin for you. Installing an older Torvik still works exactly as before.
rune is tested in its own repository, so each project can be checked on its own.
Fixed
- A user identifier named
entrymade the compiler fail with an internal error — LLVM shares
one namespace for values and block labels, and every function emitted anentry:label. - Defining a function or global named after a linked symbol (
puts,malloc,torvik_argc)
reported an internal error asking you to file a compiler bug, for what was a naming choice. - A decimal used as an index, and several float paths, produced internal errors.
- Reporting a diagnostic could itself crash the compiler on an out-of-range position.
- Paths containing spaces broke the build.
galdrassembly text is escaped, so a quote inside an instruction can no longer inject
arbitrary IR.- Shape and array variables were wrongly reported as unused when only their fields or
elements were read. - Unsupported field and element types (
str,list,i128, …) used to compile and store
garbage; they are now refused with an error listing what is allowed.
Notes
f128is a storage and interop type. Arithmetic on it computes at double precision —
declaration, storage and layout are exact, computation is not. x86 also has no hardware for
128-bit floats, so real f128 arithmetic needs soft-float support that a--barebuild does
not link.- Heap-backed types (
str,list,table,bag,i128/u128), nested shapes, and shape
parameters are not supported in shapes or fixed arrays yet. They need reference counting
threaded through struct copies, which is real work and is scheduled for a future version. Unsupported types are refused with a clear error, never mis-stored.
Install
# Linux
curl -fsSL https://raw.githubusercontent.com/torvik-lang/torvik/main/linux/install.sh | sh
# Windows (PowerShell)
iwr -useb https://raw.githubusercontent.com/torvik-lang/torvik/main/windows/install.ps1 | iexAlready installed? rune update.
Docs: Language guide ·
Build a kernel ·
Tooling
Torvik v1.4.0 — Optional & variadic parameters, and networking
Torvik v1.4.0 makes function calls more flexible and safer, closes the last of the
expression-chaining rough edges, and adds a small, opt-in HTTP layer so Torvik programs
can serve files and answer requests. The compiler remains fully self-hosting — it builds
itself byte-identically on Linux and Windows.
Highlights
Optional (^) and variadic (*) parameters.
Mark a trailing parameter ^ and callers may omit it (it takes a type-appropriate zero),
or * to gather the remaining arguments into a list<T>. They combine in the order
required → optional → variadic, and wrong argument counts are clean, located range errors.
df greet(name: str, ^count: i64) -> void {
echo!("hi {name} x{count}");
}
greet("a"); // hi a x0
greet("b", 3); // hi b x3
df join_words(sep: str, *words: str) -> str { /* ... */ }
join_words(", ", "a", "b", "c"); // a, b, c
Arguments are type-checked at the call site.
Passing an argument whose type can't match the parameter — a number where a string is
expected, a decimal where an integer is expected, a list where a scalar is expected — is
now a clean error instead of silent garbage or a crash at run time. Ambiguous cases are
left alone, so correct programs are never rejected.
Boolean chaining is limit-free.
&& and || now work as values bound to a variable (fixed ok: bool = a && b;,
including three-way), not just inside conditions, and a boolean-returning builtin or user
function can be compared to a literal:
fixed ready: bool = a && b;
check contains(line, "error") == false { ... }
std::net — a minimal HTTP layer.
apply std::net; brings in request parsing, MIME types, path_is_safe, and response
builders over transport primitives — net_listen, net_accept, net_recv, net_send,
net_send_file, net_close — that dead-strip out of programs that never call them, so
you pay nothing unless you use them. net_send_file streams bytes verbatim, so binary
assets serve intact. It's enough to serve a static site or a small localhost API (and it
powers Vefna's new vefna serve).
Also: chr(code) and fs_size(path) builtins; standard library v1.3.0.
rune has moved
As of this release, rune lives in its own repository —
https://github.com/torvik-lang/rune — and versions independently. It still installs
alongside torvc and manages Torvik exactly as before; rune update moves you to the
latest of both.
Fixes
- Exhaustive
whenno longer trips a flow-checker false positive. - A boolean literal passed as an argument no longer produces malformed IR.
- A
bool(or narrow-int) value returned from a user function is now stored correctly. - Single-character function names (
a,g,p, …) resolve everywhere — previously they
could be wrongly reported "undefined" whenapply std;was present.
Install / upgrade
Already on Torvik? Just run rune update.
New install (needs clang on your PATH):
# Linux
curl -fsSL https://raw.githubusercontent.com/torvik-lang/torvik/main/linux/install.sh | sh
# Windows (PowerShell)
iwr -useb https://raw.githubusercontent.com/torvik-lang/torvik/main/windows/install.ps1 | iex
Linux and Windows (x86-64); macOS is planned once real Apple hardware is available for
testing.
Links
- Full changelog: https://github.com/torvik-lang/torvik/blob/main/CHANGELOG.md
- Guide, standard library, and tooling docs: https://github.com/torvik-lang/torvik/tree/main/docs
- Wiki: https://github.com/torvik-lang/torvik/wiki
Torvik v1.3.0 — Ravens & Bridges
The concurrency release. Torvik programs can now run work on multiple threads — designed so data races aren't something you avoid, they're something the language gives you no way to write.
🐦⬛ Ravens: concurrent tasks
raven spawns any df function on its own OS thread — fire-and-forget, or bind a task<T> handle and collect the result with join:
set h: task<str> = raven fetch(url);
// ...other work...
fixed page: str = join(h);
- Arguments deep-copy at the spawn, so a task owns its own copy of everything it receives — after the spawn line, the two threads share nothing.
- Spawned functions must be self-contained (no globals, transitively) — enforced at compile time with an error naming the function and the global. This is what keeps the reference-counting runtime lock-free.
- Every crossable type works: all integer widths,
f64,bool,str,i128/u128, andaettvalues. - Each handle joins exactly once; a panic in a task stops the whole process immediately — background failures are never silently swallowed.
🌉 Bridges: typed channels between tasks
A bridge<T> is a buffered, thread-safe queue — the one object tasks share, protected by its own lock:
df produce(ch: bridge<i64>, n: i64) -> void {
set i: i64 = 0;
whilst i < n { send(ch, i); i += 1; }
bridge_close(ch);
}
df main() -> void {
set ch: bridge<i64> = bridge_new(4);
raven produce(ch, 10);
set total: i64 = 0;
whilst true {
set r: result<i64> = try_recv(ch);
check is_err(r) { break; }
total += unwrap(r);
}
echo!("sum: {total}");
}
- Values deep-copy on send;
recvblocks while empty;bridge_closeis idempotent and wakes everyone. try_recvreturns aresult<T>that goeserrexactly once the bridge is closed and drained — the clean end for a worker loop.- Multiple producers and consumers on one bridge are fully supported.
- A bridge's own refcount is the single atomic operation in the entire design; every other refcount in the language stays lock-free.
🧵 Strings are data; interpolation belongs to echo and fmt
String interpolation is now scoped to echo, echo!, and fmt — a string literal written directly as one of their arguments interpolates exactly as before ({name}, expressions, {{ escapes). A string literal anywhere else is plain data:
fixed css: str = ".a{color:red}"; // just characters — compiles fine now
fixed slot: str = "{{title}}"; // exactly {{title}}, no escapes needed
fixed msg: str = fmt("Hi {name}"); // fmt still interpolates
Previously every literal interpolated, so brace-heavy data — CSS, JSON, HTML templates — needed {{ escapes everywhere and could trip "undefined variable" errors on ordinary text. Code that kept interpolated literals inside echo/fmt (the documented idiom all along) is unaffected.
🔤 Variadic str_concat
str_concat(a, b, c, ...) now folds any number of arguments left-to-right — no more nesting. Calling it with one argument is a clean compile error; previously a fixed two-argument form silently dropped extra arguments, which could hide real bugs.
📚 Standard library v1.2.0
Additive growth, no breaking changes:
std::convert(new module) —to_hex,to_bin,from_hex,to_int(parsers returnresult<i64>, so bad input is recoverable)std::strings—strip_prefix,strip_suffix,is_digits,is_alpha,center,truncatestd::list—contains_int,contains_str_in,count_int,unique,take,drop,meanstd::math—pow_checked(overflow-signalling power),digit_count,at_least,at_most,ilog2
Projects can require the new floor with std = "1.2.0" in torvik.rune.
🗂 Table iteration + a memory-safety fix
table_keys(t)returns every key as a sortedlist<str>(sorted so loops over a table are reproducible — hash order shifts as a table resizes). Iterate the keys, fetch values withtable_get.- Fixed: functions returning a
tableorbaghanded back freed memory (a missing +1-on-return retain).fixed cfg: table<str, str> = read_config(path);now works exactly as you'd expect.
🔧 Also in this release
- File-system surface for real tools:
dir_list,fs_is_dir, binary-safefs_copy, recoverable I/O viatry_writefile/try_appendline/try_fs_copy, and a newunused_resultwarning category. - The lexer's two keyword lists are now one —
is_keywordderives fromkeyword_for, eliminating an entire drift-bug class. - Installer hardening: std modules are now derived from
std.tvitself, so a new module can never be silently skipped again; the Windows installer also gained the compiler-lib sources it was missing. - Test suite grown from 100 to 123 cases, including deterministic concurrency tests (no sleeps — ordering is forced by joins and bridge blocking).
📖 Documentation
The Language Guide has a full new chapter — Concurrency: ravens and bridges — covering the ownership model, the self-contained rule and its why, and the worker-loop pattern. STDLIB.md, the Roadmap, and the wiki are all updated.
Install
Linux
curl -fsSL https://raw.githubusercontent.com/torvik-lang/torvik/main/linux/install.sh | sh
Windows (PowerShell)
iwr -useb https://raw.githubusercontent.com/torvik-lang/torvik/main/windows/install.ps1 | iex
Already installed? rune update brings you to v1.3.0.
Full details in the CHANGELOG. Binaries for Linux x86-64 and Windows x86-64 are attached below.
Self-hosting, compiled to native via LLVM. No VM, no GC — automatic reference counting, now lock-free across threads by design.
Torvik v1.2.1 — Fix to warning system and general patch release
A patch release fixing four correctness bugs in v1.2.0. No new features, no breaking
changes for correct programs. Verified by three-generation self-hosting fixpoints and
a 97-case end-to-end suite on Linux and Windows.
Update with:
rune update
Warnings now reach rune run users — and stay visible
Two compounding bugs hid the entire v1.2.0 warnings system from anyone building
through rune (which is to say: almost everyone). rune compiles with -q, and -q
implied --no-warn; and on cached runs no compiler ran at all, so nothing could
print. Both are fixed: warnings are diagnostics, like errors — -q now
suppresses only the informational build chatter — and rune captures the compiler's
diagnostics and replays them on every cached run, until the code is fixed or a
suppressor (--no-warn, !@NO_WARN;, !@ALLOW[...]) is used. The suite now
asserts warnings survive -q builds, so this class can't regress silently.
Unannotated declarations no longer compile silently broken
set v = trim(x); compiled — and v read back as garbage aliasing unrelated
memory. set b = a; segfaulted. Both are now clean located errors pointing at the
fix. The guide has always required type annotations; only the documented
set xs = list_new(); inference may omit one.
Bare statement calls of builtins get a truthful error
trim(s); as a statement claimed "call to undefined function 'trim'" — factually
wrong. It now says what's actually happening: the builtin returns a value, a bare
statement discards it, and here's how to use it.
Diagnostics under apply report your real line numbers
Applied modules are prepended to the compilation unit, so an error on your line 3
was reported as line ~330 of your file. Line numbers past the applied prelude are
now translated back to your file; diagnostics inside an applied module say so
explicitly.
Full Changelog: v1.2.0...v1.2.1
Torvik v1.2.0 — Ættir, Pattern Matching, and a Compiler That Warns You
v1.2.0 is a language-ergonomics and tooling release — the biggest addition to Torvik's surface since 1.0, with no breaking changes. Verified green on Linux and Windows: three-generation self-hosting fixpoints on both platforms and a 94-case end-to-end suite.
Update with:
rune update
aett — enumerations, the Torvik way
The runes of the futhark are grouped into families called ættir — and now, so are your named values. An aett is Torvik's enumeration:
aett Status { Pending, Active, Closed }
set s: Status = Status::Pending;
echo!(s); // 0 — variants are i64 ordinals, in declaration order
echo!(typeof(s)); // Status
Variants are accessed with :: and behave like integers everywhere — print, compare, store in lists, pass to functions — while the aett name works as a type annotation for variables, parameters, and return types. Unknown variants, duplicate declarations, and X::Y on a non-aett are all clean located errors (an unknown variant even lists the real ones).
when — pattern matching with exhaustiveness
df describe(s: Status) -> str {
when s {
Status::Pending => return "waiting";
Status::Active => return "live";
Status::Closed => return "done";
}
return "";
}
Arms use => with a single statement or a block, and fallback => as the default (Torvik has no else — consistency kept). The headline: a when over an aett with no fallback is checked for exhaustiveness. Miss a variant and the compile error names exactly what's uncovered — so adding a variant to an aett later instantly surfaces every when in your codebase that needs updating. Integer-literal patterns (negatives included) work too, with a required fallback.
A warnings system that never cries wolf
The compiler now warns — never fails — on legal-but-suspicious code, with the same line-and-caret rendering as errors:
- Unused variables, including write-only bindings that are assigned but never read. Prefix a name with
_to mark a deliberate discard. - Unreachable code after
return/break/continue/halt/exit— with thehalt(...); return x;all-paths-return idiom sensibly exempt. - A deprecation channel, so future removals warn at every call site (nothing is deprecated today).
Warnings render before errors, so an "unreachable code" note can explain the "must return a value" error it caused. Suppress globally with torvc --no-warn (-q implies it), or per file with directives at the top: !@NO_WARN; for everything, stackable !@ALLOW[category]; for one category. A typo'd directive is a clean compile error, not a silent no-op. Proof it works: enabling the system on the compiler's own source found and removed four dead variables in torvc and rune.
Result types, completed
The consumer side (is_ok, unwrap, unwrap_or, err_msg, err_code, try_readfile, try_toint, try_tofloat) has been in the runtime for a while. v1.2.0 adds the producer side — ok(value) and err(msg) / err(code, msg) — so your own functions can return result<i64>, result<str>, or result<f64>:
df safe_div(a: i64, b: i64) -> result<i64> {
check b == 0 { return err("division by zero"); }
return ok(a / b);
}
Standard library: grown, and now properly versioned
- New core builtin
find(s, sub)— the byte index of a substring,-1when absent.containstold you whether;findtells you where. - New module
std::path—path_base,path_dir,path_ext,path_join, with both/and\recognized on input. std::listgains in-placesort/sort_str(stable insertion sort),reverse_list, andindex_of/index_of_str;std::mathgainssignandisqrt;std::stringsgainscount_str,reverse_str, andcapitalize.- Independent std versioning. The standard library now carries its own semver, decoupled from the compiler's: additive growth bumps its minor, breaking changes bump its major.
torvc --versionandrune versionreport it, and a project can require a minimum withstd = "1.1.0"intorvik.rune— a too-old installation is a clean build error pointing atrune update. This release demonstrates the policy: std moves to 1.1.0, fully backward compatible.
Sharper diagnostics
- Version-string-looking literals get a real error.
0.1.0is not a number (a float has one decimal point), but it previously produced a confusing cascade of parse errors. Now it's one clean located error with the fix: quote it as a string.
Tooling & platform
- Windows
.tvfile-type & icon integration — the installer registers the.tvextension with a friendly name and the Torvik file icon (per-user, no admin needed; no open-command is hijacked — source files belong to your editor).rune uninstallremoves the registration. - The end-to-end suite grows to 94 cases (aett/when, results,
find, std growth, the warnings system and its directives, the std version gate, and the malformed-literal diagnostic all covered).
Roadmap notes
task (async) headlines v1.3.0. Official macOS support remains on the future list, waiting on real Apple hardware for credible testing.
Full details in CHANGELOG.md.
Full Changelog: v1.1.3...v1.2.0
Torvik v1.1.3 — Stability Release: Silent Bugs Removal And Fixes For Both Platforms
v1.1.3 is a patch release with no new syntax and no breaking changes — just a compiler-wide audit that hunted down silent wrong answers, silent no-ops, and compile-then-crash holes, on Linux and Windows. It's backed by a new end-to-end test suite (78 cases, now shipping in the repo under dev/tests with both sh and PowerShell runners) that runs green on both platforms, with the full suite ASAN-clean and the compiler verified through a three-generation self-hosting fixpoint on each OS.
If you're on v1.1.x, update with:
rune update
Silent wrong answers fixed
These compiled and ran — and quietly produced the wrong result.
- Leading unary minus precedence.
-m + 2compiled as-(m + 2)(so-7instead of-3form = 5). A leading-now binds to exactly one value in every position, and negation is type-aware:-xworks onf64andi128(previously invalid IR or a segfault), while negating astr,list,bool, oru128is a clean error. fixedis now enforced. Reassigning afixedbinding — plain or compound, local or global — previously compiled and mutated silently. It's now the compile error the guide always documented. Enforcement surfaced 131 latentfixed-then-mutated bindings inside the compiler itself, all converted toset(behavior identical).- Return types are checked.
df f() -> str { return 5; }compiled and crashed at run time;-> i64 { return "x"; }printed a pointer. Both are now clean, located compile errors. - Weave stages are arity- and type-checked.
5 ~> addn(missing argument) called the function with garbage;5 ~> shout(ani64woven into astrparameter) segfaulted. Both are now clean compile errors — and integer-returning weaves insideecho!()no longer crash (that one was a documented example!). - Booleans print consistently.
echo!(true)printed1,tostr(true)returned"1", andfs_exists()echoed1/0. Everything bool now printstrue/false. - Collection element fixes.
list<bool>element reads segfaulted;list<u64>elements leading an expression used signed division (a full-range value divided negative). Both fixed. - Large integers printed truncated on Windows.
longis 32-bit on Win64, soint_to_str(and whole-list printing) mangled big values —-9223372036854775807printed as1. Linux was never affected. Fixed with 64-bit formatting throughout.
Crashes fixed
bag<T>was released out from under you. A one-character class of bug (an exact-match type check where lists and tables use a prefix match) meantbag<str>declarations skipped reference counting entirely — the bag was freed at end-of-statement and every later use was a use-after-free. Linux's allocator happened to tolerate it; Windows access-violated. Found with AddressSanitizer, fixed at all five sites, re-verified clean.set c: bool = true;could silently drop the store, leaving the variable uninitialized — ternaries on such variables picked arbitrary branches.
Windows fixes
apply std;now works on Windows. The installer shipped thestd::submodules but never the umbrellastd.tv, soapply std;failed on every Windows install whileapply std::math;worked. The installer now install the same library set as Linux. (Reinstall or re-run the installer to pick this up.)readkey()honors redirected stdin. It previously read only the console device, so piped input hung forever waiting for a physical keypress.
Implemented as documented
~(bitwise NOT) — in the operator table since v1.0, previously a parse error.upper(s)/lower(s)as ordinary calls — previously they existed only as weave stages.- Zero-argument console builtins —
read(),readint(),readfloat(),readbool()now work exactly as STDLIB.md shows (an optional prompt argument is still accepted). torvc file.tvderives its output name from the source file (./file,file.exeon Windows) as TOOLING.md documents — previously the binary silently went to~/tv_out.- Full-range
i128/u128global literals — up tou128max and down toi128min, boxed at startup exactly like locals, with clean range errors.
Added
readkey_str()— the pressed key as a 1-character string (pairs withchar_at), complementingreadkey()'s unchanged byte-code return (pairs withbyte_at). STDLIB.md previously misdocumentedreadkey()as returningstr; that form never compiled in any release, so no working code is affected — the docs now describe the real behavior, andreadkey_str()provides the string form.- The end-to-end test suite now ships in
dev/tests: 34 positive programs, 30 expected-clean-error cases, 8runeproject cases, and 6torvcflag cases, with runners for both platforms.
Notes
- One documented limitation remains: 128-bit globals take a single literal initializer (expressions in a 128-bit global initializer are a clean compile error — compute in a function instead).
- Full details in CHANGELOG.md.
Full Changelog: v1.1.2...v1.1.3