Merge: new option --define
authorJean Privat <jean@pryen.org>
Sat, 11 Oct 2014 12:24:15 +0000 (08:24 -0400)
committerJean Privat <jean@pryen.org>
Sat, 11 Oct 2014 12:24:15 +0000 (08:24 -0400)
It is standard in compiled programs to be able to setup some program configuration at compile-time.

Eg. C compilers accept a command-line option `-D` to define macros that will be used inside programs.
It is useful for configuring some string (like `-D PREFIX=/opt/nit/`, or `-D PORT=8081`) or activate some parts (conditional compilation, eg `-D WITH_SSL`).

This PR brings an equivalent capability to Nit engines through the new `-D` (`--define`) option.

The design behind the -D it to enable specific refinement of top-level functions at link-time.
Thus, basically

~~~sh
$ cat my_foo.nit
import foo
redef fun prefix do return "/opt/nit/"
$ nitg my_foo.nit
~~~

can be simplified into

~~~sh
$ nitg foo.nit -D prefix=/opt/nit/
~~~

Like `-m`, the `-D` creates a fictive module that refines the main module of the program.

`-D` also use the return type of the refined method to know how to interpret the text of the value.
Currently only Int, String and Bool is supported.

~~~nit
module foo
fun str: String do return "test"
fun num: Int do return 1
fun flag: Bool do return false
print str
print num
print flag
~~~

~~~sh
$ nitg foo.nit
$ ./foo
test
1
false
$ nitg foo.nit -D str=hello -D num=42 -D flag
$ ./foo
hello
42
true
~~~

The code of the PR is quite straightforward and show again that the new model is quite robust.

As usual, the first commits are some cleanup. The fun stuff is in the latter commits.

Pull-Request: #815
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

16 files changed:
src/compiler/abstract_compiler.nit
src/interpreter/naive_interpreter.nit
src/mixin.nit [new file with mode: 0644]
src/model/model.nit
src/modelbuilder.nit
src/nit.nit
src/rapid_type_analysis.nit
tests/nit.args
tests/nitg.args
tests/niti.skip
tests/sav/nit_args4.res [new file with mode: 0644]
tests/sav/nitg_args8.res [new file with mode: 0644]
tests/sav/niti/error_needed_method_alt3.res [deleted file]
tests/sav/niti/error_needed_method_alt4.res
tests/sav/test_define.res [new file with mode: 0644]
tests/test_define.nit [new file with mode: 0644]

index a6f6afa..04e0719 100644 (file)
@@ -22,6 +22,7 @@ import semantize
 import platform
 import c_tools
 private import annotation
+import mixin
 
 # Add compiling options
 redef class ToolContext
@@ -310,7 +311,16 @@ class MakefileToolchain
 
        fun makefile_name(mainmodule: MModule): String do return "{mainmodule.name}.mk"
 
-       fun default_outname(mainmodule: MModule): String do return mainmodule.name
+       fun default_outname(mainmodule: MModule): String
+       do
+               # Search a non fictive module
+               var res = mainmodule.name
+               while mainmodule.is_fictive do
+                       mainmodule = mainmodule.in_importation.direct_greaters.first
+                       res = mainmodule.name
+               end
+               return res
+       end
 
        # Combine options and platform informations to get the final path of the outfile
        fun outfile(mainmodule: MModule): String
