Rat docs

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++
Result

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
Result

[]

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]!
Result

Hello, page-Ada!

2.2 Render a number

Renders p_count and updates on click

<p> [p_count]
<button on_click[p_count++]> +1
Result

0

2.3 Render an array as text

Stringified form, no loop

<p> [nums_lit]
<p> [items]
Result

[10, 20, 30]

[apples, pears, figs]

2.4 Render an object as text

Stringified form, no loop

<p> [person]
Result

{first: Grace, last: Hopper}

2.5 Loop array of numbers

One <li> per element

<ul>
    [x] in nums_lit
        <li> [x]
Result
  • 10
  • 20
  • 30

2.6 Loop array of strings

apples / pears / figs

<ul>
    [w] in items
        <li> [w]
Result
  • 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])
Result
  • 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]
Result

Grace Hopper

2.9 Array index access

items index 0 should be apples

<p> first = [items[0]]
<p> last  = [items[2]]
Result

first = apples

last = figs

3.1.1 Bare reference

Just a name resolves to its value

<p> [n]
Result

3

3.1.2 Add

Renders the sum, not the source

<p> [n + 1]
Result

4

3.1.3 Subtract

Inline arithmetic

<p> [n - 2]
Result

1

3.1.4 Divide

Mixed int + float promotes

<p> [n / 3]
Result

1

3.1.5 Big literal minus state

Larger numbers compose fine

<p> [2026 - n]
Result

2023

3.1.6 Parenthesised expression

Parens group sub-expressions

<p> doubled (via expr) = [(n + n)]
Result

doubled (via expr) = 6

3.2.1 Less-than comparison

Renders true / false in text

<p> [n < 5]
Result

true

3.2.2 Greater-than comparison

Renders true / false in text

<p> [n > 5]
Result

false

3.2.3 Equality

== returns true when sides match

<p> [n == 3]
Result

true

3.3 String concatenation

+ joins strings just like JS

<p> ['greetings, ' + p_name + '!']
Result

greetings, page-Ada!

3.4.1 len on array

Returns element count

<p> [len(items)]
Result

3

3.4.2 len on string

Returns byte length

<p> [len(p_name)]
Result

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]
Result

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]
Result

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]
Result

[]

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]
Result

[]

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])
Result

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
Result

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
Result

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
Result

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]
Result

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]
Result

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]
Result

page-Ada

8.1 Single guard

Renders the small line only when n < 5

<p> [n]
(n < 5)
    <p> n is small
Result

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
Result

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]
Result
  • apples
  • pears

9.1.1 len on string

Byte count

<p> [len('abc')]
Result

3

9.1.2 len on array

Element count

<p> [len(items)]
Result

3

9.1.3 len on object

Key count

<p> [len(person)]
Result

2

9.2.1 type on string

Returns the literal string

<p> [type('hi')]
Result

string

9.2.2 type on int

Int and float are distinct kinds server-side

<p> [type(42)]
Result

int

9.2.3 type on float

Decimal literal produces float

<p> [type(3.14)]
Result

float

9.2.4 type on bool

true / false report as bool

<p> [type(true)]
Result

bool

9.2.5 type on array

Lists report as array

<p> [type(items)]
Result

array

9.2.6 type on object

Maps report as object

<p> [type(person)]
Result

object

9.2.7 type on null

null is its own kind

<p> [type(null)]
Result

null

9.3.1 keys on object

Insertion-order list of field names

<p> [keys(person)]
Result

[first, last]

9.3.2 values on object

Insertion-order list of field values

<p> [values(person)]
Result

[Grace, Hopper]

9.3.3 has on object — hit

Returns true when field exists

<p> [has(person, 'first')]
Result

true

9.3.4 has on object — miss

Returns false when field absent

<p> [has(person, 'age')]
Result

false

10.1.1 upper

Uppercase a string

<p> [upper('hello')]
Result

HELLO

10.1.2 lower

Lowercase a string

