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 | 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.