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}