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