@@ -891,6 +901,7 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref ) {
                var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
                self.mainmodule.linearize_mclassdefs(cds)
                for cd in cds do
+                       if not self.modelbuilder.mclassdef2nclassdef.has_key(cd) then continue
                        var n = self.modelbuilder.mclassdef2nclassdef[cd]
                        for npropdef in n.n_propdefs do
                                if npropdef isa AAttrPropdef then
@@ -906,6 +917,7 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref ) {
                var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
                self.mainmodule.linearize_mclassdefs(cds)
                for cd in cds do
+                       if not self.modelbuilder.mclassdef2nclassdef.has_key(cd) then continue
                        var n = self.modelbuilder.mclassdef2nclassdef[cd]
                        for npropdef in n.n_propdefs do
                                if npropdef isa AAttrPropdef then
@@ -1044,7 +1056,7 @@ abstract class AbstractCompilerVisitor
        fun get_property(name: String, recv: MType): MMethod
        do
                assert recv isa MClassType
-               return self.compiler.modelbuilder.force_get_primitive_method(self.current_node.as(not null), name, recv.mclass, self.compiler.mainmodule)
+               return self.compiler.modelbuilder.force_get_primitive_method(self.current_node, name, recv.mclass, self.compiler.mainmodule)
        end
 
        fun compile_callsite(callsite: CallSite, arguments: Array[RuntimeVariable]): nullable RuntimeVariable
@@ -1353,6 +1365,18 @@ abstract class AbstractCompilerVisitor
                return res
        end
 
+       # Generate an integer value
+       fun bool_instance(value: Bool): RuntimeVariable
+       do
+               var res = self.new_var(self.get_class("Bool").mclass_type)
+               if value then
+                       self.add("{res} = 1;")
+               else
+                       self.add("{res} = 0;")
+               end
+               return res
+       end
+
        # Generate a string value
        fun string_instance(string: String): RuntimeVariable
        do
@@ -1373,6 +1397,19 @@ abstract class AbstractCompilerVisitor
                return res
        end
 
+       fun value_instance(object: Object): RuntimeVariable
+       do
+               if object isa Int then
+                       return int_instance(object)
+               else if object isa Bool then
+                       return bool_instance(object)
+               else if object isa String then
+                       return string_instance(object)
+               else
+                       abort
+               end
+       end
+
        # Generate an array value
        fun array_instance(array: Array[RuntimeVariable], elttype: MType): RuntimeVariable is abstract
 
@@ -1813,6 +1850,7 @@ redef class MMethodDef
        fun compile_inside_to_c(v: VISITOR, arguments: Array[RuntimeVariable]): nullable RuntimeVariable
        do
                var modelbuilder = v.compiler.modelbuilder
+               var val = constant_value
                if modelbuilder.mpropdef2npropdef.has_key(self) then
                        var npropdef = modelbuilder.mpropdef2npropdef[self]
                        var oldnode = v.current_node
@@ -1827,6 +1865,8 @@ redef class MMethodDef
                        self.compile_parameter_check(v, arguments)
                        nclassdef.compile_to_c(v, self, arguments)
                        v.current_node = oldnode
+               else if val != null then
+                       v.ret(v.value_instance(val))
                else
                        abort
                end
@@ -3025,9 +3065,6 @@ end
 # Create a tool context to handle options and paths
 var toolcontext = new ToolContext
 
-var opt_mixins = new OptionArray("Additionals module to min-in", "-m")
-toolcontext.option_context.add_option(opt_mixins)
-
 toolcontext.tooldescription = "Usage: nitg [OPTION]... file.nit...\nCompiles Nit programs."
 
 # We do not add other options, so process them now!
@@ -3046,7 +3083,6 @@ end
 
 # Here we load an process all modules passed on the command line
 var mmodules = modelbuilder.parse(arguments)
-var mixins = modelbuilder.parse(opt_mixins.value)
 
 if mmodules.is_empty then return
 modelbuilder.run_phases
@@ -3054,8 +3090,5 @@ modelbuilder.run_phases
 for mmodule in mmodules do
        toolcontext.info("*** PROCESS {mmodule} ***", 1)
        var ms = [mmodule]
-       if not mixins.is_empty then
-               ms.add_all mixins
-       end
        toolcontext.run_global_phases(ms)
 end
index 92b658d..55c6567 100644 (file)
@@ -20,6 +20,7 @@ module naive_interpreter
 import literal
 import semantize
 private import parser::tables
+import mixin
 
 redef class ToolContext
        # --discover-call-trace
@@ -247,6 +248,19 @@ class NaiveInterpreter
                return res
        end
 
+       fun value_instance(object: Object): Instance
+       do
+               if object isa Int then
+                       return int_instance(object)
+               else if object isa Bool then
+                       return bool_instance(object)
+               else if object isa String then
+                       return string_instance(object)
+               else
+                       abort
+               end
+       end
+
        # Return a new native string initialized with `txt`
        fun native_string_instance(txt: String): Instance
        do
@@ -256,6 +270,15 @@ class NaiveInterpreter
                return new PrimitiveInstance[Buffer](ic.mclass_type, val)
        end
 
+       # Return a new String instance for `txt`
+       fun string_instance(txt: String): Instance
+       do
+               var nat = native_string_instance(txt)
+               var res = self.send(self.force_get_primitive_method("to_s_with_length", nat.mtype), [nat, self.int_instance(txt.length)])
+               assert res != null
+               return res
+       end
+
        # The current frame used to store local variables of the current method executed
        fun frame: Frame do return frames.first
 
@@ -366,6 +389,7 @@ class NaiveInterpreter
 
                # Look for the AST node that implements the property
                var mproperty = mpropdef.mproperty
+               var val = mpropdef.constant_value
                if self.modelbuilder.mpropdef2npropdef.has_key(mpropdef) then
                        var npropdef = self.modelbuilder.mpropdef2npropdef[mpropdef]
                        self.parameter_check(npropdef, mpropdef, args)
@@ -374,6 +398,8 @@ class NaiveInterpreter
                        var nclassdef = self.modelbuilder.mclassdef2nclassdef[mpropdef.mclassdef]
                        self.parameter_check(nclassdef, mpropdef, args)
                        return nclassdef.call(self, mpropdef, args)
+               else if val != null then
+                       return value_instance(val)
                else
                        fatal("Fatal Error: method {mpropdef} not found in the AST")
                        abort
@@ -495,6 +521,7 @@ class NaiveInterpreter
                var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
                self.mainmodule.linearize_mclassdefs(cds)
                for cd in cds do
+                       if not self.modelbuilder.mclassdef2nclassdef.has_key(cd) then continue
                        var n = self.modelbuilder.mclassdef2nclassdef[cd]
                        for npropdef in n.n_propdefs do
                                if npropdef isa AAttrPropdef then
@@ -1464,9 +1491,7 @@ redef class AStringFormExpr
        redef fun expr(v)
        do
                var txt = self.value.as(not null)
-               var nat = v.native_string_instance(txt)
-               var res = v.send(v.force_get_primitive_method("to_s", nat.mtype), [nat]).as(not null)
-               return res
+               return v.string_instance(txt)
        end
 end
 
diff --git a/src/mixin.nit b/src/mixin.nit
new file mode 100644 (file)
index 0000000..94d0c32
--- /dev/null
@@ -0,0 +1,110 @@
+# 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.
+
+# Loading and additional module refinements at link-time.
+#
+# Used to factorize some code used by the engines.
+module mixin
+
+import modelbuilder
+
+redef class ToolContext
+       # --mixin
+       var opt_mixins = new OptionArray("Additionals module to min-in", "-m", "--mixin")
+       # --define
+       var opt_defines = new OptionArray("Define a specific property", "-D", "--define")
+
+       redef init
+       do
+               super
+               option_context.add_option(opt_mixins, opt_defines)
+       end
+
+       redef fun make_main_module(mmodules)
+       do
+               var mixins = opt_mixins.value
+               if not mixins.is_empty then
+                       mmodules.add_all modelbuilder.parse(opt_mixins.value)
+                       modelbuilder.run_phases
+               end
+
+               var mainmodule = super
+
+               var defines = opt_defines.value
+               if not defines.is_empty then
+                       var location = mainmodule.location
+                       var model = mainmodule.model
+
+                       if mainmodule == mmodules.first then
+                               mainmodule = new MModule(model, null, mainmodule.name + "-d", location)
+                               mainmodule.set_imported_mmodules(mmodules)
+                               mainmodule.is_fictive = true
+                       end
+
+                       var recv = mainmodule.object_type
+                       var mclassdef = new MClassDef(mainmodule, recv, location)
+                       mclassdef.add_in_hierarchy
+
+                       for define in defines do
+                               var spl = define.split_once_on('=')
+                               var name = spl.first
+                               var val = null
+                               if spl.length > 1 then val = spl[1]
+                               var prop = mainmodule.try_get_primitive_method(name, recv.mclass)
+                               if prop == null then
+                                       error(null, "Error: --define: no top-level function `{name}`")
+                                       continue
+                               end
+                               var ret = prop.intro.msignature.return_mtype
+                               var v
+                               if ret == null then
+                                       error(null, "Error: --define: Method `{prop}` is not a function")
+                                       continue
+                               else if ret.to_s == "Bool" then
+                                       if val == null or val == "true" then
+                                               v = true
+                                       else if val == "false" then
+                                               v = false
+                                       else
+                                               error(null, "Error: --define: Method `{prop}` need a Bool.")
+                                               continue
+                                       end
+                               else if ret.to_s == "Int" then
+                                       if val != null and val.is_numeric then
+                                               v = val.to_i
+                                       else
+                                               error(null, "Error: --define: Method `{prop}` need a Int.")
+                                               continue
+                                       end
+                               else if ret.to_s == "String" then
+                                       if val != null then
+                                               v = val
+                                       else
+                                               error(null, "Error: --define: Method `{prop}` need a String.")
+                                               continue
+                                       end
+                               else
+                                       error(null, "Error: --define: Method `{prop}` return an unmanaged type {ret}.")
+                                       continue
+                               end
+                               var pd = new MMethodDef(mclassdef, prop, location)
+                               pd.msignature = prop.intro.msignature
+                               pd.constant_value = v
+                       end
+                       check_errors
+               end
+
+               return mainmodule
+       end
+end
index eab60f7..f26eb2e 100644 (file)
@@ -1977,6 +1977,16 @@ class MMethodDef
 
        # Is the method definition extern?
        var is_extern = false is writable
+
+       # An optional constant value returned in functions.
+       #
+       # Only some specific primitife value are accepted by engines.
+       # Is used when there is no better implementation available.
+       #
+       # Currently used only for the implementation of the `--define`
+       # command-line option.
+       # SEE: module `mixin`.
+       var constant_value: nullable Object = null is writable
 end
 
 # A local definition of an attribute
index d702369..30a0617 100644 (file)
@@ -53,8 +53,8 @@ redef class ToolContext
 
        private var modelbuilder_real: nullable ModelBuilder = null
 
-       # Run `process_mainmodule` on all phases
-       fun run_global_phases(mmodules: Array[MModule])
+       # Combine module to make a single one if required.
+       fun make_main_module(mmodules: Array[MModule]): MModule
        do
                assert not mmodules.is_empty
                var mainmodule
@@ -62,10 +62,17 @@ redef class ToolContext
                        mainmodule = mmodules.first
                else
                        # We need a main module, so we build it by importing all modules
-                       mainmodule = new MModule(modelbuilder.model, null, mmodules.first.name, new Location(mmodules.first.location.file, 0, 0, 0, 0))
+                       mainmodule = new MModule(modelbuilder.model, null, mmodules.first.name + "-m", new Location(mmodules.first.location.file, 0, 0, 0, 0))
                        mainmodule.is_fictive = true
                        mainmodule.set_imported_mmodules(mmodules)
                end
+               return mainmodule
+       end
+
+       # Run `process_mainmodule` on all phases
+       fun run_global_phases(mmodules: Array[MModule])
+       do
+               var mainmodule = make_main_module(mmodules)
                for phase in phases_list do
                        if phase.disabled then continue
                        phase.process_mainmodule(mainmodule, mmodules)
@@ -738,11 +745,13 @@ class ModelBuilder
        end
 
        # Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
-       fun force_get_primitive_method(n: ANode, name: String, recv: MClass, mmodule: MModule): MMethod
+       fun force_get_primitive_method(n: nullable ANode, name: String, recv: MClass, mmodule: MModule): MMethod
        do
                var res = mmodule.try_get_primitive_method(name, recv)
                if res == null then
-                       self.toolcontext.fatal_error(n.hot_location, "Fatal Error: {recv} must have a property named {name}.")
+                       var l = null
+                       if n != null then l = n.hot_location
+                       self.toolcontext.fatal_error(l, "Fatal Error: {recv} must have a property named {name}.")
                        abort
                end
                return res
index 24bb00e..9a95194 100644 (file)
@@ -27,10 +27,9 @@ toolcontext.tooldescription = "Usage: nit [OPTION]... <file.nit>...\nInterprets
 # Add an option "-o" to enable compatibilit with the tests.sh script
 var opt = new OptionString("compatibility (does noting)", "-o")
 toolcontext.option_context.add_option(opt)
-var opt_mixins = new OptionArray("Additionals module to min-in", "-m")
 var opt_eval = new OptionBool("Specifies the program from command-line", "-e")
 var opt_loop = new OptionBool("Repeatedly run the program for each line in file-name arguments", "-n")
-toolcontext.option_context.add_option(opt_mixins, opt_eval, opt_loop)
+toolcontext.option_context.add_option(opt_eval, opt_loop)
 # We do not add other options, so process them now!
 toolcontext.process_options(args)
 
@@ -66,20 +65,11 @@ else
        mmodules = modelbuilder.parse([progname])
 end
 
-mmodules.add_all modelbuilder.parse(opt_mixins.value)
 modelbuilder.run_phases
 
 if toolcontext.opt_only_metamodel.value then exit(0)
 
-var mainmodule: nullable MModule
-
-# Here we launch the interpreter on the main module
-if mmodules.length == 1 then
-       mainmodule = mmodules.first
-else
-       mainmodule = new MModule(model, null, mmodules.first.name, mmodules.first.location)
-       mainmodule.set_imported_mmodules(mmodules)
-end
+var mainmodule = toolcontext.make_main_module(mmodules)
 
 var self_mm = mainmodule
 var self_args = arguments
index 6ce94cb..897d7b7 100644 (file)
@@ -244,6 +244,9 @@ class RapidTypeAnalysis
                                        if mmethoddef.mproperty.is_root_init and not mmethoddef.is_intro then
                                                self.add_super_send(v.receiver, mmethoddef)
                                        end
+                               else if mmethoddef.constant_value != null then
+                                       # Make the return type live
+                                       v.add_type(mmethoddef.msignature.return_mtype.as(MClassType))
                                else
                                        abort
                                end
index 3011fc1..0c14c1c 100644 (file)
@@ -1,3 +1,4 @@
 --log --log-dir out/test_nitc_logs ../examples/hello_world.nit
 base_simple3.nit
 -m test_mixin.nit ../examples/hello_world.nit
+test_define.nit -D text=hello -D num=42 -D flag
index 3b38c54..6a47146 100644 (file)
@@ -5,3 +5,4 @@
 --global ../examples/hello_world.nit -m test_mixin.nit -o out/nitg-hello_world_mixed ; out/nitg-hello_world_mixed
 --separate ../examples/hello_world.nit -m test_mixin.nit -o out/nitgs-hello_world_mixed ; out/nitgs-hello_world_mixed
 base_simple_import.nit base_simple.nit --dir out/ ; out/base_simple ; out/base_simple_import
+test_define.nit -D text=hello -D num=42 -D flag --dir out/ ; out/test_define
index c052cff..baf5b76 100644 (file)
@@ -5,6 +5,7 @@ shoot_logic
 bench_
 nit_args1
 nit_args3
+nit_args4
 nitvm_args1
 nitvm_args3
 nitc_args1
@@ -12,6 +13,7 @@ nitg_args1
 nitg_args3
 nitg_args5
 nitg_args6
+nitg_args8
 test_markdown_args1
 pep8analysis
 nitcc_parser_gen
diff --git a/tests/sav/nit_args4.res b/tests/sav/nit_args4.res
new file mode 100644 (file)
index 0000000..483d841
--- /dev/null
@@ -0,0 +1,3 @@
+hello
+42
+true
diff --git a/tests/sav/nitg_args8.res b/tests/sav/nitg_args8.res
new file mode 100644 (file)
index 0000000..483d841
--- /dev/null
@@ -0,0 +1,3 @@
+hello
+42
+true
diff --git a/tests/sav/niti/error_needed_method_alt3.res b/tests/sav/niti/error_needed_method_alt3.res
deleted file mode 100644 (file)
index 34dd8c9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-alt/error_needed_method_alt3.nit:48,9--13: Fatal Error: NativeString must have a property named to_s.
index 4daa384..d1e2d76 100644 (file)
@@ -1 +1 @@
-alt/error_needed_method_alt4.nit:49,10--14: Fatal Error: NativeString must have a property named to_s.
+alt/error_needed_method_alt4.nit:49,10--14: Fatal Error: NativeString must have a property named to_s_with_length.
diff --git a/tests/sav/test_define.res b/tests/sav/test_define.res
new file mode 100644 (file)
index 0000000..8d40171
--- /dev/null
@@ -0,0 +1,3 @@
+some text
+1
+false
diff --git a/tests/test_define.nit b/tests/test_define.nit
new file mode 100644 (file)
index 0000000..4b8ebc7
--- /dev/null
@@ -0,0 +1,20 @@
+# 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.
+
+fun text: String do return "some text"
+fun num: Int do return 1
+fun flag: Bool do return false
+print text
+print num
+print flag