lambda # Start REPL
// REPL Commands: quit, help, clear
lambda script.ls # Eval functional script
lambda run script.ls # Run procedual script
lambda --help # Show helpValidation:
lambda validate file.json -s schema.ls # With schema
lambda validate file.json # Default schemaScalar Types:
| Form | Meaning |
|---|---|
null bool int float decimal |
Primitives |
string symbol binary datetime path |
Primitives |
i8 i16 i32 i64 |
Sized signed integers |
u8 u16 u32 u64 |
Sized unsigned integers |
f16 f32 f64 |
Sized floats |
Container Types:
| Form | Meaning |
|---|---|
range, 1 to 10 |
Range (inclusive both ends; also "a" to "z") |
array, [123, true] |
Array of values |
map, {key: 'symbol'} |
Map |
element, <div class: bold, "text" <br>> |
Element |
Type Operators:
| Form | Meaning |
|---|---|
int | string |
Union type |
int & number |
Intersection |
int? |
Optional (int | null) |
int* |
Zero or more |
int+ |
One or more |
int[] |
Array of ints (same as int*) |
int[5] |
Array of exactly 5 ints |
[int*] |
Bracket form: array of 0+ ints |
[int+] |
Bracket form: array of 1+ ints |
fn (a: int, b: string) bool |
Function type |
fn int |
Same as fn () int |
{a: int, b: bool} |
Map type |
<div id:symbol; <br>> |
Element type |
Type Declarations:
| Form | Meaning |
|---|---|
type User = {name: string, age: int} |
Map type alias |
type Point = (float, float) |
Tuple type |
type Result = int | error |
Union type |
Definition:
type Point { x: float, y: float } // Fields only
type Counter {
value: int = 0, // Default value
fn double() => value * 2 // Functional
fn add(n: int) => value + n
pn inc() { value = value + 1 } // Procedural
}
type Circle : Shape { radius: float } // Inheritance
Literals & Access:
| Form | Meaning |
|---|---|
let p = <Point x: 1.0, y: 2.0> |
Object literal |
let c = <Counter> |
All defaults |
p.x |
Field access |
c.double() |
Method call |
<Point *:p, x: 5.0> |
Copy and override |
Type Checking (nominal only):
| Form | Meaning |
|---|---|
p is Point |
true |
p is object |
true |
p is map |
true (objects are map-compatible) |
{x: 1} is Point |
false (plain maps don't match) |
Constraints:
type User {
name: string that (len(~) > 1), // Field constraint
age: int that (0 <= ~ <= 150),
that (~.name != "admin") // Object constraint
}
Self reference ~:
type Vec {
x: float, y: float,
fn len() => math.sqrt(x**2 + y**2)
fn scale(f) => <Vec *:~, x: ~.x*f, y: ~.y*f> // ~ = self
}
Numbers:
| Form | Meaning |
|---|---|
42 |
Integer |
42n |
Arbitrary precision integer |
3.14 |
Float |
1.5e-10 |
Scientific notation |
123.45m |
Decimal |
1e-3m |
Decimal (negative exponent) |
inf nan |
Special values |
0xFF |
Hexadecimal int literal |
Sized numeric literals (postfix suffix):
| Form | Meaning |
|---|---|
42i8 -128i8 1000i16 100000i32 100i64 |
Signed |
255u8 60000u16 3000000000u32 1000u64 |
Unsigned |
0.5f16 3.14f32 2.7f64 |
Float |
Strings & Symbols:
| Form | Meaning |
|---|---|
"hello" |
String |
"multi-line string" |
Multi-line string — a literal newline may appear inside the quotes |
'symbol' |
Symbol |
symbol |
Unquoted symbol |
'name' == "name" |
false — symbol is not string |
"" == null |
false — empty string is a string value |
not "" |
true — empty string is falsy |
'' is invalid: empty symbols do not exist.
Binary:
| Form | Meaning |
|---|---|
b'\xDEADBEEF' |
Hex binary |
b'\64QUVGRw==' |
Base64 binary |
DateTime:
| Form | Meaning |
|---|---|
t'2025-01-01T14:30:00Z' |
DateTime |
t'2025-04-26' is date |
Sub-type: date |
t'10:30:00' is time |
Sub-type: time |
Member properties:
| Form | Meaning |
|---|---|
dt.date dt.year dt.month dt.day |
Date parts |
dt.time dt.hour dt.minute dt.second dt.millisecond |
Time parts |
dt.weekday dt.yearday dt.week dt.quarter |
Derived |
dt.unix dt.timezone dt.utc_offset dt.utc dt.local |
Zone / epoch |
Formatting:
| Form | Meaning |
|---|---|
dt.format("YYYY-MM-DD") |
Pattern |
dt.format('iso') |
Named format |
Constructors:
| Form | Meaning |
|---|---|
datetime() today() justnow() |
Current date/time |
datetime(2025, 4, 26, 10, 30) |
From parts |
date(2025, 4, 26) time(10, 30, 45) |
Date / time only |
Collections:
| Form | Meaning |
|---|---|
[1, 2, 3] |
Array |
{a: 1, b: 2} |
Map |
<div id: "main"> |
Element |
Indexing & Slicing:
| Form | Meaning |
|---|---|
arr[0] |
First element |
arr.0 |
Alt. syntax for const index |
arr[1 to 3] |
Slice (indices 1, 2, 3) |
map.key |
Map field access |
map["key"] |
Map field by string |
"hello"[1 to 3] |
"ell" — string slicing |
'hello'[1 to 3] |
'ell' — symbol slicing |
"café"[2 to 3] |
"fé" — UTF-8 aware |
Namespaces (via import with bare URI):
import svg: 'http://www.w3.org/2000/svg'
import xlink: 'http://www.w3.org/1999/xlink'
| Form | Meaning |
|---|---|
<svg.rect svg.width: 100> |
Namespaced tag & attribute — desugars to <svg.rect svg: {width: 100}> |
elem.svg.width |
Chained sub-map access |
elem.svg |
{width: 100} sub-map |
svg.rect |
Qualified symbol |
symbol("href", 'xlink_url') |
Namespaced symbol |
Let Expressions (immutable):
| Form | Meaning |
|---|---|
(let x = 5, x + 1, x * 2) |
Single binding |
(let a = 1, b = 2, a + b) |
Multiple bindings |
Let Statements (immutable):
| Form | Meaning |
|---|---|
let x = 42 |
Immutable binding |
let y : int = 100 |
With type annotation |
let a: i8 = 42i8 |
Sized type annotation |
let b: f32 = 3.14f32 |
Sized float annotation |
let a = 1, b = 2 |
Multiple bindings |
x = 10 |
ERROR E211: cannot reassign let binding |
Var Statements (mutable, pn only):
| Form | Meaning |
|---|---|
var x = 0 |
Mutable variable |
var y: int = 42 |
With type annotation |
var a: int[] = [1, 2, 3] |
Array of ints |
var b: float[] = [0.1, 0.2] |
Array of floats |
x = x + 1 |
OK: reassignment |
x = "hello" |
OK: type widening (int → string) |
y = "oops" |
ERROR E201: annotated type enforced |
Arithmetic:
| Operator | Meaning |
|---|---|
+ |
addition |
- |
subtraction |
* |
multiplication |
/ |
division |
div |
integer division |
% |
modulo |
** |
exponentiation |
Spread *:
| Given | Expression | Result |
|---|---|---|
let a = [1, 2, 3] |
(*a, *[10, 20]) |
(1, 2, 3, 10, 20) |
Comparison:
| Operator | Meaning |
|---|---|
== |
equal |
!= |
not equal |
< |
less than |
<= |
less or equal |
> |
greater than |
>= |
greater or equal |
== performs structural deep equality on all types:
| Form | Meaning |
|---|---|
[1, 2] == [1, 2] |
true (array) |
{a:1, b:2} == {b:2, a:1} |
true (map, order-independent) |
[1] == [1.0] |
true (numeric promotion) |
(1 to 3) == [1, 2, 3] |
true (cross-type sequence) |
Logical:
| Operator | Meaning |
|---|---|
and |
logical and |
or |
logical or |
not |
logical not |
Type & Set:
| Operator | Meaning |
|---|---|
is |
type check |
in |
membership |
to |
range |
| |
union |
& |
intersection |
! |
exclusion |
Query: type-based search
| Form | Meaning |
|---|---|
? .? |
recursive descendant search |
expr[T] |
child-level query (direct only) |
Vector Arithmetic:
| Expression | Result | |
|---|---|---|
1 + [2, 3] |
[3, 4] |
scalar broadcast |
[1, 2] * 2 |
[2, 4] |
scalar broadcast |
[1, 2] + [3, 4] |
[4, 6] |
element-wise |
Use ++ for list/array concat: [1,2] ++ [3,4] = [1,2,3,4]. |
Pipe |> with current item ~:
| Form | Meaning |
|---|---|
[1,2,3] |> ~ * 2 |
[2,4,6] - map over items |
[1,2,3] |> sum |
6 - aggregate (no ~) |
users |> ~.age |
[12,20,62] - extract field |
['a','b'] |> {i:~#, v:~} |
~# = index/key |
Filter with that:
| Form | Meaning |
|---|---|
[1,2,3,4,5] that (~ > 3) |
[4,5] |
users that (age >= 18) |> ~.name |
filter then map |
[1,2,3] |> ~ ** 2 that (~ > 5) |> sum |
13 (4+9) |
Spreading in Array Literals: pipe and filter results flatten
| Form | Meaning |
|---|---|
[1, [2,3] |> ~, 4, 5] |
[1, 2, 3, 4, 5] |
[0, [1,2,3] |> ~ * 10, 99] |
[0, 10, 20, 30, 99] |
[1, [3,5,7] that (~ > 4), 9] |
[1, 5, 7, 9] |
Results in document order (depth-first, pre-order).
Query ? — attributes + all descendants:
| Form | Meaning |
|---|---|
html?<img> |
all <img> at any depth |
html?<div class: string> |
<div> with class attr |
data?int |
all int values in tree |
data?(int | string) |
all int or string values |
data?{name: string} |
maps with string 'name' |
data?{status: "ok"} |
maps where status == "ok" |
Self-inclusive query .? — self + attributes + all descendants:
| Form | Meaning |
|---|---|
div.?<div> |
includes div itself if it matches |
el.?int |
self + all int values in subtree |
42.?int |
[42] — trivial self-match |
Child-level query [T] — direct attributes + children only (no recursion):
| Form | Meaning |
|---|---|
[1, "a", 3, true][int] |
[1, 3] — int items |
{name: "Alice", age: 30}[string] |
["Alice"] |
el[element] |
direct child elements only |
el[string] |
attr values + text children |
The target can be a string, a symbol, or a path.
| Form | Meaning |
|---|---|
output(data, 'output.txt') |
File under CWD |
output(data, "./temp/output.txt") |
Relative path |
output(data, "output.txt", {mode: "append"}) |
Append mode |
output(data |> format('json'), "output.json") |
Format then write |
| Data type determines output format: |
- String: raw text (no formatting)
- Binary: raw binary data
- Other types: Lambda/Mark format
If Expressions (parenthesized condition, else required):
| Form | Meaning |
|---|---|
if (x > 0) "positive" else "non-positive" |
Simple |
if (score >= 90) "A" else if (score >= 80) "B" else "C" |
Chained |
if (x > 0) "pos" else { "neg" } |
Block else |
If Statements (block body, else optional):
| Form | Meaning |
|---|---|
if x > 0 { "positive" } |
No else |
if condition { something() } else { otherThing() } |
Both blocks |
if x > 0 { compute() } else "default" |
Expression else |
Both forms share the same else syntax: else expr, else { stam }, or else if ....
Match Expressions:
// Type patterns or Literal
match value {
case 200: "OK" // case literal
case string: "text" // case type
case int | float: "number" // case type union
case Circle: // case object type
3.14 * ~.r ** 2
default: "other"
}
// String pattern arms (full-match semantics)
type digits = \(d+)
type alpha = \(a+)
match input {
case digits: "number" // case named pattern
case alpha: "word"
default: "other"
}
For Expressions: (produce spreadable arrays — pipe/filter also spread, see above)
| Expression | Result | |
|---|---|---|
for (x in [1, 2, 3]) x * 2 |
[2, 4, 6] |
|
for (i in 1 to 5) i * i |
[1, 4, 9, 16, 25] |
|
for (x in data) if (x > 0) x else 0 |
conditional body | |
[for (i in 1 to 2) for (j in 1 to 2) i*j] |
[1, 2, 2, 4] |
nested for-expressions flatten |
[for (x in []) x] |
[] |
empty for produces spreadable null, which is skipped |
for (k at {a: 1, b: 2}) k |
['a', 'b'] |
iterate a map by key |
for (k, v at {a: 1, b: 2}) k ++ v |
['a1', 'b2'] |
key and value (S8.1.3) |
For Expression Clauses: let, where, group by, order by, limit, offset
for (x in data where x > 0) x // filter
for (x in data, let sq = x*x) sq // let binding
for (x in [3,1,2] order by x) x // (1,2,3)
for (x in [3,1,2] order by x desc) x // (3,2,1)
for (x in data limit 5 offset 10) x // pagination
for (x in data, let y=x*2
where y>5 order by y desc limit 3) y
// group by → g is an <group> element (keys=attrs, members=children)
for (x in sales group by x.region into g)
{region: g.region, n: len(g)}
Joins (on): relate comma sources; ? = left join
for (o in orders, c in customers on o.cust_id == c.id)
{id: o.id, name: c.name} // inner equi-join
for (o in orders, c? in customers on o.cust_id == c.id)
{id: o.id, name: c} // left join (c=null on miss)
for (o in os, c in cs on o.a==c.a and o.b==c.b) {...} // multi-key
For Statements:
for item in collection { transform(item) }
Procedural Control (in pn):
| Form | Meaning |
|---|---|
var x = 0 |
Mutable variable |
while (c) { break; continue; return x } |
Loop and its jumps |
Assignment Targets (in pn):
| Form | Meaning |
|---|---|
x = 10 |
Variable reassignment (var only) |
arr[i] = val |
Array element reassignment |
obj.field = val |
Map field reassignment |
elem.attr = val |
Element attribute reassignment |
elem[i] = val |
Element child reassignment |
Function Declaration:
| Form | Meaning |
|---|---|
fn add(a: int, b: int) int { a + b } |
Statement body |
fn multiply(x: int, y: int) => x * y |
Expression body |
let square = (x) => x * x |
Anonymous function |
pn f(n) { var x = 0; while (x < n) { x = x + 1 }; x } |
Procedural function |
pn advance(pos: float[], vel: float[], n: int) { ... } |
Array parameters |
pn worker() { return receive()^ }
pn main() {
let h = start(worker) // scoped child, opaque identity handle
send(h, "job")^ // bounded FIFO mailbox (default 1024)
print(wait(h)^) // wait for T^E result
}
start(worker, [job], {mode: 'task'}) // args array + launch options (S13.1.1v2)
wait(h, timeout: 10) // timeout does not cancel h
select(h1, h2, timeout: 100)^ // first completed handle
sleep(5)^ // shared libuv timer
self() // current handle
cancel(h) // idempotent cancellation request
io.read("file.txt")^ // async local file read
No async/await keywords are used. Normal block exit joins children; error
exit cancels then joins. A start operand may not capture an outer var by
reference—copy to let or use messages. Imported JS Promises are wait-able;
exported Lambda pns return Promises to JavaScript.
Advanced Features:
| Form | Meaning |
|---|---|
fn f(x?:int) |
optional param |
fn f(x=10) |
default param value |
fn f(...) |
variadic args |
f(b:2, a:1) |
named param call |
fn outer(n) { fn inner(x)=>x+n; inner } |
closure |
Define named patterns for string validation and matching. Uses regex-like syntax integrated into the type system.
Definition:
| Form | Meaning |
|---|---|
type digits = \(d+) |
one or more digits |
type email = \(w+ "@" w+ "." a[2,6]) |
email-like |
type ws = \(s+) |
whitespace |
type keyword = 'if' | 'else' | 'for' |
symbol literal union |
Type check (is) — full-match semantics:
| Form | Meaning |
|---|---|
"hello@world.com" is email |
true |
"abc" is email |
false |
"123" is digits |
true |
Character classes inside \(...): d digit, w word, s whitespace, a alpha, . any char, ... any string
Reserved inside the island only — quote to match one literally: \("d" w+).
Quantifiers: ? optional, + one or more, * zero or more, [n] exactly n, [n,m] range
Domain: \(...) matches strings, \symbol(...) matches symbols; the tag is checked before the content, and pattern bodies always use string literals for content.
Given type SymIdent = \symbol(a w*):
| Form | Result |
|---|---|
'foo' is SymIdent |
true |
"foo" is SymIdent |
false — string value, symbol pattern |
Inline (unnamed) patterns work anywhere a type does:
| Form | Meaning |
|---|---|
"abc" is \(a+) |
true |
fn f(x: \(d+)) => x |
Parameter annotation |
match s { case \(d+): "num" default: "other" } |
Match arm |
Type:
int(v) int64(v) float(v) decimal(v) string(v) symbol(v) binary(v) number(v) type(v) len(v)
type() returns the canonical numeric name: type(42i8) → "i8", type(3.14f32) → "f32", type(1.0f64) → "float"
Math:
math.pi math.e abs(x) sign(x) min(a,b) max(a,b) round(x) floor(x) ceil(x) math.trunc(x) math.sqrt(x) math.cbrt(x) math.hypot(x,y) math.pow(b,e) math.log(x) math.log2(x) math.log10(x) math.log1p(x) math.exp(x) math.exp2(x) math.expm1(x) math.sin(x) math.cos(x) math.tan(x) math.asin(x) math.acos(x) math.atan(x) math.atan2(y,x) math.sinh(x) math.cosh(x) math.tanh(x) math.asinh(x) math.acosh(x) math.atanh(x) math.random(seed) → [value, new_seed]
Stats:
sum(v) avg(v) math.mean(v) math.median(v) math.variance(v) math.deviation(v) math.quantile(v,p) math.prod(v)
Date/Time:
datetime() today() now() justnow() date(dt) time(dt)
Range:
s to e creates a range from s to e (inclusive both ends). Bounds are exact integers or single-codepoint strings. range(s,e,step) creates a range with custom step (exclusive end).
| Form | Meaning |
|---|---|
1 to 5 |
[1, 2, 3, 4, 5] |
"a" to "e" |
["a", "b", "c", "d", "e"] — character range |
range(0, 10, 2) |
[0, 2, 4, 6, 8] |
String:
replace(str,old,new) split(str,sep) join(strs,sep) find(str,pattern) normalize(str) ord(str) chr(int)
All three accept both plain strings and named patterns as the second argument:
Given type digit = \(d), type digits = \(d+), type ws = \(s+):
replace(str, pattern_or_string, replacement)
| Form | Result |
|---|---|
replace("a1b2c3", digit, "X") |
"aXbXcX" |
replace("hello world", ws, " ") |
"hello world" |
replace("abc", "b", "") |
"ac" |
split(str, pattern_or_string)
| Form | Result |
|---|---|
split("a,b,c", ",") |
["a", "b", "c"] |
split("a1b2c3", digit) |
["a", "b", "c", ""] |
split("hello world", ws) |
["hello", "world"] |
split("a1b2c3", digit, true) |
["a", "1", "b", "2", "c", "3", ""] — keep delimiters |
split("ab", \(d*)) |
["a", "b"] — zero-width: no end empties |
split("", ",") |
[""]; split("", \(d*)) → [] |
Follows ECMAScript String.prototype.split (S17.1.1).
find(str, pattern_or_string) → [{value, index}, ...]
| Form | Result |
|---|---|
find("a1b22c333", digits) |
[{value:"1", index:1}, {value:"22", index:3}, ...] |
find("hello world", "lo") |
[{value: "lo", index: 3}] |
Collection:
slice(v,i) slice(v,i,j) set(v) all(v) any(v) reverse(v) sort(v) unique(v) take(v,n) drop(v,n) zip(a,b) fill(n,x) range(s,e,step) map(f,v) filter(f,v) reduce(f,v,init)
Vector:
math.dot(a,b) math.norm(v) math.cumsum(v) math.cumprod(v) argmin(v) argmax(v) diff(v)
I/O:
input(file,fmt) format(data,fmt) print(v) output(data,file) fetch(url,opts) cmd(c,args) error(msg) varg()
Supported Input Types: json, xml, yaml, markdown, csv, html, latex, toml, rtf, css, ini, math, pdf
input("path/file.md", 'markdown') // Input Markdown
Input with Flavors: e.g. math flavors: latex, typst, ascii
input("math.txt", {'type':'math', 'flavor':'ascii'})
Output Formatting: json, yaml, xml, html, markdown
format(data, 'yaml') // Format as YAML
Import Syntax:
| Form | Meaning |
|---|---|
import module_name |
Built-in module |
import .relative_module |
Relative to script's dir |
import .path.to.module |
Nested relative import |
import alias: .module |
Import with alias |
Export Declarations:
pub let PI = 3.14159 // Export variable
pub fn square(x) => x * x // Export function
pub pn log(msg) { print(msg) } // Export procedure
pub type Score = int // Export type alias
pub type Counter { // Export object type
value: int = 0,
fn double() => value * 2
}
pub let data = input("f") ^ { {} } // Export, handling err
Module Usage:
// In math_utils.ls:
pub let PI = 3.14159
pub type Vec2 { x: float, y: float, fn len() =>
math.sqrt(x**2 + y**2) }
// In main.ls:
import .math_utils
let area = PI * r ** 2
let v = <Vec2 x: 3.0, y: 4.0>
v.len() // 5.0
v is Vec2 // true
Creating Errors:
| Form | Meaning |
|---|---|
error("Message") |
Message only |
error("load failed", inner_err) |
With an inner error |
error({code: 304, message: "div by zero"}) |
Structured payload |
Error Return Types (T^E):
| Form | Meaning |
|---|---|
fn parse(s: string) int^ {...} |
int or any error |
fn divide(a, b) int ^ DivErr {...} |
specific error |
fn load(p) Config ^ ParseErr|IOErr {...} |
multi errors |
raise error , or propagate error with ^
fn compute(x: int) int^ {
if (b == 0) raise error("div by zero") // raise error
let a = parse(input)^ // return immediately on error
let b = divide(a, x)^ // return immediately on error
a + b
}
fun()^ // propagate error, discard value
e ^ { } — handle the error here (~ is the error):
let result = divide(10, x) ^ {
print(^.message) // ^ = the current handler error
0 // handler value, or raise/return
}
result * 2 // result is clean here
e or default rescues any falsy value without seeing it;
x is error tests a soft T | error value.
()[].?.?- Primary, query-+not!- Unary (not: logical NOT,!: type negation)**- Exponentiation*/div%- Multiplicative+-- Additive<<=>>=- Relational==!=- Equalityand- Logical ANDor- Logical ORto- Rangeisin- Type operations (is nanfor NaN detection)|where- Pipe and Filter
Data Processing:
let data = input("sales.json", 'json')
let total = sum(
(for (sale in data.sales) sale.amount))
let report = {total: total,
count: len(data.sales)}
format(report, 'json')
Function Definition:
fn factorial(n: int) int {
if (n <= 1) 1 else n * factorial(n - 1)
}
Element Creation:
let article = <article title: "My Article",
<h1 "Introduction">
<p "Content goes here.">
>
format(article, 'html')
Comprehensions - Complex data processing:
(let data = [1, 2, 3, 4, 5],
let filtered = (for (x in data)
if (x % 2 == 0) x else 0),
let doubled = (for (x in filtered) x * 2), doubled)