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). The right side is 186⍝# code, like every macro argument: to recover with a string, write a 187⍝# string literal there, its quotes escaped once more inside t_ry<. 188⍝# >> "1 d_iv 0" t_ry< "@ r_ecover< \"2 + 2\"" 189⍝# 4 190⍝# 191⍝# >> "\"io\" []S_IGNAL \"gone\"" t_ry< "@ r_ecover< \"\\\"none\\\"\"" 192⍝# none 193ˢr̲ecover< ← { @ value → "[]R_ECOVER (" c̲at (ʰt̲rim value) c̲at ")" } 194 195⍝# In a handler: run the body again (at most 1000 times; []R_ETRY). 196⍝# >> n! := 0 197⍝# >> "n! := n! + 1; n! < 3 ? \"again\" []S_IGNAL \"not yet\"; n!" t_ry< "@ r_etry< @" 198⍝# 3 199ˢr̲etry< ← { @ @ → "[]R_ETRY e" } 200 201⍝# In a handler: let the error go on as it was ([]H_ALT). 202⍝# >> "1 d_iv 0" t_ry< "@ h_alt< @" 203⍝# error[division-by-zero] 204ˢh̲alt< ← { @ @ → "[]H_ALT e" } 205 206⍝# In a handler: go on from a warning ([]W_ARN) with its value 207⍝# ([]C_ONTINUE). 208⍝# >> "10 + (0 []W_ARN \"empty\" \"nothing\")" t_ry< "@ c_ontinue< @" 209⍝# 10 210ˢc̲ontinue< ← { @ @ → "[]C_ONTINUE e" } 211 212⍝## Private helpers 213 214catchCodes ← "c_atch< needs error codes on its left: \"io\" c_atch< \"@ r_ecover< \\\"0\\\"\"" 215 216⍝ One test of the error's code against the word w, with the | that 217⍝ joins it to the next. 218ʰm̲atchCode ← { w → "(([]E_CODE e) m_atch " c̲at (ʰq̲uote w) c̲at ") | " } 219 220 221ifArgs ← "i_f< takes two expressions on its right, separated by ;: \"c\" i_f< \"a; b\"" 222eachAlone ← "e_ach< writes statements: it stands as a statement of its own" 223eachHole ← "an e_ach< template names the word as $w: \"a b\" e_ach< \"u:$w := 1\"" 224 225⍝ The text t without white space at its ends. 226ʰt̲rim ← { t → 227 m ← n̲ot t m̲ember? " \n\t" 228 0 = '+ r̲/ 0 + m ? "" 229 i ← w̲here m 230 (1 + (f̲irst r̲ev i) − f̲irst i) t̲ake ((f̲irst i) − 1) d̲rop t 231} 232 233⍝ The words of t: runs of characters other than white space, boxed. 234ʰw̲ords ← { t → (n̲ot t m̲ember? " \n\t") p̲artition t } 235 236⍝ Where t separates statements: a ; or a newline outside brackets and 237⍝ strings. 238ʰs̲eps ← { t → 239 q ← ('+ s̲\ 0 + t = f̲irst "\"") m̲od 2 240 d ← '+ s̲\ (0 + t m̲ember? "([{") − 0 + t m̲ember? ")]}" 241 (t m̲ember? ";\n") ∧ (0 = d) ∧ 0 = q 242} 243 244⍝ Where $w starts in t. 245ʰh̲oles ← { t → (t = f̲irst "$") ∧ (1 d̲rop t = f̲irst "w") c̲at 0 } 246 247⍝ The boxed texts b joined into one text. 248ʰj̲oin ← { b → d̲isclose '{ x y → e̲nclose (d̲isclose x) c̲at d̲isclose y } r̲/ b } 249 250⍝ t as a string literal: in quotes, with the characters a literal 251⍝ escapes (quote, backslash, newline, tab) escaped. 252ʰq̲uote ← { t → 253 0 = t̲ally t ? "\"\"" 254 e̲ ← { c → c = f̲irst "\"" ? "\\\""◆ c = f̲irst "\\" ? "\\\\"◆ c = f̲irst "\n" ? "\\n"◆ c = f̲irst "\t" ? "\\t"◆ c } 255 "\"" c̲at (ʰj̲oin 'e̲ m̲ap t) c̲at "\"" 256} 257 258⍝ The template t with each $w replaced by the word w. 259ʰc̲opy ← { t w → 260 h ← ʰh̲oles t 261 g ← 0 c̲at -1 d̲rop h 262 p̲ ← { i → (i s̲elect h) ? w◆ (i s̲elect g) ? ""◆ i s̲elect t } 263 ʰj̲oin 'p̲ m̲ap r̲ange t̲ally t 264} 265 266⍝ An assertion's message in parentheses; none for @ (as f_ormat writes 267⍝ it). 268ʰw̲hy ← { m → "@" m̲atch m ? ""◆ " (" c̲at m c̲at ")" } 269 270⍝ Source p, or an empty string literal when there is none. 271ʰo̲rEmpty ← { p → 0 = t̲ally p ? "\"\""◆ p } 272 273⍝ t as a string literal, or nothing when it is empty. 274ʰl̲it ← { t → 0 = t̲ally t ? ""◆ ʰq̲uote t } 275 276⍝ Two pieces of source joined by c_at (either may be missing). 277ʰc̲j ← { a b → 0 = t̲ally a ? b◆ 0 = t̲ally b ? a◆ a c̲at " c_at " c̲at b } 278 279⍝ The format text t as source: its literal text quoted, each {expr} as 280⍝ (f_ormat (expr)), joined by c_at; {{ and }} are braces. 281ʰp̲ieces ← { t → 282 b ← t m̲ember? "{}" 283 0 = '+ r̲/ 0 + b ? ʰl̲it t 284 k ← f̲irst w̲here b 285 pre ← (k − 1) t̲ake t 286 c ← k s̲elect t 287 rest ← k d̲rop t 288 n ← (k + 1) s̲elect t c̲at " " 289 c = n ? (ʰl̲it pre c̲at c) ʰc̲j ʰp̲ieces 1 d̲rop rest 290 c = f̲irst "}" ? "bad-format right" ⎕R̲EJECT "a } in a format text is written }}" 291 e ← w̲here rest = f̲irst "}" 292 0 = t̲ally e ? "bad-format right" ⎕R̲EJECT "an unclosed { in a format text" 293 x ← ʰt̲rim ((f̲irst e) − 1) t̲ake rest 294 0 = t̲ally x ? "bad-format right" ⎕R̲EJECT "an empty {} in a format text: write {expr}, or {{ for a brace" 295 (ʰl̲it pre) ʰc̲j ("(f_ormat (" c̲at x c̲at "))") ʰc̲j ʰp̲ieces (f̲irst e) d̲rop rest 296}