+++ /dev/null
--{ extension 'match' }
-
---------------------------------------------------------------------------------
---
--- TODO:
---
--- * Hygienize calls to pcall()
---
---------------------------------------------------------------------------------
-
--{ extension 'H' }
--{ extension 'log' }
-
--- Get match parsers and builder, for catch cases handling:
-local match_alpha = require 'metalua.extension.match'
-local H = H:new{side='inside', alpha = match_alpha }
-
--- We'll need to track rogue return statements:
-require 'metalua.walk'
-
--- Put a block AST into a pcall():
-local mkpcall = |block| +{pcall(function() -{block} end)}
-
--- The statement builder:
-function trycatch_builder(x)
- --$log ("trycatch_builder", x, 'nohash', 60)
- local try_code, catch_cases, finally_code = unpack(x)
- local insert_return_catcher = false
-
- -- Can't be hygienize automatically by the current version of H, as
- -- it must bridge from inside user code (hacjed return statements)
- -- to outside macro code.
- local caught_return = !mlp.gensym 'caught_return'
- local saved_args
-
- !try_code; !(finally_code or { })
- -- FIXME: Am I sure there's no need to hygienize inside?
- --[[if catch_cases then
- for case in ivalues(catch_cases) do
- --$log(case,'nohash')
- local patterns, guard, block = unpack(case)
- ! block
- end
- end]]
-
-
- ----------------------------------------------------------------
- -- Returns in the try-block must be transformed:
- -- from the user's PoV, the code in the try-block isn't
- -- a function, therefore a return in it must not merely
- -- end the execution of the try block, but:
- -- * not cause any error to be caught;
- -- * let the finally-block be executed;
- -- * only then, let the enclosing function return with the
- -- appropraite values.
- -- The way to handle that is that any returned value is stored
- -- into the runtime variable caught_return, then a return with
- -- no value is sent, to stop the execution of the try-code.
- --
- -- Similarly, a return in a catch case code must not prevent
- -- the finally-code from being run.
- --
- -- This walker catches return statements and perform the relevant
- -- transformation into caught_return setting + empty return.
- --
- -- There is an insert_return_catcher compile-time flag, which
- -- allows to avoid inserting return-handling code in the result
- -- when not needed.
- ----------------------------------------------------------------
- local replace_returns_and_dots do
- local function f(x)
- match x with
- | `Return{...} ->
- insert_return_catcher = true
- -- Setvar's 'caught_return' code can't be hygienize by H currently.
- local setvar = `Set{ {caught_return}, { `Table{ unpack(x) } } }
- x <- { setvar; `Return }; x.tag = nil;
- --$log('transformed return stat:', x, 60)
- return 'break'
- | `Function{...} -> return 'break'
- -- inside this, returns would be the nested function's, not ours.
- | `Dots ->
- if not saved_args then saved_args = mlp.gensym 'args' end
- x <- `Call{ `Id 'unpack', saved_args }
- | _ -> -- pass
- end
- end
- local cfg = { stat = {down=f}, expr = {down=f} }
- replace_returns_and_dots = |x| walk.block(cfg, x)
- end
-
- -- parse returns in the try-block:
- replace_returns_and_dots (try_code)
-
- -- code handling the error catching process:
- local catch_result do
- if catch_cases and #catch_cases>0 then
- ----------------------------------------------------------
- -- Protect catch code against failures: they run in a pcall(), and
- -- the result is kept in catch_* vars so that it can be used to
- -- relaunch the error after the finally code has been executed.
- ----------------------------------------------------------
- for x in ivalues (catch_cases) do
- local case_code = x[3]
- -- handle rogue returns:
- replace_returns_and_dots (case_code)
- -- in case of error in the catch, we still need to run "finally":
- x[3] = +{block: catch_success, catch_error = -{mkpcall(case_code)}}
- end
- ----------------------------------------------------------
- -- Uncaught exceptions must not cause a mismatch,
- -- so we introduce a catch-all do-nothing last case:
- ----------------------------------------------------------
- table.insert (catch_cases, { { { `Id '_' } }, false, { } })
- catch_result = spmatch.match_builder{ {+{user_error}}, catch_cases }
- else
- catch_result = { }
- end
- end
-
- ----------------------------------------------------------------
- -- Build the bits of code that will handle return statements
- -- in the user code (try-block and catch-blocks).
- ----------------------------------------------------------------
- local caught_return_init, caught_return_rethrow do
- if insert_return_catcher then
- caught_return_init = `Local{{caught_return}}
- caught_return_rethrow =
- +{stat: if -{caught_return} then return unpack(-{caught_return}) end}
- else
- caught_return_init, caught_return_rethrow = { }, { }
- end
- end
-
- local saved_args_init =
- saved_args and `Local{ {saved_args}, { `Table{`Dots} } } or { }
-
- -- The finally code, to execute no matter what:
- local finally_result = finally_code or { }
-
- -- And the whole statement, gluing all taht together:
- local result = +{stat:
- do
- -{ saved_args_init }
- -{ caught_return_init }
- local user_success, user_error = -{mkpcall(try_code)}
- local catch_success, catch_error = false, user_error
- if not user_success then -{catch_result} end
- -{finally_result}
- if not user_success and not catch_success then error(catch_error) end
- -{ caught_return_rethrow }
- end }
-
- H(result)
-
- return result
-end
-
-function catch_case_builder(x)
- --$log ("catch_case_builder", x, 'nohash', 60)
- local patterns, guard, _, code = unpack(x)
- -- patterns ought to be a pattern_group, but each expression must
- -- be converted into a single-element pattern_seq.
- for i = 1, #patterns do patterns[i] = {patterns[i]} end
- return { patterns, guard, code }
-end
-
-mlp.lexer:add{ 'try', 'catch', 'finally', '->' }
-mlp.block.terminators:add{ 'catch', 'finally' }
-
-mlp.stat:add{
- 'try',
- mlp.block,
- gg.onkeyword{ 'catch',
- gg.list{
- gg.sequence{
- mlp.expr_list,
- gg.onkeyword{ 'if', mlp.expr },
- gg.optkeyword 'then',
- mlp.block,
- builder = catch_case_builder },
- separators = 'catch' } },
- gg.onkeyword{ 'finally', mlp.block },
- 'end',
- builder = trycatch_builder }
-
-return H.alpha
-
-