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>
import platform
import c_tools
private import annotation
+import mixin
# Add compiling options
redef class ToolContext
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
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
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
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
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
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
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
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
# 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!
# 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
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
import literal
import semantize
private import parser::tables
+import mixin
redef class ToolContext
# --discover-call-trace
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
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
# 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)
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
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
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
--- /dev/null
+# 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
# 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
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
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)
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
# 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)
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
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
--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
--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
bench_
nit_args1
nit_args3
+nit_args4
nitvm_args1
nitvm_args3
nitc_args1
nitg_args3
nitg_args5
nitg_args6
+nitg_args8
test_markdown_args1
pep8analysis
nitcc_parser_gen
--- /dev/null
+hello
+42
+true
--- /dev/null
+hello
+42
+true
+++ /dev/null
-alt/error_needed_method_alt3.nit:48,9--13: 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.
+alt/error_needed_method_alt4.nit:49,10--14: Fatal Error: NativeString must have a property named to_s_with_length.
--- /dev/null
+some text
+1
+false
--- /dev/null
+# 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