Merge: Catch em all
authorJean Privat <jean@pryen.org>
Wed, 26 Apr 2017 14:38:26 +0000 (10:38 -0400)
committerJean Privat <jean@pryen.org>
Wed, 26 Apr 2017 14:38:26 +0000 (10:38 -0400)
Extends the `do catch` construct to catch all run time errors.
The interpreter also keep the error information if one wants it.

Pull-Request: #2411

misc/gtksourceview/nit.lang
misc/highlight/nit.lang
misc/source-highlight/nit.lang
misc/syntaxhighlighter/shBrushNit.js
misc/vim/indent/nit.vim
misc/vim/syntax/nit.vim
src/compiler/abstract_compiler.nit
src/interpreter/naive_interpreter.nit
tests/sav/test_catch_runtime.res [new file with mode: 0644]
tests/test_catch_runtime.nit [new file with mode: 0644]

index 8edd22e..2e7a08e 100644 (file)
@@ -84,6 +84,7 @@
     <context id="keywords" style-ref="keyword">
       <keyword>do</keyword>
       <keyword>end</keyword>
+      <keyword>catch</keyword>
       <keyword>intrude</keyword>
       <keyword>private</keyword>
       <keyword>if</keyword>
index 1787113..a1a71ee 100644 (file)
@@ -6,7 +6,7 @@ Digits=[[ (?:0x|0X|0b)[0-9a-fA-F]+|\d*[\.\_]?\d+(?:[eE][\-\+]\d+)? ]]
 
 Keywords={
   { Id=1,
-    List={"abort", "abstract", "as", "assert", "break", "continue", "do", "else", "end", "enum", "extern", "for", "if", "import", "in", "interface", "intern", "intrude", "is", "isa", "isset", "new", "label", "loop", "private", "protected", "readable", "return", "super", "then", "while", "false", "null", "nullable", "redef", "self", "true", "and", "not", "or", "fun", "var", "type", "init", "class", "package", "module", "special", "universal", "writable"},
+    List={"abort", "abstract", "as", "assert", "break", "continue", "do", "else", "end", "catch", "enum", "extern", "for", "if", "import", "in", "interface", "intern", "intrude", "is", "isa", "isset", "new", "label", "loop", "private", "protected", "readable", "return", "super", "then", "while", "false", "null", "nullable", "redef", "self", "true", "and", "not", "or", "fun", "var", "type", "init", "class", "package", "module", "special", "universal", "writable"},
   },
   { Id=2,
     Regex=[[[A-Z][\/\w]+]],
index 754266d..2564958 100644 (file)
@@ -29,7 +29,7 @@ environment string delim '(\"{3})' '(\"{3})' escape "\\" multiline begin
        specialchar = '\\.'
 end
 
-keyword = "abort|abstract|as|assert|break|continue|do|else|end|enum|extern|for|if|import|in|interface|intern|intrude|is|isa|isset|new|label|loop|private|protected|readable|return|super|then|while|false|null|nullable|redef|self|true|and|not|or|fun|var|type|init|class|package|module|special|universal|writable"
+keyword = "abort|abstract|as|assert|break|continue|do|else|end|catch|enum|extern|for|if|import|in|interface|intern|intrude|is|isa|isset|new|label|loop|private|protected|readable|return|super|then|while|false|null|nullable|redef|self|true|and|not|or|fun|var|type|init|class|package|module|special|universal|writable"
 
 type = '[[:upper:]]([[:word:]]*)'
 
index e9d1f78..5b6f0ee 100644 (file)
@@ -23,7 +23,7 @@
 
        function Brush()
        {
-               var keywords =  'abort abstract and as assert break class continue do else end enum extern false for fun' +
+               var keywords =  'abort abstract and as assert break class continue do else end catch enum extern false for fun' +
                                                'if import in init interface intrude is isa isset label loop module new null nullable not' +
                                                'once or protected private redef return self super then type true universal var' +
                                                'when while writable';
index 10c4a2e..4e1555d 100644 (file)
@@ -30,7 +30,7 @@ setlocal nosmartindent
 setlocal nocindent
 setlocal autoindent
 setlocal comments=:#
-setlocal indentkeys+==end,=else,=do,=var,0!,=then,=loop,=special,=class,=interface,=universal
+setlocal indentkeys+==end,=else,=catch,=do,=var,0!,=then,=loop,=special,=class,=interface,=universal
 
 " Only define the function once.
 if exists("*GetNITIndent")
@@ -38,9 +38,9 @@ if exists("*GetNITIndent")
 endif
 
 " Indent after
-let s:relative_indent = '\<\(do\|loop\|then\|else\|if\)\s*\(#\|$\)\|^\s*\(\<\(redef\|private\)\>\s*\)\?\(\<abstract\>\s*\)\?\<\(class\|interface\|universal\|special\)\>'
+let s:relative_indent = '\<\(do\|loop\|then\|else\|catch\|if\)\s*\(#\|$\)\|^\s*\(\<\(redef\|private\)\>\s*\)\?\(\<abstract\>\s*\)\?\<\(class\|interface\|universal\|special\)\>'
 " Unindent on them
-let s:outdent = '^\s*\(else\|then\|end\)\>'
+let s:outdent = '^\s*\(catch\|else\|then\|end\)\>'
 " At 0
 let s:no_indent = '^\s*\(class\|import\|special\)\>'
 
index b5d86ac..b72ee52 100644 (file)
@@ -57,7 +57,7 @@ syn match NITClosure "!\h\w*"
 
 " Fallback highlight keywords
 syn match NITNull "\<\(null\)\>"
-syn match NITControl "\<\(init\|end\|not null\|not\|var\|do\|then\|else\|loop\|is\)\>"
+syn match NITControl "\<\(init\|end\|not null\|not\|var\|do\|then\|catch\|else\|loop\|is\)\>"
 syn match NITKeyword "\<\(super\)\>"
 " Unmatchning error
 syn match Error "\<end\>"
index 0d04167..9ec830a 100644 (file)
@@ -1825,17 +1825,30 @@ abstract class AbstractCompilerVisitor
        # used by aborts, asserts, casts, etc.
        fun add_abort(message: String)
        do
+               add_raw_throw
+               self.add("PRINT_ERROR(\"Runtime error: %s\", \"{message.escape_to_c}\");")
+               add_raw_abort
+       end
+
+       # Generate a long jump if there is a catch block.
+       #
+       # This method should be called before the error messages and before a `add_raw_abort`.
+       fun add_raw_throw
+       do
                self.add("if(catchStack.cursor >= 0)\{")
                self.add("longjmp(catchStack.envs[catchStack.cursor], 1);")
                self.add("\}")
-               self.add("PRINT_ERROR(\"Runtime error: %s\", \"{message.escape_to_c}\");")
-               add_raw_abort
        end
 
+       # Generate abort without a message.
+       #
+       # Used when one need a more complex message.
+       # Do not forget to call `add_raw_abort` before the display of a custom user message.
        fun add_raw_abort
        do
-               if self.current_node != null and self.current_node.location.file != null and
-                               self.current_node.location.file.mmodule != null then
+               var current_node = self.current_node
+               if current_node != null and current_node.location.file != null and
+                               current_node.location.file.mmodule != null then
                        var f = "FILE_{self.current_node.location.file.mmodule.c_name}"
                        self.require_declaration(f)
                        self.add("PRINT_ERROR(\" (%s:%d)\\n\", {f}, {current_node.location.line_start});")
@@ -1850,6 +1863,7 @@ abstract class AbstractCompilerVisitor
        do
                var res = self.type_test(value, mtype, tag)
                self.add("if (unlikely(!{res})) \{")
+               self.add_raw_throw
                var cn = self.class_name_string(value)
                self.add("PRINT_ERROR(\"Runtime error: Cast failed. Expected `%s`, got `%s`\", \"{mtype.to_s.escape_to_c}\", {cn});")
                self.add_raw_abort
@@ -2179,6 +2193,7 @@ redef class MMethodDef
                var node = modelbuilder.mpropdef2node(self)
 
                if is_abstract then
+                       v.add_raw_throw
                        var cn = v.class_name_string(arguments.first)
                        v.current_node = node
                        v.add("PRINT_ERROR(\"Runtime error: Abstract method `%s` called on `%s`\", \"{mproperty.name.escape_to_c}\", {cn});")
@@ -2290,6 +2305,7 @@ redef class AMethPropdef
                end
 
                # We have a problem
+               v.add_raw_throw
                var cn = v.class_name_string(arguments.first)
                v.add("PRINT_ERROR(\"Runtime error: uncompiled method `%s` called on `%s`. NOT YET IMPLEMENTED\", \"{mpropdef.mproperty.name.escape_to_c}\", {cn});")
                v.add_raw_abort
index 6e6b24b..991d8b5 100644 (file)
@@ -119,7 +119,10 @@ class NaiveInterpreter
        var escapemark: nullable EscapeMark = null
 
        # The count of `catch` blocs that have been encountered and can catch an abort
-       var catch_count = 0
+       var catch_count = 0 is writable
+
+       # The last error thrown on abort/runtime error where catch_count > 0
+       var last_error: nullable FatalError = null
 
        # Is a return or a break or a continue executed?
        # Use this function to know if you must skip the evaluation of statements
@@ -685,6 +688,15 @@ class NaiveInterpreter
        var error_instance = new MutableInstance(modelbuilder.model.null_type) is lazy
 end
 
+# A runtime error
+class FatalError
+       # The error message
+       var message: String
+
+       # The problematic node, if any
+       var node: nullable ANode
+end
+
 # An instance represents a value of the executed program.
 abstract class Instance
        # The dynamic type of the instance
@@ -821,6 +833,12 @@ redef class ANode
        # `v` is used to know if a colored message is displayed or not
        fun fatal(v: NaiveInterpreter, message: String)
        do
+               # Abort if there is a `catch` block
+               if v.catch_count > 0 then
+                       v.last_error = new FatalError(message, self)
+                       abort
+               end
+
                if v.modelbuilder.toolcontext.opt_no_color.value == true then
                        sys.stderr.write("Runtime error: {message} ({location.file.filename}:{location.line_start})\n")
                else
@@ -1689,13 +1707,8 @@ end
 redef class AAbortExpr
        redef fun stmt(v)
        do
-               # Abort as asked if there is no `catch` bloc
-               if v.catch_count <= 0 then
-                       fatal(v, "Aborted")
-                       exit(1)
-               else
-                       abort
-               end
+               fatal(v, "Aborted")
+               exit(1)
        end
 end
 
diff --git a/tests/sav/test_catch_runtime.res b/tests/sav/test_catch_runtime.res
new file mode 100644 (file)
index 0000000..0698165
--- /dev/null
@@ -0,0 +1,14 @@
+1
+1
+2
+2
+3
+3
+4
+4
+5
+5
+6
+6
+7
+7
diff --git a/tests/test_catch_runtime.nit b/tests/test_catch_runtime.nit
new file mode 100644 (file)
index 0000000..6c676a7
--- /dev/null
@@ -0,0 +1,83 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import core::kernel
+
+fun foo is abstract
+fun bar is intern
+
+class A
+       var a: A is noautoinit
+       fun foo do 0.output
+end
+
+var a = new A
+var n: nullable A = null
+var o: Object = 1
+
+do
+       1.output
+       if true then abort
+       666.output
+catch
+       1.output
+end
+
+do
+       2.output
+       n.foo
+       666.output
+catch
+       2.output
+end
+
+do
+       3.output
+       foo
+       666.output
+catch
+       3.output
+end
+
+do
+       4.output
+       assert false
+       666.output
+catch
+       4.output
+end
+
+do
+       5.output
+       o.as(A).foo
+       666.output
+catch
+       5.output
+end
+
+do
+       6.output
+       a.a.foo
+       666.output
+catch
+       6.output
+end
+
+do
+       7.output
+       bar
+       666.output
+catch
+       7.output
+end