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:
SELECT * FROM users.fetch compiles into a query execution call. See the Database guide.Template strings: var output =
Hello {{ name }}
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
null is false; everything else (including 0 and "") 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.
Null#
var val = null
In Bialet, null is safe: accessing a key or calling a method on null returns
null instead of throwing an error. This makes template interpolation forgiving
when data is missing.
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.
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)
"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 the routing guide for details on external imports and 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.
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 Reference |
|
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.