| ------------------------------------------------------------------------------- |
| -- Copyright (c) 2006-2013 Fabien Fleutot and others. |
| -- |
| -- All rights reserved. |
| -- |
| -- This program and the accompanying materials are made available |
| -- under the terms of the Eclipse Public License v1.0 which |
| -- accompanies this distribution, and is available at |
| -- http://www.eclipse.org/legal/epl-v10.html |
| -- |
| -- This program and the accompanying materials are also made available |
| -- under the terms of the MIT public license which accompanies this |
| -- distribution, and is available at http://www.lua.org/license.html |
| -- |
| -- Contributors: |
| -- Fabien Fleutot - API and implementation |
| -- |
| ------------------------------------------------------------------------------- |
| |
| -{ 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 |
| |
| |