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}