From: Jean Privat Date: Wed, 12 Feb 2014 13:50:44 +0000 (-0500) Subject: Merge branch 'patch-sdl' X-Git-Tag: v0.6.4~37 X-Git-Url: http://nitlanguage.org?hp=0d2e5c33c2470ecf285f8d3c24a9e9b1b689c0a6 Merge branch 'patch-sdl' --- diff --git a/README b/README index 0b17475..c3e7c1b 100644 --- a/README +++ b/README @@ -20,10 +20,12 @@ Requirement: * 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: diff --git a/clib/gc_chooser.c b/clib/gc_chooser.c index 5a8cffe..ca06552 100644 --- a/clib/gc_chooser.c +++ b/clib/gc_chooser.c @@ -25,7 +25,6 @@ enum gc_option { gc_opt_large, gc_opt_malloc, gc_opt_boehm } gc_option; #ifdef WITH_LIBGC -#define GC_DEBUG #include #endif diff --git a/contrib/sort_downloads/src/sort_downloads.nit b/contrib/sort_downloads/src/sort_downloads.nit index ba96d5d..cb9caa5 100755 --- a/contrib/sort_downloads/src/sort_downloads.nit +++ b/contrib/sort_downloads/src/sort_downloads.nit @@ -26,7 +26,8 @@ module sort_downloads import opts `{ -#include + #include + #include `} # Local config diff --git a/lib/ordered_tree.nit b/lib/ordered_tree.nit new file mode 100644 index 0000000..ede2196 --- /dev/null +++ b/lib/ordered_tree.nit @@ -0,0 +1,93 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2012 Jean Privat +# +# 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 diff --git a/lib/privileges.nit b/lib/privileges.nit index 05f8fc7..20a2179 100644 --- a/lib/privileges.nit +++ b/lib/privileges.nit @@ -22,10 +22,16 @@ module privileges 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) diff --git a/lib/standard/collection/abstract_collection.nit b/lib/standard/collection/abstract_collection.nit index ab04618..b178386 100644 --- a/lib/standard/collection/abstract_collection.nit +++ b/lib/standard/collection/abstract_collection.nit @@ -113,7 +113,7 @@ interface Collection[E] 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 @@ -151,6 +151,7 @@ interface Iterator[E] end # A collection that contains only one item. +# Used to pass arguments by reference class Container[E] super Collection[E] @@ -222,7 +223,7 @@ end # 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" diff --git a/lib/standard/collection/array.nit b/lib/standard/collection/array.nit index 5de71cd..073471e 100644 --- a/lib/standard/collection/array.nit +++ b/lib/standard/collection/array.nit @@ -80,7 +80,7 @@ abstract class AbstractArrayRead[E] 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 diff --git a/lib/standard/collection/range.nit b/lib/standard/collection/range.nit index eb3aef3..818813a 100644 --- a/lib/standard/collection/range.nit +++ b/lib/standard/collection/range.nit @@ -73,7 +73,7 @@ class Range[E: Discrete] end end -class IteratorRange[E: Discrete] +private class IteratorRange[E: Discrete] # Iterator on ranges. super Iterator[E] var _range: Range[E] diff --git a/lib/standard/file.nit b/lib/standard/file.nit index 9114ff1..239f785 100644 --- a/lib/standard/file.nit +++ b/lib/standard/file.nit @@ -251,6 +251,14 @@ redef class String 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 @@ -261,9 +269,10 @@ redef class String # * 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("/") @@ -277,6 +286,7 @@ redef class String end a2.push(x) end + if a2.is_empty then return "." return a2.join("/") end @@ -360,6 +370,7 @@ redef class NativeString 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 * `} diff --git a/lib/standard/file_nit.h b/lib/standard/file_nit.h index f34769d..6d7967f 100644 --- a/lib/standard/file_nit.h +++ b/lib/standard/file_nit.h @@ -50,6 +50,7 @@ extern int string_NativeString_NativeString_file_delete_0(char *f); #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); diff --git a/lib/standard/math.nit b/lib/standard/math.nit index eaaf2e6..269f3bc 100644 --- a/lib/standard/math.nit +++ b/lib/standard/math.nit @@ -16,6 +16,10 @@ module math import kernel import collection +in "C header" `{ +#include +`} + redef class Int fun rand: Int is extern "kernel_Int_Int_rand_0" fun bin_and(i: Int): Int is extern "kernel_Int_Int_binand_0" @@ -44,10 +48,11 @@ redef class Float end redef class Collection[ E ] - # Return a random element in the collection - fun rand : nullable E + # Return a random element form the collection + # There must be at least one element in the collection + fun rand: E do - if is_empty then return null + if is_empty then abort var rand_index = length.rand for e in self do diff --git a/lib/standard/stream.nit b/lib/standard/stream.nit index c4fe818..9e2b494 100644 --- a/lib/standard/stream.nit +++ b/lib/standard/stream.nit @@ -77,6 +77,7 @@ interface IStream end # Is there something to read. + # This function returns 'false' if there is something to read. fun eof: Bool is abstract end diff --git a/misc/README b/misc/README index 5166943..6f19e2c 100644 --- a/misc/README +++ b/misc/README @@ -1,6 +1,16 @@ # 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: + + + # vim vim is a powerful text editor. diff --git a/misc/syntaxhighlighter/shBrushNit.js b/misc/syntaxhighlighter/shBrushNit.js new file mode 100644 index 0000000..e9d1f78 --- /dev/null +++ b/misc/syntaxhighlighter/shBrushNit.js @@ -0,0 +1,52 @@ +/* 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; +})(); + diff --git a/share/nitdoc/scripts/Nitdoc.GitHub.js b/share/nitdoc/scripts/Nitdoc.GitHub.js index 37f4104..5b5782c 100644 --- a/share/nitdoc/scripts/Nitdoc.GitHub.js +++ b/share/nitdoc/scripts/Nitdoc.GitHub.js @@ -49,6 +49,9 @@ Nitdoc.GitHub.UI = function() { // parse origin var parts = upstream.split(":"); + if(parts.length < 3) { + console.error("Incorrect upstream name `" + upstream + "`, should be of the form user:repo:branch"); + } origin = { user: parts[0], repo: parts[1], @@ -572,7 +575,7 @@ Nitdoc.GitHub.API = function() { base_tree: baseTree.sha, tree: [{ path: path, - mode: 100644, // file (blob) + mode: "100644", // file (blob) type: "blob", sha: blob.sha }] @@ -740,10 +743,6 @@ Nitdoc.GitHub.LoginBox = function() { .addClass("nitdoc-github-loginbox-arrow") .append(" ") ) - .append( - $(document.createElement("h3")) - .append("Github Sign In") - ) .append(loginBoxContent); loginBoxLi.append(loginBox); @@ -754,6 +753,10 @@ Nitdoc.GitHub.LoginBox = function() { var displayLogout = function(origin, user) { var panel = $(document.createElement("div")) .append( + $(document.createElement("h3")) + .append("Signed in Github") + ) + .append( $(document.createElement("h4")) .append("Hello ") .append( @@ -765,30 +768,32 @@ Nitdoc.GitHub.LoginBox = function() { .append( $(document.createElement("label")) .attr("for", "github-origin") - .append("Origin") + .append("Upstram branch") ) .append( - $(document.createElement("input")) + $(document.createElement("a")) + .addClass("nitdoc-github-loginbox-githublink") .attr({ - id: "github-origin", - type: "text", - disabled: "disabled", - value: origin.user + ":" + origin.repo + ":" + origin.branch + title: "Open branch in GitHub", + href: "https://github.com/" + origin.user + "/" + origin.repo + "/tree/" + origin.branch, + target: "_blank" }) + .append(origin.user + ":" + origin.repo + ":" + origin.branch) ) .append( $(document.createElement("label")) .attr("for", "github-base") - .append("Base") + .append("Your branch") ) .append( - $(document.createElement("input")) + $(document.createElement("a")) + .addClass("nitdoc-github-loginbox-githublink") .attr({ - id: "github-base", - type: "text", - disabled: "disabled", - value: user.login + ":" + user.repo + ":" + user.branch + title: "Open branch in GitHub", + href: "https://github.com/" + user.login + "/" + user.repo + "/tree/" + user.branch, + target: "_blank" }) + .append(origin.user + ":" + origin.repo + ":" + origin.branch) ) .append( $(document.createElement("button")) @@ -812,6 +817,10 @@ Nitdoc.GitHub.LoginBox = function() { var displayLogin = function() { var panel = $(document.createElement("form")) .append( + $(document.createElement("h3")) + .append("Sign in Github") + ) + .append( $(document.createElement("label")) .attr("for", "nitdoc-github-login-field") .append("Username") @@ -971,7 +980,7 @@ Nitdoc.GitHub.CommentBox.prototype.open = function(baseArea) { $(document.createElement("button")) .addClass("nitdoc-github-button") .addClass("nitdoc-github-commit") - .append("Commit") + .append("Commit...") .click(function() { instance.infos.newComment = tarea.val(); instance.infos.commentBox = instance; diff --git a/share/nitdoc/styles/Nitdoc.GitHub.css b/share/nitdoc/styles/Nitdoc.GitHub.css index 3c7179b..c4cd574 100644 --- a/share/nitdoc/styles/Nitdoc.GitHub.css +++ b/share/nitdoc/styles/Nitdoc.GitHub.css @@ -120,6 +120,12 @@ margin-bottom: 20px; } +#nitdoc-github-loginbox a.nitdoc-github-loginbox-githublink { + display: block; + margin: 10px; + color: #0D8921; +} + /* * Nitdoc Github buttons */ diff --git a/src/abstract_compiler.nit b/src/abstract_compiler.nit index dc364ad..e6f8aa6 100644 --- a/src/abstract_compiler.nit +++ b/src/abstract_compiler.nit @@ -50,6 +50,8 @@ redef class ToolContext 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 @@ -57,6 +59,7 @@ redef class ToolContext 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 @@ -111,6 +114,9 @@ redef class ModelBuilder 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 @@ -193,7 +199,7 @@ redef class ModelBuilder p = orig_dir.join_path(p).simplify_path cc_includes += " -I \"" + p + "\"" end - makefile.write("CC = ccache cc\nCFLAGS = -g -O2\nCINCL = {cc_includes}\nLDFLAGS ?= \nLDLIBS ?= -lm -lgc\n\n") + makefile.write("CC = ccache cc\nCFLAGS = -g -O2\nCINCL = {cc_includes}\nLDFLAGS ?= \nLDLIBS ?= -lunwind -lm -lgc\n\n") makefile.write("all: {outpath}\n\n") var ofiles = new Array[String] @@ -256,6 +262,9 @@ end 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 @@ -306,17 +315,60 @@ abstract class AbstractCompiler 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 var v = self.header + self.header.add_decl("#define UNW_LOCAL_ONLY") self.header.add_decl("#include ") self.header.add_decl("#include ") self.header.add_decl("#include ") + if modelbuilder.toolcontext.opt_stacktrace.value then + self.header.add_decl("#include \"c_functions_hash.h\"") + end + self.header.add_decl("#include ") + self.header.add_decl("#include ") self.header.add_decl("#include ") compile_header_structs + # Signal handler function prototype + self.header.add_decl("void show_backtrace(int);") + # Global variable used by the legacy native interface self.header.add_decl("extern int glob_argc;") self.header.add_decl("extern char **glob_argv;") @@ -348,7 +400,47 @@ abstract class AbstractCompiler v.compiler.header.add_decl("extern long count_type_test_skipped_{tag};") end end + + v.add_decl("void show_backtrace (int signo) \{") + v.add_decl("char* opt = getenv(\"NIT_NO_STACK\");") + v.add_decl("unw_cursor_t cursor;") + v.add_decl("if(opt==NULL)\{") + v.add_decl("unw_context_t uc;") + v.add_decl("unw_word_t ip;") + v.add_decl("char* procname = malloc(sizeof(char) * 100);") + v.add_decl("unw_getcontext(&uc);") + v.add_decl("unw_init_local(&cursor, &uc);") + v.add_decl("printf(\"-------------------------------------------------\\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);") + v.add_decl("\}") + v.add_decl("exit(signo);") + v.add_decl("\}") + v.add_decl("int main(int argc, char** argv) \{") + + v.add("signal(SIGABRT, show_backtrace);") + v.add("signal(SIGFPE, show_backtrace);") + v.add("signal(SIGILL, show_backtrace);") + v.add("signal(SIGINT, show_backtrace);") + v.add("signal(SIGTERM, show_backtrace);") + v.add("signal(SIGSEGV, show_backtrace);") + v.add("glob_argc = argc; glob_argv = argv;") v.add("initialize_gc_option();") var main_type = mainmodule.sys_type @@ -391,6 +483,7 @@ abstract class AbstractCompiler v.add("printf(\"\\t%ld (%.2f%%)\\n\", count_type_test_total_{tag}, 100.0*count_type_test_total_{tag}/count_type_test_total_total);") end end + v.add("return 0;") v.add("\}") end @@ -401,9 +494,6 @@ abstract class AbstractCompiler # This is used to avoid adding an extern file more than once private var seen_extern = new ArraySet[String] - # Generate code that check if an instance is correctly initialized - fun generate_check_init_instance(mtype: MClassType) is abstract - # Generate code that initialize the attributes on a new instance fun generate_init_attr(v: VISITOR, recv: RuntimeVariable, mtype: MClassType) do @@ -711,9 +801,6 @@ abstract class AbstractCompilerVisitor end end - # Generate a check-init-instance - fun check_init_instance(recv: RuntimeVariable, mtype: MClassType) is abstract - # Names handling private var names: HashSet[String] = new HashSet[String] @@ -913,7 +1000,7 @@ abstract class AbstractCompilerVisitor else self.add("fprintf(stderr, \"\\n\");") end - self.add("exit(1);") + self.add("show_backtrace(1);") end # Add a dynamic cast @@ -1561,7 +1648,7 @@ redef class AInternMethPropdef return end if pname == "exit" then - v.add("exit({arguments[1]});") + v.add("show_backtrace({arguments[1]});") return else if pname == "sys" then v.ret(v.new_expr("glob_sys", ret.as(not null))) @@ -1611,7 +1698,7 @@ redef class AExternMethPropdef var nextern = self.n_extern if nextern == null then v.add("fprintf(stderr, \"NOT YET IMPLEMENTED nitni for {mpropdef} at {location.to_s}\\n\");") - v.add("exit(1);") + v.add("show_backtrace(1);") return end externname = nextern.text.substring(1, nextern.text.length-2) @@ -1643,7 +1730,7 @@ redef class AExternInitPropdef var nextern = self.n_extern if nextern == null then v.add("printf(\"NOT YET IMPLEMENTED nitni for {mpropdef} at {location.to_s}\\n\");") - v.add("exit(1);") + v.add("show_backtrace(1);") return end externname = nextern.text.substring(1, nextern.text.length-2) @@ -2142,7 +2229,6 @@ redef class ACrangeExpr var mtype = self.mtype.as(MClassType) var res = v.init_instance(mtype) var it = v.send(v.get_property("init", res.mtype), [res, i1, i2]) - v.check_init_instance(res, mtype) return res end end @@ -2155,7 +2241,6 @@ redef class AOrangeExpr var mtype = self.mtype.as(MClassType) var res = v.init_instance(mtype) var it = v.send(v.get_property("without_last", res.mtype), [res, i1, i2]) - v.check_init_instance(res, mtype) return res end end @@ -2312,7 +2397,6 @@ redef class ANewExpr #self.debug("got {res2} from {mproperty}. drop {recv}") return res2 end - v.check_init_instance(recv, mtype) return recv end end diff --git a/src/debugger.nit b/src/debugger.nit index 9aa622e..be9c26b 100644 --- a/src/debugger.nit +++ b/src/debugger.nit @@ -334,7 +334,6 @@ class Debugger if initprop != null then self.send(initprop, [mobj]) end - self.check_init_instance(mobj) var mainprop = mmod.try_get_primitive_method("main", sys_type.mclass) if mainprop != null then self.rt_send(mainprop, [mobj]) diff --git a/src/frontend.nit b/src/frontend.nit index 04254a7..2cdde25 100644 --- a/src/frontend.nit +++ b/src/frontend.nit @@ -37,19 +37,4 @@ redef class ToolContext 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 - diff --git a/src/global_compiler.nit b/src/global_compiler.nit index 7c00d09..004c8a3 100644 --- a/src/global_compiler.nit +++ b/src/global_compiler.nit @@ -48,7 +48,6 @@ redef class ModelBuilder for t in runtime_type_analysis.live_types do if t.ctype == "val*" then compiler.generate_init_instance(t) - compiler.generate_check_init_instance(t) else compiler.generate_box_instance(t) end @@ -223,19 +222,6 @@ class GlobalCompiler v.add("\}") end - redef fun generate_check_init_instance(mtype) - do - if self.modelbuilder.toolcontext.opt_no_check_initialization.value then return - - var v = self.new_visitor - var res = new RuntimeVariable("self", mtype, mtype) - self.header.add_decl("void CHECK_NEW_{mtype.c_name}({mtype.ctype});") - v.add_decl("/* allocate {mtype} */") - v.add_decl("void CHECK_NEW_{mtype.c_name}({mtype.ctype} {res}) \{") - self.generate_check_attr(v, res, mtype) - v.add("\}") - end - fun generate_box_instance(mtype: MClassType) do assert self.runtime_type_analysis.live_types.has(mtype) @@ -278,7 +264,7 @@ class GlobalCompilerVisitor var res = self.new_var(mtype) if not compiler.runtime_type_analysis.live_types.has(valtype) then self.add("/*no autobox from {value.mtype} to {mtype}: {value.mtype} is not live! */") - self.add("printf(\"Dead code executed!\\n\"); exit(1);") + self.add("printf(\"Dead code executed!\\n\"); show_backtrace(1);") return res end self.add("{res} = BOX_{valtype.c_name}({value}); /* autobox from {value.mtype} to {mtype} */") @@ -287,7 +273,7 @@ class GlobalCompilerVisitor # Bad things will appen! var res = self.new_var(mtype) self.add("/* {res} left unintialized (cannot convert {value.mtype} to {mtype}) */") - self.add("printf(\"Cast error: Cannot cast %s to %s.\\n\", \"{value.mtype}\", \"{mtype}\"); exit(1);") + self.add("printf(\"Cast error: Cannot cast %s to %s.\\n\", \"{value.mtype}\", \"{mtype}\"); show_backtrace(1);") return res end end @@ -462,7 +448,7 @@ class GlobalCompilerVisitor private fun finalize_call(m: MMethodDef, recvtype: MClassType, args: Array[RuntimeVariable]): nullable RuntimeVariable do if args.length != m.msignature.arity + 1 then # because of self - add("printf(\"NOT YET IMPLEMENTED: Invalid arity for {m}. {args.length} arguments given.\\n\"); exit(1);") + add("printf(\"NOT YET IMPLEMENTED: Invalid arity for {m}. {args.length} arguments given.\\n\"); show_backtrace(1);") debug("NOT YET IMPLEMENTED: Invalid arity for {m}. {args.length} arguments given.") return null end @@ -571,7 +557,7 @@ class GlobalCompilerVisitor do if recv.mtype.ctype != "val*" then return self.add("fprintf(stderr, \"BTD BUG: Dynamic type is %s, static type is %s\\n\", class_names[{recv}->classid], \"{recv.mcasttype}\");") - self.add("exit(1);") + self.add("show_backtrace(1);") end redef fun isset_attribute(a, recv) @@ -826,18 +812,6 @@ class GlobalCompilerVisitor return res end - redef fun check_init_instance(recv, mtype) - do - if self.compiler.modelbuilder.toolcontext.opt_no_check_initialization.value then return - - mtype = self.anchor(mtype).as(MClassType) - if not self.compiler.runtime_type_analysis.live_types.has(mtype) then - debug "problem: {mtype} was detected dead" - end - - self.add("CHECK_NEW_{mtype.c_name}({recv});") - end - redef fun array_instance(array, elttype) do elttype = self.anchor(elttype) @@ -853,7 +827,6 @@ class GlobalCompilerVisitor end var length = self.int_instance(array.length) self.send(self.get_property("with_native", arraytype), [res, nat, length]) - self.check_init_instance(res, arraytype) self.add("\}") return res end @@ -978,6 +951,7 @@ private class CustomizedRuntimeFunction 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 diff --git a/src/interpretor_type_test.nit b/src/interpretor_type_test.nit deleted file mode 100644 index b023130..0000000 --- a/src/interpretor_type_test.nit +++ /dev/null @@ -1,1110 +0,0 @@ -# This file is part of NIT ( http://www.nitlanguage.org ). -# -# Copyright 2012 Alexandre Terrasa -# -# 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 - var opt_ic_threshold_count = new OptionInt("Threshold count. Take an integer", 0, "--ic-threshold-count") - # --ic-cache-size - 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 diff --git a/src/metrics/generate_hierarchies.nit b/src/metrics/generate_hierarchies.nit index 9765a2e..448bada 100644 --- a/src/metrics/generate_hierarchies.nit +++ b/src/metrics/generate_hierarchies.nit @@ -21,6 +21,7 @@ module generate_hierarchies import model private import metrics_base import frontend +import model_viz redef class ToolContext var generate_hierarchies_phase: Phase = new GenerateHierarchyPhase(self, null) @@ -44,40 +45,15 @@ end # 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. diff --git a/src/metrics/model_hyperdoc.nit b/src/metrics/model_hyperdoc.nit index 0e66ba4..fea58da 100644 --- a/src/metrics/model_hyperdoc.nit +++ b/src/metrics/model_hyperdoc.nit @@ -43,17 +43,50 @@ do buf.append("\n\n") buf.append("

