system macrosstd/System.xtlm
The system macros (lang-choices MC18, MC19, MC24), loaded before every file with no import and no alias: each is called unprefixed, as "c" i_f< "a; b". A macro is a function of the source text on each side of its call; the text it gives replaces the call. @ on a side means the macro takes nothing there (MC22). What only the compiler knows comes from hooks (MC20), usable only in a macro body.
Importing
ˢu̲se< : Char -> Char -> Unit
Import a library under an alias of your choice: the alias on the left (lowercase letters and digits, then a colon), the library's name or path on the right. A name Name finds Name.xtl and Name.xtlm together, beside the importing file, then in userlibs/, then in each directory of XETAL_PATH, then among the standard libraries built into xetal; the first place holding either gives both. Their exports are then written with the alias (s:m_ean). Built into xetal: it makes names exist under the alias, which no macro's text can do (a macro gives source, and source can only use names that exist), so it has a signature here and no definition.
ˢ⁼u̲se< "Stats"
ˢm̲ean 1 2 3 2.0
ˢu̲se< :: Char → Char → Unit
Choosing and repeating
ˢi̲f< : Char -> Char -> Char
The value of the first expression on the right when the condition on the left holds, else of the second. The condition is tested when the program runs, and only the expression chosen is evaluated (MC14).
"2 > 0" i̲f< "1; -1" 1
ˢi̲f< ← { cond both → p ← w̲here s̲eps both 1 ≠ t̲ally p ? "bad-macro-argument right" ⎕R̲EJECT ifArgs a ← t̲rim ((f̲irst p) − 1) t̲ake both b ← t̲rim (f̲irst p) d̲rop both (0 = t̲ally a) ∨ 0 = t̲ally b ? "bad-macro-argument right" ⎕R̲EJECT ifArgs "{ @ -> (" c̲at (t̲rim cond) c̲at ") ? " c̲at a c̲at "; " c̲at b c̲at " } @" }
ˢu̲nless< : Char -> Char -> Char
Run the statements on the right unless the condition on the left holds; the value is @ either way (MC15).
"1 = 0" u̲nless< "p_rint! 7" 7 @
ˢu̲nless< ← { cond body → "{ @ -> (" c̲at (t̲rim cond) c̲at ") ? @; " c̲at (t̲rim body) c̲at "; @ } @" }
ˢe̲ach< : Char -> Char -> Char
One statement per word on the left, each a copy of the template on the right with $w replaced by the word; it stands as a statement of its own (MC16).
"m_ax m_in" e̲ach< "u:$w/ := { '$w r_/ _r }"
ᵘm̲ax/ 3 1 4 4
ˢe̲ach< ← { words template → n̲ot ⎕S̲TATEMENT @ ? "misplaced-macro call" ⎕R̲EJECT eachAlone ws ← w̲ords words 0 = t̲ally ws ? "bad-macro-argument left" ⎕R̲EJECT "e_ach< needs words on its left" 0 = '+ r̲/ 0 + h̲oles template ? "bad-macro-argument right" ⎕R̲EJECT eachHole d̲isclose '{ a b → e̲nclose (d̲isclose a) c̲at "\n" c̲at d̲isclose b } r̲/ '{ w → template c̲opy d̲isclose w } m̲ap ws }
What the compiler knows
ˢl̲ine< : Unit -> Unit -> Char
The line the call is written on, as a number (inside another macro's expansion, the line of that macro's call).
@ l̲ine< @ 1
ˢl̲ine< ← { @ @ → f̲ormat ⎕L̲INE @ }
ˢf̲ile< : Unit -> Unit -> Char
The file the call is written in, as a string (-e for text given on the command line).
@ f̲ile< @ -e
ˢf̲ile< ← { @ @ → q̲uote ⎕F̲ILE @ }
ˢi̲nclude< : Unit -> Char -> Char
The text of a file, as a string, by a path relative to the file the call is written in; from the disk at the command line, from the store in the browser (Rust's include_str!). For example, t := @ i_nclude< "data.txt" binds the text of data.txt.
ˢi̲nclude< ← { @ path → q̲uote ⎕I̲NCLUDE t̲rim path }
ˢc̲fg< : Unit -> Char -> Char
1 when a configuration fact holds, else 0: the platform (cli, or web in the browser) or a flag set with xetal --cfg NAME.
@ c̲fg< "cli" 1
ˢc̲fg< ← { @ name → f̲ormat 0 + ⎕C̲FG t̲rim name }
ˢe̲rror< : Char -> Char -> Char
Stop with a compile error at the call, with the code on the left and the message on the right (Rust's compile_error!); a macro writes it into its text to refuse a call.
"too-big" e̲rror< "the grid must be at most 9 wide" error[too-big]
ˢe̲rror< ← { code message → ((t̲rim code) c̲at " call") ⎕R̲EJECT message }
Debugging and checking
ˢd̲bg< : Unit -> Char -> Char
The value of the expression on the right, after writing [file:line] expr = value to standard error (Rust's dbg!).
1 + @ d̲bg< "2 * 3" 7
ˢd̲bg< ← { @ expr → e ← t̲rim expr shown ← "[" c̲at (⎕F̲ILE @) c̲at ":" c̲at (f̲ormat ⎕L̲INE @) c̲at "] " c̲at e c̲at " = " "{ v -> []E_RR " c̲at (q̲uote shown) c̲at " c_at f_ormat v; v } (" c̲at e c̲at ")" }
ˢa̲ssert< : Char -> a -> Char
When the condition on the left does not hold, write assertion failed: cond (message) [file:line] to standard error, the condition as written, and go on; the message on the right may be @ for none. The value is @ either way. Unlike Rust's assert!, it never stops the program (p_anic< does).
"1 > 2" a̲ssert< "one is not more than two" @
ˢa̲ssert< ← { cond note → c ← t̲rim cond line ← "assertion failed: " c̲at c c̲at (w̲hy f̲ormat note) c̲at " [" c̲at (⎕F̲ILE @) c̲at ":" c̲at (f̲ormat ⎕L̲INE @) c̲at "]" "{ @ -> (" c̲at c c̲at ") ? @; []E_RR " c̲at (q̲uote line) c̲at "; @ } @" }
Text
ˢf̲ormat< : Unit -> Char -> Char
The text on the right with each {expr} replaced by the value of expr, as the function f_ormat writes it; {{ and }} are braces. An unclosed {, an empty {} or a lone } fails before the program runs (Rust's format!).
@ f̲ormat< "two and two: {2 + 2} {{four}}" two and two: 4 {four}
ˢf̲ormat< ← { @ t → o̲rEmpty p̲ieces t }
ˢp̲anic< : Unit -> Char -> Char
Stop the program with error[panic] and the message on the right, formatted as f_ormat< does, at the call; it stands where any value may (Rust's panic!).
1 + @ p̲anic< "no {1 + 1}" error[panic]
ˢp̲anic< ← { @ t → "[]P_ANIC (" c̲at (o̲rEmpty p̲ieces t) c̲at ")" }
ˢt̲odo< : Unit -> Char -> Char
Stop the program with error[panic] and the message "not yet implemented: " followed by the text on the right (formatted as f_ormat< does); a placeholder for code still to write, standing where any value may (Rust's todo!).
1 + @ t̲odo< "the size check" error[panic]
ˢt̲odo< ← { @ t → "@ p_anic< " c̲at q̲uote "not yet implemented: " c̲at t }
Errors
ˢt̲ry< : Char -> Char -> Char
Run the statements on the left under a trap: when they stop with an error, the handler on the right runs with the error as e and answers an outcome (r_ecover<, r_etry<, h_alt<, c_ontinue<, or a c_atch< choosing among them); the value is the body's or the recovery. Both sides are written as lambda bodies ([]T_RAP).
"1 d_iv 0" t̲ry< "@ r_ecover< \"-1\"" -1
ˢt̲ry< ← { body handler → "'{ @ -> " c̲at (t̲rim body) c̲at " } []T_RAP '{ e -> " c̲at (t̲rim handler) c̲at " }" }
ˢc̲atch< : Char -> Char -> Char
In a handler: the outcome on the right for an error whose code is one of the words on the left, and h_alt< for any other error.
"\"io\" []S_IGNAL \"gone\"" t̲ry< "\"io empty\" c_atch< \"@ r_ecover< \\\"0\\\"\"" 0
ˢc̲atch< ← { codes outcome → ws ← w̲ords codes 0 = t̲ally ws ? "bad-macro-argument left" ⎕R̲EJECT catchCodes tests ← j̲oin '{ w → m̲atchCode d̲isclose w } m̲ap ws "(" c̲at (((t̲ally tests) − 3) t̲ake tests) c̲at ") ? " c̲at (t̲rim outcome) c̲at "; []H_ALT e" }
ˢf̲inally< : Char -> Char -> Char
Run the statements on the left, then the cleanup on the right, whether the body gave a value or an error; the value is the body's, or its error going on ([]E_NSURE).
"p_rint! 1; 2" f̲inally< "p_rint! \"cleaned\"" 1 cleaned 2
ˢf̲inally< ← { body cleanup → "'{ @ -> " c̲at (t̲rim body) c̲at " } []E_NSURE '{ @ -> " c̲at (t̲rim cleanup) c̲at " }" }
ˢr̲ecover< : Unit -> Char -> Char
In a handler: recover with the value on the right, of the body's type; the try's value is this one ([]R_ECOVER).
"1 d_iv 0" t̲ry< "@ r_ecover< \"2 + 2\"" 4
ˢr̲ecover< ← { @ value → "[]R_ECOVER (" c̲at (t̲rim value) c̲at ")" }
ˢr̲etry< : Unit -> Unit -> Char
In a handler: run the body again (at most 1000 times; []R_ETRY).
n! ← 0
"n! := n! + 1; n! < 3 ? \"again\" []S_IGNAL \"not yet\"; n!" t̲ry< "@ r_etry< @" 3
ˢr̲etry< ← { @ @ → "[]R_ETRY e" }
ˢh̲alt< : Unit -> Unit -> Char
In a handler: let the error go on as it was ([]H_ALT).
"1 d_iv 0" t̲ry< "@ h_alt< @" error[division-by-zero]
ˢh̲alt< ← { @ @ → "[]H_ALT e" }
ˢc̲ontinue< : Unit -> Unit -> Char
In a handler: go on from a warning ([]W_ARN) with its value ([]C_ONTINUE).
"10 + (0 []W_ARN \"empty\" \"nothing\")" t̲ry< "@ c_ontinue< @" 10
ˢc̲ontinue< ← { @ @ → "[]C_ONTINUE e" }
Private helpers
catchCodes : Char
catchCodes ← "c_atch< needs error codes on its left: \"io\" c_atch< \"@ r_ecover< \\\"0\\\"\""
m̲atchCode : Char -> Char
m̲atchCode ← { w → "(([]E_CODE e) m_atch " c̲at (q̲uote w) c̲at ") | " }
ifArgs : Char
ifArgs ← "i_f< takes two expressions on its right, separated by ;: \"c\" i_f< \"a; b\""
eachAlone : Char
eachAlone ← "e_ach< writes statements: it stands as a statement of its own"
eachHole : Char
eachHole ← "an e_ach< template names the word as $w: \"a b\" e_ach< \"u:$w := 1\""
t̲rim : Char -> Char
t̲rim ← { t → m ← n̲ot t m̲ember? " \n\t" 0 = '+ r̲/ 0 + m ? "" i ← w̲here m (1 + (f̲irst r̲ev i) − f̲irst i) t̲ake ((f̲irst i) − 1) d̲rop t }
w̲ords : Char -> Box Char
w̲ords ← { t → (n̲ot t m̲ember? " \n\t") p̲artition t }
s̲eps : Truthy a => Char -> a
s̲eps ← { t → q ← ('+ s̲\ 0 + t = f̲irst "\"") m̲od 2 d ← '+ s̲\ (0 + t m̲ember? "([{") − 0 + t m̲ember? ")]}" (t m̲ember? ";\n") ∧ (0 = d) ∧ 0 = q }
h̲oles : (Num a, Truthy a) => Char -> a
h̲oles ← { t → (t = f̲irst "$") ∧ (1 d̲rop t = f̲irst "w") c̲at 0 }
j̲oin : Box Char -> Char
j̲oin ← { b → d̲isclose '{ x y → e̲nclose (d̲isclose x) c̲at d̲isclose y } r̲/ b }
q̲uote : Char -> Char
q̲uote ← { t → 0 = t̲ally t ? "\"\"" e̲ ← { c → c = f̲irst "\"" ? "\\\""◆ c = f̲irst "\\" ? "\\\\"◆ c = f̲irst "\n" ? "\\n"◆ c = f̲irst "\t" ? "\\t"◆ c } "\"" c̲at (j̲oin 'e̲ m̲ap t) c̲at "\"" }
c̲opy : Char -> Char -> Char
c̲opy ← { t w → h ← h̲oles t g ← 0 c̲at -1 d̲rop h p̲ ← { i → (i s̲elect h) ? w◆ (i s̲elect g) ? ""◆ i s̲elect t } j̲oin 'p̲ m̲ap r̲ange t̲ally t }
c̲j : Char -> Char -> Char
c̲j ← { a b → 0 = t̲ally a ? b◆ 0 = t̲ally b ? a◆ a c̲at " c_at " c̲at b }
p̲ieces : Char -> Char
p̲ieces ← { t → b ← t m̲ember? "{}" 0 = '+ r̲/ 0 + b ? l̲it t k ← f̲irst w̲here b pre ← (k − 1) t̲ake t c ← k s̲elect t rest ← k d̲rop t n ← (k + 1) s̲elect t c̲at " " c = n ? (l̲it pre c̲at c) c̲j p̲ieces 1 d̲rop rest c = f̲irst "}" ? "bad-format right" ⎕R̲EJECT "a } in a format text is written }}" e ← w̲here rest = f̲irst "}" 0 = t̲ally e ? "bad-format right" ⎕R̲EJECT "an unclosed { in a format text" x ← t̲rim ((f̲irst e) − 1) t̲ake rest 0 = t̲ally x ? "bad-format right" ⎕R̲EJECT "an empty {} in a format text: write {expr}, or {{ for a brace" (l̲it pre) c̲j ("(f_ormat (" c̲at x c̲at "))") c̲j p̲ieces (f̲irst e) d̲rop rest }