sourcestd/System.xtlm

1⍝# The system macros (lang-choices MC18, MC19, MC24), loaded before 2⍝# every file with no import and no alias: each is called unprefixed, 3⍝# as "c" i_f< "a; b". A macro is a function of the source text on each 4⍝# side of its call; the text it gives replaces the call. @ on a side 5⍝# means the macro takes nothing there (MC22). What only the compiler 6⍝# knows comes from hooks (MC20), usable only in a macro body. 7 8⍝## Importing 9 10⍝# Import a library under an alias of your choice: the alias on the 11⍝# left (lowercase letters and digits, then a colon), the library's name 12⍝# or path on the right. A name Name finds Name.xtl and Name.xtlm 13⍝# together, beside the importing file, then in userlibs/, then in each 14⍝# directory of XETAL_PATH, then among the standard libraries built 15⍝# into xetal; the first place holding either gives both. Their exports 16⍝# are then written with the alias (s:m_ean). Built into xetal: it makes 17⍝# names exist under the alias, which no macro's text can do (a macro 18⍝# gives source, and source can only use names that exist), so it has a 19⍝# signature here and no definition. 20⍝# >> "s:" u_se< "Stats" 21⍝# >> s:m_ean 1 2 3 22⍝# 2.0 23 24 25⍝## Choosing and repeating 26 27⍝# The value of the first expression on the right when the condition 28⍝# on the left holds, else of the second. The condition is tested when 29⍝# the program runs, and only the expression chosen is evaluated 30⍝# (MC14). 31⍝# >> "2 > 0" i_f< "1; -1" 32⍝# 1 33ˢi̲f< ← { cond both → 34 p ← w̲here s̲eps both 35 1 ≠ t̲ally p ? "bad-macro-argument right" ⎕R̲EJECT ifArgs 36 a ← t̲rim ((f̲irst p) − 1) t̲ake both 37 b ← t̲rim (f̲irst p) d̲rop both 38 (0 = t̲ally a) ∨ 0 = t̲ally b ? "bad-macro-argument right" ⎕R̲EJECT ifArgs 39 "{ @ -> (" c̲at (t̲rim cond) c̲at ") ? " c̲at a c̲at "; " c̲at b c̲at " } @" 40} 41 42⍝# Run the statements on the right unless the condition on the left 43⍝# holds; the value is @ either way (MC15). 44⍝# >> "1 = 0" u_nless< "p_rint! 7" 45⍝# 7 46⍝# @ 47ˢu̲nless< ← { cond body → 48 "{ @ -> (" c̲at (t̲rim cond) c̲at ") ? @; " c̲at (t̲rim body) c̲at "; @ } @" 49} 50 51⍝# One statement per word on the left, each a copy of the template on 52⍝# the right with $w replaced by the word; it stands as a statement of 53⍝# its own (MC16). 54⍝# >> "m_ax m_in" e_ach< "u:$w/ := { '$w r_/ _r }" 55⍝# >> u:m_ax/ 3 1 4 56⍝# 4 57ˢe̲ach< ← { words template → 58 n̲ot ⎕S̲TATEMENT @ ? "misplaced-macro call" ⎕R̲EJECT eachAlone 59 ws ← w̲ords words 60 0 = t̲ally ws ? "bad-macro-argument left" ⎕R̲EJECT "e_ach< needs words on its left" 61 0 = '+ r̲/ 0 + h̲oles template ? "bad-macro-argument right" ⎕R̲EJECT eachHole 62 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 63} 64 65⍝## What the compiler knows 66 67⍝# The line the call is written on, as a number (inside another 68⍝# macro's expansion, the line of that macro's call). 69⍝# >> @ l_ine< @ 70⍝# 1 71ˢl̲ine< ← { @ @ → f̲ormat ⎕L̲INE @ } 72 73⍝# The file the call is written in, as a string (-e for text given on 74⍝# the command line). 75⍝# >> @ f_ile< @ 76⍝# -e 77ˢf̲ile< ← { @ @ → q̲uote ⎕F̲ILE @ } 78 79⍝# The text of a file, as a string, by a path relative to the file the 80⍝# call is written in; from the disk at the command line, from the 81⍝# store in the browser (Rust's include_str!). For example, 82⍝# t := @ i_nclude< "data.txt" binds the text of data.txt. 83ˢi̲nclude< ← { @ path → q̲uote ⎕I̲NCLUDE t̲rim path } 84 85⍝# 1 when a configuration fact holds, else 0: the platform (cli, or web 86⍝# in the browser) or a flag set with xetal --cfg NAME. 87⍝# >> @ c_fg< "cli" 88⍝# 1 89ˢc̲fg< ← { @ name → f̲ormat 0 + ⎕C̲FG t̲rim name } 90 91⍝# Stop with a compile error at the call, with the code on the left and 92⍝# the message on the right (Rust's compile_error!); a macro writes it 93⍝# into its text to refuse a call. 94⍝# >> "too-big" e_rror< "the grid must be at most 9 wide" 95⍝# error[too-big] 96ˢe̲rror< ← { code message → ((t̲rim code) c̲at " call") ⎕R̲EJECT message } 97 98⍝## Debugging and checking 99 100⍝# The value of the expression on the right, after writing 101⍝# [file:line] expr = value to standard error (Rust's dbg!). 102⍝# >> 1 + @ d_bg< "2 * 3" 103⍝# 7 104ˢd̲bg< ← { @ expr → 105 e ← t̲rim expr 106 shown ← "[" c̲at (⎕F̲ILE @) c̲at ":" c̲at (f̲ormat ⎕L̲INE @) c̲at "] " c̲at e c̲at " = " 107 "{ v -> []E_RR " c̲at (q̲uote shown) c̲at " c_at f_ormat v; v } (" c̲at e c̲at ")" 108} 109 110⍝# When the condition on the left does not hold, write 111⍝# assertion failed: cond (message) [file:line] to standard error, the 112⍝# condition as written, and go on; the message on the right may be @ 113⍝# for none. The value is @ either way. Unlike Rust's assert!, it never 114⍝# stops the program (p_anic< does). 115⍝# >> "1 > 2" a_ssert< "one is not more than two" 116⍝# @ 117ˢa̲ssert< ← { cond note → 118 c ← t̲rim cond 119 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 "]" 120 "{ @ -> (" c̲at c c̲at ") ? @; []E_RR " c̲at (q̲uote line) c̲at "; @ } @" 121} 122 123⍝## Text 124 125⍝# The text on the right with each {expr} replaced by the value of 126⍝# expr, as the function f_ormat writes it; {{ and }} are braces. An 127⍝# unclosed {, an empty {} or a lone } fails before the program runs 128⍝# (Rust's format!). 129⍝# >> @ f_ormat< "two and two: {2 + 2} {{four}}" 130⍝# two and two: 4 {four} 131ˢf̲ormat< ← { @ t → o̲rEmpty p̲ieces t } 132 133⍝# Stop the program with error[panic] and the message on the right, 134⍝# formatted as f_ormat< does, at the call; it stands where any value 135⍝# may (Rust's panic!). 136⍝# >> 1 + @ p_anic< "no {1 + 1}" 137⍝# error[panic] 138ˢp̲anic< ← { @ t → "[]P_ANIC (" c̲at (o̲rEmpty p̲ieces t) c̲at ")" } 139 140⍝# Stop the program with error[panic] and the message 141⍝# "not yet implemented: " followed by the text on the right (formatted 142⍝# as f_ormat< does); a placeholder for code still to write, standing 143⍝# where any value may (Rust's todo!). 144⍝# >> 1 + @ t_odo< "the size check" 145⍝# error[panic] 146ˢt̲odo< ← { @ t → "@ p_anic< " c̲at q̲uote "not yet implemented: " c̲at t } 147 148⍝## Errors 149 150⍝# Run the statements on the left under a trap: when they stop with an 151⍝# error, the handler on the right runs with the error as e and 152⍝# answers an outcome (r_ecover<, r_etry<, h_alt<, c_ontinue<, or a 153⍝# c_atch< choosing among them); the value is the body's or the 154⍝# recovery. Both sides are written as lambda bodies ([]T_RAP). 155⍝# binds: e 156⍝# >> "1 d_iv 0" t_ry< "@ r_ecover< \"-1\"" 157⍝# -1 158ˢt̲ry< ← { body handler → 159 "'{ @ -> " c̲at (t̲rim body) c̲at " } []T_RAP '{ e -> " c̲at (t̲rim handler) c̲at " }" 160} 161 162⍝# In a handler: the outcome on the right for an error whose code is 163⍝# one of the words on the left, and h_alt< for any other error. 164⍝# >> "\"io\" []S_IGNAL \"gone\"" t_ry< "\"io empty\" c_atch< \"@ r_ecover< \\\"0\\\"\"" 165⍝# 0 166ˢc̲atch< ← { codes outcome → 167 ws ← w̲ords codes 168 0 = t̲ally ws ? "bad-macro-argument left" ⎕R̲EJECT catchCodes 169 tests ← j̲oin '{ w → m̲atchCode d̲isclose w } m̲ap ws 170 "(" c̲at (((t̲ally tests) − 3) t̲ake tests) c̲at ") ? " c̲at (t̲rim outcome) c̲at "; []H_ALT e" 171} 172 173⍝# Run the statements on the left, then the cleanup on the right, 174⍝# whether the body gave a value or an error; the value is the body's, 175⍝# or its error going on ([]E_NSURE). 176⍝# >> "p_rint! 1; 2" f_inally< "p_rint! \"cleaned\"" 177⍝# 1 178⍝# cleaned 179⍝# 2 180ˢf̲inally< ← { body cleanup → 181 "'{ @ -> " c̲at (t̲rim body) c̲at " } []E_NSURE '{ @ -> " c̲at (t̲rim cleanup) c̲at " }" 182} 183 184⍝# In a handler: recover with the value on the right, of the body's 185⍝# type; the try's value is this one ([]R_ECOVER). 186⍝# >> "1 d_iv 0" t_ry< "@ r_ecover< \"2 + 2\"" 187⍝# 4 188ˢr̲ecover< ← { @ value → "[]R_ECOVER (" c̲at (t̲rim value) c̲at ")" } 189 190⍝# In a handler: run the body again (at most 1000 times; []R_ETRY). 191⍝# >> n! := 0 192⍝# >> "n! := n! + 1; n! < 3 ? \"again\" []S_IGNAL \"not yet\"; n!" t_ry< "@ r_etry< @" 193⍝# 3 194ˢr̲etry< ← { @ @ → "[]R_ETRY e" } 195 196⍝# In a handler: let the error go on as it was ([]H_ALT). 197⍝# >> "1 d_iv 0" t_ry< "@ h_alt< @" 198⍝# error[division-by-zero] 199ˢh̲alt< ← { @ @ → "[]H_ALT e" } 200 201⍝# In a handler: go on from a warning ([]W_ARN) with its value 202⍝# ([]C_ONTINUE). 203⍝# >> "10 + (0 []W_ARN \"empty\" \"nothing\")" t_ry< "@ c_ontinue< @" 204⍝# 10 205ˢc̲ontinue< ← { @ @ → "[]C_ONTINUE e" } 206 207⍝## Private helpers 208 209catchCodes ← "c_atch< needs error codes on its left: \"io\" c_atch< \"@ r_ecover< \\\"0\\\"\"" 210 211⍝ One test of the error's code against the word w, with the | that 212⍝ joins it to the next. 213m̲atchCode ← { w → "(([]E_CODE e) m_atch " c̲at (q̲uote w) c̲at ") | " } 214 215 216ifArgs ← "i_f< takes two expressions on its right, separated by ;: \"c\" i_f< \"a; b\"" 217eachAlone ← "e_ach< writes statements: it stands as a statement of its own" 218eachHole ← "an e_ach< template names the word as $w: \"a b\" e_ach< \"u:$w := 1\"" 219 220⍝ The text t without white space at its ends. 221t̲rim ← { t → 222 m ← n̲ot t m̲ember? " \n\t" 223 0 = '+ r̲/ 0 + m ? "" 224 i ← w̲here m 225 (1 + (f̲irst r̲ev i) − f̲irst i) t̲ake ((f̲irst i) − 1) d̲rop t 226} 227 228⍝ The words of t: runs of characters other than white space, boxed. 229w̲ords ← { t → (n̲ot t m̲ember? " \n\t") p̲artition t } 230 231⍝ Where t separates statements: a ; or a newline outside brackets and 232⍝ strings. 233s̲eps ← { t → 234 q ← ('+ s̲\ 0 + t = f̲irst "\"") m̲od 2 235 d ← '+ s̲\ (0 + t m̲ember? "([{") − 0 + t m̲ember? ")]}" 236 (t m̲ember? ";\n") ∧ (0 = d) ∧ 0 = q 237} 238 239⍝ Where $w starts in t. 240h̲oles ← { t → (t = f̲irst "$") ∧ (1 d̲rop t = f̲irst "w") c̲at 0 } 241 242⍝ The boxed texts b joined into one text. 243j̲oin ← { b → d̲isclose '{ x y → e̲nclose (d̲isclose x) c̲at d̲isclose y } r̲/ b } 244 245⍝ t as a string literal: in quotes, with the characters a literal 246⍝ escapes (quote, backslash, newline, tab) escaped. 247q̲uote ← { t → 248 0 = t̲ally t ? "\"\"" 249 e̲ ← { c → c = f̲irst "\"" ? "\\\""◆ c = f̲irst "\\" ? "\\\\"◆ c = f̲irst "\n" ? "\\n"◆ c = f̲irst "\t" ? "\\t"◆ c } 250 "\"" c̲at (j̲oin 'e̲ m̲ap t) c̲at "\"" 251} 252 253⍝ The template t with each $w replaced by the word w. 254c̲opy ← { t w → 255 h ← h̲oles t 256 g ← 0 c̲at -1 d̲rop h 257 p̲ ← { i → (i s̲elect h) ? w◆ (i s̲elect g) ? ""◆ i s̲elect t } 258 j̲oin 'p̲ m̲ap r̲ange t̲ally t 259} 260 261⍝ An assertion's message in parentheses; none for @ (as f_ormat writes 262⍝ it). 263w̲hy ← { m → "@" m̲atch m ? ""◆ " (" c̲at m c̲at ")" } 264 265⍝ Source p, or an empty string literal when there is none. 266o̲rEmpty ← { p → 0 = t̲ally p ? "\"\""◆ p } 267 268⍝ t as a string literal, or nothing when it is empty. 269l̲it ← { t → 0 = t̲ally t ? ""◆ q̲uote t } 270 271⍝ Two pieces of source joined by c_at (either may be missing). 272c̲j ← { a b → 0 = t̲ally a ? b◆ 0 = t̲ally b ? a◆ a c̲at " c_at " c̲at b } 273 274⍝ The format text t as source: its literal text quoted, each {expr} as 275⍝ (f_ormat (expr)), joined by c_at; {{ and }} are braces. 276p̲ieces ← { t → 277 b ← t m̲ember? "{}" 278 0 = '+ r̲/ 0 + b ? l̲it t 279 k ← f̲irst w̲here b 280 pre ← (k − 1) t̲ake t 281 c ← k s̲elect t 282 rest ← k d̲rop t 283 n ← (k + 1) s̲elect t c̲at " " 284 c = n ? (l̲it pre c̲at c) c̲j p̲ieces 1 d̲rop rest 285 c = f̲irst "}" ? "bad-format right" ⎕R̲EJECT "a } in a format text is written }}" 286 e ← w̲here rest = f̲irst "}" 287 0 = t̲ally e ? "bad-format right" ⎕R̲EJECT "an unclosed { in a format text" 288 x ← t̲rim ((f̲irst e) − 1) t̲ake rest 289 0 = t̲ally x ? "bad-format right" ⎕R̲EJECT "an empty {} in a format text: write {expr}, or {{ for a brace" 290 (l̲it pre) c̲j ("(f_ormat (" c̲at x c̲at "))") c̲j p̲ieces (f̲irst e) d̲rop rest 291}