]> git.lizzy.rs Git - metalua.git/commitdiff
added a try...catch...with extension
authorFabien Fleutot <fabien@macfabien.local>
Tue, 22 Jan 2008 22:48:18 +0000 (23:48 +0100)
committerFabien Fleutot <fabien@macfabien.local>
Tue, 22 Jan 2008 22:48:18 +0000 (23:48 +0100)
src/lib/extension-compiler/match.mlua
src/lib/extension-compiler/trycatch.mlua [new file with mode: 0644]
src/lib/extension-runtime/trycatch.lua [new file with mode: 0644]
src/samples/trycatch_test.mlua [new file with mode: 0644]

index 36242c500412e1d75f26cefea334d8d71926f241..e35630eefc31f6d3bf907cd0d3d184eac8692305 100755 (executable)
 -- Convert a tested term and a list of (pattern, statement) pairs
 -- into a pattern-matching AST.
 ----------------------------------------------------------------------
-local function match_builder (tested_terms_list, cases)
+function match_builder (x)
+
+   local tested_terms_list, cases = unpack(x)
 
    local local_vars = { }
    local var = |n| `Id{ "$v" .. n }
@@ -299,20 +301,28 @@ end
 mlp.lexer:add{ "match", "with", "->" }
 mlp.block.terminators:add "|"
 
+-- Output-format:
+-- match_cases_list    := list of patterns_with_block
+-- patterns_with_block := { list of patterns, guard?, block }
+-- pattern             := mlp.expr
+-- guard?              := mlp.expr | false
+match_cases_list_parser = gg.list{ name = "match cases list",
+   gg.sequence{ name = "match case",
+      gg.list{ name  = "match case patterns list",
+         primary     = mlp.expr_list,
+         separators  = "|",
+         terminators = { "->", "if" } },
+      gg.onkeyword{ "if", mlp.expr, consume = true },
+      "->",
+      mlp.block },
+   separators  = "|",
+   terminators = "end" }
+
 mlp.stat:add{ name = "match statement",
-   "match", mlp.expr_list, "with",
-   gg.optkeyword "|",
-   gg.list{ name = "match cases list",
-      primary     = gg.sequence{ name = "match case",
-         gg.list{ name = "patterns",
-            primary = mlp.expr_list,
-            separators = "|",
-            terminators = { "->", "if" } },
-         gg.onkeyword{ "if", mlp.expr, consume = true },
-         "->",
-         mlp.block },
-      separators  = "|",
-      terminators = "end" },
+   "match", 
+   mlp.expr_list, 
+   "with", gg.optkeyword "|",
+   match_cases_list_parser,
    "end",
-   builder = |x| match_builder (x[1], x[3]) }
+   builder = |x| match_builder{ x[1], x[3] } }
 
diff --git a/src/lib/extension-compiler/trycatch.mlua b/src/lib/extension-compiler/trycatch.mlua
new file mode 100644 (file)
index 0000000..7a72a24
--- /dev/null
@@ -0,0 +1,153 @@
+-{ extension 'match' }\r
+\r
+-- This deserves to go to the library\r
+-{ block:\r
+   require 'dollar'\r
+   function mlp.macros.log(...)\r
+      local acc = `Do{ }\r
+      for i in ivalues{...} do\r
+         local name\r
+         match i with \r
+         | `Id{n} -> name = n \r
+         | _      -> name = table.tostring(i, 'nohash')\r
+         end\r
+         table.insert(acc, +{printf("log: %s = %s",\r
+                                    -{`String{name}},\r
+                                    table.tostring(-{i}, 80, 'nohash'))})\r
+      end\r
+      return acc\r
+   end }\r
+\r
+\r
+-- Get match parsers and builder, for catch cases handling:\r
+require 'extension-compiler.match' \r
+\r
+-- We'll need to track rogue return statements:\r
+require 'walk'\r
+\r
+-- Put a block AST into a pcall():\r
+local mkpcall = |block| +{pcall(function() -{block} end)}\r
+\r
+-- The statement builder:\r
+function trycatch_builder(x)\r
+   local try_code, catch_cases, finally_code = unpack(x)\r
+   local insert_return_catcher = false\r
+\r
+   --$log(try_code, catch_cases, finally_code)\r
+\r
+   ----------------------------------------------------------------\r
+   -- Returns in the try-block must be transformed:\r
+   -- from the user's PoV, the code in the try-block isn't \r
+   -- a function, therefore a return in it must not merely\r
+   -- end the execution of the try block, but:\r
+   --  * not cause any error to be caught;\r
+   --  * let the finally-block be executed;\r
+   --  * only then, let the enclosing function return with the\r
+   --    appropraite values.\r
+   -- The way to handle that is that any returned value is stored \r
+   -- into the runtime variable caught_return, then a return with\r
+   -- no value is sent, to stop the execution of the try-code.\r
+   --\r
+   -- Similarly, a return in a catch case code must not prevent\r
+   -- the finally-code from being run.\r
+   --\r
+   -- This walker catches return statements and perform the relevant\r
+   -- transformation into caught_return setting + empty return.\r
+   --\r
+   -- There is an insert_return_catcher compile-time flag, which\r
+   -- allows to avoid inserting return-handling code in the result\r
+   -- when not needed.\r
+   ----------------------------------------------------------------\r
+   local replace_returns do\r
+      local cfg = { stat = { } }\r
+      function cfg.stat.down(x)\r
+         match x with \r
+         | `Return{...} -> \r
+            insert_return_catcher = true\r
+            local setvar = \r
+               +{stat:caught_return = -{ `Table{ unpack(x) } } }\r
+            x.tag = nil\r
+            x <- { setvar; `Return }\r
+            return 'break'\r
+         | `Function{...} -> return 'break' \r
+            -- inside this, returns would be the function's, not ours.\r
+         | _ -> -- pass\r
+         end\r
+      end\r
+      replace_returns = |x| walk.block(cfg, x)\r
+   end\r
+\r
+   -- parse returns in the try-block:\r
+   replace_returns (try_code)\r
+\r
+   -- code handling the error catching process:\r
+   local catch_result do\r
+      if catch_cases then\r
+         ----------------------------------------------------------\r
+         -- Protect catch code against failures: they run in a pcall(), and\r
+         -- the result is kept in catch_* vars so that it can be used to\r
+         -- relaunch the error after the finally code has been executed.\r
+         ----------------------------------------------------------\r
+         for x in ivalues (catch_cases) do\r
+            local case_code = x[3]\r
+            -- handle rogue returns:\r
+            replace_returns (case_code)\r
+            -- in case of error in the catch, we still need to run "finally":\r
+            x[3] = +{block: catch_success, catch_error = -{mkpcall(case_code)}}\r
+         end\r
+         ----------------------------------------------------------\r
+         -- Uncaught exceptions must not cause a mismatch,\r
+         -- so we introduce a catch-all do-nothing last case:\r
+         ----------------------------------------------------------\r
+         table.insert (catch_cases, { { { `Id '_' } }, false, { } }) \r
+         catch_result = match_builder{ {+{user_error}}, catch_cases }\r
+      else\r
+         catch_result = { }\r
+      end\r
+   end\r
+\r
+   ----------------------------------------------------------------\r
+   -- Build the bits of code that will handle return statements\r
+   -- in the user code (try-block and catch-blocks).\r
+   ----------------------------------------------------------------\r
+   local caught_return_init, caught_return_rethrow do\r
+      if insert_return_catcher then\r
+         caught_return_init    = +{stat: local caught_return }\r
+         caught_return_rethrow =\r
+            +{stat: if caught_return then return unpack(caught_return) end}\r
+      else\r
+         caught_return_init, caught_return_rethrow = { }, { }\r
+      end\r
+   end\r
+\r
+   -- The finally code, to execute no matter what:\r
+   local finally_result = finally_code or { }\r
+\r
+   -- And the whole statement, gluing all taht together:\r
+   local result = +{stat: \r
+      do\r
+         -{ caught_return_init }\r
+         local user_success,  user_error  = -{mkpcall(try_code)}\r
+         local catch_success, catch_error = false, user_error\r
+         if not user_success then -{catch_result} end\r
+         -{finally_result}\r
+         if not user_success and not catch_success then error(catch_error) end \r
+         -{ caught_return_rethrow }\r
+      end }\r
+   return result\r
+end\r
+\r
+mlp.lexer:add{ 'try', 'catch', 'finally', '->' }\r
+mlp.block.terminators:add{ 'catch', 'finally' }\r
+table.insert(match_cases_list_parser.terminators, 'finally')\r
+\r
+mlp.stat:add{\r
+   'try', \r
+   mlp.block, \r
+   gg.onkeyword{ 'catch', match_cases_list_parser },\r
+   gg.onkeyword{ 'finally', mlp.block },\r
+   'end',\r
+   builder = trycatch_builder }\r
+\r
+\r
+\r
diff --git a/src/lib/extension-runtime/trycatch.lua b/src/lib/extension-runtime/trycatch.lua
new file mode 100644 (file)
index 0000000..ace0f14
--- /dev/null
@@ -0,0 +1 @@
+do end
\ No newline at end of file
diff --git a/src/samples/trycatch_test.mlua b/src/samples/trycatch_test.mlua
new file mode 100644 (file)
index 0000000..c6153d2
--- /dev/null
@@ -0,0 +1,91 @@
+-{ extension 'trycatch' }
+
+
+----------------------------------------------------------------------
+print "1) no error"
+try
+   print("   Hi")
+end
+
+
+----------------------------------------------------------------------
+print "2) caught error"
+try
+   error "some_error"
+catch x ->
+   printf("   Successfully caught %q", x)
+end
+
+
+----------------------------------------------------------------------
+print "3) no error, with a finally"
+try
+   print "   Hi"
+finally
+   print "   Finally OK"
+end
+
+
+----------------------------------------------------------------------
+print "4) error, with a finally"
+try
+   print "   Hi"
+   error "bang"
+catch "bang" ->
+   -- nothing
+finally
+   print "   Finally OK"
+end
+
+
+----------------------------------------------------------------------
+print "5) nested catchers"
+try
+   try
+      error "some_error"
+   catch "some_other_error" ->
+      assert (false, "mismatch, this must not happen")
+   end
+catch "some_error"/x ->
+   printf("   Successfully caught %q across a try that didn't catch", x)
+| x ->
+   assert (false, "We shouldn't reach this catch-all")
+end
+
+
+----------------------------------------------------------------------
+print "6) nested catchers, with a 'finally in the inner one"
+try
+   try
+      error "some_error"
+   catch "some_other_error" ->
+      assert (false, "mismatch, this must not happen")
+   finally
+      print "   Leaving the inner try-catch"
+   end
+catch "some_error"/x ->
+   printf("   Successfully caught %q across a try that didn't catch", x)
+| x ->
+   assert (false, "We shouldn't reach this catch-all")
+end
+
+
+----------------------------------------------------------------------
+print "7) 'finally' intercepts a return from a function"
+function f()
+   try
+      print "   into f:"
+      return "F_RESULT"
+      assert (false, "I'll never go there")
+   catch _ ->
+      assert (false, "No exception should be thrown")
+   finally
+      print "   I do the finally before leaving f()"
+   end
+end
+local fr = f()
+printf("   f returned %q", fr)
+
+
+----------------------------------------------------------------------
+print "*) done."