contrib/nitin: new interactive interpreter proof of concept
authorJean Privat <jean@pryen.org>
Fri, 21 Apr 2017 19:01:29 +0000 (15:01 -0400)
committerJean Privat <jean@pryen.org>
Mon, 24 Apr 2017 19:13:16 +0000 (15:13 -0400)
Signed-off-by: Jean Privat <jean@pryen.org>

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]

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..0d4484d
--- /dev/null
@@ -0,0 +1,174 @@
+# 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
+       # 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 = readline(prompt)
+                       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