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}