This is the reference for Seen's syntax and semantics.
Seen supports // line comments and block comments delimited by standalone /// lines:
// Single-line comment
///
Anything between standalone delimiter lines is ignored by the lexer.
Use this for longer notes without prefixing every line.
///
/// text is still treated as a line comment; block delimiters must be the only non-whitespace text on the line.
let)let name = "Alice"
let age = 30
let pi = 3.14159
var)var count = 0
count = count + 1
val)val MAX_SIZE = 1024
const PI = 3.14159265
static var instance_count = 0
Types can be explicit or inferred:
let x: Int = 42
let y: Float = 3.14
let s: String = "hello"
let b: Bool = true
let c: Char = 'A'
| Type | Description |
|---|---|
Int |
64-bit signed integer |
Float |
64-bit floating point |
Bool |
Boolean (true/false) |
String |
UTF-8 string |
Char |
Unicode code point |
Void |
No value |
Never |
Function never returns |
fun greet(name: String) r: String {
return "Hello, {name}!"
}
The r: syntax specifies the return type.
fun logMessage(msg: String) {
println(msg)
}
For single-expression bodies:
fun double(x: Int) r: Int => x * 2
fun add(a: Int, b: Int) r: Int {
return a + b
}
Facade component functions use the contextual component form. Inside a component body, state, computed, and uiEffect declare UI-local state, derived values, and explicit side-effect blocks:
component Counter(title: String) {
state count: Int = 0
computed label: String = title + ": " + count.toString()
uiEffect {
println(label)
}
}
Component calls support named arguments and trailing or named slot blocks for declarative APIs. Dynamic child lists should provide stable keys so frontend and editor diagnostics can catch missing or duplicate child identity.
if x > 0 {
println("positive")
} else {
println("non-positive")
}
let result = if x > 0 { "positive" } else { "non-positive" }
var i = 0
while i < 10 {
println("{i}")
i = i + 1
}
for item in items {
println("{item}")
}
for i in 0..10 {
println("{i}") // 0 through 9
}
for i in 0..=10 {
println("{i}") // 0 through 10
}
loop {
let input = readLine()
if input == "quit" {
break
}
}
for i in 0..100 {
if i == 50 { break }
if i % 2 == 0 { continue }
println("{i}")
}
let result = when value {
is 1 => "one"
is 2 => "two"
is 3 => "three"
else => "other"
}
when shape {
is Circle(r) => println("Circle with radius {r}")
is Rectangle(w, h) => println("Rectangle {w}x{h}")
}
class Point {
var x: Float
var y: Float
static fun new(x: Float, y: Float) r: Point {
return Point { x: x, y: y }
}
fun distanceTo(other: Point) r: Float {
let dx = this.x - other.x
let dy = this.y - other.y
return sqrt(dx * dx + dy * dy)
}
}
Use static fun new(...) by convention:
let p = Point.new(3.0, 4.0)
this keywordInstance methods access fields via this:
fun getX() r: Float {
return this.x
}
class Shape {
var name: String
fun describe() r: String {
return "Shape: {this.name}"
}
}
class Circle extends Shape {
var radius: Float
fun area() r: Float {
return 3.14159 * this.radius * this.radius
}
}
sealed restricts subclassing to the same compilation/package boundary used by the compiler's type checks:
sealed class Expr {
}
data struct Color(r: Int, g: Int, b: Int)
This is a compact value type declaration.
struct Config {
var width: Int
var height: Int
var title: String
}
enum Direction {
North
South
East
West
}
enum Shape {
Circle(radius: Float)
Rectangle(width: Float, height: Float)
Triangle(base: Float, height: Float)
}
let dir = Direction.North
let shape = Shape.Circle(5.0)
trait Printable {
fun display() r: String
}
impl Printable for Point {
fun display() r: String {
return "({this.x}, {this.y})"
}
}
fun printItem<T: Printable>(item: T) {
println(item.display())
}
fun max<T>(a: T, b: T) r: T {
if a > b { return a }
return b
}
class Stack<T> {
var items: Array<T>
static fun new() r: Stack<T> {
return Stack { items: Array<T>() }
}
fun push(item: T) {
this.items.push(item)
}
fun pop() r: T {
return this.items.pop()
}
fun isEmpty() r: Bool {
return this.items.length() == 0
}
}
fun sort<T: Ord>(arr: Array<T>) r: Array<T> {
// T must implement Ord
}
let doubled = apply(nums, |x| x * 2)
let filtered = items.filter(|x| x > 0)
Closures use |params| expression syntax. Currently no-capture (function pointer) semantics.
T?var name: String? = null
name = "Alice"
?.let len = name?.length() // returns null if name is null
??let displayName = name ?? "Anonymous"
if let n = name {
println("Name is {n}")
}
fun divide(a: Int, b: Int) r: Result<Int, String> {
if b == 0 {
return Err("division by zero")
}
return Ok(a / b)
}
? operatorPropagates errors to the caller:
fun compute() r: Result<Int, String> {
let x = divide(10, 2)?
let y = divide(x, 3)?
return Ok(x + y)
}
Allocation-heavy APIs expose fallible try* forms using the same Result<T, E> style. AllocError carries the requested size plus current runtime memory-budget state.
import core.result.{Result, Ok}
import core.unit.{Unit}
import memory.allocation.{AllocError, ensureAllocationBudget}
fun prepareBuffer(bytes: Int) r: Result<Unit, AllocError> {
ensureAllocationBudget(bytes)?
return Ok(Unit{})
}
Set SEEN_MEMORY_LIMIT_BYTES to enforce a process allocation budget from the outside, or call setMemoryLimitBytes(bytes) inside a program.
try {
let result = riskyOperation()
println("Success: {result}")
} catch e {
println("Error: {e}")
}
Use {expression} inside double-quoted strings:
let name = "World"
println("Hello, {name}!")
let x = 42
println("The answer is {x}")
let p = Point.new(3.0, 4.0)
println("Distance: {p.distanceTo(Point.new(0.0, 0.0))}")
let nums = [1, 2, 3, 4, 5]
let names = ["Alice", "Bob", "Charlie"]
var arr = Array<Int>()
arr.push(1)
arr.push(2)
let first = arr.get(0)
let len = arr.length()
let zeros = Array<Int>.withLength(100)
var map = HashMap<String, Int>()
map.insert("alice", 30)
map.insert("bob", 25)
let age = map.get("alice")
let bytes = ByteBuffer.withCapacity(256)
bytes.reserve(1024)
bytes.push(255)
let raw = Int32Buffer.withCapacity(128)
raw.push(42)
let values = [5, 1, 3]
unstableSortInt(values)
let slot = lowerBoundInt(values, 3)
let scores = Array<Float>()
scores.push(3.5)
scores.push(1.25)
unstableSortFloat(scores)
See the Collections API for Vec, BTreeMap, ByteBuffer, primitive buffers, sort/search helpers, priority queues, and more.
0..10 // exclusive: 0, 1, 2, ..., 9
0..=10 // inclusive: 0, 1, 2, ..., 10
+, -, *, /, %
==, !=, <, <=, >, >=
and, or, not (also &&, ||, !)
&, |, ^, ~, <<, >>
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
Use as for type conversions:
let x: Int = 42
let f = x as Float
let s = x as String
import io
import collections.HashMap
pub makes declarations visible outside the module. Capitalized declaration names are also public by convention, so both forms remain valid:
pub fun publicFunction() {
// accessible from other modules
}
class PublicByName {
// capitalized declarations are externally visible
}
fun privateFunction() {
// only accessible within this module
}
Package-private declarations are visible inside the same package and rejected from outside-package imports.
import package_name
import package_name::src::module.{Symbol}
import local_module.{Thing}
Prebuilt package artifacts expose declarations through interface.index.tsv and link implementation objects through objects.tsv.
Functions can declare required capabilities with effect(Token):
fun readConfig(token: FileToken) effect(FileToken) r: String {
return readText("config.toml")
}
Effects propagate through calls; missing or wrong capability tokens are diagnosed by the compiler.
Annotations start with @ and attach metadata to declarations:
@using("libm")
extern fun cos(x: Float) r: Float
@operator("+")
fun addVec(a: Vec2, b: Vec2) r: Vec2 {
return Vec2.new(a.x + b.x, a.y + b.y)
}
Common annotations include @using, @operator, @export, @cfg, @compute, @derive, and @reflect.
class Vec2 {
var x: Float
var y: Float
operator fun +(other: Vec2) r: Vec2 {
return Vec2.new(this.x + other.x, this.y + other.y)
}
operator fun *(scalar: Float) r: Vec2 {
return Vec2.new(this.x * scalar, this.y * scalar)
}
}
For low-level operations that bypass safety checks:
unsafe {
let ptr = transmute(address)
// raw pointer operations
}
Execute cleanup code when leaving a scope:
fun processFile(path: String) {
let file = File.open(path)
defer { file.close() }
// file is automatically closed when scope exits
let content = file.readContent()
}
Only executes if the scope exits via error:
fun allocateResource() r: Result<Resource, String> {
let res = acquire()
errdefer { release(res) }
let configured = configure(res)? // if this fails, res is released
return Ok(configured)
}
type Callback = Fun
type StringList = Array<String>
distinct Meters = Float
distinct Seconds = Float
// Meters and Seconds are incompatible even though both are Float
extension fun String.isBlank() r: Bool {
return trim(this) == ""
}