Model

\n") + buf.append("

Projects

\n") + for mproject in model.mprojects do + buf.append("

Project {mproject}

\n") + buf.append("
\n") + buf.append("
groups
\n") + for x in mproject.mgroups do + buf.append("
{linkto(x)}
\n") + end + buf.append("
\n") + end + + buf.append("

Groups

\n") + for mproject in model.mprojects do + for mgroup in mproject.mgroups do + buf.append("

Group {mgroup}

\n") + buf.append("
\n") + buf.append("
project
\n") + buf.append("
{linkto(mproject)}
\n") + buf.append("
filepath
\n") + buf.append("
{mgroup.filepath}
\n") + var p = mgroup.parent + if p != null then + buf.append("
parent group
\n") + buf.append("
{linkto(p)}
\n") + end + buf.append("
nested groups
\n") + for x in mgroup.in_nesting.direct_smallers do + buf.append("
{linkto(x)}
\n") + end + buf.append("
modules
\n") + for x in mgroup.mmodules do + buf.append("
{linkto(x)}
\n") + end + end + buf.append("
\n") + end + buf.append("

Modules

\n") for mmodule in model.mmodules do buf.append("

{mmodule}

\n") buf.append("
\n") - buf.append("
direct owner
\n") - var own = mmodule.direct_owner - if own != null then buf.append("
{linkto(own)}
\n") - buf.append("
nested
\n") - for x in mmodule.in_nesting.direct_greaters do - buf.append("
{linkto(x)}
\n") - end + buf.append("
group
\n") + var grp = mmodule.mgroup + if grp != null then buf.append("
{linkto(grp)}
\n") buf.append("
direct import
\n") for x in mmodule.in_importation.direct_greaters do buf.append("
{linkto(x)}
\n") @@ -163,7 +196,11 @@ end private fun linkto(o: Object): String do - if o isa MModule then + if o isa MProject then + return "{o}" + else if o isa MGroup then + return "{o}" + else if o isa MModule then return "{o}" else if o isa MClass then return "{o}" diff --git a/src/metrics/poset_metrics.nit b/src/metrics/poset_metrics.nit index 8ec89b1..2276742 100644 --- a/src/metrics/poset_metrics.nit +++ b/src/metrics/poset_metrics.nit @@ -34,8 +34,6 @@ private class PosetMetricsPhase 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" diff --git a/src/model/mmodule.nit b/src/model/mmodule.nit new file mode 100644 index 0000000..96779cc --- /dev/null +++ b/src/model/mmodule.nit @@ -0,0 +1,207 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2012 Jean Privat +# +# 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 diff --git a/src/model/model.nit b/src/model/model.nit index c356f8d..ff211ea 100644 --- a/src/model/model.nit +++ b/src/model/model.nit @@ -31,7 +31,7 @@ module model import poset import location -import model_base +import mmodule private import more_collections redef class Model diff --git a/src/model/model_base.nit b/src/model/model_base.nit index c72297e..dda72bb 100644 --- a/src/model/model_base.nit +++ b/src/model/model_base.nit @@ -14,173 +14,12 @@ # 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) diff --git a/src/model/mproject.nit b/src/model/mproject.nit new file mode 100644 index 0000000..9c1765f --- /dev/null +++ b/src/model/mproject.nit @@ -0,0 +1,108 @@ +# 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 diff --git a/src/model_viz.nit b/src/model_viz.nit new file mode 100644 index 0000000..1471a49 --- /dev/null +++ b/src/model_viz.nit @@ -0,0 +1,269 @@ +# 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 diff --git a/src/modelbuilder.nit b/src/modelbuilder.nit index bb7d46c..c9e5603 100644 --- a/src/modelbuilder.nit +++ b/src/modelbuilder.nit @@ -52,8 +52,22 @@ redef class ToolContext 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 @@ -118,9 +132,9 @@ class ModelBuilder 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 @@ -134,7 +148,7 @@ class ModelBuilder exit(0) end - return mmodules + return mmodules.to_a end # Return a class named `name` visible by the module `mmodule`. @@ -238,73 +252,52 @@ class ModelBuilder # 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 @@ -339,33 +332,100 @@ class ModelBuilder 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.") @@ -376,13 +436,7 @@ class ModelBuilder 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) @@ -391,41 +445,71 @@ class ModelBuilder 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 @@ -438,14 +522,14 @@ class ModelBuilder 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 @@ -518,6 +602,25 @@ class ModelBuilder 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 diff --git a/src/naive_interpreter.nit b/src/naive_interpreter.nit index 3058bec..ef2a5f3 100644 --- a/src/naive_interpreter.nit +++ b/src/naive_interpreter.nit @@ -62,7 +62,6 @@ redef class ModelBuilder if initprop != null then interpreter.send(initprop, [mainobj]) end - interpreter.check_init_instance(mainobj) var mainprop = mainmodule.try_get_primitive_method("main", sys_type.mclass) if mainprop != null then interpreter.send(mainprop, [mainobj]) @@ -243,7 +242,6 @@ private class NaiveInterpreter var res = new MutableInstance(mtype) self.init_instance(res) self.send(self.force_get_primitive_method("with_native", mtype), [res, nat, self.int_instance(values.length)]) - self.check_init_instance(res) return res end @@ -463,19 +461,6 @@ private class NaiveInterpreter end end - # Check that non nullable attributes of `recv` are correctly initialized. - # This function is used as the last instruction of a new - fun check_init_instance(recv: Instance) - do - if not recv isa MutableInstance then return - for npropdef in collect_attr_propdef(recv.mtype) do - if npropdef.n_expr == null then - # Force read to check the initialization - self.read_attribute(npropdef.mpropdef.mproperty, recv) - end - end - end - # This function determine the correct type according the reciever of the current definition (self). fun unanchor_type(mtype: MType): MType do @@ -900,6 +885,8 @@ redef class AExternMethPropdef 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) @@ -1421,7 +1408,6 @@ redef class ACrangeExpr var res = new MutableInstance(mtype) v.init_instance(res) v.send(v.force_get_primitive_method("init", mtype), [res, e1, e2]) - v.check_init_instance(res) return res end end @@ -1437,7 +1423,6 @@ redef class AOrangeExpr var res = new MutableInstance(mtype) v.init_instance(res) v.send(v.force_get_primitive_method("without_last", mtype), [res, e1, e2]) - v.check_init_instance(res) return res end end @@ -1617,7 +1602,6 @@ redef class ANewExpr #self.debug("got {res2} from {mproperty}. drop {recv}") return res2 end - v.check_init_instance(recv) return recv end end diff --git a/src/nitdoc.nit b/src/nitdoc.nit index f8bb81c..280e273 100644 --- a/src/nitdoc.nit +++ b/src/nitdoc.nit @@ -78,7 +78,7 @@ class NitdocContext if arguments.length < 1 then print "usage: nitdoc [options] file..." toolcontext.option_context.usage - exit(1) + exit(0) end self.process_options @@ -161,7 +161,7 @@ class NitdocContext 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 @@ -173,14 +173,14 @@ class NitdocContext private fun modules do for mmodule in model.mmodules do if mmodule.name == "
" 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 @@ -223,8 +223,6 @@ end # Nitdoc base page abstract class NitdocPage - var dot_dir: nullable String - var source: nullable String var ctx: NitdocContext init(ctx: NitdocContext) do @@ -280,7 +278,7 @@ abstract class NitdocPage # 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) @@ -297,6 +295,7 @@ abstract class NitdocPage # 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 @@ -307,6 +306,7 @@ abstract class NitdocPage 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 " (source)" end end @@ -371,10 +371,9 @@ class NitdocOverview 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 @@ -464,7 +463,6 @@ class NitdocSearch init(ctx: NitdocContext) do super(ctx) - self.dot_dir = null end redef fun title do return "Search" @@ -553,11 +551,10 @@ class NitdocModule 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 @@ -746,11 +743,9 @@ class NitdocClass 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 diff --git a/src/nitunit.nit b/src/nitunit.nit index 05cdcc5..5bb2b67 100644 --- a/src/nitunit.nit +++ b/src/nitunit.nit @@ -149,15 +149,22 @@ end 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 @@ -167,39 +174,36 @@ redef class ModelBuilder 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 + ".") - tc.attr("name", "") - 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", "") - 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 + ".") + tc.attr("name", "") + 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", "") + 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 diff --git a/src/nitx.nit b/src/nitx.nit index 4b5183f..84c0815 100644 --- a/src/nitx.nit +++ b/src/nitx.nit @@ -370,16 +370,16 @@ 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 @@ -408,7 +408,7 @@ redef class MModule # 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) @@ -421,19 +421,6 @@ redef class MModule 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] diff --git a/src/separate_compiler.nit b/src/separate_compiler.nit index ada994e..3ba9452 100644 --- a/src/separate_compiler.nit +++ b/src/separate_compiler.nit @@ -814,8 +814,6 @@ class SeparateCompiler v.add("return {res};") end v.add("\}") - - generate_check_init_instance(mtype) end # Add a dynamic test to ensure that the type referenced by `t` is a live type @@ -831,24 +829,6 @@ class SeparateCompiler v.add("\}") end - redef fun generate_check_init_instance(mtype) - do - if self.modelbuilder.toolcontext.opt_no_check_initialization.value then return - - var v = self.new_visitor - var c_name = mtype.mclass.c_name - var res = new RuntimeVariable("self", mtype, mtype) - self.provide_declaration("CHECK_NEW_{c_name}", "void CHECK_NEW_{c_name}({mtype.ctype});") - v.add_decl("/* allocate {mtype} */") - v.add_decl("void CHECK_NEW_{c_name}({mtype.ctype} {res}) \{") - if runtime_type_analysis.live_classes.has(mtype.mclass) then - self.generate_check_attr(v, res, mtype) - else - v.add_abort("{mtype.mclass} is DEAD") - end - v.add("\}") - end - redef fun new_visitor do return new SeparateCompilerVisitor(self) # Stats @@ -945,7 +925,7 @@ class SeparateCompilerVisitor var res = self.new_var(mtype) if not compiler.runtime_type_analysis.live_types.has(valtype) then self.add("/*no autobox from {value.mtype} to {mtype}: {value.mtype} is not live! */") - self.add("printf(\"Dead code executed!\\n\"); exit(1);") + self.add("printf(\"Dead code executed!\\n\"); show_backtrace(1);") return res end self.add("{res} = BOX_{valtype.c_name}({value}); /* autobox from {value.mtype} to {mtype} */") @@ -954,7 +934,7 @@ class SeparateCompilerVisitor # Bad things will appen! var res = self.new_var(mtype) self.add("/* {res} left unintialized (cannot convert {value.mtype} to {mtype}) */") - self.add("printf(\"Cast error: Cannot cast %s to %s.\\n\", \"{value.mtype}\", \"{mtype}\"); exit(1);") + self.add("printf(\"Cast error: Cannot cast %s to %s.\\n\", \"{value.mtype}\", \"{mtype}\"); show_backtrace(1);") return res end end @@ -1286,13 +1266,6 @@ class SeparateCompilerVisitor return self.new_expr("NEW_{mtype.mclass.c_name}(&type_{mtype.c_name})", mtype) end - redef fun check_init_instance(value, mtype) - do - if self.compiler.modelbuilder.toolcontext.opt_no_check_initialization.value then return - self.require_declaration("CHECK_NEW_{mtype.mclass.c_name}") - self.add("CHECK_NEW_{mtype.mclass.c_name}({value});") - end - redef fun type_test(value, mtype, tag) do self.add("/* {value.inspect} isa {mtype} */") @@ -1359,7 +1332,7 @@ class SeparateCompilerVisitor self.add("count_type_test_resolved_{tag}++;") end else - self.add("printf(\"NOT YET IMPLEMENTED: type_test(%s, {mtype}).\\n\", \"{value.inspect}\"); exit(1);") + self.add("printf(\"NOT YET IMPLEMENTED: type_test(%s, {mtype}).\\n\", \"{value.inspect}\"); show_backtrace(1);") end # check color is in table @@ -1543,7 +1516,6 @@ class SeparateCompilerVisitor self.add("((struct instance_{nclass.c_name}*){nat})->values[{i}] = (val*) {r};") end self.send(self.get_property("with_native", arrayclass.intro.bound_mtype), [res, nat, length]) - self.check_init_instance(res, arraytype) self.add("\}") return res end @@ -1700,6 +1672,7 @@ class SeparateRuntimeFunction 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 @@ -1777,6 +1750,7 @@ class VirtualRuntimeFunction 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 ? diff --git a/src/separate_erasure_compiler.nit b/src/separate_erasure_compiler.nit index e4256ea..7657c5c 100644 --- a/src/separate_erasure_compiler.nit +++ b/src/separate_erasure_compiler.nit @@ -333,8 +333,6 @@ class SeparateErasureCompiler self.generate_init_attr(v, res, mtype) v.add("return {res};") v.add("\}") - - generate_check_init_instance(mtype) end private fun build_class_vts_table(mclass: MClass): Bool do @@ -604,7 +602,6 @@ class SeparateErasureCompilerVisitor end var length = self.int_instance(array.length) self.send(self.get_property("with_native", arraytype), [res, nat, length]) - self.check_init_instance(res, arraytype) self.add("\}") return res end diff --git a/src/test_phase.nit b/src/test_phase.nit new file mode 100644 index 0000000..1b90355 --- /dev/null +++ b/src/test_phase.nit @@ -0,0 +1,57 @@ +# 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, "
", new Location(null, 0, 0, 0, 0)) + mainmodule.set_imported_mmodules(mmodules) +end + +toolcontext.run_global_phases(mainmodule) diff --git a/tests/nitdoc.args b/tests/nitdoc.args index f984808..7b96e23 100644 --- a/tests/nitdoc.args +++ b/tests/nitdoc.args @@ -1 +1 @@ -module_1.nit -d out/nitdoc_args1.write +module_1.nit -d $WRITE diff --git a/tests/nitmetrics.args b/tests/nitmetrics.args index f118275..3555fdb 100644 --- a/tests/nitmetrics.args +++ b/tests/nitmetrics.args @@ -1 +1 @@ ---all base_simple3.nit -d out/nitmetrics_args1.write +--all base_simple3.nit -d $WRITE diff --git a/tests/nitunit.args b/tests/nitunit.args index 2f1b55a..7572830 100644 --- a/tests/nitunit.args +++ b/tests/nitunit.args @@ -1 +1 @@ -test_nitunit.nit --no-color -o out/nitunit_args1.write +test_nitunit.nit --no-color -o $WRITE diff --git a/tests/sav/base_attr_gen_alt1.res b/tests/sav/base_attr_gen_alt1.res index 8b464b6..84092c8 100644 --- a/tests/sav/base_attr_gen_alt1.res +++ b/tests/sav/base_attr_gen_alt1.res @@ -1 +1 @@ -Runtime error: Uninitialized attribute @e (alt/base_attr_gen_alt1.nit:25) +Runtime error: Uninitialized attribute @e (alt/base_attr_gen_alt1.nit:26) diff --git a/tests/sav/base_attr_isset_alt3.res b/tests/sav/base_attr_isset_alt3.res index 87e43ad..4444c33 100644 --- a/tests/sav/base_attr_isset_alt3.res +++ b/tests/sav/base_attr_isset_alt3.res @@ -8,3 +8,20 @@ false 3 true false +4 +false +false +false +5 +true +false +false +6 +true +true +false +7 +true +true +true +1 diff --git a/tests/sav/base_attr_isset_alt4.res b/tests/sav/base_attr_isset_alt4.res index 132439d..9bf0682 100644 --- a/tests/sav/base_attr_isset_alt4.res +++ b/tests/sav/base_attr_isset_alt4.res @@ -8,3 +8,29 @@ false 3 true false +1 +false +false +2 +true +false +3 +true +false +4 +false +false +false +5 +true +false +false +6 +true +true +false +7 +true +true +true +1 diff --git a/tests/sav/error_mod_unk.res b/tests/sav/error_mod_unk.res index 095aae7..6df8be4 100644 --- a/tests/sav/error_mod_unk.res +++ b/tests/sav/error_mod_unk.res @@ -1 +1 @@ -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 diff --git a/tests/sav/nitg.res b/tests/sav/nitg.res index 55c09f5..7b0a116 100644 --- a/tests/sav/nitg.res +++ b/tests/sav/nitg.res @@ -22,6 +22,7 @@ --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 diff --git a/tests/sav/niti/base_attr_init_val_raf_alt1.res b/tests/sav/niti/base_attr_init_val_raf_alt1.res index 5f621c3..865f879 100644 --- a/tests/sav/niti/base_attr_init_val_raf_alt1.res +++ b/tests/sav/niti/base_attr_init_val_raf_alt1.res @@ -1 +1,3 @@ -Runtime error: Uninitialized attribute @j (base_attr_init_val1.nit:29) +Runtime error: Uninitialized attribute @j (alt/base_attr_init_val_raf_alt1.nit:24) +1 +1 diff --git a/tests/sav/niti/base_attr_isset_alt3.res b/tests/sav/niti/base_attr_isset_alt3.res index a2b8de7..650801b 100644 --- a/tests/sav/niti/base_attr_isset_alt3.res +++ b/tests/sav/niti/base_attr_isset_alt3.res @@ -1,4 +1,4 @@ -Runtime error: Uninitialized attribute _a2 (alt/base_attr_isset_alt3.nit:101) +Runtime error: Uninitialized attribute _a2 (alt/base_attr_isset_alt3.nit:43) 1 false false @@ -8,3 +8,20 @@ false 3 true false +4 +false +false +false +5 +true +false +false +6 +true +true +false +7 +true +true +true +1 diff --git a/tests/sav/niti/base_attr_isset_alt4.res b/tests/sav/niti/base_attr_isset_alt4.res index b83eb13..f569857 100644 --- a/tests/sav/niti/base_attr_isset_alt4.res +++ b/tests/sav/niti/base_attr_isset_alt4.res @@ -1,4 +1,4 @@ -Runtime error: Uninitialized attribute _a2 (alt/base_attr_isset_alt4.nit:100) +Runtime error: Uninitialized attribute _a2 (alt/base_attr_isset_alt4.nit:43) 1 false false @@ -8,3 +8,29 @@ false 3 true false +1 +false +false +2 +true +false +3 +true +false +4 +false +false +false +5 +true +false +false +6 +true +true +false +7 +true +true +true +1 diff --git a/tests/sav/niti/rterror_attr_def_alt7.res b/tests/sav/niti/rterror_attr_def_alt7.res index 88a5a1e..4c87b06 100644 --- a/tests/sav/niti/rterror_attr_def_alt7.res +++ b/tests/sav/niti/rterror_attr_def_alt7.res @@ -1 +1,2 @@ -Runtime error: Uninitialized attribute @a (alt/rterror_attr_def_alt7.nit:58) +Runtime error: Uninitialized attribute @a (alt/rterror_attr_def_alt7.nit:48) +c \ No newline at end of file diff --git a/tests/sav/nitmetrics_args1.res b/tests/sav/nitmetrics_args1.res index a773cd8..ebc4cab 100644 --- a/tests/sav/nitmetrics_args1.res +++ b/tests/sav/nitmetrics_args1.res @@ -36,42 +36,6 @@ Distribution of direct smallers 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) @@ -362,7 +326,10 @@ Number of live method definitions: 14 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 diff --git a/tests/sav/nitunit_args1.res b/tests/sav/nitunit_args1.res index eb930af..b37f396 100644 --- a/tests/sav/nitunit_args1.res +++ b/tests/sav/nitunit_args1.res @@ -1,10 +1,10 @@ -test_nitunit.nit:18,1--19,0: ERROR: test_nitunit.test_nitunit::X. (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. (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. - assert true - assert false + assert true + assert false assert undefined_identifier +"> assert undefined_identifier \ No newline at end of file +"> \ No newline at end of file diff --git a/tests/sav/rterror_attr_def_alt7.res b/tests/sav/rterror_attr_def_alt7.res index 11e8687..c4a680e 100644 --- a/tests/sav/rterror_attr_def_alt7.res +++ b/tests/sav/rterror_attr_def_alt7.res @@ -1 +1,2 @@ Runtime error: Uninitialized attribute @a (alt/rterror_attr_def_alt7.nit:24) +c \ No newline at end of file diff --git a/tests/sav/test_phase.res b/tests/sav/test_phase.res new file mode 100644 index 0000000..8bc4233 --- /dev/null +++ b/tests/sav/test_phase.res @@ -0,0 +1,12 @@ + -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 diff --git a/tests/test_nitunit.nit b/tests/test_nitunit.nit new file mode 100644 index 0000000..4afd56d --- /dev/null +++ b/tests/test_nitunit.nit @@ -0,0 +1,26 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2004-2008 Jean Privat +# +# 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 diff --git a/tests/testfull.sh b/tests/testfull.sh index 91e45bd..a7af53c 100755 --- a/tests/testfull.sh +++ b/tests/testfull.sh @@ -6,4 +6,19 @@ do 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 diff --git a/tests/tests.sh b/tests/tests.sh index 57f0a37..8de440f 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -176,7 +176,7 @@ function process_result() fi echo >>$xml "" echo >>$xml ">$xml -n 50 out/$pattern.diff.sav.log + cat -v out/$pattern.diff.sav.log | head >>$xml -n 50 echo >>$xml "]]>" nok="$nok $pattern" echo "$ii" >> "$ERRLIST" @@ -188,7 +188,7 @@ function process_result() fi echo >>$xml "" echo >>$xml ">$xml -n 50 out/$pattern.diff.sav.log + cat -v out/$pattern.diff.sav.log | head >>$xml -n 50 echo >>$xml "]]>" nok="$nok $pattern" echo "$ii" >> "$ERRLIST" @@ -200,13 +200,13 @@ function process_result() fi echo >>$xml "" echo >>$xml ">$xml out/$pattern.res + cat -v >>$xml out/$pattern.res echo >>$xml "]]>" nos="$nos $pattern" fi if test -s out/$pattern.cmp.err; then echo >>$xml ">$xml out/$pattern.cmp.err + cat -v >>$xml out/$pattern.cmp.err echo >>$xml "]]>" fi echo >>$xml "" @@ -456,7 +456,7 @@ END 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"