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

14 files changed:
contrib/nitin/Makefile [new file with mode: 0644]
contrib/nitin/README.md [new file with mode: 0644]
contrib/nitin/nitin.nit [new file with mode: 0644]
contrib/nitin/nitin_readline.nit [new file with mode: 0644]
contrib/nitin/package.ini [new file with mode: 0644]
src/interpreter/naive_interpreter.nit
src/loader.nit
src/toolcontext.nit
tests/listfull.sh
tests/niti.skip
tests/nitin.inputs [new file with mode: 0644]
tests/nitvm.skip
tests/sav/nitin.res [new file with mode: 0644]
tests/tests.sh

diff --git a/contrib/nitin/Makefile b/contrib/nitin/Makefile
new file mode 100644 (file)
index 0000000..c505bcd
--- /dev/null
@@ -0,0 +1,3 @@
+all:
+       mkdir -p bin
+       nitc --semi-global nitin_readline.nit -o bin/nitin
diff --git a/contrib/nitin/README.md b/contrib/nitin/README.md
new file mode 100644 (file)
index 0000000..0cd4c3a
--- /dev/null
@@ -0,0 +1,134 @@
+# An experimental Nit interactive interpreter
+
+This tool is outside src/ because:
+
+1. Is is greatly experimental
+2. It can depend on readline (GPL3) whereas the rest of nitc is Apache2.
+   Both are compatible but the final binary result is GLP3.
+
+## Features
+
+* use GNU readline to read lines
+* use importation/refinement to handle incremental execution (so basically everything works out of the box)
+* maintain an interpreter and live objects (the model grows but the interpreter and runtime data are reused)
+
+Main missing features
+
+* top-level variables are local
+* runtime error/aborts just abort the interactive loop
+* FFI is strange
+* No model/object inspection
+
+## Examples
+
+`-->` is the prompt, `...` the continuation prompt. What follows is the user input.
+The rest is the output.
+
+### Basic instructions
+
+~~~raw
+$ nitin
+--> print 5+2
+7
+~~~
+
+### Complex and control statements
+
+~~~raw
+-->for i in [0..5[ do
+...print i
+...end
+0
+1
+2
+3
+4
+~~~
+
+You can use `do` blocks to delay the execution and control the scope of variables.
+
+~~~raw
+-->do
+...var sum=0
+...for i in [0..50[ do
+...sum += i
+...end
+...print sum
+...end
+1225
+~~~
+
+### Classes and methods
+
+~~~raw
+-->class A
+...fun foo do
+...print "hello"
+...end
+...end
+-->(new A).foo
+hello
+~~~
+
+### Error management
+
+In case of static errors, the history (up arrow) can be reused and updated.
+
+~~~raw
+-->class
+...end
+       end
+       ^: Syntax Error: unexpected keyword 'end'.
+-->class A   **up arrow and update, thanks readline**
+end
+1,7: Redef Error: `A` is an imported class. Add the `redef` keyword to refine it.
+       class A
+             ^
+-->redef class A **up arrow and update again, thanks readline**
+redef fun foo do print "Bye"
+end
+-->(new A).foo
+bye
+~~~
+
+### Class refinement
+
+Already instantiated objects gain the new methods, attributes and specializations.
+However, the new attributes are left uninitialized (default values or init are not recomputed on existing objects)
+
+Top-level methods automatically refine Sys.
+
+~~~raw
+-->foo
+1,1--3: Error: method or variable `foo` unknown in `Sys`.
+       foo
+       ^
+-->fun foo do
+...print "I'm sys"
+...end
+-->foo
+I'm sys
+~~~
+
+You can store global variables as attributes of Sys
+
+~~~raw
+-->redef class Sys
+...var my_int: Int is writable
+...end
+-->my_int = 5
+-->print my_int
+5
+~~~
+
+### Dynamic importation
+
+~~~
+-->print([0..10[.to_a.to_json)
+1,20--26: Error: method `to_json` does not exists in `Array[Int]`.
+       print([0..10[.to_a.to_json)
+                          ^
+-->import json
+-->print([0..10[.to_a.to_json)
+[0,1,2,3,4,5,6,7,8,9]
+~~~
diff --git a/contrib/nitin/nitin.nit b/contrib/nitin/nitin.nit
new file mode 100644 (file)
index 0000000..641644f
--- /dev/null
@@ -0,0 +1,189 @@
+# 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.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# The Nit interactive interpreter
+module nitin
+
+import nitc::interpreter
+import nitc::frontend
+import nitc::parser_util
+
+redef class ToolContext
+
+       # --no-prompt
+       var opt_no_prompt = new OptionBool("Disable writing a prompt.", "--no-prompt")
+
+       redef init do
+               super
+               option_context.add_option(opt_no_prompt)
+       end
+
+       # Parse a full module given as a string
+       #
+       # Return a AModule or a AError
+       fun p_module(string: String): ANode
+       do
+               var source = new SourceFile.from_string("", string)
+               var lexer = new Lexer(source)
+               var parser = new Parser(lexer)
+               var tree = parser.parse
+
+               var eof = tree.n_eof
+               if eof isa AError then
+                       return eof
+               end
+               return tree.n_base.as(not null)
+       end
+
+       # Read an user-line with a given `prompt`
+       #
+       # Return `null` if end of file
+       fun readline(prompt: String): nullable String do
+               printn prompt
+               var res = stdin.read_line
+               if res == "" and stdin.eof then return null
+               return res
+       end
+
+       # Add `text` in the history for `readline`.
+       #
+       # With the default implementation, the history is dropped
+       fun readline_add_history(text: String) do end
+
+       # Parse the input of the user as a module
+       fun i_parse(prompt: String): nullable ANode
+       do
+               var oldtext = ""
+
+               loop
+                       var s
+                       if opt_no_prompt.value then
+                               s = stdin.read_line
+                               if s == "" and stdin.eof then s = null
+                       else
+                               s = readline(prompt)
+                       end
+                       if s == null then return null
+                       if s == "" then continue
+
+                       if s.chars.first == ':' then
+                               var res = new TString
+                               res.text = s
+                               return res
+                       end
+
+                       var text = oldtext + s + "\n"
+                       oldtext = ""
+                       var n = p_module(text)
+
+                       if n isa AParserError and (n.token isa EOF) then
+                               # Unexpected end of file, thus continuing
+                               if oldtext == "" then prompt = "." * prompt.length
+                               oldtext = text
+                               continue
+                       end
+
+                       readline_add_history(text.chomp)
+                       return n
+               end
+       end
+end
+
+
+# Create a tool context to handle options and paths
+var toolcontext = new ToolContext
+toolcontext.option_context.options_before_rest = true
+toolcontext.accept_no_arguments = true
+toolcontext.keep_going = true
+toolcontext.process_options(args)
+
+# We need a model to collect stufs
+var model = new Model
+# An a model builder to parse files
+var modelbuilder = new ModelBuilder(model, toolcontext)
+
+var arguments = toolcontext.option_context.rest
+
+# Our initial program is an empty module
+var amodule = toolcontext.parse_module("")
+var mmodule = modelbuilder.load_rt_module(null, amodule, "input-0")
+modelbuilder.run_phases
+if not toolcontext.check_errors then return
+assert mmodule != null
+var mmodules = [mmodule]
+var mainmodule = toolcontext.make_main_module(mmodules)
+
+# Start and run the interpreter on the empty module
+var interpreter = new NaiveInterpreter(modelbuilder, mainmodule, arguments)
+interpreter.start(mainmodule)
+
+# Get the main object and the main method
+var mainobj = interpreter.mainobj
+assert mainobj != null
+var sys_type = mainobj.mtype.as(MClassType)
+var mainprop = mainmodule.try_get_primitive_method("main", sys_type.mclass)
+assert mainprop != null
+
+var l = 0
+loop
+       # Next piece of Nit code
+       var n = toolcontext.i_parse("-->")
+       if n == null then
+               break
+       end
+
+       # Special adhoc command
+       if n isa TString then
+               var s = n.text
+               if s == ":q" then
+                       break
+               else
+                       print "`:q` to quit"
+               end
+               continue
+       end
+
+       # An error
+       if n isa AError then
+               print "{n.location.colored_line("0;31")}: {n.message}"
+               continue
+       end
+
+       #n.dump_tree
+
+       # A syntactically module!
+       amodule = n.as(AModule)
+
+       # Try to load it as a submodule
+       l += 1
+       var newmodule = modelbuilder.load_rt_module(mainmodule, amodule, "input-{l}")
+       if newmodule == null then continue
+       modelbuilder.run_phases
+       if not toolcontext.check_errors then
+               toolcontext.error_count = 0
+               continue
+       end
+       # Everything is fine, the module is the new main module!
+       mainmodule = newmodule
+       interpreter.mainmodule = mainmodule
+
+       # Run the main if the AST contains a main
+       if amodule.n_classdefs.not_empty and amodule.n_classdefs.last isa AMainClassdef then
+               interpreter.send(mainprop, [mainobj])
+       end
+end
diff --git a/contrib/nitin/nitin_readline.nit b/contrib/nitin/nitin_readline.nit
new file mode 100644 (file)
index 0000000..d193059
--- /dev/null
@@ -0,0 +1,25 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Add GNU readline capabilities to nitin
+module nitin_readline
+
+import nitin
+import readline
+
+redef class ToolContext
+       redef fun readline(prompt) do return sys.readline(prompt)
+       redef fun readline_add_history(text) do sys.add_history(text)
+end
diff --git a/contrib/nitin/package.ini b/contrib/nitin/package.ini
new file mode 100644 (file)
index 0000000..f6ac556
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=nitin
+tags=devel
+maintainer=Jean Privat <jean@pryen.org>
+license=GPL-3
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/contrib/nitin/
+git=https://github.com/nitlang/nit.git
+git.directory=contrib/nitin/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
index eced30f..991d8b5 100644 (file)
@@ -61,7 +61,7 @@ class NaiveInterpreter
        var modelbuilder: ModelBuilder
 
        # The main module of the program (used to lookup method)
-       var mainmodule: MModule
+       var mainmodule: MModule is writable
 
        # The command line arguments of the interpreted program
        # arguments.first is the program name
index 37812f7..1502c40 100644 (file)
@@ -742,7 +742,7 @@ redef class ModelBuilder
 
        # Injection of a new module without source.
        # Used by the interpreter.
-       fun load_rt_module(parent: nullable MModule, nmodule: AModule, mod_name: String): nullable AModule
+       fun load_rt_module(parent: nullable MModule, nmodule: AModule, mod_name: String): nullable MModule
        do
                # Create the module
 
@@ -759,11 +759,10 @@ redef class ModelBuilder
                        imported_modules.add(parent)
                        mmodule.set_visibility_for(parent, intrude_visibility)
                        mmodule.set_imported_mmodules(imported_modules)
-               else
-                       build_module_importation(nmodule)
                end
+               build_module_importation(nmodule)
 
-               return nmodule
+               return mmodule
        end
 
        # Visit the AST and create the `MModule` object
index 4fd3fa6..0797723 100644 (file)
@@ -143,7 +143,7 @@ end
 # Global context for tools
 class ToolContext
        # Number of errors
-       var error_count: Int = 0
+       var error_count: Int = 0 is writable
 
        # Number of warnings
        var warning_count: Int = 0
index 841fdad..edbc37e 100755 (executable)
@@ -14,6 +14,7 @@ ls -1 -- "$@" \
        ../contrib/friendz/src/solver_cmd.nit \
        ../contrib/neo_doxygen/src/tests/neo_doxygen_*.nit \
        ../contrib/pep8analysis/src/pep8analysis.nit \
+       ../contrib/nitin/nitin.nit \
        ../contrib/nitiwiki/src/nitiwiki.nit \
        *.nit \
        | grep -v ../lib/popcorn/examples/
index d93ce27..b3a9b20 100644 (file)
@@ -7,6 +7,7 @@ nit_args6
 nit_args8
 nitvm_args1
 nitvm_args3
+nitin
 nitc_args1
 nitc_args3
 nitc_args5
diff --git a/tests/nitin.inputs b/tests/nitin.inputs
new file mode 100644 (file)
index 0000000..266b05c
--- /dev/null
@@ -0,0 +1,47 @@
+print 5+2
+
+for i in [0..5[ do
+print i
+end
+
+do
+var sum = 0
+for i in [0..50[ do
+sum += i
+end
+print sum
+end
+
+class A
+fun foo do
+print "hello"
+end
+end
+(new A).foo
+
+class
+end
+class A
+end
+redef class A
+redef fun foo do print "Bye"
+end
+(new A).foo
+
+foo
+fun foo do
+print "I'm sys"
+end
+foo
+
+redef class Sys
+var my_int: Int is writable
+end
+my_int = 5
+print my_int
+
+print([0..10[.to_a.to_json)
+import json
+print([0..10[.to_a.to_json)
+
+%$^&
index b8ae2ec..b312543 100644 (file)
@@ -7,6 +7,7 @@ nit_args6
 nit_args8
 nitvm_args1
 nitvm_args3
+nitin
 nitc_args1
 nitc_args3
 nitc_args5
diff --git a/tests/sav/nitin.res b/tests/sav/nitin.res
new file mode 100644 (file)
index 0000000..0e83a4c
--- /dev/null
@@ -0,0 +1,26 @@
+\e[0;33m1,7\e[0m: Redef Error: `A` is an imported class. Add the `redef` keyword to refine it.
+       class \e[1;31mA\e[0m
+             ^
+\e[0;33m1,1--3\e[0m: Error: method or variable `foo` unknown in `Sys`.
+       \e[1;31mfoo\e[0m
+       ^
+\e[0;33m1,20--26\e[0m: Error: method `to_json` does not exists in `Array[Int]`.
+       print([0..10[.to_a.\e[1;31mto_json\e[0m)
+                          ^
+-->7
+-->-->......0
+1
+2
+3
+4
+-->-->..................1225
+-->-->............-->hello
+-->-->...      \e[0;31mend\e[0m
+       ^: Syntax Error: unexpected keyword 'end'.
+-->...-->......-->Bye
+-->-->-->......-->I'm sys
+-->-->......-->-->5
+-->-->-->-->[0,1,2,3,4,5,6,7,8,9]
+-->--> \e[0;31m%\e[0m$^&
+       ^: Syntax Error: unexpected operator '%'.
+-->
\ No newline at end of file
index 2fbab89..e413069 100755 (executable)
@@ -436,7 +436,7 @@ istodo()
 find_nitc()
 {
        local name="$enginebinname"
-       local recent=`ls -t ../src/$name ../src/$name_[0-9] ../bin/$name ../c_src/$name 2>/dev/null | head -1`
+       local recent=`ls -t ../src/$name ../src/$name_[0-9] ../bin/$name ../contrib/nitin/bin/$name ../c_src/$name 2>/dev/null | head -1`
        if [[ "x$recent" == "x" ]]; then
                echo "Could not find binary for engine $engine, aborting"
                exit 1
@@ -470,6 +470,7 @@ while [ $stop = false ]; do
 done
 enginebinname=$engine
 isinterpret=
+isinteractive=
 case $engine in
        nitc|nitg)
                engine=nitcs;
@@ -515,6 +516,10 @@ case $engine in
                OPT="--vm $OPT"
                savdirs="sav/niti/"
                ;;
+       nitin)
+               enginebinname=nitin
+               isinteractive=true
+               ;;
        nitj)
                engine=nitj;
                OPT="--compile-dir $compdir --ant"
@@ -629,6 +634,15 @@ END
                        > "$ff.compile.log"
                        ERR=0
                        echo 0.0 > "$ff.time.out"
+               elif [ -n "$isinteractive" ]; then
+                       cat > "$ff.bin" <<END
+exec $NITC --no-color --no-prompt $OPT $includes < $(printf '%q' "$i") "\$@"
+END
+                       chmod +x "$ff.bin"
+                       > "$ff.cmp.err"
+                       > "$ff.compile.log"
+                       ERR=0
+                       echo 0.0 > "$ff.time.out"
                else
                        if skip_cc "$bf"; then
                                nocc="--no-cc"