Rat playground
Open the browser console (F12) to inspect output from the log tests.
1.1 Log a literal string
Console should print: hello
<button on_click[log['hello']]> log "hello" 1.2 Log a literal number
Console should print: 42
<button on_click[log[42]]> log 42 1.3 Log a page-state number
Console prints current p_count; the +1 button changes it
<button on_click[log[p_count]]> log p_count
<button on_click[p_count++]> p_count++ 0
1.4 Log a page-state string
Console should print: page-Ada
<button on_click[log[p_name]]> log p_name 1.5 Log a literal array of numbers
Console should print: [10, 20, 30]
<button on_click[log[nums_lit]]> log nums_lit 1.6 Log a literal array of strings
Console should print three strings
<button on_click[log[items]]> log items 1.7 Log an object
Console should print: {first, last}
<button on_click[log[person]]> log person 1.8 Log an array of objects
Console should print three name+age entries
<button on_click[log[users_lit]]> log users_lit 1.9 Log a mutable page array
Click add a few times then log — should grow each click
<button on_click[nums.add(1)]> add 1
<button on_click[nums.add(2)]> add 2
<button on_click[log[nums]]> log nums
<button on_click[nums.clear()]> clear []
1.10 Log multiple args
log is variadic; console prints all four values
<button on_click[log['name:', p_name, 'count:', p_count]]> log mixed 2.1 Render a string
Should render: Hello, page-Ada!
<p> Hello, [p_name]! Hello, page-Ada!
2.2 Render a number
Renders p_count and updates on click
<p> [p_count]
<button on_click[p_count++]> +1 0
2.3 Render an array as text
Stringified form, no loop
<p> [nums_lit]
<p> [items] [10, 20, 30]
[apples, pears, figs]
2.4 Render an object as text
Stringified form, no loop
<p> [person] {first: Grace, last: Hopper}
2.5 Loop array of numbers
One <li> per element
<ul>
[x] in nums_lit
<li> [x] - __RAT_LOOP_ITER__
- 10
- 20
- 30
2.6 Loop array of strings
apples / pears / figs
<ul>
[w] in items
<li> [w] - __RAT_LOOP_ITER__
- apples
- pears
- figs
2.7 Loop array of objects
Each row reads two fields per item
<ul>
[u] in users_lit
<li> [u.name] (age [u.age]) - (age )
- A (age 1)
- B (age 2)
- C (age 3)
2.8 Object field access
Two reads from the same object
<p> [person.first] [person.last] Grace Hopper
2.9 Array index access
items index 0 should be apples
<p> first = [items[0]]
<p> last = [items[2]] first = apples
last = figs
3.1.1 Bare reference
Just a name resolves to its value
<p> [n] 3
3.1.2 Add
Renders the sum, not the source
<p> [n + 1] 4
3.1.3 Subtract
Inline arithmetic
<p> [n - 2] 1
3.1.4 Divide
Mixed int + float promotes
<p> [n / 3] 1
3.1.5 Big literal minus state
Larger numbers compose fine
<p> [2026 - n] 2023
3.1.6 Parenthesised expression
Parens group sub-expressions
<p> doubled (via expr) = [(n + n)] doubled (via expr) = 6
3.2.1 Less-than comparison
Renders true / false in text
<p> [n < 5] true
3.2.2 Greater-than comparison
Renders true / false in text
<p> [n > 5] false
3.2.3 Equality
== returns true when sides match
<p> [n == 3] true
3.3 String concatenation
+ joins strings just like JS
<p> ['greetings, ' + p_name + '!'] greetings, page-Ada!
3.4.1 len on array
Returns element count
<p> [len(items)] 3
3.4.2 len on string
Returns byte length
<p> [len(p_name)] 8
3.5 Attribute interpolation
bracket reads also work inside attribute values
<a href['/users/[p_name]'] title['hi [p_name]']> hover-link to /users/[p_name] 4.1 Derived number
double recomputes when p_count changes
double >> p_count * 2
<p> [p_count]
<p> [double] 0
0
4.2 Derived string
shout recomputes when p_name changes
shout >> 'HI ' + p_name + '!'
<input value[p_name] on_input[p_name << event.target.value]>
<p> [p_name]
<p> [shout] page-Ada
HI page-Ada!
5.1 add / clear on a page array of numbers
List re-renders as nums mutates
<button on_click[nums.add(1)]> add 1
<button on_click[nums.add(2)]> add 2
<button on_click[nums.add(99)]> add 99
<button on_click[nums.clear()]> clear
<p> [nums]
<ul>
[x] in nums
<li> [x] []
- __RAT_LOOP_ITER__
5.2 add / clear on a page array of strings
Same shape, string payloads
<button on_click[words.add('foo')]> add "foo"
<button on_click[words.add('bar')]> add "bar"
<button on_click[words.clear()]> clear
<p> [words]
<ul>
[w] in words
<li> [w] []
- __RAT_LOOP_ITER__
5.3 add objects to a page array
Each click pushes a new name+age entry
<button on_click[people.add({name: 'Ada', age: 36})]> add Ada
<button on_click[people.add({name: 'Grace', age: 85})]> add Grace
<button on_click[people.clear()]> clear
<ul>
[p] in people
<li> [p.name] (age [p.age]) - (age )
6.1 > server counter
Click round-trips to the server; resets on each new request
> server
s_count: 0
<p> [s_count]
<button on_click[s_count++]> +1 0
6.2 > global counter
Shared across every tab on this URL; persists until server restart
> global
g_count: 0
<p> [g_count]
<button on_click[g_count++]> +1 0
6.3 > page counter
Lives only in this browser tab; lost on refresh
> page
p_count: 0
<p> [p_count]
<button on_click[p_count++]> +1 0
7.1 > server string set
s_name is reset to server-Ada on every new request
<button on_click[s_name << 'changed']> set to "changed"
<button on_click[s_name << 'server-Ada']> reset
<p> [s_name] server-Ada
7.2 > global string set
Visible to every browser on this URL; survives until server restart
<button on_click[g_name << 'changed']> set to "changed"
<button on_click[g_name << 'global-Ada']> reset
<p> [g_name] global-Ada
7.3 > page string set
Per-tab; refresh restores page-Ada
<input value[p_name] on_input[p_name << event.target.value]>
<button on_click[p_name << 'changed']> set to "changed"
<button on_click[p_name << 'page-Ada']> reset
<p> [p_name] page-Ada
8.1 Single guard
Renders the small line only when n < 5
<p> [n]
(n < 5)
<p> n is small 3
n is small
8.2 First-match chain with (else)
Exactly one branch renders
(p_count >= 5)
<p> p_count >= 5
(p_count >= 1)
<p> p_count >= 1
(else)
<p> p_count is zero p_count is zero
8.3 Guard inside a loop
Only items with 5+ chars render
<ul>
[w] in items
(len(w) >= 5)
<li> [w] - __RAT_LOOP_ITER__
- apples
- pears
9.1.1 len on string
Byte count
<p> [len('abc')] 3
9.1.2 len on array
Element count
<p> [len(items)] 3
9.1.3 len on object
Key count
<p> [len(person)] 2
9.2.1 type on string
Returns the literal string
<p> [type('hi')] string
9.2.2 type on int
Int and float are distinct kinds server-side
<p> [type(42)] int
9.2.3 type on float
Decimal literal produces float
<p> [type(3.14)] float
9.2.4 type on bool
true / false report as bool
<p> [type(true)] bool
9.2.5 type on array
Lists report as array
<p> [type(items)] array
9.2.6 type on object
Maps report as object
<p> [type(person)] object
9.2.7 type on null
null is its own kind
<p> [type(null)] null
9.3.1 keys on object
Insertion-order list of field names
<p> [keys(person)] [first, last]
9.3.2 values on object
Insertion-order list of field values
<p> [values(person)] [Grace, Hopper]
9.3.3 has on object — hit
Returns true when field exists
<p> [has(person, 'first')] true
9.3.4 has on object — miss
Returns false when field absent
<p> [has(person, 'age')] false
10.1.1 upper
Uppercase a string
<p> [upper('hello')] HELLO
10.1.2 lower
Lowercase a string
<p> [lower('WORLD')] world
10.1.3 trim
Strip leading + trailing whitespace
<p> ([trim(' hi ')]) (hi)
10.2.1 split
Comma-separated string to array
<p> [split('a,b,c', ',')] [a, b, c]
10.2.2 join
Array to delimited string
<p> [join(items, ' / ')] apples / pears / figs
10.3 replace
Substring substitution, all occurrences
<p> [replace('foo bar foo', 'foo', 'baz')] baz bar baz
10.4.1 starts_with
Boolean prefix check
<p> [starts_with('hello world', 'hello')] true
10.4.2 ends_with
Boolean suffix check
<p> [ends_with('hello world', 'world')] true
10.4.3 contains on string
Substring check
<p> [contains('hello world', 'lo wo')] true
10.4.4 contains on array
Element membership
<p> [contains(items, 'pears')] true
10.4.5 contains on object
Key membership
<p> [contains(person, 'first')] true
10.5.1 pad_left
Right-align by left-padding
<p> ([pad_left('7', 3, '0')]) (007)
10.5.2 pad_right
Left-align by right-padding
<p> ([pad_right('7', 3, '.')]) (7..)
10.5.3 repeat
Concat a string N times
<p> [repeat('ab', 3)] ababab
10.6.1 format with one placeholder
Single brace consumes one arg
<p> [format('hi {}', 'world')] hi world
10.6.2 format with three placeholders
Args bind positionally in order
<p> [format('{} + {} = {}', 1, 2, 3)] 1 + 2 = 3
11.1.1 floor
Round toward negative infinity
<p> [floor(3.7)] 3
11.1.2 ceil
Round toward positive infinity
<p> [ceil(3.2)] 4
11.1.3 round
Banker-style half-away-from-zero
<p> [round(3.5)] 4
11.1.4 round_to with positive places
Trim to N decimal places
<p> [round_to(3.14159, 2)] 3.14
11.1.5 round_to with negative places
Round to powers of ten
<p> [round_to(1234, 0 - 2)] 1200
11.2.1 abs
Absolute value
<p> [abs(0 - 7)] 7
11.2.2 min
Smallest variadic arg
<p> [min(5, 2, 8, 1, 9)] 1
11.2.3 max
Largest variadic arg
<p> [max(5, 2, 8, 1, 9)] 9
11.3.1 clamp upper
Bound a value at the high end
<p> [clamp(15, 0, 10)] 10
11.3.2 clamp lower
Bound a value at the low end
<p> [clamp(0 - 3, 0, 10)] 0
11.3.3 lerp
Linear interpolation between a and b at t
<p> [lerp(0, 100, 0.25)] 25
12.1 str — int to string
Coerces a number to its rendered form
<p> ([str(42)]) (42)
12.2 num — string to float
Parses numeric text
<p> [num('3.14')] 3.14
12.3 bool of zero
Zero is falsy
<p> [bool(0)] false
12.4 bool of non-empty string
Non-empty string is truthy
<p> [bool('hi')] true
13.1.1 range with one arg
Zero-based count
<p> [range(5)] [0, 1, 2, 3, 4]
13.1.2 range with start + end
Half-open interval
<p> [range(2, 6)] [2, 3, 4, 5]
13.1.3 range with step
Step-walked half-open interval
<p> [range(0, 10, 3)] [0, 3, 6, 9]
13.2.1 sum
Reduce array to total
<p> [sum(nums_lit)] 60
13.2.2 avg
Mean of numeric array
<p> [avg(nums_lit)] 20
13.2.3 sort
Stable ascending sort
<p> [sort([3, 1, 4, 1, 5, 9])] [1, 1, 3, 4, 5, 9]
13.3.1 first — head
Single first element
<p> [first(items)] apples
13.3.2 last — tail
Single last element
<p> [last(items)] figs
13.3.3 first with N
First N elements as array
<p> [first(nums_lit, 2)] [10, 20]
13.3.4 last with N
Last N elements as array
<p> [last(nums_lit, 2)] [20, 30]
13.4.1 push
Returns a new array with the value appended
<p> [push(items, 'kiwi')] [apples, pears, figs, kiwi]
13.4.2 pop
Returns the last element
<p> [pop(items)] figs
13.5.1 filter
Keep elements where the lambda returns truthy
<p> [filter(nums_lit, [x] >> x > 10)] [20, 30]
13.5.2 map
Transform each element via lambda
<p> [map(nums_lit, [x] >> x + 1)] [11, 21, 31]
13.5.3 reduce
Fold an array using a 2-arg lambda + initial value
<p> [reduce(nums_lit, [a, b] >> a + b, 0)] 60
13.6.1 group_by
Bucket elements by the lambda result
<p> [group_by(nums_lit, [x] >> x > 15)] {false: [10], true: [20, 30]}
13.6.2 count_by
Tally elements by the lambda result
<p> [count_by(items, [w] >> len(w))] {6: 1, 5: 1, 4: 1}
13.7.1 unique
Dedup preserving first-seen order
<p> [unique([1, 2, 2, 3, 1])] [1, 2, 3]
13.7.2 flatten
Strip one level of nesting
<p> [flatten([[1, 2], [3], [4, 5]])] [1, 2, 3, 4, 5]
13.7.3 chunk
Split into fixed-size sub-arrays
<p> [chunk(range(7), 3)] [[0, 1, 2], [3, 4, 5], [6]]
13.7.4 zip
Tuple-merge two arrays elementwise
<p> [zip([1, 2, 3], ['a', 'b', 'c'])] [[1, a], [2, b], [3, c]]
14.1.1 base64_encode
UTF-8-safe binary-to-ASCII
<p> [base64_encode('hi rat')] aGkgcmF0
14.1.2 base64_decode
Inverse of base64_encode
<p> [base64_decode('aGkgcmF0')] hi rat
14.2.1 url_encode
Query-string escaping; space becomes +
<p> [url_encode('hello world & rat')] hello+world+%26+rat
14.2.2 url_decode
Inverse of url_encode
<p> [url_decode('hello+world+%26+rat')] hello world & rat
14.3.1 hex_encode
Lowercase hex pairs
<p> [hex_encode('hi')] 6869
14.3.2 hex_decode
Inverse of hex_encode
<p> [hex_decode('6869')] hi
15.1.1 json.stringify on object
Compact form, alphabetized keys server-side
<p> [json.stringify(person)] {"first":"Grace","last":"Hopper"}
15.1.2 json.stringify on array
Compact form, source order preserved
<p> [json.stringify(nums_lit)] [10,20,30]
15.2 json.stringify with indent
Pretty-print with 2-space indent
<pre> [json.stringify(person, 2)] {
"first": "Grace",
"last": "Hopper"
}15.3 json.parse
String to value
<p> [json.parse('{"n": 42}').n] 42
15.4 json round-trip in a handler
Click logs the stringified person object
<button on_click[log[json.stringify(person)]]> log json person 16.1.1 date.now server render
Epoch seconds at render time
<p> [date.now()] 1780931944
16.1.2 date.now in a handler
Logs the browser clock; rerun for a fresh value
<button on_click[log[date.now()]]> log client date.now() 16.2 date.parse ISO string
Round-trips to epoch seconds
<p> [date.parse('2026-06-07T12:34:56Z')] 1780835696
16.3 date.format with token layout
YYYY-MM-DD HH:mm:ss
<p> date.format(date.parse('2026-06-07T12:34:56Z'), 'YYYY-MM-DD HH:mm:ss') 2026-06-07 12:34:56
16.4.1 date.add — month wrap
Jan 31 + 1 month lands on Feb 28/29, not Mar 3
<p> [date.format(date.add(date.parse('2026-01-31'), 'M', 1), 'YYYY-MM-DD')] 2026-02-28
16.4.2 date.add — day shift
Plus 7 days
<p> [date.format(date.add(date.parse('2026-06-07'), 'd', 7), 'YYYY-MM-DD')] 2026-06-14
16.5 date.diff
Whole-unit difference between two epochs
<p> [date.diff(date.parse('2026-06-07'), date.parse('2026-06-01'), 'd')] 6
17.1 regex.match
Match object: match, groups, start, end
<p> [regex.match('\\d+', 'abc 123 xyz')] {match: 123, groups: [], start: 4, end: 7}
17.2 regex.find_all
Every hit as its own match object
<p> [regex.find_all('\\d+', '1 two 3 four 55')] [{match: 1, groups: [], start: 0, end: 1}, {match: 3, groups: [], start: 6, end: 7}, {match: 55, groups: [], start: 13, end: 15}]
17.3 regex.replace
$1 backrefs work the same on both tiers
<p> [regex.replace('(\\w+)=(\\d+)', 'a=1, b=2', '$2 of $1')] 1 of a, 2 of b
17.4 regex.split
Any whitespace run as separator
<p> [regex.split('\\s+', 'one two three')] [one, two, three]
18.1 debug
Routes to console.debug — appears in browser devtools
<button on_click[debug['debug message']]> debug(...) 18.2 error
Routes to console.error — red in devtools
<button on_click[error['error message']]> error(...) 19.1.1 clamp in server render
Evaluated server-side, value embedded in HTML
<p> [clamp(p_count, 0, 5)] 0
19.1.2 +1 mutates p_count
Triggers reactive recompute of clamp spans above
<button on_click[p_count++]> +1 0
19.1.3 clamp in a handler — client-side
Same call, runs in browser, no server round-trip
<button on_click[log[clamp(p_count, 0, 5)]]> log clamp(p_count, 0, 5) 19.2.1 json.stringify in server render
Stringified object embedded in HTML
<p> [json.stringify(person)] {"first":"Grace","last":"Hopper"}
19.2.2 json.stringify in a handler
Same call, client-side, logs to console
<button on_click[log[json.stringify(person)]]> log json.stringify(person)