* ccache http://ccache.samba.org/ to improve recompilation
* libgc-dev http://www.hpl.hp.com/personal/Hans_Boehm/gc/
* graphviz http://www.graphviz.org/ to enable graphes with the nitdoc tool
+ * libunwind http://nongnu.org/libunwind
+ * gperf http://gnu.org/software/gperf to enable mapping from C to Nit function names in a stack trace
Those are available in most linux distributions
- # sudo apt-get install build-essential ccache libgc-dev graphviz
+ # sudo apt-get install build-essential ccache libgc-dev graphviz libunwind gperf
Important files and directory:
enum gc_option { gc_opt_large, gc_opt_malloc, gc_opt_boehm } gc_option;
#ifdef WITH_LIBGC
-#define GC_DEBUG
#include <gc/gc.h>
#endif
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012 Jean Privat <jean@pryen.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.
+
+# management and display of ordered trees
+module ordered_tree
+
+# Generic structure to manage and display an ordered tree
+#
+# Ordered tree are tree where the elements of a same parent are in a specific order
+#
+# The class can be used as it to work with generic tree.
+# The class can also be specialized to provide more specific behavior.
+class OrderedTree[E: Object]
+ # Sequence
+ var roots = new Array[E]
+ var sub = new HashMap[E, Array[E]]
+
+ # Add a new element `e` in the tree
+ # `p` is the parent of `e`.
+ # if `p` is null, then `e` is a root element.
+ #
+ # By defauld, the elements with a same parent
+ # are displayed in the order they are added.
+ #
+ # The `sort_with` method can be used reorder elements
+ fun add(p: nullable E, e: E)
+ do
+ if p == null then
+ roots.add(e)
+ else if sub.has_key(p) then
+ sub[p].add(e)
+ else
+ sub[p] = [e]
+ end
+ end
+
+ # print the full tree on `o`
+ # Write a ASCII-style tree and use the `display` method to label elements
+ fun pretty(o: OStream)
+ do
+ var last = roots.last
+ for r in roots do
+ o.write display(r)
+ o.write "\n"
+ sub_pretty(o, r, "")
+ end
+ end
+
+ private fun sub_pretty(o: OStream, e: E, prefix: String)
+ do
+ if not sub.has_key(e) then return
+ var subs = sub[e]
+ var last = subs.last
+ for e2 in subs do
+ if e2 != last then
+ o.write "{prefix}|--{display(e2)}\n"
+ sub_pretty(o, e2, prefix+"| ")
+ else
+ o.write "{prefix}`--{display(e2)}\n"
+ sub_pretty(o, e2, prefix+" ")
+ end
+ end
+ end
+
+ # Sort roots and other elements using a comparator method
+ # This method basically sorts roots then each group of children
+ fun sort_with(comparator: AbstractSorter[E])
+ do
+ comparator.sort(roots)
+ for a in sub.values do
+ comparator.sort(a)
+ end
+ end
+
+ # How to display a specific element of the tree
+ # By defaut, uses `to_s`
+ #
+ # Subclasses should redefine this method to provide a specific output
+ fun display(e: E): String do return e.to_s
+end
import opts
+# Class to manage user groups
class UserGroup
+
+ # User name
var user: String
+
+ # Group name
var group: nullable String
+ # Drop privileges of a user and set his privileges back to default (program privileges)
fun drop_privileges
do
var passwd = new Passwd.from_name(user)
return nb
end
- # Return one the item of the collection
+ # Return the first item of the collection
#
# assert [1,2,3].first == 1
fun first: E
end
# A collection that contains only one item.
+# Used to pass arguments by reference
class Container[E]
super Collection[E]
# Abstract sets.
#
-# Set contains contains only one element with the same value (according to ==).
+# Set is a collection without ducplicates (according to ==)
# var s: Set[String] = new ArraySet[String]
# var a = "Hello"
# var b = "Hel" + "lo"
return -1
end
- # The index of the last occurrence of an element starting from pos.
+ # The index of the first occurrence of an element starting from pos, by decremanting the index
# Return -1 if not found.
fun last_index_of_from(item: E, pos: Int): Int
do
end
end
-class IteratorRange[E: Discrete]
+private class IteratorRange[E: Discrete]
# Iterator on ranges.
super Iterator[E]
var _range: Range[E]
end
end
+ # Return the canonicalized absolute pathname (see POSIX function `realpath`)
+ fun realpath: String do
+ var cs = to_cstring.file_realpath
+ var res = cs.to_s_with_copy
+ # cs.free_malloc # FIXME memory leak
+ return res
+ end
+
# Simplify a file path by remove useless ".", removing "//", and resolving ".."
# ".." are not resolved if they start the path
# starting "/" is not removed
# * the validity of the path is not checked
#
# assert "some/./complex/../../path/from/../to/a////file//".simplify_path == "path/to/a/file"
- # assert "../dir/file".simplify_path == "../dir/file"
- # assert "dir/../../".simplify_path == ".."
- # assert "//absolute//path/".simplify_path == "/absolute/path"
+ # assert "../dir/file".simplify_path == "../dir/file"
+ # assert "dir/../../".simplify_path == ".."
+ # assert "dir/..".simplify_path == "."
+ # assert "//absolute//path/".simplify_path == "/absolute/path"
fun simplify_path: String
do
var a = self.split_with("/")
end
a2.push(x)
end
+ if a2.is_empty then return "."
return a2.join("/")
end
private fun file_mkdir: Bool is extern "string_NativeString_NativeString_file_mkdir_0"
private fun file_delete: Bool is extern "string_NativeString_NativeString_file_delete_0"
private fun file_chdir is extern "string_NativeString_NativeString_file_chdir_0"
+ private fun file_realpath: NativeString is extern "file_NativeString_realpath"
end
extern FileStat `{ struct stat * `}
#define string_NativeString_NativeString_file_mkdir_0(p) (mkdir(p, 0777))
#define string_NativeString_NativeString_file_getcwd_0(p) (getcwd(NULL, 0))
#define string_NativeString_NativeString_file_chdir_0(p) (chdir(p)?-1:0) /* hack to avoid warn_unused_result */
+#define file_NativeString_realpath(p) (realpath(p, NULL))
#define file_stdin_poll_in(self) file_stdin_poll_in_()
int file_stdin_poll_in_(void);
# gtksourceview (gedit and other GTK editors)
To install in your home, just link (or copy) the language definition file in ~/.local/share/gtksourceview-2.0/language-specs
+# syntaxhighlighter
+
+Nit brush for the Alex Gorbatchev's JS syntaxhighlighter.
+
+To install the JS syntaxhighlighter, please refer to http://alexgorbatchev.com/SyntaxHighlighter/
+
+Then can add the brush to your html page:
+
+ <script type="text/javascript" src="shBrushNit.js"></script>
+
# vim
vim is a powerful text editor.
--- /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.
+ *
+ * Nit Brush for SyntaxHighlighter
+ * see http://alexgorbatchev.com/SyntaxHighlighter
+ */
+
+;(function()
+{
+ // CommonJS
+ typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
+
+ function Brush()
+ {
+ var keywords = 'abort abstract and as assert break class continue do else end 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';
+ var builtins = 'exit sys args get_time getc getcwd gets print printn';
+
+ this.regexList = [
+ { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments
+ { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings
+ { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings
+ { regex: /\b[A-Z0-9_]+\b/g, css: 'constants' }, // types
+ { regex: /[A-Z][A-Za-z0-9_]*/g, css: 'color2' }, // classes
+ { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords
+ { regex: new RegExp(this.getKeywords(builtins), 'gm'), css: 'color3' } // builtins
+ ];
+ this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);
+ };
+
+ Brush.prototype = new SyntaxHighlighter.Highlighter();
+ Brush.aliases = ['nit'];
+
+ SyntaxHighlighter.brushes.Nit = Brush;
+
+ // CommonJS
+ typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
+})();
+
\r
// parse origin\r
var parts = upstream.split(":");\r
+ if(parts.length < 3) {\r
+ console.error("Incorrect upstream name `" + upstream + "`, should be of the form user:repo:branch");\r
+ }\r
origin = {\r
user: parts[0],\r
repo: parts[1],\r
base_tree: baseTree.sha,\r
tree: [{\r
path: path,\r
- mode: 100644, // file (blob)\r
+ mode: "100644", // file (blob)\r
type: "blob",\r
sha: blob.sha\r
}]\r
.addClass("nitdoc-github-loginbox-arrow")\r
.append(" ")\r
)\r
- .append(\r
- $(document.createElement("h3"))\r
- .append("Github Sign In")\r
- )\r
.append(loginBoxContent);\r
\r
loginBoxLi.append(loginBox);\r
var displayLogout = function(origin, user) {\r
var panel = $(document.createElement("div"))\r
.append(\r
+ $(document.createElement("h3"))\r
+ .append("Signed in Github")\r
+ )\r
+ .append(\r
$(document.createElement("h4"))\r
.append("Hello ")\r
.append(\r
.append(\r
$(document.createElement("label"))\r
.attr("for", "github-origin")\r
- .append("Origin")\r
+ .append("Upstram branch")\r
)\r
.append(\r
- $(document.createElement("input"))\r
+ $(document.createElement("a"))\r
+ .addClass("nitdoc-github-loginbox-githublink")\r
.attr({\r
- id: "github-origin",\r
- type: "text",\r
- disabled: "disabled",\r
- value: origin.user + ":" + origin.repo + ":" + origin.branch\r
+ title: "Open branch in GitHub",\r
+ href: "https://github.com/" + origin.user + "/" + origin.repo + "/tree/" + origin.branch,\r
+ target: "_blank"\r
})\r
+ .append(origin.user + ":" + origin.repo + ":" + origin.branch)\r
)\r
.append(\r
$(document.createElement("label"))\r
.attr("for", "github-base")\r
- .append("Base")\r
+ .append("Your branch")\r
)\r
.append(\r
- $(document.createElement("input"))\r
+ $(document.createElement("a"))\r
+ .addClass("nitdoc-github-loginbox-githublink")\r
.attr({\r
- id: "github-base",\r
- type: "text",\r
- disabled: "disabled",\r
- value: user.login + ":" + user.repo + ":" + user.branch\r
+ title: "Open branch in GitHub",\r
+ href: "https://github.com/" + user.login + "/" + user.repo + "/tree/" + user.branch,\r
+ target: "_blank"\r
})\r
+ .append(origin.user + ":" + origin.repo + ":" + origin.branch)\r
)\r
.append(\r
$(document.createElement("button"))\r
var displayLogin = function() {\r
var panel = $(document.createElement("form"))\r
.append(\r
+ $(document.createElement("h3"))\r
+ .append("Sign in Github")\r
+ )\r
+ .append(\r
$(document.createElement("label"))\r
.attr("for", "nitdoc-github-login-field")\r
.append("Username")\r
$(document.createElement("button"))\r
.addClass("nitdoc-github-button")\r
.addClass("nitdoc-github-commit")\r
- .append("Commit")\r
+ .append("Commit...")\r
.click(function() {\r
instance.infos.newComment = tarea.val();\r
instance.infos.commentBox = instance;\r
margin-bottom: 20px;\r
}\r
\r
+#nitdoc-github-loginbox a.nitdoc-github-loginbox-githublink {\r
+ display: block;\r
+ margin: 10px;\r
+ color: #0D8921;\r
+}\r
+\r
/* \r
* Nitdoc Github buttons\r
*/\r
var opt_no_check_other: OptionBool = new OptionBool("Disable implicit tests: unset attribute, null receiver (dangerous)", "--no-check-other")
# --typing-test-metrics
var opt_typing_test_metrics: OptionBool = new OptionBool("Enable static and dynamic count of all type tests", "--typing-test-metrics")
+ # --stack-trace-C-to-Nit-name-binding
+ var opt_stacktrace: OptionBool = new OptionBool("Enables the use of gperf to bind C to Nit function names when encountering a Stack trace at runtime", "--nit-stacktrace")
redef init
do
self.option_context.add_option(self.opt_output, self.opt_no_cc, self.opt_make_flags, self.opt_compile_dir, self.opt_hardening, self.opt_no_shortcut_range)
self.option_context.add_option(self.opt_no_check_covariance, self.opt_no_check_initialization, self.opt_no_check_assert, self.opt_no_check_autocast, self.opt_no_check_other)
self.option_context.add_option(self.opt_typing_test_metrics)
+ self.option_context.add_option(self.opt_stacktrace)
end
end
if compile_dir == null then compile_dir = ".nit_compile"
compile_dir.mkdir
+
+ if self.toolcontext.opt_stacktrace.value then compiler.build_c_to_nit_bindings
+
var orig_dir=".." # FIXME only works if `compile_dir` is a subdirectory of cwd
var outname = self.toolcontext.opt_output.value
abstract class AbstractCompiler
type VISITOR: AbstractCompilerVisitor
+ # Table corresponding c_names to nit names (methods)
+ var names = new HashMap[String, String]
+
# The main module of the program currently compiled
# Is assigned during the separate compilation
var mainmodule: MModule writable
private var provided_declarations = new HashMap[String, String]
+ # Builds the .c and .h files to be used when generating a Stack Trace
+ # Binds the generated C function names to Nit function names
+ fun build_c_to_nit_bindings
+ do
+ var compile_dir = modelbuilder.toolcontext.opt_compile_dir.value
+ if compile_dir == null then compile_dir = ".nit_compile"
+
+ var stream = new OFStream.open("{compile_dir}/C_fun_names")
+ stream.write("%\{\n#include \"c_functions_hash.h\"\n%\}\n")
+ stream.write("%define lookup-function-name get_nit_name\n")
+ stream.write("struct C_Nit_Names;\n")
+ stream.write("%%\n")
+ stream.write("####\n")
+ for i in names.keys do
+ stream.write(i)
+ stream.write(",\t\"")
+ stream.write(names[i])
+ stream.write("\"\n")
+ end
+ stream.write("####\n")
+ stream.write("%%\n")
+ stream.close
+
+ stream = new OFStream.open("{compile_dir}/c_functions_hash.h")
+ stream.write("typedef struct C_Nit_Names\{char* name; char* nit_name;\}C_Nit_Names;\n")
+ stream.write("const struct C_Nit_Names* get_nit_name(register const char *str, register unsigned int len);\n")
+ stream.close
+
+ var x = new Process("gperf","{compile_dir}/C_fun_names","-t","-7","--output-file={compile_dir}/c_functions_hash.c","-C")
+ x.wait
+
+ extern_bodies.add(new ExternCFile("{compile_dir}/c_functions_hash.c", ""))
+ end
+
# Compile C headers
# This method call compile_header_strucs method that has to be refined
fun compile_header do
self.header.add_decl("#include <stdlib.h>")
self.header.add_decl("#include <stdio.h>")
self.header.add_decl("#include <string.h>")
+ if modelbuilder.toolcontext.opt_stacktrace.value then
+ self.header.add_decl("#include \"c_functions_hash.h\"")
+ end
self.header.add_decl("#include <libunwind.h>")
self.header.add_decl("#include <signal.h>")
self.header.add_decl("#include <gc_chooser.h>")
v.add_decl("unw_getcontext(&uc);")
v.add_decl("unw_init_local(&cursor, &uc);")
v.add_decl("printf(\"-------------------------------------------------\\n\");")
- v.add_decl("printf(\"-- C Stack Trace ------------------------------\\n\");")
+ v.add_decl("printf(\"-- Stack Trace ------------------------------\\n\");")
v.add_decl("printf(\"-------------------------------------------------\\n\");")
v.add_decl("while (unw_step(&cursor) > 0) \{")
v.add_decl(" unw_get_proc_name(&cursor, procname, 100, &ip);")
+ if modelbuilder.toolcontext.opt_stacktrace.value then
+ v.add_decl(" const C_Nit_Names* recv = get_nit_name(procname, strlen(procname));")
+ v.add_decl(" if (recv != 0)\{")
+ v.add_decl(" printf(\"` %s\\n\", recv->nit_name);")
+ v.add_decl(" \}else\{")
+ v.add_decl(" printf(\"` %s\\n\", procname);")
+ v.add_decl(" \}")
+ else
v.add_decl(" printf(\"` %s \\n\",procname);")
+ end
v.add_decl("\}")
v.add_decl("printf(\"-------------------------------------------------\\n\");")
v.add_decl("free(procname);")
phases.add_edge(scope_phase, simple_misc_analysis_phase)
return true
end
-
- fun run_global_phases(mainmodule: MModule)
- do
- for phase in phases_list do
- phase.process_mainmodule(mainmodule)
- end
- end
end
-
-redef class Phase
- # Specific action to execute on the whole program
- # Called by the `ToolContext::run_global_phases`
- # @toimplement
- fun process_mainmodule(mainmodule: MModule) do end
-end
-
v.add("return {frame.returnvar.as(not null)};")
end
v.add("\}")
+ if not self.c_name.has_substring("VIRTUAL", 0) then compiler.names[self.c_name] = "{mmethoddef.mclassdef.mmodule.name}::{mmethoddef.mclassdef.mclass.name}::{mmethoddef.mproperty.name} ({mmethoddef.location.file.filename}:{mmethoddef.location.line_start})"
end
redef fun call(v: VISITOR, arguments: Array[RuntimeVariable]): nullable RuntimeVariable
+++ /dev/null
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2012 Alexandre Terrasa <alexandre@moz-concept.com>
-#
-# 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.
-
-# Introduce incremental typing in naive interpreter
-# Typing modes are :
-# * Legacy: default mode, using request to precomputed metamodel
-# * BM: binary matrix
-# * CL: coloration
-# * PHAND: perfect hashing using binary AND
-# * PHMOD: perfect hashing using modulo
-module interpretor_type_test
-
-intrude import naive_interpreter
-
-# Add options for incremental typing
-redef class ToolContext
- # --ic-typing-mode BM | CL | PHMOD | PHAND
- var opt_ic_typing_mode = new OptionString("Incremental typing method. Possible values are:\n\t\t\t\t - BM: binary matrix\n\t\t\t\t - CL: coloration\n\t\t\t\t - PHAND: perfect hashing whith AND\n\t\t\t\t - PHMOD: perfect hashing whith MOD", "--ic-typing-mode")
- # --ic-load-mode
- var opt_ic_load_mode = new OptionString("Dynamic loading mode. Possible values are:\n\t\t\t\t - type (default): load only the new type\n\t\t\t\t - module: load the module of new type", "--ic-load-mode")
- # --ic-bootstrap
- var opt_ic_bootstrap = new OptionBool("Bootstrap typing with standard library (default is no bootstrap)", "--ic-bootstrap")
- # --ic-recompute-mode always | never | threshold | increment
- var opt_ic_recompute_mode = new OptionString("When to recompute typing. Possible values are:\n\t\t\t\t - always: recompute for each new type\n\t\t\t\t - never (default): never recompute, use fallback instead\n\t\t\t\t - threshold: recompute only when the threshold is reached, use fallback before\n\t\t\t\t - increment: try to update structures instead of recompute", "--ic-recompute-mode")
- # --ic-threshold-mode increment | fallback
- var opt_ic_threshold_mode = new OptionString("Threshold mode. Possible values are:\n\t\t\t\t - increment: recompute when the number of new type reach the threshold count\n\t\t\t\t -fallback: recompute when the use number of fallback use reach the threshold count", "--ic-threshold-mode")
- # --ic-threshold-count <int>
- var opt_ic_threshold_count = new OptionInt("Threshold count. Take an integer", 0, "--ic-threshold-count")
- # --ic-cache-size <int>
- var opt_ic_cache_size = new OptionInt("Cache size. Take an integer", 0, "--ic-cache-size")
- # --ic-show-stats
- var opt_ic_show_stats = new OptionBool("Show statistics about typing at the end of the interpretation", "--ic-show-stats")
-
- redef init
- do
- super
- self.option_context.add_option(self.opt_ic_typing_mode)
- self.option_context.add_option(self.opt_ic_load_mode)
- self.option_context.add_option(self.opt_ic_bootstrap)
- self.option_context.add_option(self.opt_ic_recompute_mode)
- self.option_context.add_option(self.opt_ic_threshold_mode)
- self.option_context.add_option(self.opt_ic_threshold_count)
- self.option_context.add_option(self.opt_ic_cache_size)
- self.option_context.add_option(self.opt_ic_show_stats)
- end
-end
-
-redef class ModelBuilder
- # Add statistics display at the end of the interpretation
- redef fun run_naive_interpreter(mainmodule, arguments) do
- var time0 = get_time
- self.toolcontext.info("*** START INTERPRETING ***", 1)
-
- var interpreter = new NaiveInterpreter(self, mainmodule, arguments)
- init_naive_interpreter(interpreter, mainmodule)
-
- # stats
- if interpreter.show_stats then
- print "# Interpretation statistics"
-
- print "## Dynamic loading"
- print " - nb dynamically lodaed types: {interpreter.typing.nb_loads} ({interpreter.typing.nb_gen_loads} were generic)"
- print " - nb dynamically lodaed modules: {interpreter.typing.nb_mod_loads}"
-
- print "## Typing structures"
- print " - nb increments: {interpreter.typing.nb_increment_loads} ({interpreter.typing.nb_gen_increment_loads} were generic)"
- print " - nb destructive increments: {interpreter.typing.nb_destructive_increments} ({interpreter.typing.nb_gen_destructive_increments} were generic)"
- print " - nb full structure recomputation: {interpreter.typing.nb_recomputes}"
- print " - overall max size table: {interpreter.typing.max_size_tables}"
- print " - total: size needed for tables: {interpreter.typing.sum_size_tables}"
-
- print "## Type test"
- print " - nb isa: {interpreter.typing.nb_isas} ({interpreter.typing.nb_gen_isas} involving generic types)"
- print " - nb isa using fallback: {interpreter.typing.nb_fallbacks} ({interpreter.typing.nb_gen_fallbacks} involving generic types)"
- print " - nb isa using fallback responded from cache: {interpreter.typing.nb_cache_res}"
- end
-
- var time1 = get_time
- self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
- end
-end
-
-# Add gestion handling for incremental typing mecanisms
-redef class NaiveInterpreter
-
- var typing: Typing
- var module_increment: Bool
- var recompute_mode: String
- var threshold_mode: String = "null"
- var threshold_count: Int = 0
- var cache_size: Int = 0
- var show_stats: Bool = false
-
- # Add option handling
- redef init(modelbuilder, mainmodule, arguments) do
- super(modelbuilder, mainmodule, arguments)
-
- # choose typing method
- var mode = modelbuilder.toolcontext.opt_ic_typing_mode.value
- if mode == "BM" then
- self.modelbuilder.toolcontext.info("IC-TYPING: Using BM typing", 1)
- typing = new BMTyping(self)
- else if mode == "CL" then
- self.modelbuilder.toolcontext.info("IC-TYPING: Using CL typing", 1)
- typing = new CLTyping(self)
- else if mode == "PHMOD" then
- self.modelbuilder.toolcontext.info("IC-TYPING: Using PH-MOD typing", 1)
- typing = new PHModTyping(self)
- else if mode == "PHAND" then
- self.modelbuilder.toolcontext.info("IC-TYPING: Using PH-AND typing", 1)
- typing = new PHAndTyping(self)
- else
- self.modelbuilder.toolcontext.info("IC-TYPING: Using legacy typing", 1)
- typing = new LegacyTyping(self)
- end
-
- # load full module or juste the type ?
- mode = modelbuilder.toolcontext.opt_ic_load_mode.value
- if mode == "module" then
- self.modelbuilder.toolcontext.info("IC-TYPING: Using load mode: module", 1)
- module_increment = true
- else
- self.modelbuilder.toolcontext.info("IC-TYPING: Using load mode: type", 1)
- module_increment = false
- end
-
- # bootstrap type structures with stdlib types
- if modelbuilder.toolcontext.opt_ic_bootstrap.value then
- self.modelbuilder.toolcontext.info("IC-TYPING: Using bootstrap", 1)
- typing.bootstrap
- end
-
- # typing recomputation mode
- var recompute_mode = modelbuilder.toolcontext.opt_ic_recompute_mode.value
- if recompute_mode != null then
- self.recompute_mode = recompute_mode
-
- if recompute_mode == "threshold" then
- var threshold_mode = modelbuilder.toolcontext.opt_ic_threshold_mode.value
- if threshold_mode == null then
- print "IC-ERROR : Recompute mode 'threshold' need a threshold mode"
- abort
- else
- self.modelbuilder.toolcontext.info("IC-TYPING: Using threshold mode: {threshold_mode}", 1)
- self.threshold_mode = threshold_mode
- var threshold_count = modelbuilder.toolcontext.opt_ic_threshold_count.value
- if threshold_count == 0 then
- print "IC-ERROR : Recompute mode 'threshold' need a threshold count"
- abort
- else
- self.threshold_count = threshold_count
- self.modelbuilder.toolcontext.info("IC-TYPING: Using threshold value: {threshold_count}", 1)
- end
- end
- end
- else
- self.recompute_mode = "never"
- end
- self.modelbuilder.toolcontext.info("IC-TYPING: Using recompute mode: {self.recompute_mode}", 1)
-
- # cache size
- cache_size = modelbuilder.toolcontext.opt_ic_cache_size.value
- self.modelbuilder.toolcontext.info("IC-TYPING: Using cache size: {cache_size}", 1)
-
- # stats
- if modelbuilder.toolcontext.opt_ic_show_stats.value then show_stats = true
- end
-
- # Handle subtypes tests and redispatch to selected mode
- redef fun is_subtype(sub, sup) do
- self.modelbuilder.toolcontext.info("IC-TYPING[DEBUG]: {sub} ISA {sup}", 2)
-
- # stats
- typing.nb_isas += 1
- if sup isa MGenericType or sub isa MGenericType then typing.nb_gen_isas += 1
-
- # special case of nullables types
- if sub isa MNullType and sup isa MNullableType then
- return true
- else if sub isa MNullType and not sup isa MNullableType then
- return false
- end
- if sub isa MNullableType then sub = sub.mtype
- if sup isa MNullableType then sup = sup.mtype
-
- # std case
- var t1 = typing.load_increment(sub.as(MClassType))
- var t2 = typing.load_increment(sup.as(MClassType))
-
- var res: Bool
- if typing.is_valid then
- res = t1.is_subtype(t2)
- else
- # recompute mode = thresold
- if recompute_mode == "threshold" then
- if threshold_mode == "fallback" then
- typing.increment_count += 1
- # recompute instead fallback
- if typing.increment_count > threshold_count then
- typing.compute_typing
- typing.increment_count = 0
- return t1.is_subtype(t2)
- end
- end
- end
- res = t1.is_subtype_fallback(t2)
- end
- return res
- end
-end
-
-# Incremental typing
-
-# The base of incremental typing
-private abstract class Typing
- type T: Type
-
- # List of already loaded types
- var loaded_types: HashMap[MType, T] = new HashMap[MType, T]
- # List of already loaded modules
- var loaded_modules: HashSet[MModule] = new HashSet[MModule]
- # Last type ID used
- var lastID: Int = -1
- # Is the typing still valid (no destructive increment)
- var is_valid: Bool = false
- # Increment count since the last full recomputation
- var increment_count = 0
-
- private var interpreter: NaiveInterpreter
-
- # stats
- var nb_loads = 0
- var nb_gen_loads = 0
- var nb_mod_loads = 0
- var nb_increment_loads = 0
- var nb_gen_increment_loads = 0
- var nb_destructive_increments = 0
- var nb_gen_destructive_increments = 0
- var nb_isas = 0
- var nb_gen_isas = 0
- var nb_recomputes = 0
- var nb_fallbacks = 0
- var nb_gen_fallbacks = 0
- var nb_cache_res = 0
- var max_size_tables = 0
- var sum_size_tables = 0
-
- private init(interpreter: NaiveInterpreter) do
- self.interpreter = interpreter
- end
-
- # Load the std lib types and compute typing
- fun bootstrap do
- interpreter.modelbuilder.toolcontext.info("IC-TYPING[DEBUG]: bootstrap start", 2)
- var mods = interpreter.mainmodule.model.get_mmodules_by_name("standard")
- if mods != null then
- var std = mods.first
- for m in std.in_nesting.direct_greaters do
- load_module(m)
- end
- end
- compute_typing
- interpreter.modelbuilder.toolcontext.info("IC-TYPING[DEBUG]: bootstrap end", 2)
- end
-
- # Load all types contained in a module
- fun load_module(mmodule: MModule) do
- interpreter.modelbuilder.toolcontext.info("IC-TYPING[DEBUG]: load module {mmodule}", 2)
- # import nested module first
- for m in mmodule.in_nesting.greaters do
- if mmodule == m then continue
- load_module(m)
- end
-
- # load classes in module
- if loaded_modules.has(mmodule) then return
-
- # stats
- nb_mod_loads += 1
-
- for c in mmodule.intro_mclasses do
- # skip unanchored types (they will be handled by load_supertypes of anchored types
- if c.mclass_type.need_anchor then continue
- load_type(c.mclass_type)
- end
- loaded_modules.add(mmodule)
- end
-
- # Load a MType and return its Type representation
- fun load_type(mtype: MClassType): T do
- if is_loaded(mtype) then
- return loaded_types[mtype]
- else
- # stats
- nb_loads += 1
- if mtype isa MGenericType then nb_gen_loads += 1
-
- var supertypes = load_supertypes(mtype)
- var t = init_type(lastID + 1, mtype, supertypes, self)
- lastID = t.id
- loaded_types[mtype] = t
-
- # load formal parameter types
- if mtype isa MGenericType then
- for arg in mtype.arguments do
- if arg isa MClassType then load_type(arg)
- end
- end
-
- return t
- end
- end
-
- # Load compile time known super-types of a type
- # supertypes are all known super classes at compile time and unresolved generic classes
- fun load_supertypes(child: MClassType): Array[T] do
- var parents = child.mclass.in_hierarchy(interpreter.mainmodule).greaters
- var plist = new Array[T]
-
- # load each parent
- for p in parents do
- # not a parent but the type itself
- if p.mclass_type.mclass == child.mclass then continue
-
- if p.mclass_type.need_anchor then
- # the parent need to be anchored to the child (contains a formal type)
- var anchor = p.mclass_type.anchor_to(interpreter.mainmodule, child)
- plist.add(load_type(anchor))
- else
- # already revolved type
- plist.add(load_type(p.mclass_type))
- end
- end
- return plist
- end
-
- # Increment types structures with the new type
- fun load_increment(mtype: MClassType): Type do
- # old type, no need of recomputation
- if is_loaded(mtype) then
- return load_type(mtype)
- else
- # current structures are expired, a new type appeared
- is_valid = false
- end
-
- interpreter.modelbuilder.toolcontext.info("IC-TYPING[DEBUG]: load increment {mtype}", 2)
-
- # load the module
- if interpreter.module_increment then
- load_module(mtype.mclass.intro_mmodule)
- end
-
- var t = load_type(mtype)
-
- # stats
- nb_increment_loads += 1
- if mtype isa MGenericType then nb_gen_increment_loads += 1
-
- # recompute mode = always
- if interpreter.recompute_mode == "always" then
- compute_typing
- end
-
- # recompute mode = thresold
- if interpreter.recompute_mode == "threshold" then
- if interpreter.threshold_mode == "increment" then
- increment_count += 1
-
- if increment_count > interpreter.threshold_count then
- compute_typing
- increment_count = 0
- end
- end
- end
-
- # recompute mode = increment
- if interpreter.recompute_mode == "increment" then
- compute_increment(t)
- if not is_valid then compute_typing
- end
-
- return t
- end
-
- # Factory for specializations of Type
- fun init_type(id: Int, mtype: MClassType, supertypes: Array[T], typing: Typing): T is abstract
-
- # Is the type already loaded ?
- fun is_loaded(mtype: MType): Bool do return loaded_types.has_key(mtype)
-
- # Run the typing full computation
- # Do nothing, need to be redefined
- fun compute_typing do
- nb_recomputes += 1
- interpreter.modelbuilder.toolcontext.info("IC-TYPING[DEBUG]: compute typing", 1)
- is_valid = true
- end
-
- # Compute typing with the increment
- # if the increment is destructive, need to recompute the whole typing structure
- # else just compute typing structure for the new type
- fun compute_increment(t: T) is abstract
-
- # Is the increment destructive ?
- # destructive increment depends on typing method used
- fun is_destructive_increment(t: T): Bool is abstract
-end
-
-# An incremental typing type representation
-private abstract class Type
- type T: Type
-
- # The unique type id
- var id: Int
- # all resolved super-types
- var supertypes: Array[T]
-
- var mtype: MClassType
- var typing: Typing
- var cache: Cache
-
- init(id: Int, mtype: MClassType, supertypes: Array[T], typing: Typing) do
- self.id = id
- self.mtype = mtype
- self.supertypes = supertypes
- self.typing = typing
- self.cache = new Cache(typing.interpreter.cache_size)
- self.supertypes.add(self)
- end
-
- # Subtyping using computed representation
- fun is_subtype(sup: T): Bool is abstract
-
- # Subtyping for uncomputed types
- # unefficient algorithme using linear probing in known super-types
- fun is_subtype_fallback(sup: T): Bool do
- # stats
- typing.nb_fallbacks += 1
- if self.mtype isa MGenericType or sup.mtype isa MGenericType then typing.nb_gen_fallbacks += 1
-
- # try in cache cache
- if cache.contains(sup) then
- typing.nb_cache_res += 1
- return cache.response(sup)
- end
-
- # test implies generic typing
- if sup.mtype isa MGenericType then
- for p in supertypes do
- if p == sup then
- # found the same type (ie. p = B[Int] = sup)
- if not cache.contains(sup) then cache.add(sup, true)
- return true
- else if p.mtype.mclass == sup.mtype.mclass then
- # found the same class (ie. p.mclass = B[B#0] = sup.mclass)
- # compare formal types arguments
- for i in [0..p.mtype.arguments.length[ do
- # erase nullable annotation of p arg
- var sarg = p.mtype.arguments[i]
- if sarg isa MNullableType then sarg = sarg.mtype
- var sft = typing.load_type(sarg.as(MClassType))
- # erase nullable annotation of super arg
- var suparg = sup.mtype.arguments[i]
- if suparg isa MNullableType then suparg = suparg.mtype
- var pft = typing.load_type(suparg.as(MClassType))
- if not sft.is_subtype_fallback(pft) then
- if not cache.contains(sup) then cache.add(sup, false)
- return false
- end
- end
- if not cache.contains(sup) then cache.add(sup, true)
- return true
- end
- end
- if not cache.contains(sup) then cache.add(sup, false)
- return false
- end
-
- # for non generic type: look up in supertypes
- # 0(n) where n = nb supertypes of the current type
- for p in supertypes do
- if p == sup then
- if not cache.contains(sup) then cache.add(sup, true)
- return true
- end
- end
- if not cache.contains(sup) then cache.add(sup, false)
- return false
- end
-
- redef fun to_s do return "{mtype.to_s} (id: {id})"
-end
-
-# Binary Matrix (BM) typing
-private class BMTyping
- super Typing
-
- redef type T: BMType
-
- init(interpreter: NaiveInterpreter) do super(interpreter)
-
- redef fun init_type(id, mtype, supertypes, typing) do return new BMType(id, mtype, supertypes, typing)
-
- # Compute rows in the binary matrix for each loaded type
- redef fun compute_typing do
- super
- for t in loaded_types.values do
- t.compute_row(loaded_types, interpreter.mainmodule)
- end
- end
-
- # If the increment is destructive, the typing representation is invalidated
- # else add a row to the matrix and compute it
- redef fun compute_increment(t) do
- #print "DEBUG: compute increment {t}: is destructive = {is_destructive_increment(t)}"
-
- # compute increments of supertypes first
- for st in t.supertypes do
- if t == st then continue
- if st.row == null then compute_increment(st)
- end
-
- if is_destructive_increment(t) then
- # increment is destructive, need to recompute the whole typing structure
- is_valid = false
- else
- # increment is additive, compute only the type (and newly added parents)
- is_valid = true
- t.compute_row(loaded_types, interpreter.mainmodule)
- end
- end
-
- # For BM typing, destructive increments are only supertypes of already computed types
- # This appends only when a new generic type on an already computed generic class appears
- # ie. increment is GenericType[Something] and loaded-types already contains GenericType[SomethingElse]
- redef fun is_destructive_increment(t) do
- if t.mtype isa MGenericType then
- for ot in loaded_types.values do
- if ot.mtype.mclass == t.mtype.mclass then
- nb_destructive_increments += 1
- nb_gen_destructive_increments += 1
- return true
- end
- end
- end
- return false
- end
-end
-
-private class BMType
- super Type
-
- redef type T: BMType
-
- # A row is an array of bits
- var row: nullable Array[Bool]
-
- # For each other types, fill the cell with 1 if subtype else 0
- fun compute_row(types: HashMap[MType, T], mainmodule: MModule) do
- row = new Array[Bool].filled_with(false, types.length)
- for t in types.values do
- if self.is_subtype_fallback(t) then
- row[t.id] = true
- end
- end
-
- # stats
- if row.length > typing.max_size_tables then typing.max_size_tables = row.length
- typing.sum_size_tables += row.length
- end
-
- # O(1)
- # return self.row[t.id]
- redef fun is_subtype(t) do
- if t.id >= self.row.length then return false
- return self.row[t.id]
- end
-end
-
-# Perfect Hashing (PH) typing
-private abstract class PHTyping
- super Typing
-
- redef type T: PHType
-
- init(interpreter: NaiveInterpreter) do
- super(interpreter)
- lastID = 0
- end
-
- redef fun compute_typing do
- super
- for t in loaded_types.values do
- t.compute_row(loaded_types, interpreter.mainmodule)
- end
- end
-
- # If the increment is destructive, the typing representation is invalidated
- # else compute the hashmap of the type
- redef fun compute_increment(t) do
- # compute increments of supertypes first
- for st in t.supertypes do
- if t == st then continue
- if st.row == null then compute_increment(st)
- end
-
- if is_destructive_increment(t) then
- # increment is destructive, need to recompute the whole typing structure
- is_valid = false
- else
- # increment is additive, compute only the type (and newly added parents)
- is_valid = true
- t.compute_row(loaded_types, interpreter.mainmodule)
- end
- end
-
- # For HP typing, destructive increments are only supertypes of already computed types
- # This appends only when a new generic type on an already computed generic class appears
- # ie. increment is GenericType[Something] and loaded-types already contains GenericType[SomethingElse]
- redef fun is_destructive_increment(t) do
- if t.mtype isa MGenericType then
- for ot in loaded_types.values do
- if ot.mtype.mclass == t.mtype.mclass then
- # stats
- nb_destructive_increments += 1
- nb_gen_destructive_increments += 1
- return true
- end
- end
- end
- return false
- end
-end
-
-private abstract class PHType
- super Type
-
- redef type T: PHType
-
- # Mask is used to hash an id
- var mask: nullable Int
- # A row is an array of IDs (Int)
- var row: nullable Array[Int]
-
- # First compute the mask needed to perfectly hash the parents list
- # Then fill the hashmap
- fun compute_row(types: HashMap[MType, T], mainmodule: MModule) do
- # Create super type list
- var stypes = new List[Type]
- for st in types.values do
- if self.is_subtype_fallback(st) then stypes.add(st)
- end
-
- # Compute the hashing 'mask'
- self.mask = compute_mask(stypes)
-
- # Fill the row
- row = new Array[Int]
- for st in stypes do
- var idx = phash(st.id)
- if idx > row.length then for i in [row.length .. idx[ do row[i] = 0
- row[idx] = st.id
- end
-
- # stats
- if row.length > typing.max_size_tables then typing.max_size_tables = row.length
- typing.sum_size_tables += row.length
- end
-
- # Hash IDs
- fun phash(id: Int): Int is abstract
-
- # O(1)
- # return self.row[phash(t.id)] == t.id
- redef fun is_subtype(t) do
- if self.row.length <= phash(t.id) then return false
- return self.row[phash(t.id)] == t.id
- end
-
- fun compute_mask(stypes: List[Type]): Int is abstract
-end
-
-private class PHModTyping
- super PHTyping
- redef type T: PHModType
- redef fun init_type(id, mtype, supertypes, typing) do return new PHModType(id, mtype, supertypes, typing)
-end
-
-private class PHModType
- super PHType
-
- redef type T: PHModType
-
- # Hash using modulo
- # return mask % id
- redef fun phash(id: Int): Int do return mask.as(not null) % id
-
- # Look for the first mask allowing perfect hashing for stypes ids
- # TODO: Look for a more efficien way to do this
- redef fun compute_mask(stypes: List[Type]): Int do
- var mask = 0
- loop
- var used = new List[Int]
- for st in stypes do
- var res = (mask % st.id)
- if used.has(res) then
- break
- else
- used.add(res)
- end
- end
- if used.length == stypes.length then break
- mask += 1
- end
- return mask
- end
-end
-
-private class PHAndTyping
- super PHTyping
- redef type T: PHAndType
- redef fun init_type(id, mtype, supertypes, typing) do return new PHAndType(id, mtype, supertypes, typing)
-end
-
-private class PHAndType
- super PHType
-
- redef type T: PHAndType
-
- # Hash using binary and
- # return mask & id
- redef fun phash(id: Int): Int do return mask.as(not null).bin_and(id)
-
- # Look for the first mask allowing perfect hashing for stypes ids
- # TODO: Look for a more efficien way to do this
- redef fun compute_mask(stypes: List[Type]): Int do
- var mask = 0
- loop
- var used = new List[Int]
- for st in stypes do
- var res = (mask.bin_and(st.id))
- if used.has(res) then
- break
- else
- used.add(res)
- end
- end
- if used.length == stypes.length then break
- mask += 1
- end
- return mask
- end
-end
-
-# Coloration (CL) typing
-private class CLTyping
- super Typing
-
- redef type T: CLType
-
- # Core of type hierarchy (types with at least two supertypes and their indirect supertypes)
- var core: Array[T] = new Array[T]
- # All types in single inheritance that are not part of core
- var crown: Array[T] = new Array[T]
- # All minimal classes of core
- var border: Array[T] = new Array[T]
-
- # Conflicts graph of hierarchy
- var conflict_graph: HashMap[T, HashSet[T]] = new HashMap[T, HashSet[T]]
-
- # Max color used
- # Increments are colored with injective colors
- var max_color = 0
-
- var sorter: TypeSorter = new TypeSorter
-
- init(interpreter: NaiveInterpreter) do
- super(interpreter)
- lastID = 0
- end
-
- redef fun init_type(id, mtype, supertypes, typing) do return new CLType(id, mtype, supertypes, typing)
-
- # Colorize all known types and fill the type tables
- redef fun compute_typing do
- super
- compute_init
- compute_conflicts
- colorize(core)
- colorize(border)
- colorize(crown)
- fill_tables
- end
-
- # Compute a linear extension of parents of each types and tag each type as core, border or crown
- fun compute_init do
- core.clear
- crown.clear
- border.clear
-
- # clear already build tables
- for t in loaded_types.values do
- #t.parents = new HashSet[T]
- t.nb_parents = 0
- t.sub_types = new HashSet[T]
- end
-
- # compute linear ext of each type type
- for t in loaded_types.values do compute_linear_ext(t)
-
- # tag each type as core, crown or border
- for t in loaded_types.values do tag_type(t)
-
- # sort by reverse linearization order
- sorter.sort(core)
- sorter.sort(crown)
- sorter.sort(border)
- end
-
- # Compute linear extension of the type
- fun compute_linear_ext(t: T) do
- var lin = new Array[T]
- for st in loaded_types.values do
- if t.is_subtype_fallback(st) then
- lin.add(st)
- t.nb_parents = t.mtype.mclass.in_hierarchy(interpreter.mainmodule).direct_greaters.length
- if t != st then st.sub_types.add(t)
- end
- end
- sorter.sort(lin)
- t.lin = lin
- end
-
- # Tag type as core, crown or border
- fun tag_type(t: T) do
- if t.nb_parents > 1 then
- if t.sub_types.length <= 1 then
- # border
- border.add(t)
- else
- # core
- core.add(t)
- end
- for st in t.lin do if t != st and not core.has(st) then core.add(st)
- else
- # crown
- var subtypes_are_si = true
- for sub in t.sub_types do if sub.nb_parents > 1 then subtypes_are_si = false
- if subtypes_are_si then crown.add(t)
- end
- end
-
- # Compute related classes to detect coloring conflicts
- fun compute_conflicts do
- conflict_graph.clear
- var mi_types = new Array[T]
- mi_types.add_all(core)
- mi_types.add_all(border)
- sorter.sort(mi_types)
-
- for t in mi_types do
- for i in t.lin do
- if i == t then continue
- var lin_i = i.lin
-
- for j in t.lin do
- if i == j or j == t then continue
- var lin_j = j.lin
-
- var d_ij = lin_i - lin_j
- var d_ji = lin_j - lin_i
-
- for ed1 in d_ij do
- if not conflict_graph.has_key(ed1) then conflict_graph[ed1] = new HashSet[T]
- # add ed1 x ed2 to conflicts graph
- for ed2 in d_ji do conflict_graph[ed1].add(ed2)
- end
- for ed1 in d_ij do
- if not conflict_graph.has_key(ed1) then conflict_graph[ed1] = new HashSet[T]
- # add ed1 x ed2 to conflicts graph
- for ed2 in d_ji do conflict_graph[ed1].add(ed2)
- end
- end
- end
- end
- self.conflict_graph = conflict_graph
- end
-
- # Colorize an array of types
- fun colorize(elts: Array[T]) do
- var min_color = 0
-
- for t in elts do
- var color = min_color
- while not color_free(t, color) do
- color += 1
- end
- t.color = color
- if color > max_color then max_color = color
- color = min_color
- end
- end
-
- # Check if a related type to t already use the color
- fun color_free(t: T, color: Int): Bool do
- if conflict_graph.has_key(t) then
- for st in conflict_graph[t] do if st.color == color then return false
- end
- for st in t.lin do
- if st == t then continue
- if st.color == color then return false
- end
- return true
- end
-
- # Fill de colored tables
- fun fill_tables do for t in loaded_types.values do fill_table(t)
-
- fun fill_table(t: T) do
- t.row = new Array[Int]
- for st in t.lin do
- if t.row.length <= st.color.as(not null) then
- for i in [t.row.length..st.color.as(not null)[ do t.row.add(0)
- end
- t.row[st.color.as(not null)] = st.id
- end
-
- # stats
- if t.row.length > max_size_tables then max_size_tables = t.row.length
- sum_size_tables += t.row.length
- end
-
- # Compute table for a type increment: compute linearization of parents and fill the table
- fun compute_table_increment(t: T) do
- compute_linear_ext(t)
- fill_table(t)
- end
-
- redef fun compute_increment(t) do
- #print "DEBUG: compute increment {t}: is destructive = {is_destructive_increment(t)}"
-
- # injective color
- max_color += 1
- t.color = max_color
-
- # parents first
- for st in t.supertypes do
- if t == st then continue
- if st.row == null then compute_increment(st)
- end
-
- if is_destructive_increment(t) then
- is_valid = false
- else
- # increment is additive, compute only the type (and newly added parents)
- is_valid = true
- compute_table_increment(t)
- end
- end
-
- # For CL typing, destructive increments are supertypes of already computed types and common types of two already computed types
- redef fun is_destructive_increment(t) do
- if t.mtype isa MGenericType then
- for ot in loaded_types.values do
- if ot.mtype.mclass == t.mtype.mclass then
- # stats
- nb_destructive_increments += 1
- nb_gen_destructive_increments += 1
- return true
- end
- end
- end
-
- compute_linear_ext(t)
- if t.nb_parents > 1 then
- # stats
- nb_destructive_increments += 1
- if t.mtype isa MGenericType then nb_gen_destructive_increments = 0
- return true
- end
-
- return false
- end
-end
-
-private class CLType
- super Type
-
- redef type T: CLType
-
- # Linearization of super-types including the type itself
- var lin: Array[T] = new Array[T]
- # The color assignated to the type
- var color: nullable Int
- # Number of direct super-types
- var nb_parents: Int = 0
- # All sub types
- var sub_types: HashSet[T] = new HashSet[T]
-
- # A row is the an array of IDs
- var row: nullable Array[Int]
-
- # O(1)
- # return row[t.color] == t.id
- redef fun is_subtype(t) do
- if t.color >= row.length then return false
- return row[t.color.as(not null)] == t.id
- end
-end
-
-# Wrapper for interpretor legacy typing based on precomputed metalmodel
-private class LegacyTyping
- super Typing
- redef type T: LegacyType
- init(interpreter: NaiveInterpreter) do super(interpreter)
- redef fun init_type(id, mtype, supertypes, typing) do return new LegacyType(id, mtype, supertypes, typing)
-end
-
-# Wrapper for interpretor legacy typing based on precomputed metalmodel
-private class LegacyType
- super Type
- redef type T: LegacyType
- redef fun is_subtype(t) do return self.mtype.is_subtype(typing.interpreter.mainmodule, typing.interpreter.frame.arguments.first.mtype.as(MClassType), t.mtype)
-end
-
-# Tools
-
-private class Cache
-
- var elements: Array[CachePair]
- var size: Int
- var cache: nullable CachePair
-
- init(size: Int) do
- self.size = size
- self.elements = new Array[CachePair].with_capacity(size)
- end
-
- # Add a response to the cache
- fun add(t: Type, res: Bool) do
- var pair = new CachePair(t, res)
- elements.insert(pair, 0)
- if elements.length > size then elements.pop
- end
-
- # Check if the cache contains the subtype test response
- fun contains(t: Type): Bool do
- for e in elements do
- if e.t == t then
- cache = e
- return true
- end
- end
- return false
- end
-
- # Get a subtype test response from the cache
- # ensure contains before
- fun response(t: Type): Bool do
- if cache != null and cache.t == t then return cache.res
-
- for e in elements do if e.t == t then return e.res
-
- print "IC-ERROR: Cache fault"
- abort
- end
-end
-
-private class CachePair
- var t: Type
- var res: Bool
-end
-
-# Add operator - to Array
-redef class Array[T]
- # Return a new array with the elements only contened in 'self' and not in 'o'
- fun -(o: Array[T]): Array[T] do
- var res = new Array[T]
- for e in self do if not o.has(e) then res.add(e)
- return res
- end
-end
-
-# A sorter for reversed linearization of types
-private class TypeSorter
- super AbstractSorter[Type]
-
- redef fun compare(a, b) do
- if a == b then
- return 0
- else if a.is_subtype_fallback(b) then
- return 1
- end
- return -1
- end
-end
import model
private import metrics_base
import frontend
+import model_viz
redef class ToolContext
var generate_hierarchies_phase: Phase = new GenerateHierarchyPhase(self, null)
# Nesting relation is represented with nested boxes
fun generate_module_hierarchy(toolcontext: ToolContext, model: Model)
do
- var buf = new Buffer
- buf.append("digraph \{\n")
- buf.append("node [shape=box];\n")
- buf.append("rankdir=BT;\n")
- for mmodule in model.mmodules do
- if mmodule.direct_owner == null then
- generate_module_hierarchy_for(mmodule, buf)
- end
- end
- for mmodule in model.mmodules do
- for s in mmodule.in_importation.direct_greaters do
- buf.append("\"{mmodule}\" -> \"{s}\";\n")
- end
- end
- buf.append("\}\n")
- var f = new OFStream.open(toolcontext.output_dir.join_path("module_hierarchy.dot"))
- f.write(buf.to_s)
- f.close
-end
+ var dot = new MProjectDot(model)
-# Helper function for `generate_module_hierarchy`.
-# Create graphviz nodes for the module and recusrively for its nested modules
-private fun generate_module_hierarchy_for(mmodule: MModule, buf: Buffer)
-do
- if mmodule.in_nesting.direct_greaters.is_empty then
- buf.append("\"{mmodule.name}\";\n")
- else
- buf.append("subgraph \"cluster_{mmodule.name}\" \{label=\"\"\n")
- buf.append("\"{mmodule.name}\";\n")
- for s in mmodule.in_nesting.direct_greaters do
- generate_module_hierarchy_for(s, buf)
- end
- buf.append("\}\n")
- end
+ var projectpath = toolcontext.output_dir.join_path("project_hierarchy.dot")
+ dot.mprojects.add(model.mprojects.first)
+ dot.render(projectpath)
+
+ var modulepath = toolcontext.output_dir.join_path("module_hierarchy.dot")
+ dot.mprojects.add_all(model.mprojects)
+ dot.render(modulepath)
end
# Create a dot file representing the class hierarchy of a model.
buf.append("<html>\n<body>\n")
buf.append("<h1>Model</h1>\n")
+ buf.append("<h2>Projects</h2>\n")
+ for mproject in model.mprojects do
+ buf.append("<h3 id='project-{mproject}'>Project {mproject}</h3>\n")
+ buf.append("<dl>\n")
+ buf.append("<dt>groups</dt>\n")
+ for x in mproject.mgroups do
+ buf.append("<dd>{linkto(x)}</dd>\n")
+ end
+ buf.append("</dl>\n")
+ end
+
+ buf.append("<h2>Groups</h2>\n")
+ for mproject in model.mprojects do
+ for mgroup in mproject.mgroups do
+ buf.append("<h3 id='group-{mgroup}'>Group {mgroup}</h3>\n")
+ buf.append("<dl>\n")
+ buf.append("<dt>project</dt>\n")
+ buf.append("<dd>{linkto(mproject)}</dd>\n")
+ buf.append("<dt>filepath</dt>\n")
+ buf.append("<dd>{mgroup.filepath}</dd>\n")
+ var p = mgroup.parent
+ if p != null then
+ buf.append("<dt>parent group</dt>\n")
+ buf.append("<dd>{linkto(p)}</dd>\n")
+ end
+ buf.append("<dt>nested groups</dt>\n")
+ for x in mgroup.in_nesting.direct_smallers do
+ buf.append("<dd>{linkto(x)}</dd>\n")
+ end
+ buf.append("<dt>modules</dt>\n")
+ for x in mgroup.mmodules do
+ buf.append("<dd>{linkto(x)}</dd>\n")
+ end
+ end
+ buf.append("</dl>\n")
+ end
+
buf.append("<h2>Modules</h2>\n")
for mmodule in model.mmodules do
buf.append("<h3 id='module-{mmodule}'>{mmodule}</h3>\n")
buf.append("<dl>\n")
- buf.append("<dt>direct owner</dt>\n")
- var own = mmodule.direct_owner
- if own != null then buf.append("<dd>{linkto(own)}</dd>\n")
- buf.append("<dt>nested</dt>\n")
- for x in mmodule.in_nesting.direct_greaters do
- buf.append("<dd>{linkto(x)}</dd>\n")
- end
+ buf.append("<dt>group</dt>\n")
+ var grp = mmodule.mgroup
+ if grp != null then buf.append("<dd>{linkto(grp)}</dd>\n")
buf.append("<dt>direct import</dt>\n")
for x in mmodule.in_importation.direct_greaters do
buf.append("<dd>{linkto(x)}</dd>\n")
private fun linkto(o: Object): String
do
- if o isa MModule then
+ if o isa MProject then
+ return "<a href='#project-{o}'>{o}</a>"
+ else if o isa MGroup then
+ return "<a href='#group-{o}'>{o}</a>"
+ else if o isa MModule then
return "<a href='#module-{o}'>{o}</a>"
else if o isa MClass then
return "<a href='#class-{o}'>{o}</a>"
print "--- Poset metrics ---"
print "## Module importation hierarchy"
model.mmodule_importation_hierarchy.print_metrics
- print "## Module nesting hierarchy"
- model.mmodule_nesting_hierarchy.print_metrics
print "## Classdef hierarchy"
model.mclassdef_hierarchy.print_metrics
print "## Class hierarchy"
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012 Jean Privat <jean@pryen.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.
+
+# modules and module hierarchies in the metamodel
+module mmodule
+
+import poset
+import location
+import mproject
+private import more_collections
+
+# The container class of a Nit object-oriented model.
+# A model knows modules, classes and properties and can retrieve them.
+redef class Model
+ # All known modules
+ var mmodules: Array[MModule] = new Array[MModule]
+
+ # placebo for old module nesting hierarchy.
+ # where mainmodule < mainmodule::nestedmodule
+ #
+ # TODO REMOVE, rely on mgroup instead
+ var mmodule_nesting_hierarchy: POSet[MModule] = new POSet[MModule]
+
+ # Full module importation hierarchy including private or nested links.
+ var mmodule_importation_hierarchy: POSet[MModule] = new POSet[MModule]
+
+ # Collections of modules grouped by their short names
+ private var mmodules_by_name: MultiHashMap[String, MModule] = new MultiHashMap[String, MModule]
+
+ # Return all module named `name`
+ # If such a module does not exist, null is returned (instead of an empty array)
+ #
+ # Visibility or modules are not considered
+ fun get_mmodules_by_name(name: String): nullable Array[MModule]
+ do
+ if mmodules_by_name.has_key(name) then
+ return mmodules_by_name[name]
+ else
+ return null
+ end
+ end
+end
+
+redef class MGroup
+ # The loaded modules of this group
+ var mmodules = new Array[MModule]
+end
+
+# A Nit module is usually associated with a Nit source file.
+class MModule
+ # The model considered
+ var model: Model
+
+ # placebo for old module nesting hierarchy
+ # return null if self is not nested (ie. is a top-level module)
+ #
+ # TODO REMOVE, rely on mgroup instead
+ var direct_owner: nullable MModule
+
+ # The group of module in the project if any
+ var mgroup: nullable MGroup
+
+ # The short name of the module
+ var name: String
+
+ # The origin of the definition
+ var location: Location
+
+ # Alias for `name`
+ redef fun to_s do return self.name
+
+ # placebo for old module nesting hierarchy
+ # The view of the module in the `model.mmodule_nesting_hierarchy`
+ #
+ # TODO REMOVE, rely on mgroup instead
+ var in_nesting: POSetElement[MModule]
+
+ # The view of the module in the `model.mmodule_importation_hierarchy`
+ var in_importation: POSetElement[MModule]
+
+ # The canonical name of the module
+ # Example: `"project::name"`
+ fun full_name: String
+ do
+ var mgroup = self.mgroup
+ if mgroup == null or mgroup.mproject.name == self.name then
+ return self.name
+ else
+ return "{mgroup.mproject.name}::{self.name}"
+ end
+ end
+
+ # Create a new empty module and register it to a model
+ init(model: Model, mgroup: nullable MGroup, name: String, location: Location)
+ do
+ self.model = model
+ self.name = name
+ self.location = location
+ model.mmodules_by_name.add_one(name, self)
+ model.mmodules.add(self)
+ self.in_nesting = model.mmodule_nesting_hierarchy.add_node(self)
+ self.mgroup = mgroup
+ if mgroup != null then
+ mgroup.mmodules.add(self)
+ # placebo for old module nesting hierarchy
+ var direct_owner = mgroup.mmodules.first
+ if direct_owner == self and mgroup.parent != null and not mgroup.parent.mmodules.is_empty then
+ direct_owner = mgroup.parent.mmodules.first
+ end
+ if direct_owner != self then
+ self.direct_owner = direct_owner
+ model.mmodule_nesting_hierarchy.add_edge(direct_owner, self)
+ end
+ end
+ self.in_importation = model.mmodule_importation_hierarchy.add_node(self)
+ end
+
+ # Register the imported modules (ie "import some_module")
+ # This function can only invoked once by mmodule.
+ # The visibility must be set with `set_visibility_for`.
+ fun set_imported_mmodules(imported_mmodules: Array[MModule])
+ do
+ assert unique_invocation: self.in_importation.direct_greaters.is_empty
+ for m in imported_mmodules do
+ self.model.mmodule_importation_hierarchy.add_edge(self, m)
+ end
+ end
+
+ private var intrude_mmodules: HashSet[MModule] = new HashSet[MModule]
+ private var public_mmodules: HashSet[MModule] = new HashSet[MModule]
+ private var private_mmodules: HashSet[MModule] = new HashSet[MModule]
+
+ # Return the visibility level of an imported module `m`
+ fun visibility_for(m: MModule): MVisibility
+ do
+ if m == self then return intrude_visibility
+ if self.intrude_mmodules.has(m) then return intrude_visibility
+ if self.public_mmodules.has(m) then return public_visibility
+ if self.private_mmodules.has(m) then return private_visibility
+ return none_visibility
+ end
+
+ # Set the visibility of an imported module
+ # REQUIRE: the visibility of the modules imported by `m` are already set for `m`
+ fun set_visibility_for(m: MModule, v: MVisibility)
+ do
+ if v == intrude_visibility then
+ self.intrude_mmodules.add(m)
+ self.intrude_mmodules.add_all(m.intrude_mmodules)
+ self.public_mmodules.add_all(m.public_mmodules)
+ self.private_mmodules.add_all(m.private_mmodules)
+ else if v == public_visibility then
+ self.public_mmodules.add(m)
+ self.public_mmodules.add_all(m.intrude_mmodules)
+ self.public_mmodules.add_all(m.public_mmodules)
+ else if v == private_visibility then
+ self.private_mmodules.add(m)
+ self.private_mmodules.add_all(m.intrude_mmodules)
+ self.private_mmodules.add_all(m.public_mmodules)
+ else
+ print "{self} visibility for {m} = {v}"
+ abort # invalid visibility
+ end
+ end
+
+ # placebo for old module nesting hierarchy
+ fun public_owner: nullable MModule
+ do
+ var mgroup = self.mgroup
+ if mgroup == null then return null
+ mgroup = mgroup.mproject.root
+ if mgroup.mmodules.is_empty then return null
+ var res = mgroup.mmodules.first
+ if res == self then return null
+ return res
+ end
+
+ # Return true if a class or a property introduced in `intro_mmodule` with a visibility of `visibility` is visible in self.
+ fun is_visible(intro_mmodule: MModule, visibility: MVisibility): Bool
+ do
+ var v = visibility_for(intro_mmodule)
+ if v == intrude_visibility then
+ return visibility >= private_visibility
+ else if v == public_visibility then
+ return visibility > private_visibility
+ else if v == private_visibility then
+ return visibility > private_visibility
+ else if v == none_visibility then
+ return false
+ else
+ abort
+ end
+ end
+end
import poset
import location
-import model_base
+import mmodule
private import more_collections
redef class Model
# See the License for the specific language governing permissions and
# limitations under the License.
+# The abstract concept of model and related common things
module model_base
-import poset
-import location
-private import more_collections
-
# The container class of a Nit object-oriented model.
# A model knows modules, classes and properties and can retrieve them.
class Model
- # All known modules
- var mmodules: Array[MModule] = new Array[MModule]
-
- # Module nesting hierarchy.
- # where mainmodule < mainmodule::nestedmodule
- var mmodule_nesting_hierarchy: POSet[MModule] = new POSet[MModule]
-
- # Full module importation hierarchy including private or nested links.
- var mmodule_importation_hierarchy: POSet[MModule] = new POSet[MModule]
-
- # Collections of modules grouped by their short names
- private var mmodules_by_name: MultiHashMap[String, MModule] = new MultiHashMap[String, MModule]
-
- # Return all module named `name`
- # If such a module does not exist, null is returned (instead of an empty array)
- #
- # Visibility or modules are not considered
- fun get_mmodules_by_name(name: String): nullable Array[MModule]
- do
- if mmodules_by_name.has_key(name) then
- return mmodules_by_name[name]
- else
- return null
- end
- end
-end
-
-# A Nit module is usually associated with a Nit source file.
-# Modules can be nested (see `direct_owner`, `public_owner`, and `in_nesting`)
-class MModule
- # The model considered
- var model: Model
-
- # The direct nesting module, return null if self is not nested (ie. is a top-level module)
- var direct_owner: nullable MModule
-
- # The short name of the module
- var name: String
-
- # The origin of the definition
- var location: Location
-
- # Alias for `name`
- redef fun to_s do return self.name
-
- # The view of the module in the `model.mmodule_nesting_hierarchy`
- var in_nesting: POSetElement[MModule]
-
- # The view of the module in the `model.mmodule_importation_hierarchy`
- var in_importation: POSetElement[MModule]
-
- # The canonical name of the module
- # Example: `"owner::name"`
- fun full_name: String
- do
- var owner = self.public_owner
- if owner == null then
- return self.name
- else
- return "{owner.full_name}::{self.name}"
- end
- end
-
- # Create a new empty module and register it to a model
- # `direct_owner` is the direct owner (null if top-level module)
- init(model: Model, direct_owner: nullable MModule, name: String, location: Location)
- do
- self.model = model
- self.name = name
- self.location = location
- model.mmodules_by_name.add_one(name, self)
- model.mmodules.add(self)
- self.in_nesting = model.mmodule_nesting_hierarchy.add_node(self)
- self.direct_owner = direct_owner
- if direct_owner != null then
- model.mmodule_nesting_hierarchy.add_edge(direct_owner, self)
- end
- self.in_importation = model.mmodule_importation_hierarchy.add_node(self)
- end
-
- # Register the imported modules (ie "import some_module")
- # This function can only invoked once by mmodule.
- # The visibility must be set with `set_visibility_for`.
- fun set_imported_mmodules(imported_mmodules: Array[MModule])
- do
- assert unique_invocation: self.in_importation.direct_greaters.is_empty
- for m in imported_mmodules do
- self.model.mmodule_importation_hierarchy.add_edge(self, m)
- end
- end
-
- private var intrude_mmodules: HashSet[MModule] = new HashSet[MModule]
- private var public_mmodules: HashSet[MModule] = new HashSet[MModule]
- private var private_mmodules: HashSet[MModule] = new HashSet[MModule]
-
- # Return the visibility level of an imported module `m`
- fun visibility_for(m: MModule): MVisibility
- do
- if m == self then return intrude_visibility
- if self.intrude_mmodules.has(m) then return intrude_visibility
- if self.public_mmodules.has(m) then return public_visibility
- if self.private_mmodules.has(m) then return private_visibility
- return none_visibility
- end
-
- # Set the visibility of an imported module
- # REQUIRE: the visibility of the modules imported by `m` are already set for `m`
- fun set_visibility_for(m: MModule, v: MVisibility)
- do
- if v == intrude_visibility then
- self.intrude_mmodules.add(m)
- self.intrude_mmodules.add_all(m.intrude_mmodules)
- self.public_mmodules.add_all(m.public_mmodules)
- self.private_mmodules.add_all(m.private_mmodules)
- else if v == public_visibility then
- self.public_mmodules.add(m)
- self.public_mmodules.add_all(m.intrude_mmodules)
- self.public_mmodules.add_all(m.public_mmodules)
- else if v == private_visibility then
- self.private_mmodules.add(m)
- self.private_mmodules.add_all(m.intrude_mmodules)
- self.private_mmodules.add_all(m.public_mmodules)
- else
- print "{self} visibility for {m} = {v}"
- abort # invalid visibility
- end
- end
-
- # The first module in the nesting hierarchy to export self as public
- # This function is used to determine the canonical name of modules, classes and properties.
- # REQUIRE: the visibility of all nesting modules is already set.
- fun public_owner: nullable MModule
- do
- var res = self.direct_owner
- var last = res
- while last != null do
- if last.visibility_for(self) >= public_visibility then res = last
- last = last.direct_owner
- end
- return res
- end
-
- # Return true if a class or a property introduced in `intro_mmodule` with a visibility of `visibility` is visible in self.
- fun is_visible(intro_mmodule: MModule, visibility: MVisibility): Bool
- do
- var v = visibility_for(intro_mmodule)
- if v == intrude_visibility then
- return visibility >= private_visibility
- else if v == public_visibility then
- return visibility > private_visibility
- else if v == private_visibility then
- return visibility > private_visibility
- else if v == none_visibility then
- return false
- else
- abort
- end
- end
end
# A visibility (for modules, class and properties)
--- /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.
+
+# Modelisation of a Nit project
+module mproject
+
+import model_base
+private import more_collections
+import poset
+
+# A Nit project, thas encompass a product
+class MProject
+ # The name of the project
+ var name: String
+
+ # The model of the project
+ var model: Model
+
+ # The root of the group tree
+ var root: nullable MGroup writable = null
+
+ # The group tree, as a POSet
+ var mgroups = new POSet[MGroup]
+
+ redef fun to_s do return name
+
+ init(name: String, model: Model)
+ do
+ self.name = name
+ self.model = model
+ model.mprojects.add(self)
+ model.mproject_by_name.add_one(name, self)
+ end
+end
+
+# A group of modules in a project
+class MGroup
+ # The name of the group
+ # empty name for a default group in a single-module project
+ var name: String
+
+ # The englobing project
+ var mproject: MProject
+
+ # The parent group if any
+ # see `in_nesting` for more
+ var parent: nullable MGroup
+
+ # fully qualified name
+ fun full_name: String
+ do
+ var p = parent
+ if p == null then return name
+ return "{p.full_name}/{name}"
+ end
+
+ # The group is the group tree on the project (`mproject.mgroups`)
+ # nested groups (children) are smallers
+ # nesting group (see `parent`) is bigger
+ var in_nesting: POSetElement[MGroup]
+
+ # The filepath (usualy a directory) of the group, if any
+ var filepath: nullable String writable
+
+ init (name: String, mproject: MProject, parent: nullable MGroup)
+ do
+ self.name = name
+ self.mproject = mproject
+ self.parent = parent
+ var tree = mproject.mgroups
+ self.in_nesting = tree.add_node(self)
+ if parent != null then
+ tree.add_edge(self, parent)
+ end
+ end
+
+ redef fun to_s do return name
+end
+
+redef class Model
+ # projects of the model
+ var mprojects = new Array[MProject]
+
+ # Collections of project grouped by their names
+ private var mproject_by_name: MultiHashMap[String, MProject] = new MultiHashMap[String, MProject]
+
+ # Return all project named `name`
+ # If such a project is not yet loaded, null is returned (instead of an empty array)
+ fun get_mprojects_by_name(name: String): nullable Array[MProject]
+ do
+ if mproject_by_name.has_key(name) then
+ return mproject_by_name[name]
+ else
+ return null
+ end
+ 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.
+
+# Visualisation of Nit models
+module model_viz
+
+import model
+import ordered_tree
+
+# A simple specialisation of OrderedTree to display projects, groups and modules
+# FIXME do not use Object, but a better common interface of MModule and MGroup
+class MProjectTree
+ super OrderedTree[Object]
+
+ # The model where to look for information
+ var model: Model
+
+ init(model: Model) do self.model = model
+
+ redef fun display(a) do
+ if a isa MGroup then
+ if a.parent == null then return "{a.mproject.name} ({a.filepath})"
+ return a.name + " (group)"
+ else if a isa MModule then
+ return a.name
+ else
+ abort
+ end
+ end
+
+ var alpha_comparator = new AlphaComparator
+
+ var linex_comparator: nullable LinexComparator = null
+
+ # Sort modules and groups with their names
+ fun sort_with_alpha
+ do
+ sort_with(alpha_comparator)
+ end
+
+ # Sort modules and groups with a loosly adaptation of the linerarisation of modules
+ fun sort_with_linex
+ do
+ var c = linex_comparator
+ if c == null then
+ c = new LinexComparator(self)
+ linex_comparator = c
+ end
+ sort_with(c)
+ end
+end
+
+# Just compare objects by using the `to_s` method
+private class AlphaComparator
+ super AbstractSorter[Object]
+ redef fun compare(a,b) do return a.to_s <=> b.to_s
+end
+
+# Compare modules and groups using the
+# FIXME do not use Object, but a better common interface of MModule and MGroup
+private class LinexComparator
+ super AbstractSorter[Object]
+ var mins = new HashMap [MGroup, nullable MModule]
+ var maxs = new HashMap [MGroup, nullable MModule]
+ fun min(o: Object): nullable MModule do
+ if o isa MModule then return o
+ assert o isa MGroup
+ if not mins.has_key(o) then computeminmax(o)
+ return mins[o]
+ end
+ fun max(o: Object): nullable MModule do
+ if o isa MModule then return o
+ assert o isa MGroup
+ if not maxs.has_key(o) then computeminmax(o)
+ return maxs[o]
+ end
+ fun computeminmax(o: MGroup) do
+ if not tree.sub.has_key(o) then
+ mins[o] = null
+ maxs[o] = null
+ return
+ end
+ var subs = tree.sub[o]
+ var minres = min(subs.first)
+ var maxres = max(subs.first)
+ var order = minres.model.mmodule_importation_hierarchy
+ for o2 in subs do
+ var c = min(o2)
+ if c == null then continue
+ if minres == null or order.compare(minres, c) > 0 then minres = c
+ c = max(o2)
+ if c == null then continue
+ if maxres == null or order.compare(maxres, c) < 0 then maxres = c
+ end
+ mins[o] = minres
+ maxs[o] = maxres
+ #if minres != maxres then print "* {o} {minres}..{maxres}"
+ end
+ redef fun compare(a,b) do
+ var ma = min(a)
+ var mb = min(b)
+ if ma == null then
+ if mb == null then return 0 else return -1
+ else if mb == null then
+ return 1
+ end
+ var order = ma.model.mmodule_importation_hierarchy
+ return order.compare(ma, mb)
+ end
+ var tree: OrderedTree[Object]
+end
+
+redef class Model
+ # Generate a MProjectTree based on the projects, groups and modules known in the model
+ fun to_mproject_tree: MProjectTree
+ do
+ var res = new MProjectTree(self)
+ for p in mprojects do
+ for g in p.mgroups do
+ res.add(g.parent, g)
+ for m in g.mmodules do
+ res.add(g, m)
+ end
+ end
+ end
+ return res
+ end
+end
+
+# Generate graphiz files based on projects, groups and modules
+#
+# Interessing elements must be selected. See `mmodules`, ``
+# Display configuration can be set. See `cluster_group`, `project_group`
+class MProjectDot
+ # The model where to look for information
+ var model: Model
+
+ # Set of projects to expand fully (ie all groups and modules are displayed)
+ # Initially empty, projects can be added
+ var mprojects = new HashSet[MProject]
+
+ # Set of modules to display
+ # Initially empty, modules can be added
+ var mmodules = new HashSet[MModule]
+
+ private fun node_for(mmodule: MModule): nullable String
+ do
+ return "m_{mmodule.object_id}"
+ end
+
+ # Should groups be shown as clusters?
+ var cluster_group writable = true
+
+ # Should projects be shown as clusters?
+ var project_group writable = true
+
+ # Recursively generate noed ans clusters for a mroup
+ private fun dot_cluster(o: OStream, mgroup: MGroup)
+ do
+ # Open the cluster, if required
+ if mgroup.parent == null then
+ # is is a root group, so display the project
+ if project_group then
+ o.write("subgraph cluster_{mgroup.object_id} \{\nlabel=\"{mgroup.mproject.name}\\n({mgroup.filepath})\"\ncolor=black\nstyle=dotted\n")
+ end
+ else
+ if cluster_group then
+ o.write("subgraph cluster_{mgroup.object_id} \{\nlabel=\"{mgroup.name}\"\ncolor=blue\nstyle=dotted\n")
+ end
+ end
+
+ # outputs the mmodules to show
+ for mmodule in mgroup.mmodules do
+ if not mmodules.has(mmodule) then continue
+ o.write("\t{node_for(mmodule)} [label=\"{mmodule.name}\",color=green]\n")
+ end
+
+ # recusively progress on sub-clusters
+ for d in mgroup.in_nesting.direct_smallers do
+ dot_cluster(o, d)
+ end
+
+ # close the cluster if required
+ if mgroup.parent == null then
+ if project_group then o.write("\}\n")
+ else
+ if cluster_group then o.write("\}\n")
+ end
+ end
+
+ # Extends the set of `mmodules` by recurdively adding the most specific imported modules of foreign projects
+ fun collect_important_importation
+ do
+ var todo = new List[MModule]
+ todo.add_all(mmodules)
+ while not todo.is_empty do
+ var m = todo.pop
+
+ for psm in m.in_importation.greaters do
+ if m.mgroup.mproject != psm.mgroup.mproject then continue
+ for ssm in psm.in_importation.direct_greaters do
+ if psm.mgroup.mproject == ssm.mgroup.mproject then continue
+ mmodules.add(ssm)
+ todo.add(ssm)
+ end
+ end
+ end
+ end
+
+ # Generate the dot-file named `filepath` with the current configuration
+ fun render(filepath: String)
+ do
+ # Collect interessing nodes
+ for m in model.mmodules do
+ # filter out modules outside wanted projects
+ if not mprojects.has(m.mgroup.mproject) then continue
+
+ mmodules.add(m)
+ end
+
+ collect_important_importation
+
+ # Collect interessing edges
+ var sub_hierarchy = new POSet[MModule]
+ for m in mmodules do
+ sub_hierarchy.add_node(m)
+ for sm in m.in_importation.greaters do
+ if sm == m then continue
+ if not mmodules.has(sm) then continue
+ sub_hierarchy.add_edge(m, sm)
+ end
+ end
+
+ print "generating {filepath}"
+ var dot = new OFStream.open(filepath)
+ dot.write("digraph g \{\n")
+ dot.write("rankdir=BT;node[shape=box];\n")
+ # Generate the nodes
+ for p in model.mprojects do
+ dot_cluster(dot, p.root.as(not null))
+ end
+ # Generate the edges
+ for m in mmodules do
+ for sm in sub_hierarchy[m].direct_greaters do
+ var nm = node_for(m)
+ var nsm = node_for(sm)
+ if m.in_importation.direct_greaters.has(sm) then
+ dot.write("\t{nm} -> {nsm}[style=bold]\n")
+ else
+ dot.write("\t{nm} -> {nsm}[style=solid]\n")
+ end
+ end
+ end
+ dot.write("\}\n")
+ dot.close
+ # sys.system("xdot -f dot {filepath}")
+ end
+end
fun modelbuilder: ModelBuilder do return modelbuilder_real.as(not null)
private var modelbuilder_real: nullable ModelBuilder = null
+ fun run_global_phases(mainmodule: MModule)
+ do
+ for phase in phases_list do
+ phase.process_mainmodule(mainmodule)
+ end
+ end
end
+redef class Phase
+ # Specific action to execute on the whole program
+ # Called by the `ToolContext::run_global_phases`
+ # @toimplement
+ fun process_mainmodule(mainmodule: MModule) do end
+end
+
+
# A model builder knows how to load nit source files and build the associated model
class ModelBuilder
# The model where new modules, classes and properties are added
var time0 = get_time
# Parse and recursively load
self.toolcontext.info("*** PARSE ***", 1)
- var mmodules = new Array[MModule]
+ var mmodules = new ArraySet[MModule]
for a in modules do
- var nmodule = self.load_module(null, a)
+ var nmodule = self.load_module(a)
if nmodule == null then continue # Skip error
mmodules.add(nmodule.mmodule.as(not null))
end
exit(0)
end
- return mmodules
+ return mmodules.to_a
end
# Return a class named `name` visible by the module `mmodule`.
# FIXME: add a way to handle module name conflict
fun get_mmodule_by_name(anode: ANode, mmodule: nullable MModule, name: String): nullable MModule
do
- var origmmodule = mmodule
- var modules = model.get_mmodules_by_name(name)
-
+ # what path where tried to display on error message
var tries = new Array[String]
- var lastmodule = mmodule
- while mmodule != null do
- var dirname = mmodule.location.file.filename.dirname
-
- # Determine the owner
- var owner: nullable MModule
- if dirname.basename("") != mmodule.name then
- owner = mmodule.direct_owner
- else
- owner = mmodule
- end
-
- # First, try the already known nested modules
- if modules != null then
- for candidate in modules do
- if candidate.direct_owner == owner then
- return candidate
- end
+ # First, look in groups of the module
+ if mmodule != null then
+ var mgroup = mmodule.mgroup
+ while mgroup != null do
+ var dirname = mgroup.filepath
+ if dirname == null then break # virtual group
+ if dirname.has_suffix(".nit") then break # singleton project
+
+ # Second, try the directory to find a file
+ var try_file = dirname + "/" + name + ".nit"
+ tries.add try_file
+ if try_file.file_exists then
+ var res = self.load_module(try_file.simplify_path)
+ if res == null then return null # Forward error
+ return res.mmodule.as(not null)
end
- end
-
- # Second, try the directory to find a file
- var try_file = dirname + "/" + name + ".nit"
- tries.add try_file
- if try_file.file_exists then
- var res = self.load_module(owner, try_file.simplify_path)
- if res == null then return null # Forward error
- return res.mmodule.as(not null)
- end
-
- # Third, try if the requested module is itself an owner
- try_file = dirname + "/" + name + "/" + name + ".nit"
- if try_file.file_exists then
- var res = self.load_module(owner, try_file.simplify_path)
- if res == null then return null # Forward error
- return res.mmodule.as(not null)
- end
-
- lastmodule = mmodule
- mmodule = mmodule.direct_owner
- end
- if modules != null then
- for candidate in modules do
- if candidate.direct_owner == null then
- return candidate
+ # Third, try if the requested module is itself a group
+ try_file = dirname + "/" + name + "/" + name + ".nit"
+ if try_file.file_exists then
+ mgroup = get_mgroup(dirname + "/" + name)
+ var res = self.load_module(try_file.simplify_path)
+ if res == null then return null # Forward error
+ return res.mmodule.as(not null)
end
+
+ mgroup = mgroup.parent
end
end
# Look at some known directories
var lookpaths = self.paths
- # Look in the directory of the last module also (event if not in the path)
- if lastmodule != null then
- var dirname = lastmodule.location.file.filename.dirname
- if dirname.basename("") == lastmodule.name then
- dirname = dirname.dirname
- end
- if not lookpaths.has(dirname) then
- lookpaths = lookpaths.to_a
- lookpaths.add(dirname)
+ # Look in the directory of module project also (even if not explicitely in the path)
+ if mmodule != null and mmodule.mgroup != null then
+ # path of the root group
+ var dirname = mmodule.mgroup.mproject.root.filepath
+ if dirname != null then
+ dirname = dirname.join_path("..").simplify_path
+ if not lookpaths.has(dirname) and dirname.file_exists then
+ lookpaths = lookpaths.to_a
+ lookpaths.add(dirname)
+ end
end
end
end
end
if candidate == null then
- if origmmodule != null then
- error(anode, "Error: cannot find module {name} from {origmmodule}. tried {tries.join(", ")}")
+ if mmodule != null then
+ error(anode, "Error: cannot find module {name} from {mmodule}. tried {tries.join(", ")}")
else
error(anode, "Error: cannot find module {name}. tried {tries.join(", ")}")
end
return null
end
- var res = self.load_module(mmodule, candidate)
+ var res = self.load_module(candidate)
if res == null then return null # Forward error
return res.mmodule.as(not null)
end
- # Transform relative paths (starting with '../') into absolute paths
- private fun module_absolute_path(path: String): String do
- if path.has_prefix("..") then
- return getcwd.join_path(path).simplify_path
+ # cache for `identify_file` by realpath
+ private var identified_files = new HashMap[String, nullable ModulePath]
+
+ # Identify a source file
+ # Load the associated project and groups if required
+ private fun identify_file(path: String): nullable ModulePath
+ do
+ if not path.file_exists then
+ toolcontext.error(null, "Error: `{path}` does not exists")
+ return null
end
- return path
+
+ # Fast track, the path is already known
+ var pn = path.basename(".nit")
+ var rp = module_absolute_path(path)
+ if identified_files.has_key(rp) then return identified_files[rp]
+
+ # Search for a group
+ var mgrouppath = path.join_path("..").simplify_path
+ var mgroup = get_mgroup(mgrouppath)
+
+ if mgroup == null then
+ # singleton project
+ var mproject = new MProject(pn, model)
+ mgroup = new MGroup(pn, mproject, null) # same name for the root group
+ mgroup.filepath = path
+ mproject.root = mgroup
+ toolcontext.info("found project `{pn}` at {path}", 2)
+ end
+
+ var res = new ModulePath(pn, path, mgroup)
+
+ identified_files[rp] = res
+ return res
end
- # loaded module by absolute path
- private var loaded_nmodules = new HashMap[String, AModule]
+ # groups by path
+ private var mgroups = new HashMap[String, nullable MGroup]
+
+ # return the mgroup associated to a directory path
+ # if the directory is not a group null is returned
+ private fun get_mgroup(dirpath: String): nullable MGroup
+ do
+ var rdp = module_absolute_path(dirpath)
+ if mgroups.has_key(rdp) then
+ return mgroups[rdp]
+ end
+
+ # Hack, a dir is determined by the presence of a honomymous nit file
+ var pn = rdp.basename(".nit")
+ var mp = dirpath.join_path(pn + ".nit").simplify_path
+
+ if not mp.file_exists then return null
+
+ # check parent directory
+ var parentpath = dirpath.join_path("..").simplify_path
+ var parent = get_mgroup(parentpath)
+
+ var mgroup
+ if parent == null then
+ # no parent, thus new project
+ var mproject = new MProject(pn, model)
+ mgroup = new MGroup(pn, mproject, null) # same name for the root group
+ mproject.root = mgroup
+ toolcontext.info("found project `{mproject}` at {dirpath}", 2)
+ else
+ mgroup = new MGroup(pn, parent.mproject, parent)
+ toolcontext.info("found sub group `{mgroup.full_name}` at {dirpath}", 2)
+ end
+ mgroup.filepath = dirpath
+ mgroups[rdp] = mgroup
+ return mgroup
+ end
+
+ # Transform relative paths (starting with '../') into absolute paths
+ private fun module_absolute_path(path: String): String do
+ return getcwd.join_path(path).simplify_path
+ end
- # Try to load a module using a path.
+ # Try to load a module AST using a path.
# Display an error if there is a problem (IO / lexer / parser) and return null
- # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
- fun load_module(owner: nullable MModule, filename: String): nullable AModule
+ fun load_module_ast(filename: String): nullable AModule
do
if filename.file_extension != "nit" then
self.toolcontext.error(null, "Error: file {filename} is not a valid nit module.")
return null
end
- var module_path = module_absolute_path(filename)
- if loaded_nmodules.keys.has(module_path) then
- return loaded_nmodules[module_path]
- end
-
- var x = if owner != null then owner.to_s else "."
- self.toolcontext.info("load module {filename} in {x}", 2)
+ self.toolcontext.info("load module {filename}", 2)
# Load the file
var file = new IFStream.open(filename)
var tree = parser.parse
file.close
var mod_name = filename.basename(".nit")
- return load_module_commons(owner, tree, mod_name)
+
+ # Handle lexer and parser error
+ var nmodule = tree.n_base
+ if nmodule == null then
+ var neof = tree.n_eof
+ assert neof isa AError
+ error(neof, neof.message)
+ return null
+ end
+
+ return nmodule
end
- fun load_rt_module(owner: MModule, nmodule: AModule, mod_name: String): nullable AModule
+ # Try to load a module and its imported modules using a path.
+ # Display an error if there is a problem (IO / lexer / parser / importation) and return null
+ # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
+ fun load_module(filename: String): nullable AModule
+ do
+ # Look for the module
+ var file = identify_file(filename)
+ if file == null then return null # forward error
+
+ # Already known and loaded? then return it
+ var mmodule = file.mmodule
+ if mmodule != null then
+ return mmodule2nmodule[mmodule]
+ end
+
+ # Load it manually
+ var nmodule = load_module_ast(filename)
+ if nmodule == null then return null # forward error
+
+ # build the mmodule and load imported modules
+ mmodule = build_a_mmodule(file.mgroup, file.name, nmodule)
+
+ if mmodule == null then return null # forward error
+
+ # Update the file information
+ file.mmodule = mmodule
+
+ return nmodule
+ end
+
+ fun load_rt_module(parent: MModule, nmodule: AModule, mod_name: String): nullable AModule
do
# Create the module
- var mmodule = new MModule(model, owner, mod_name, nmodule.location)
+ var mmodule = new MModule(model, parent.mgroup, mod_name, nmodule.location)
nmodule.mmodule = mmodule
nmodules.add(nmodule)
self.mmodule2nmodule[mmodule] = nmodule
var imported_modules = new Array[MModule]
- imported_modules.add(owner)
- mmodule.set_visibility_for(owner, intrude_visibility)
+ imported_modules.add(parent)
+ mmodule.set_visibility_for(parent, intrude_visibility)
mmodule.set_imported_mmodules(imported_modules)
return nmodule
end
- # Try to load a module using a path.
- # Display an error if there is a problem (IO / lexer / parser) and return null
- # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
- private fun load_module_commons(owner: nullable MModule, tree: Start, mod_name: String): nullable AModule
+ # Visit the AST and create the `MModule` object
+ # Then, recursively load imported modules
+ private fun build_a_mmodule(mgroup: nullable MGroup, mod_name: String, nmodule: AModule): nullable MModule
do
- # Handle lexer and parser error
- var nmodule = tree.n_base
- if nmodule == null then
- var neof = tree.n_eof
- assert neof isa AError
- error(neof, neof.message)
- return null
- end
-
# Check the module name
var decl = nmodule.n_moduledecl
if decl == null then
end
# Create the module
- var mmodule = new MModule(model, owner, mod_name, nmodule.location)
+ var mmodule = new MModule(model, mgroup, mod_name, nmodule.location)
nmodule.mmodule = mmodule
nmodules.add(nmodule)
self.mmodule2nmodule[mmodule] = nmodule
build_module_importation(nmodule)
- return nmodule
+ return mmodule
end
# Analysis the module importation and fill the module_importation_hierarchy
end
end
+# placeholder to a module file identified but not always loaded in a project
+private class ModulePath
+ # The name of the module
+ # (it's the basename of the filepath)
+ var name: String
+
+ # The human path of the module
+ var filepath: String
+
+ # The group (and the project) of the possible module
+ var mgroup: MGroup
+
+ # The loaded module (if any)
+ var mmodule: nullable MModule = null
+
+ redef fun to_s do return filepath
+end
+
+
redef class AStdImport
# The imported module once determined
var mmodule: nullable MModule = null
else if pname == "file_chdir" then
recvval.to_s.chdir
return null
+ else if pname == "file_realpath" then
+ return v.native_string_instance(recvval.to_s.realpath)
else if pname == "get_environ" then
var txt = recvval.to_s.environ
return v.native_string_instance(txt)
if arguments.length < 1 then
print "usage: nitdoc [options] file..."
toolcontext.option_context.usage
- exit(1)
+ exit(0)
end
self.process_options
end
private fun overview do
- var overviewpage = new NitdocOverview(self, dot_dir)
+ var overviewpage = new NitdocOverview(self)
overviewpage.save("{output_dir.to_s}/index.html")
end
private fun modules do
for mmodule in model.mmodules do
if mmodule.name == "<main>" then continue
- var modulepage = new NitdocModule(mmodule, self, dot_dir)
+ var modulepage = new NitdocModule(mmodule, self)
modulepage.save("{output_dir.to_s}/{mmodule.url}")
end
end
private fun classes do
for mclass in mbuilder.model.mclasses do
- var classpage = new NitdocClass(mclass, self, dot_dir, source)
+ var classpage = new NitdocClass(mclass, self)
classpage.save("{output_dir.to_s}/{mclass.url}")
end
end
# Nitdoc base page
abstract class NitdocPage
- var dot_dir: nullable String
- var source: nullable String
var ctx: NitdocContext
init(ctx: NitdocContext) do
# Generate a clickable graphviz image using a dot content
protected fun generate_dot(dot: String, name: String, alt: String) do
- var output_dir = dot_dir
+ var output_dir = ctx.dot_dir
if output_dir == null then return
var file = new OFStream.open("{output_dir}/{name}.dot")
file.write(dot)
# Add a (source) link for a given location
protected fun show_source(l: Location): String
do
+ var source = ctx.source
if source == null then
return "({l.file.filename.simplify_path})"
else
source = x.join(l.line_start.to_s)
x = source.split_with("%L")
source = x.join(l.line_end.to_s)
+ source = source.simplify_path
return " (<a target='_blank' title='Show source' href=\"{source.to_s}\">source</a>)"
end
end
private var mbuilder: ModelBuilder
private var mmodules = new Array[MModule]
- init(ctx: NitdocContext, dot_dir: nullable String) do
+ init(ctx: NitdocContext) do
super(ctx)
self.mbuilder = ctx.mbuilder
- self.dot_dir = dot_dir
# get modules
var mmodules = new HashSet[MModule]
for mmodule in mbuilder.model.mmodule_importation_hierarchy do
init(ctx: NitdocContext) do
super(ctx)
- self.dot_dir = null
end
redef fun title do return "Search"
private var intro_mclasses = new HashSet[MClass]
private var redef_mclasses = new HashSet[MClass]
- init(mmodule: MModule, ctx: NitdocContext, dot_dir: nullable String) do
+ init(mmodule: MModule, ctx: NitdocContext) do
super(ctx)
self.mmodule = mmodule
self.mbuilder = ctx.mbuilder
- self.dot_dir = dot_dir
# get local mclasses
for m in mmodule.in_nesting.greaters do
for mclassdef in m.mclassdefs do
private var meths = new HashSet[MMethodDef]
private var inherited = new HashSet[MPropDef]
- init(mclass: MClass, ctx: NitdocContext, dot_dir: nullable String, source: nullable String) do
+ init(mclass: MClass, ctx: NitdocContext) do
super(ctx)
self.mclass = mclass
- self.dot_dir = dot_dir
- self.source = source
# load properties
var locals = new HashSet[MProperty]
for mclassdef in mclass.mclassdefs do
redef class ModelBuilder
fun test_markdown(mmodule: MModule): HTMLTag
do
+ var ts = new HTMLTag("testsuite")
toolcontext.info("nitunit: {mmodule}", 2)
+ if not mmodule2nmodule.has_key(mmodule) then return ts
+
+ var nmodule = mmodule2nmodule[mmodule]
+ assert nmodule != null
+
+ # what module to import in the unit test.
+ # try to detect the main module of the project
+ # TODO do things correctly once the importation of arbitraty nested module is legal
var o = mmodule
- var d = o.public_owner
- while d != null do
- o = d
- d = o.public_owner
+ var g = o.mgroup
+ if g != null then
+ o = get_mmodule_by_name(nmodule, mmodule, g.mproject.name).as(not null)
end
- var ts = new HTMLTag("testsuite")
ts.attr("package", mmodule.full_name)
var prefix = toolcontext.opt_dir.value
var tc
- if mmodule2nmodule.has_key(mmodule) then
- var nmodule = mmodule2nmodule[mmodule]
- do
- var nmoduledecl = nmodule.n_moduledecl
- if nmoduledecl == null then break label x
- var ndoc = nmoduledecl.n_doc
- if ndoc == null then break label x
- tc = new HTMLTag("testcase")
- # NOTE: jenkins expects a '.' in the classname attr
- tc.attr("classname", mmodule.full_name + ".<module>")
- tc.attr("name", "<module>")
- d2m.extract(ndoc, tc)
- end label x
- for nclassdef in nmodule.n_classdefs do
- var mclassdef = nclassdef.mclassdef.as(not null)
- if nclassdef isa AStdClassdef then
- var ndoc = nclassdef.n_doc
- if ndoc != null then
- tc = new HTMLTag("testcase")
- tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name)
- tc.attr("name", "<class>")
- d2m.extract(ndoc, tc)
- end
+ do
+ var nmoduledecl = nmodule.n_moduledecl
+ if nmoduledecl == null then break label x
+ var ndoc = nmoduledecl.n_doc
+ if ndoc == null then break label x
+ tc = new HTMLTag("testcase")
+ # NOTE: jenkins expects a '.' in the classname attr
+ tc.attr("classname", mmodule.full_name + ".<module>")
+ tc.attr("name", "<module>")
+ d2m.extract(ndoc, tc)
+ end label x
+ for nclassdef in nmodule.n_classdefs do
+ var mclassdef = nclassdef.mclassdef.as(not null)
+ if nclassdef isa AStdClassdef then
+ var ndoc = nclassdef.n_doc
+ if ndoc != null then
+ tc = new HTMLTag("testcase")
+ tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name)
+ tc.attr("name", "<class>")
+ d2m.extract(ndoc, tc)
end
- for npropdef in nclassdef.n_propdefs do
- var mpropdef = npropdef.mpropdef.as(not null)
- var ndoc = npropdef.n_doc
- if ndoc != null then
- tc = new HTMLTag("testcase")
- tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name)
- tc.attr("name", mpropdef.mproperty.full_name)
- d2m.extract(ndoc, tc)
- end
+ end
+ for npropdef in nclassdef.n_propdefs do
+ var mpropdef = npropdef.mpropdef.as(not null)
+ var ndoc = npropdef.n_doc
+ if ndoc != null then
+ tc = new HTMLTag("testcase")
+ tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name)
+ tc.attr("name", mpropdef.mproperty.full_name)
+ d2m.extract(ndoc, tc)
end
end
end
redef class MModule
super IndexMatch
# prototype of the module
- # module ownername::name
+ # module name
private fun prototype: String do return "module {name.bold}"
# namespace of the module
- # ownername::name
+ # project::name
private fun namespace: String do
- if public_owner == null then
+ if mgroup == null or mgroup.mproject.name == self.name then
return self.name
else
- return "{public_owner.namespace}::{self.name}"
+ return "{mgroup.mproject}::{self.name}"
end
end
# imported modules
var imports = new Array[MModule]
for mmodule in in_importation.direct_greaters.to_a do
- if not in_nesting.direct_greaters.has(mmodule) then imports.add(mmodule)
+ imports.add(mmodule)
end
if not imports.is_empty then
sorter.sort(imports)
end
pager.indent = pager.indent - 1
end
- # nested modules
- var nested = in_nesting.direct_greaters.to_a
- if not nested.is_empty then
- sorter.sort(nested)
- pager.add("")
- pager.add("== nested modules".bold)
- pager.indent = pager.indent + 1
- for mmodule in nested do
- pager.add("")
- mmodule.preview(index, pager)
- end
- pager.indent = pager.indent - 1
- end
# mclassdefs
var csorter = new MClassDefNameSorter
var intros = new Array[MClassDef]
v.add("return {frame.returnvar.as(not null)};")
end
v.add("\}")
+ if not self.c_name.has_substring("VIRTUAL", 0) then compiler.names[self.c_name] = "{mmethoddef.mclassdef.mmodule.name}::{mmethoddef.mclassdef.mclass.name}::{mmethoddef.mproperty.name} ({mmethoddef.location.file.filename}:{mmethoddef.location.line_start})"
end
end
v.add("return {frame.returnvar.as(not null)};")
end
v.add("\}")
+ if not self.c_name.has_substring("VIRTUAL", 0) then compiler.names[self.c_name] = "{mmethoddef.mclassdef.mmodule.name}::{mmethoddef.mclassdef.mclass.name}::{mmethoddef.mproperty.name} ({mmethoddef.location.file.filename}--{mmethoddef.location.line_start})"
end
# TODO ?
--- /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.
+
+# Stub for loading a runing phases on a bunch of modules
+#
+# The point is to refine this module is executable that does things.
+# One simple way is to use the `-m` option of engines plug various phases.
+module test_phase
+
+import modelbuilder
+
+# Create a tool context to handle options and paths
+var toolcontext = new ToolContext
+# We do not add other options, so process them now!
+toolcontext.process_options
+
+# Get arguments
+var arguments = toolcontext.option_context.rest
+if arguments.is_empty or toolcontext.opt_help.value then
+ toolcontext.option_context.usage
+ return
+end
+
+# We need a model to collect stufs
+var model = new Model
+# An a model builder to parse files
+var modelbuilder = new ModelBuilder(model, toolcontext)
+
+# Here we load an process all modules passed on the command line
+var mmodules = modelbuilder.parse(arguments)
+modelbuilder.run_phases
+
+if mmodules.length == 0 then
+ return
+end
+
+var mainmodule: MModule
+if mmodules.length == 1 then
+ mainmodule = mmodules.first
+else
+ # We need a main module, so we build it by importing all modules
+ mainmodule = new MModule(model, null, "<main>", new Location(null, 0, 0, 0, 0))
+ mainmodule.set_imported_mmodules(mmodules)
+end
+
+toolcontext.run_global_phases(mainmodule)
-module_1.nit -d out/nitdoc_args1.write
+module_1.nit -d $WRITE
---all base_simple3.nit -d out/nitmetrics_args1.write
+--all base_simple3.nit -d $WRITE
-test_nitunit.nit --no-color -o out/nitunit_args1.write
+test_nitunit.nit --no-color -o $WRITE
-error_mod_unk.nit:17,8--11: Error: cannot find module dfgd from error_mod_unk. tried ./dfgd.nit, dfgd.nit, ../lib/standard/dfgd.nit, ../lib/standard/collection/dfgd.nit, alt/dfgd.nit, ../lib/dfgd.nit, ../lib/dfgd.nit
+error_mod_unk.nit:17,8--11: Error: cannot find module dfgd from error_mod_unk. tried dfgd.nit, ../lib/standard/dfgd.nit, ../lib/standard/collection/dfgd.nit, alt/dfgd.nit, ../lib/dfgd.nit, ../lib/dfgd.nit
--no-check-autocast Disable implicit casts on unsafe expression usage (dangerous)
--no-check-other Disable implicit tests: unset attribute, null receiver (dangerous)
--typing-test-metrics Enable static and dynamic count of all type tests
+ --nit-stacktrace Enables the use of gperf to bind C to Nit function names when encountering a Stack trace at runtime
--separate Use separate compilation
--no-inline-intern Do not inline call to intern methods
--no-union-attribute Put primitive attibutes in a box instead of an union
average value: 0.0
distribution:
<=0: sub-population=1 (100.00%); cumulated value=0 (na%)
-## Module nesting hierarchy
-Number of nodes: 1
-Number of edges: 1 (1.00 per node)
-Number of direct edges: 0 (0.0 per node)
-Distribution of greaters
- population: 1
- minimum value: 1
- maximum value: 1
- total value: 1
- average value: 1.00
- distribution:
- <=1: sub-population=1 (100.00%); cumulated value=1 (100.00%)
-Distribution of direct greaters
- population: 1
- minimum value: 0
- maximum value: 0
- total value: 0
- average value: 0.0
- distribution:
- <=0: sub-population=1 (100.00%); cumulated value=0 (na%)
-Distribution of smallers
- population: 1
- minimum value: 1
- maximum value: 1
- total value: 1
- average value: 1.00
- distribution:
- <=1: sub-population=1 (100.00%); cumulated value=1 (100.00%)
-Distribution of direct smallers
- population: 1
- minimum value: 0
- maximum value: 0
- total value: 0
- average value: 0.0
- distribution:
- <=0: sub-population=1 (100.00%); cumulated value=0 (na%)
## Classdef hierarchy
Number of nodes: 8
Number of edges: 22 (2.75 per node)
Number of live runtime cast types (ie used in as and isa): 0
Number of dead method definitions of live methods: 0
+generating out/nitmetrics_args1.write/project_hierarchy.dot
+generating out/nitmetrics_args1.write/module_hierarchy.dot
class_hierarchy.dot
classdef_hierarchy.dot
model.html
module_hierarchy.dot
+project_hierarchy.dot
-test_nitunit.nit:18,1--19,0: ERROR: test_nitunit.test_nitunit::X.<class> (in .nitunit/test_nitunit2.nit): Runtime error: Assert failed (.nitunit/test_nitunit2.nit:5)
+test_nitunit.nit:20,1--22,0: ERROR: test_nitunit.test_nitunit::X.<class> (in .nitunit/test_nitunit2.nit): Runtime error: Assert failed (.nitunit/test_nitunit2.nit:5)
-test_nitunit.nit:20,2--21,0: FAILURE: test_nitunit.test_nitunit::X.test_nitunit::X::toto (in .nitunit/test_nitunit3.nit): .nitunit/test_nitunit3.nit:5,9--28: Error: Method or variable 'undefined_identifier' unknown in Sys.
+test_nitunit.nit:23,2--25,0: FAILURE: test_nitunit.test_nitunit::X.test_nitunit::X::toto (in .nitunit/test_nitunit3.nit): .nitunit/test_nitunit3.nit:5,9--28: Error: Method or variable 'undefined_identifier' unknown in Sys.
-<testsuites><testsuite package="test_nitunit"><testcase classname="test_nitunit.<module>" name="<module>"><system-err/><system-out> assert true
-</system-out></testcase><testcase classname="test_nitunit.test_nitunit::X" name="<class>"><system-err/><system-out> assert false
+<testsuites><testsuite package="test_nitunit"><testcase classname="test_nitunit.<module>" name="<module>"><system-err></system-err><system-out> assert true
+</system-out></testcase><testcase classname="test_nitunit.test_nitunit::X" name="<class>"><system-err></system-err><system-out> assert false
</system-out><error message="Runtime error: Assert failed (.nitunit/test_nitunit2.nit:5)
-"/></testcase><testcase classname="test_nitunit.test_nitunit::X" name="test_nitunit::X::toto"><system-err/><system-out> assert undefined_identifier
+"></error></testcase><testcase classname="test_nitunit.test_nitunit::X" name="test_nitunit::X::toto"><system-err></system-err><system-out> assert undefined_identifier
</system-out><failure message=".nitunit/test_nitunit3.nit:5,9--28: Error: Method or variable 'undefined_identifier' unknown in Sys.
-"/></testcase></testsuite></testsuites>
\ No newline at end of file
+"></failure></testcase></testsuite></testsuites>
\ No newline at end of file
--- /dev/null
+ -W, --warn Show warnings
+ -q, --quiet Do not show warnings
+ --stop-on-first-error Stop on first error
+ --no-color Do not use color to display errors and warnings
+ --log Generate various log files
+ --log-dir Directory where to generate log files
+ -h, -?, --help Show Help (This screen)
+ --version Show version and exit
+ -v, --verbose Verbose
+ -I, --path Set include path for loaders (may be used more than once)
+ --only-parse Only proceed to parse step of loaders
+ --only-metamodel Stop after meta-model processing
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2004-2008 Jean Privat <jean@pryen.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.
+
+# a 'success' unit test (pass)
+# assert true
+module test_nitunit
+# a 'error' unit test (do not pass)
+# assert false
+class X
+ # a 'failure' unit test (does not compile)
+ # assert undefined_identifier
+ fun toto do end
+end
y="$x/$bx.nit"
test -f $y && list="$list $y"
done
-./tests.sh "$@" *.nit ../examples/*.nit ../examples/leapfrog/leapfrog.nit ../examples/shoot/src/shoot_logic.nit ../lib/*.nit $list ../src/nitdoc.nit ../src/test_parser.nit ../src/nit.nit ../src/nitmetrics.nit ../src/nitg.nit ../src/nitx.nit ../src/netdbg.nit ../src/nitlight.nit ../contrib/sort_downloads/src/sort_downloads.nit
+./tests.sh "$@" *.nit \
+ ../examples/*.nit \
+ ../examples/leapfrog/leapfrog.nit \
+ ../examples/shoot/src/shoot_logic.nit \
+ ../lib/*.nit $list \
+ ../src/nitdoc.nit \
+ ../src/test_parser.nit \
+ ../src/test_phase.nit \
+ ../src/nit.nit \
+ ../src/nitmetrics.nit \
+ ../src/nitg.nit \
+ ../src/nitx.nit \
+ ../src/nitunit.nit \
+ ../src/netdbg.nit \
+ ../src/nitlight.nit \
+ ../contrib/sort_downloads/src/sort_downloads.nit
fi
echo >>$xml "<error message='fail out/$pattern.res $NSAV'/>"
echo >>$xml "<system-out><![CDATA["
- head >>$xml -n 50 out/$pattern.diff.sav.log
+ cat -v out/$pattern.diff.sav.log | head >>$xml -n 50
echo >>$xml "]]></system-out>"
nok="$nok $pattern"
echo "$ii" >> "$ERRLIST"
fi
echo >>$xml "<error message='changed out/$pattern.res $NFIXME'/>"
echo >>$xml "<system-out><![CDATA["
- head >>$xml -n 50 out/$pattern.diff.sav.log
+ cat -v out/$pattern.diff.sav.log | head >>$xml -n 50
echo >>$xml "]]></system-out>"
nok="$nok $pattern"
echo "$ii" >> "$ERRLIST"
fi
echo >>$xml "<skipped/>"
echo >>$xml "<system-out><![CDATA["
- cat >>$xml out/$pattern.res
+ cat -v >>$xml out/$pattern.res
echo >>$xml "]]></system-out>"
nos="$nos $pattern"
fi
if test -s out/$pattern.cmp.err; then
echo >>$xml "<system-err><![CDATA["
- cat >>$xml out/$pattern.cmp.err
+ cat -v >>$xml out/$pattern.cmp.err
echo >>$xml "]]></system-err>"
fi
echo >>$xml "</testcase>"
test -z "$tap" && echo -n "==> $name "
echo "./$ff.bin $args" > "./$fff.bin"
chmod +x "./$fff.bin"
- sh -c "NIT_NO_STACK=1 $TIMEOUT ./$fff.bin < $inputs > $fff.res 2>$fff.err"
+ WRITE="$fff.write" sh -c "NIT_NO_STACK=1 $TIMEOUT ./$fff.bin < $inputs > $fff.res 2>$fff.err"
if [ "x$verbose" = "xtrue" ]; then
cat "$fff.res"
cat >&2 "$fff.err"