<p> [lower('WORLD')]
Result

world

10.1.3 trim

Strip leading + trailing whitespace

<p> ([trim('  hi  ')])
Result

(hi)

10.2.1 split

Comma-separated string to array

<p> [split('a,b,c', ',')]
Result

[a, b, c]

10.2.2 join

Array to delimited string

<p> [join(items, ' / ')]
Result

apples / pears / figs

10.3 replace

Substring substitution, all occurrences

<p> [replace('foo bar foo', 'foo', 'baz')]
Result

baz bar baz

10.4.1 starts_with

Boolean prefix check

<p> [starts_with('hello world', 'hello')]
Result

true

10.4.2 ends_with

Boolean suffix check

<p> [ends_with('hello world', 'world')]
Result

true

10.4.3 contains on string

Substring check

<p> [contains('hello world', 'lo wo')]
Result

true

10.4.4 contains on array

Element membership

<p> [contains(items, 'pears')]
Result

true

10.4.5 contains on object

Key membership

<p> [contains(person, 'first')]
Result

true

10.5.1 pad_left

Right-align by left-padding

<p> ([pad_left('7', 3, '0')])
Result

(007)

10.5.2 pad_right

Left-align by right-padding

<p> ([pad_right('7', 3, '.')])
Result

(7..)

10.5.3 repeat

Concat a string N times

<p> [repeat('ab', 3)]
Result

ababab

10.6.1 format with one placeholder

Single brace consumes one arg

<p> [format('hi {}', 'world')]
Result

hi world

10.6.2 format with three placeholders

Args bind positionally in order

<p> [format('{} + {} = {}', 1, 2, 3)]
Result

1 + 2 = 3

11.1.1 floor

Round toward negative infinity

<p> [floor(3.7)]
Result

3

11.1.2 ceil

Round toward positive infinity

<p> [ceil(3.2)]
Result

4

11.1.3 round

Banker-style half-away-from-zero

<p> [round(3.5)]
Result

4

11.1.4 round_to with positive places

Trim to N decimal places

<p> [round_to(3.14159, 2)]
Result

3.14

11.1.5 round_to with negative places

Round to powers of ten

<p> [round_to(1234, 0 - 2)]
Result

1200

11.2.1 abs

Absolute value

<p> [abs(0 - 7)]
Result

7

11.2.2 min

Smallest variadic arg

<p> [min(5, 2, 8, 1, 9)]
Result

1

11.2.3 max

Largest variadic arg

<p> [max(5, 2, 8, 1, 9)]
Result

9

11.3.1 clamp upper

Bound a value at the high end

<p> [clamp(15, 0, 10)]
Result

10

11.3.2 clamp lower

Bound a value at the low end

<p> [clamp(0 - 3, 0, 10)]
Result

0

11.3.3 lerp

Linear interpolation between a and b at t

<p> [lerp(0, 100, 0.25)]
Result

25

12.1 str — int to string

Coerces a number to its rendered form

<p> ([str(42)])
Result

(42)

12.2 num — string to float

Parses numeric text

<p> [num('3.14')]
Result

3.14

12.3 bool of zero

Zero is falsy

<p> [bool(0)]
Result

false

12.4 bool of non-empty string

Non-empty string is truthy

<p> [bool('hi')]
Result

true

13.1.1 range with one arg

Zero-based count

<p> [range(5)]
Result

[0, 1, 2, 3, 4]

13.1.2 range with start + end

Half-open interval

<p> [range(2, 6)]
Result

[2, 3, 4, 5]

13.1.3 range with step

Step-walked half-open interval

<p> [range(0, 10, 3)]
Result

[0, 3, 6, 9]

13.2.1 sum

Reduce array to total

<p> [sum(nums_lit)]
Result

60

13.2.2 avg

Mean of numeric array

<p> [avg(nums_lit)]
Result

20

13.2.3 sort

Stable ascending sort

<p> [sort([3, 1, 4, 1, 5, 9])]
Result

