Wren Language Guide#
Wren is a small object-oriented scripting language. See the full documentation at wren.io.
Bialet embeds a heavily modified dialect of Wren. The syntax itself has been extended — not through libraries, but directly in the parser — to support native SQL queries and rich string interpolation:
Native queries:
var users = `SELECT * FROM users`.fetchcompiles into a query execution call. See the Database guide.Template strings:
var output = <p>Hello {{ name }}</p>is syntax that evaluates to a string with interpolation. See Templates for details.
This is a deliberate dialect, not standard Wren. Code written for stock Wren will not necessarily run unchanged in Bialet, and the reverse is true. Treat the Bialet dialect as its own thing.
Quick Guide#
// Variables
var name = "Alice"
var count = 42
// Classes and constructors
class User {
construct new(data) { _data = data }
name { _data["name"] }
}
// Methods return the last expression — no `return` needed
class Math {
static square(n) { n * n }
static foo {
var x = 2
return x // only needed for early return
}
}
// Functions and closures
var doubled = [1, 2, 3].map { |n| n * 2 }
// String interpolation
System.log("Hello %(name)!")
// Control flow
if (count > 10) { System.print("high") }
for (item in [1, 2, 3]) { System.print(item) }
for (i in 0...100) { System.print(i) }
// Conditions are expressions
var label = count > 10 ? "yes" : "no"
// Lists and Maps
var list = [1, 2, 3]
list.add(4)
var map = { "key": "value" }
map["name"] = "Bob"
// Filters and chains
var positives = [1, -2, 3].where { |n| n > 0 }
var names = users.map { |u| u.name }.join(", ")
Why Wren?#
Familiar syntax. If you’ve used C, Java, JavaScript, or Python, Wren reads
naturally. Classes, methods, if/while/for — it’s all there without
surprises.
Truly object-oriented. Wren doesn’t split the world into primitives and
objects. Everything is an object — classes, instances, booleans, null, even the
number -3.5. You can ask anything for its type: both Num.name and
(-3.5).type.name output Num. No primitives, no wrappers; it’s objects all the
way down, which keeps the language small, consistent, and predictable.
Designed for embedding. The Wren VM is clean, well-documented C with no dependencies. Bialet creates and destroys a fresh VM for every request — no state leaks between users, no garbage-collection surprises under load.
Try Wren Online#
The upstream language has an interactive playground where you can experiment with Wren syntax outside of Bialet:
Values and Types#
Wren has a small, predictable set of built-in types.
Booleans#
true
false
⚠️ Pitfall:
nullis false; everything else (including0and"") is true.
Numbers#
var a = 42
var b = 3.14
var c = 0xff // hex literal
All numbers are 64-bit floats (IEEE 754). Arithmetic: +, -, *, /, %,
plus bitwise &, |, ^, ~, <<, >>.
Convert strings to numbers with Num.fromString() — essential in Bialet since
database values come back as strings:
var votes = Num.fromString(row["votes"])
Strings#
var s = "hello"
var t = s + " world" // "hello world"
Strings are immutable. Index with s[i] to get a single-character string at
byte offset i. .count is the byte length.
⚠️ Pitfall: strings don’t implement
<,>,<=, or>=—"a" < "b"raisesString does not implement '<(_)'.. Only==and!=work directly. To sort or compare strings by value (e.g."YYYY-MM-DD"dates as text), convert to a comparable number first — for dates, a Julian day number; for single characters,.codePointsgives you comparableNums you can rebuild withString.fromCodePoint(n).
Null#
var val = null
Bialet makes null forgiving in templates: a small set of methods never throws,
so missing data renders as empty output instead of crashing the request. On
null:
null["key"]returnsnullnull.countreturns0null.map { ... }returns an empty listnull.to(Class)returnsnullnull.toStringreturns""
Any other method call on null still throws — Null does not implement 'x'.
For example null.get("key") is a runtime error. Use the safe methods above, or
a || default fallback, in templates.
Lists#
var list = [1, 2, 3, 4]
list.add(5)
list[0] // 1
list.count // 5
Maps#
var map = { "name": "Alice", "age": "30" }
map["name"] // "Alice"
map.containsKey("email") // false
Ranges#
1..5 // inclusive: 1, 2, 3, 4, 5
1...5 // exclusive: 1, 2, 3, 4
Variables and Blocks#
var x = 10
var y = x + 1
Variables are block-scoped. A block is delimited by { }:
{
var inside = "visible here"
}
// System.print(inside) — ERROR, not accessible
Closures capture variables from enclosing scopes:
var counter = 0
var increment = Fn.new { counter = counter + 1 }
increment.call()
System.print(counter) // 1
Control Flow#
If / Else#
if (score > 10) {
System.print("high")
} else if (score > 5) {
System.print("medium")
} else {
System.print("low")
}
While#
var i = 0
while (i < 5) {
System.print(i)
i = i + 1
}
For#
Wren’s for iterates over any sequence — lists, maps, ranges, strings:
for (item in [1, 2, 3]) {
System.print(item)
}
for (i in 0...100) {
System.print(i) // 0 through 99
}
Ternary (Conditional)#
Wren doesn’t have ?: as a dedicated operator. But if/else is an expression
— the condition with a ? prefix:
var label = totalVotes > 0 ? "active" : "empty"
Break and Continue#
for (item in list) {
if (item == null) continue
if (item == "stop") break
System.print(item)
}
No break with a label, no switch/case.
Functions#
Functions are first-class values created with Fn.new:
var add = Fn.new { |a, b| a + b }
add.call(3, 4) // 7
The parameter list uses pipe syntax: |a, b|.
Closures as Blocks#
Wren has a sugary shorthand for closures passed as the last argument to a method — called a block argument:
[1, 2, 3].map { |n| n * 2 } // [2, 4, 6]
items.where { |item| item != null } // filter in place
Use _ for the argument name when you don’t need it:
items.where { |_| true }
This is the same Fn you’d create with Fn.new — just more readable when
chaining.
Classes#
class Animal {
construct new(name) {
_name = name
}
name { _name }
name=(n) { _name = n }
speak { "(silence)" }
}
class Dog is Animal {
construct new(name, breed) {
super(name)
_breed = breed
}
speak { "Woof!" }
}
Wren is single-inheritance. Use super(...) to call the parent constructor and
super.method() to call overridden methods.
Getters and Setters#
Methods with zero arguments that are accessed without () are getters:
var d = Dog.new("Rex", "Labrador")
d.name // getter, returns "Rex"
d.name = "Max" // setter
Static Methods#
class MathUtil {
static square(n) { n * n }
}
MathUtil.square(5) // 25
Privacy Convention#
Wren has no private keyword. Prefix field and method names with an underscore
to signal they’re internal:
class Poll {
votes_(opt) { Num.fromString(opt["votes"]) }
}
The is Operator#
Check if an object is an instance of a class:
var d = Dog.new("Rex", "Labrador")
d is Dog // true
d is Animal // true
d is String // false
Single-Expression Blocks#
This is Wren’s most distinctive feature and the one that makes Bialet code unusually compact.
A block (method body or class body) that ends with an expression returns that
expression automatically — no return keyword needed:
class Poll {
construct new() {
_opts = null
}
// Single-expression getter: caches and returns query results
options { _opts || (_opts = `SELECT * FROM poll`.fetch) }
// Multi-statement method: performs a side effect (UPDATE), clears cache.
// Last expression (_opts = null) evaluates to null — no return needed.
vote(opt) {
`UPDATE poll SET votes = votes + 1 WHERE id = ?`.query(opt)
_opts = null
}
// Reduce over cached options — reads like a description, not plumbing
totalVotes { options.reduce(0) { |sum, opt| sum + votes_(opt) } }
// Ternary in a single expression
percentage(opt) { totalVotes > 0 ? ((votes_(opt) / totalVotes) * 100).round : 0 }
votes_(opt) { Num.fromString(opt["votes"]) }
}
This is a complete domain class. No ORM, no annotations, no configuration — just Wren methods that express the logic directly.
Same line vs. next line#
The implicit return only applies when the expression starts on the same line as
the opening {. If the body starts on a following line, Wren treats it as a
statement body — each statement runs, the last value is discarded, and the
method returns null:
class Shape {
// Expression body — starts on the `{` line, the value is returned
area(w, h) { w * h }
// Same for HTML — the string starts on the `{` line
badge(label) { <span class="badge">{{ label }}</span> }
// Expression moved to the next line — computes then discards, returns null
brokenArea(w, h) {
w * h
}
// Statement body — runs the query, returns null unless you `return`
increment(id) {
`UPDATE votes SET n = n + 1 WHERE id = ?`.query(id)
}
}
brokenArea(2, 3) evaluates 2 * 3 and throws the result away, returning
null. In a template this renders as empty output — no error tells you the
value was dropped. Keep the expression on the same line as { when you want the
implicit return, and use an explicit return for any multi-line method that
must return a value.
⚠️ Pitfall: this rule applies to block arguments (
.map { },.where { },Fn.new { }, …) exactly the same way, and it’s easy to miss there because there’s no error — the block just silently returnsnullfor every call:// Bug: filters out everything. `libre` never gets returned because it's // not on the same line as `{`, so the block's value is null every time. list.where { |x| var libre = true if (x == 2) libre = false libre }.toList // -> [] // Fixed: explicit return list.where { |x| var libre = true if (x == 2) libre = false return libre }.toList // -> the actual filtered listA single-expression block (
{ |x| x * 2 }) is unaffected — implicit return always works there since the expression is already on the{line.
When You Need return#
For multiline methods that branch early, use explicit return:
static footer {
if (requestHasVoted) {
return <footer>You voted. Thanks!</footer>
}
return <footer>Cast your vote above.</footer>
}
Bialet’s template system leans on this: a template function is typically a
single-expression method whose body is an HTML string block — no return
clutter.
String Interpolation#
Use %(expr) anywhere in a string:
var name = "Alice"
"Hello, %(name)!" // "Hello, Alice!"
var total = 42
System.log("Found %(total) results")
Any expression works, not just variables:
"Users online: %(onlineUsers.count - idleUsers.count)"
"<a href='/user/%(user["id"])'>%(user["name"])</a>"
Templates: HTML String Blocks#
String interpolation is the foundation of Bialet’s template syntax. When you write:
<main>
<h1>{{ title }}</h1>
<ul>
{{ items.map { |item|
<li>{{ item }}</li>
} }}
</ul>
</main>
The <main>...</main> is a string expression — Bialet transforms HTML inline
blocks into string literals with %(...) interpolation. {{ }} is syntactic
sugar for %(...) inside HTML blocks.
The result is a seamless flow between HTML markup and Wren logic. Iterate over
database rows and emit <li> tags without context-switching to a separate
template language. See the Templates guide for the full template
system including layout patterns, partials, and the _app.wren convention.
Collections and Sequences#
Sequence is the parent class of all iterable types (List, Map, Range, String).
It provides a rich set of higher-order methods:
Transform#
[1, 2, 3].map { |n| n * n } // [1, 4, 9]
Filter#
[1, null, 3].where { |n| n != null } // [1, 3]
Reduce#
[1, 2, 3].reduce(0) { |sum, n| sum + n } // 6
Test#
[1, 2, 3].any { |n| n > 2 } // true
[1, 2, 3].all { |n| n > 0 } // true
items.contains("target") // true/false
Accumulate#
[1, 2, 3].join(", ") // "1, 2, 3"
[1, 2, 3].count // 3
items.isEmpty // true/false
Paginate#
[1, 2, 3, 4, 5].take(2) // [1, 2]
[1, 2, 3, 4, 5].skip(2) // [3, 4, 5]
Materialize#
(1..5).toList // [1, 2, 3, 4, 5]
Bialet Extensions#
Bialet adds a few convenience methods:
[1, 2, 3].first // 1 (null for empty lists)
"<script>".safe // "<script>" (HTML escape, pre-escape)
"<b>x</b>".raw // HtmlNode (mark as safe HTML)
"hello".upper // "HELLO"
"HELLO".lower // "hello"
"42".toNum // 42 (as Number)
"1".toBool // true
list.to(User) // map list of Maps into User instances
The .to(Class) method on Sequence and Map is especially useful with database
results:
var posts = `SELECT * FROM posts`.fetch.to(Post)
var post = `SELECT * FROM posts WHERE id = ?`.first(1).to(Post)
Imports#
import "shared" for Shared
import "math" for Math, PI
import "liquids" for Water
import "beverages" for Coffee, Water as H2O, Tea
Use as to import a variable under a different name:
import "beverages" for Water as H2O
// var water = H2O.new()
Module paths are relative to the importing file. In Bialet you can also import from GitHub:
import "gh:user/repo/module" for ClassName
See External Modules for details on external imports, and the advanced routing guide for file-based module resolution.
Bialet vs. Standard Wren#
Bialet uses a modified Wren VM with additions for web development. Here’s what differs from upstream Wren.
No Concurrency#
Bialet is single-threaded per request. Each HTTP request gets a fresh Wren VM — created when the request arrives, destroyed after the response is sent. There is no shared state between requests (use SQLite or Session for persistence).
Wren’s cooperative multitasking features (Fiber) depend on VMs persisting
across yield points. Since Bialet tears down the VM after every request, the
concurrency model doesn’t apply.
Fiber Is Not Tested#
The Fiber class exists in the embedded VM and Fiber.abort() is used
internally for error signaling. However, Fiber.yield(), Fiber.call(),
Fiber.transfer(), and all cooperative multitasking features are untested and
unsupported in Bialet.
Do not rely on Fibers for application logic. Use the database or session store when you need to persist state across request cycles.
Fiber.try() for Contained Failure#
While cooperative multitasking (yield/call/transfer) is unsupported,
Fiber.new { ... }.try() works well for a different, common purpose:
containing a call that might throw so it can’t take the whole request down.
This is the idiomatic way to call an external service (or any risky code)
as best-effort, degrading gracefully instead of failing the page:
var fiber = Fiber.new {
return Http.get("https://flaky-service.example.com/status")
}
var value = fiber.try()
if (fiber.error) {
// fiber.error holds the error message (a String); log it and degrade
System.log("Best-effort call failed: %(fiber.error)")
} else {
// use value
}
fiber.try() never propagates the exception to the caller — fiber.error
is set (non-null) when the block raised one, and stays null otherwise.
Http.get/post/put/delete already do this internally (see
HTTP Calls), so you only need this pattern yourself for
other code paths that can throw — a third-party import, Json.parse on
untrusted input, or your own domain logic.
Bialet-Specific Classes#
Bialet adds these classes on top of standard Wren:
Class |
Purpose |
|---|---|
|
Backtick SQL: |
|
Incoming HTTP request (method, URI, headers, body, files) — see Reference |
|
Outgoing HTTP response (status, headers, body, redirect) — see Reference |
|
Parse, set, and delete cookies — see Reference |
|
Server-side session storage with CSRF protection — see Reference |
|
Database migrations and ORM-like save/delete — see Reference |
|
Outbound HTTP requests (GET/POST/PUT/DELETE) — see HTTP Calls |
|
Date and time with formatting, arithmetic, and timezones — see Reference |
|
File uploads stored in SQLite — see Reference |
|
Render Markdown to HTML — see Reference |
|
JSON parse and stringify — see Reference |
|
Key-value configuration store — see Reference |
|
Scheduled tasks — see Reference |
|
Logging and stdout output — see Reference |
|
Helpers: hashing, encoding, random strings, URL encoding — see Reference |
The full API reference with method signatures and examples is on the Reference page.