1 -{ extension 'match' }
\r
3 --------------------------------------------------------------------------------
\r
7 -- * Hygienize calls to pcall()
\r
9 --------------------------------------------------------------------------------
\r
12 -{ extension 'log' }
\r
14 require 'extension.match'
\r
16 -- Get match parsers and builder, for catch cases handling:
\r
17 local match_alpha = require 'extension.match'
\r
18 local H = H:new{side='inside', alpha = match_alpha }
\r
20 -- We'll need to track rogue return statements:
\r
23 -- Put a block AST into a pcall():
\r
24 local mkpcall = |block| +{pcall(function() -{block} end)}
\r
26 -- The statement builder:
\r
27 function trycatch_builder(x)
\r
28 --$log ("trycatch_builder", x, 'nohash', 60)
\r
29 local try_code, catch_cases, finally_code = unpack(x)
\r
30 local insert_return_catcher = false
\r
32 -- Can't be hygienize automatically by the current version of H, as
\r
33 -- it must bridge from inside user code (hacjed return statements)
\r
34 -- to outside macro code.
\r
35 local caught_return = !mlp.gensym 'caught_return'
\r
38 !try_code; !(finally_code or { })
\r
39 -- FIXME: Am I sure there's no need to hygienize inside?
\r
40 --[[if catch_cases then
\r
41 for case in ivalues(catch_cases) do
\r
42 --$log(case,'nohash')
\r
43 local patterns, guard, block = unpack(case)
\r
49 ----------------------------------------------------------------
\r
50 -- Returns in the try-block must be transformed:
\r
51 -- from the user's PoV, the code in the try-block isn't
\r
52 -- a function, therefore a return in it must not merely
\r
53 -- end the execution of the try block, but:
\r
54 -- * not cause any error to be caught;
\r
55 -- * let the finally-block be executed;
\r
56 -- * only then, let the enclosing function return with the
\r
57 -- appropraite values.
\r
58 -- The way to handle that is that any returned value is stored
\r
59 -- into the runtime variable caught_return, then a return with
\r
60 -- no value is sent, to stop the execution of the try-code.
\r
62 -- Similarly, a return in a catch case code must not prevent
\r
63 -- the finally-code from being run.
\r
65 -- This walker catches return statements and perform the relevant
\r
66 -- transformation into caught_return setting + empty return.
\r
68 -- There is an insert_return_catcher compile-time flag, which
\r
69 -- allows to avoid inserting return-handling code in the result
\r
71 ----------------------------------------------------------------
\r
72 local replace_returns_and_dots do
\r
76 insert_return_catcher = true
\r
77 -- Setvar's 'caught_return' code can't be hygienize by H currently.
\r
78 local setvar = `Set{ {caught_return}, { `Table{ unpack(x) } } }
\r
79 x <- { setvar; `Return }; x.tag = nil;
\r
80 --$log('transformed return stat:', x, 60)
\r
82 | `Function{...} -> return 'break'
\r
83 -- inside this, returns would be the nested function's, not ours.
\r
85 if not saved_args then saved_args = mlp.gensym 'args' end
\r
86 x <- `Call{ `Id 'unpack', saved_args }
\r
90 local cfg = { stat = {down=f}, expr = {down=f} }
\r
91 replace_returns_and_dots = |x| walk.block(cfg, x)
\r
94 -- parse returns in the try-block:
\r
95 replace_returns_and_dots (try_code)
\r
97 -- code handling the error catching process:
\r
98 local catch_result do
\r
99 if catch_cases and #catch_cases>0 then
\r
100 ----------------------------------------------------------
\r
101 -- Protect catch code against failures: they run in a pcall(), and
\r
102 -- the result is kept in catch_* vars so that it can be used to
\r
103 -- relaunch the error after the finally code has been executed.
\r
104 ----------------------------------------------------------
\r
105 for x in ivalues (catch_cases) do
\r
106 local case_code = x[3]
\r
107 -- handle rogue returns:
\r
108 replace_returns_and_dots (case_code)
\r
109 -- in case of error in the catch, we still need to run "finally":
\r
110 x[3] = +{block: catch_success, catch_error = -{mkpcall(case_code)}}
\r
112 ----------------------------------------------------------
\r
113 -- Uncaught exceptions must not cause a mismatch,
\r
114 -- so we introduce a catch-all do-nothing last case:
\r
115 ----------------------------------------------------------
\r
116 table.insert (catch_cases, { { { `Id '_' } }, false, { } })
\r
117 catch_result = spmatch.match_builder{ {+{user_error}}, catch_cases }
\r
123 ----------------------------------------------------------------
\r
124 -- Build the bits of code that will handle return statements
\r
125 -- in the user code (try-block and catch-blocks).
\r
126 ----------------------------------------------------------------
\r
127 local caught_return_init, caught_return_rethrow do
\r
128 if insert_return_catcher then
\r
129 caught_return_init = `Local{{caught_return}}
\r
130 caught_return_rethrow =
\r
131 +{stat: if -{caught_return} then return unpack(-{caught_return}) end}
\r
133 caught_return_init, caught_return_rethrow = { }, { }
\r
137 local saved_args_init =
\r
138 saved_args and `Local{ {saved_args}, { `Table{`Dots} } } or { }
\r
140 -- The finally code, to execute no matter what:
\r
141 local finally_result = finally_code or { }
\r
143 -- And the whole statement, gluing all taht together:
\r
144 local result = +{stat:
\r
146 -{ saved_args_init }
\r
147 -{ caught_return_init }
\r
148 local user_success, user_error = -{mkpcall(try_code)}
\r
149 local catch_success, catch_error = false, user_error
\r
150 if not user_success then -{catch_result} end
\r
152 if not user_success and not catch_success then error(catch_error) end
\r
153 -{ caught_return_rethrow }
\r
161 function catch_case_builder(x)
\r
162 --$log ("catch_case_builder", x, 'nohash', 60)
\r
163 local patterns, guard, _, code = unpack(x)
\r
164 -- patterns ought to be a pattern_group, but each expression must
\r
165 -- be converted into a single-element pattern_seq.
\r
166 for i = 1, #patterns do patterns[i] = {patterns[i]} end
\r
167 return { patterns, guard, code }
\r
170 mlp.lexer:add{ 'try', 'catch', 'finally', '->' }
\r
171 mlp.block.terminators:add{ 'catch', 'finally' }
\r
176 gg.onkeyword{ 'catch',
\r
180 gg.onkeyword{ 'if', mlp.expr },
\r
181 gg.optkeyword 'then',
\r
183 builder = catch_case_builder },
\r
184 separators = 'catch' } },
\r
185 gg.onkeyword{ 'finally', mlp.block },
\r
187 builder = trycatch_builder }
\r