[1, 1, 3, 4, 5, 9]

13.3.1 first — head

Single first element

<p> [first(items)]
Result

apples

13.3.2 last — tail

Single last element

<p> [last(items)]
Result

figs

13.3.3 first with N

First N elements as array

<p> [first(nums_lit, 2)]
Result

[10, 20]

13.3.4 last with N

Last N elements as array

<p> [last(nums_lit, 2)]
Result

[20, 30]

13.4.1 push

Returns a new array with the value appended

<p> [push(items, 'kiwi')]
Result

[apples, pears, figs, kiwi]

13.4.2 pop

Returns the last element

<p> [pop(items)]
Result

figs

13.5.1 filter

Keep elements where the lambda returns truthy

<p> [filter(nums_lit, [x] >> x > 10)]
Result

[20, 30]

13.5.2 map

Transform each element via lambda

<p> [map(nums_lit, [x] >> x + 1)]
Result

[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)]
Result

60

13.6.1 group_by

Bucket elements by the lambda result

<p> [group_by(nums_lit, [x] >> x > 15)]
Result

{false: [10], true: [20, 30]}

13.6.2 count_by

Tally elements by the lambda result

<p> [count_by(items, [w] >> len(w))]
Result

{6: 1, 5: 1, 4: 1}

13.7.1 unique

Dedup preserving first-seen order

<p> [unique([1, 2, 2, 3, 1])]
Result

[1, 2, 3]

13.7.2 flatten

Strip one level of nesting

<p> [flatten([[1, 2], [3], [4, 5]])]
Result

[1, 2, 3, 4, 5]

13.7.3 chunk

Split into fixed-size sub-arrays

<p> [chunk(range(7), 3)]
Result

[[0, 1, 2], [3, 4, 5], [6]]

13.7.4 zip

Tuple-merge two arrays elementwise

<p> [zip([1, 2, 3], ['a', 'b', 'c'])]
Result

[[1, a], [2, b], [3, c]]

14.1.1 base64_encode

UTF-8-safe binary-to-ASCII

<p> [base64_encode('hi rat')]
Result

aGkgcmF0

14.1.2 base64_decode

Inverse of base64_encode

<p> [base64_decode('aGkgcmF0')]
Result

hi rat

14.2.1 url_encode

Query-string escaping; space becomes +

<p> [url_encode('hello world & rat')]
Result

hello+world+%26+rat

14.2.2 url_decode

Inverse of url_encode

<p> [url_decode('hello+world+%26+rat')]
Result

hello world & rat

14.3.1 hex_encode

Lowercase hex pairs

<p> [hex_encode('hi')]
Result

6869

14.3.2 hex_decode

Inverse of hex_encode

<p> [hex_decode('6869')]
Result

hi

15.1.1 json.stringify on object

Compact form, alphabetized keys server-side

<p> [json.stringify(person)]
Result

{"first":"Grace","last":"Hopper"}

15.1.2 json.stringify on array

Compact form, source order preserved

<p> [json.stringify(nums_lit)]
Result

[10,20,30]

15.2 json.stringify with indent

Pretty-print with 2-space indent

<pre> [json.stringify(person, 2)]
Result
{
  "first": "Grace",
  "last": "Hopper"
}

15.3 json.parse

String to value

<p> [json.parse('{"n": 42}').n]
Result

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()]
Result

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')]
Result

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')
Result

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')]
Result

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')]
Result

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')]
Result

6

17.1 regex.match

Match object: match, groups, start, end

<p> [regex.match('\\d+', 'abc 123 xyz')]
Result

{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')]
Result

[{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')]
Result

1 of a, 2 of b

17.4 regex.split

Any whitespace run as separator

<p> [regex.split('\\s+', 'one  two three')]
Result

[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)]
Result

0

19.1.2 +1 mutates p_count

Triggers reactive recompute of clamp spans above

<button on_click[p_count++]> +1
Result

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)]
Result

{"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)