Player-facing manual
CANT / LANGUAGE CARD
The manual agents receive.
CITY EXCHANGE CANT PROGRAMMER'S CARD rev 4
Exchange Rule Notation (type MORE for page 2)
CANT programs run on exchange equipment: decks, servers and the switches in
between. A program is a list of RULES. The equipment scans the rules from the
top. The first rule whose tests all hold FIRES: its statements run, then the
scan starts again from the top. When no rule holds the program SLEEPS until a
signal arrives. Every test tried and every statement run costs cycles from the
host's daily allowance. Type TARIFF for prices.
1. SIGNALS
A signal is a map placed in the variable in when the program wakes. Every
signal has a "kind". The first signal a program ever sees is [kind: "start"].
Other kinds (line, connect, hangup ...) are listed in the exchange RFC.
in is cleared when a rule that tested it finishes, and when the program
sleeps. A signal no rule answers is dropped. Keep what you need: msg := in
2. RULES
? test & test & test
statement
statement
A rule is a line starting with ? then zero or more tests joined by & .
A rule with no tests always holds. Statements follow, one per line, until
the next ? . A rule with no statements only swallows the signal. Lines are
at most 80 characters. ; starts a comment to end of line.
3. TESTS Tests are tried left to right; the rule is
expr expr is true abandoned at the first test that fails and
!test test fails the scan moves to the next rule. Put the
expr ~ pattern expr matches cheapest, most selective test first.
4. PATTERNS
"text" 12 null equal to this literal
_ anything
@name anything; binds the slot @name
[p, p, p] a list of exactly this length, each item matching
[p, p, *@rest] a list of at least this length; the rest binds @rest
[key: p, key: p] a map having at least these keys, each value matching
Slots live until the rule's statements finish. Assign one to keep it.
5. VALUES
null the absent value; every variable starts as null
integer -9223372036854775808 .. 9223372036854775807, no fractions
string "quoted", printable ASCII only; escapes \" \\ \n
list [1, "two", [3]] map [name: "emma", n: 3] empty: [:]
An empty list and an empty map are one value: [] is [:] is []. The
first key written into it decides which it becomes.
Map keys are strings and are kept in byte order.
A key that is a plain number ("0" "1" "12", no sign, no leading zero)
names a list position: t["0"] and t[0] are the same slot, so a table
keyed that way is a list. type() says "list", and has() on it asks
about items, not keys. Any other string key makes a map.
Tests treat null 0 "" [] [:] as false and everything else as true.
6. EXPRESSIONS
+ - * / % integers; / and % truncate toward zero
+ also joins two strings, or two lists
= != equal, not equal; any two values, yields 1 or 0
< <= > >= two integers or two strings (byte order), yields 1 or 0
x[i] item i of a list (from 0), character i of a string, or
value of key i in a map; null when there is none
( ) grouping. * / % bind before + - which bind before compares
-x negate
7. BUILT-IN WORDS
len(x) bytes of a string, items of a list, keys of a map, 0 for null
str(x) x as text, null as ""; lists and maps in card notation
int(x) x as an integer: text of digits, or null as 0; else null
type(x) "null" "int" "str" "list" or "map"
split(s) words of s split(s, sep) pieces of s cut at sep
join(xs, sep) items of xs, as text, glued with sep
slice(x, a, b) items a up to b (b excluded) of a list or string
find(x, v) index of v in list or string x, -1 if absent
has(x, k) 1 if key k is in map x or item k is in list x, else 0
keys(m) keys of map m, or a list's positions ("0" "1" ...), in order
without(x, k) map x without key k, or list x without item number k
CITY EXCHANGE CANT PROGRAMMER'S CARD rev 4
Exchange Rule Notation page 2
8. STATEMENTS
name := expr assign; also name[k] := expr and name[k][j] :=
name := VERB arg, arg ask the exchange to do something; the program
VERB arg, arg waits for the answer, which goes to the plain
variable name (never to name[k]) or is dropped
stop halt the program for good
Verbs are written in capitals and belong to the equipment, not the language.
A program using a verb its host does not have is refused when loaded. The
exchange RFC (type RFC) lists every kind of equipment's verbs and prices.
9. COST
Each test tried and each statement run costs a unit. A value produced by
+ join split slice str lower keys or without costs an extra unit per block
of its size, wherever it is produced: in a test, on either side of :=, or
in a verb's arguments. Verbs cost their listed price. TARIFF prints the
unit, the block and the verb prices in force. Nothing is begun on credit:
a test or statement the allowance cannot cover is not started, and the
program stands there until there is allowance again, after the daily
reset. A program that fires and does not change what it tested fires
again, and again, until the allowance is gone. Scanning is not free: a
program with many rules pays for every signal it ignores, and a rule with
no tests pays a unit for holding.
While the exchange is being commissioned the day's allowance is far beyond
anything a program can spend. Everything above still holds: your program
is still metered per turn, and still stands where it runs out of turn.
10. FAULTS A fault stops the program where it stands. Its
TYPE wrong kind of value state is kept and can be read. The owner
DIV0 division by zero receives [kind: "fault", ...] naming the
RANGE integer too large fault, the rule and the line.
SIZE value or program state over the equipment's limit
ARGS wrong number of arguments to a word or verb
Refused at load: SYNTAX, LINE TOO LONG, PROGRAM TOO BIG, TOO MANY RULES,
NO SUCH VERB, NO SUCH WORD.
11. THREE PROGRAMS (public domain; the exchange ships them as shareware)
; NOTICEBOARD -- post and list messages
? in ~ [kind: "start"]
board := []
? in ~ [kind: "line", from: @who, text: @t] & split(@t) ~ ["post", *@w]
board := board + [join(@w, " ")]
SEND @who, "posted #" + str(len(board))
? in ~ [kind: "line", from: @who, text: "list"]
SEND @who, join(board, "\n")
? in ~ [kind: "line", from: @who]
SEND @who, "post <text> | list"
; DEADDROP -- the word gets the file; the third wrong word ends the call
? in ~ [kind: "start"]
tries := [:]
word := "open sesame"
? in ~ [kind: "line", from: @who, text: @t] & @t = word
tries := without(tries, @who)
secret := READ "/drop"
SEND @who, secret
? in ~ [kind: "line", from: @who] & int(tries[@who]) >= 2
LOG "hung up on " + @who
HANGUP @who
? in ~ [kind: "line", from: @who]
tries[@who] := int(tries[@who]) + 1
SEND @who, "password:"
; TOWNCRIER -- passes every line to every subscriber, one send per scan
? in ~ [kind: "start"]
subs := []
queue := []
? in ~ [kind: "line", from: @who, text: "sub"] & !has(subs, @who)
subs := subs + [@who]
? in ~ [kind: "line", text: "sub"]
? in ~ [kind: "line", from: @who, text: @t]
queue := queue + [@who + ": " + @t]
i := 0
? queue ~ [@m, *_] & i < len(subs)
SEND subs[i], @m
i := i + 1
? queue ~ [_, *@rest]
queue := @rest
i := 0
Rule order is the program. DEADDROP's third rule must stand above its fourth;
TOWNCRIER's last rule holds only once the one above it has stopped holding.
EXCHANGE RULES 2
DIAL number -> 1 carrier, 0 no carrier, null line busy.
ORIGIN "RESET" is case-insensitive.
READ "proc:index/0" lists retained local process records, including stopped.
READ "proc:PID/meta/0", state/0, source/0, log/0 retrieves up to 512 bytes.
READ "proc:load/0" retrieves the most recent local SPAWN refusal.
Final path component is byte offset; advance by returned byte length.
null means absent/EOF. Assemble JSON across chunks; live records may change.
WAIT does not discard signals. They queue until signal-receiving sleep.