Merge: Faster hex parsing
authorJean Privat <jean@pryen.org>
Fri, 18 Dec 2015 20:27:41 +0000 (15:27 -0500)
committerJean Privat <jean@pryen.org>
Fri, 18 Dec 2015 20:27:41 +0000 (15:27 -0500)
As said in #1895, we need faster parsing of UTF-16 escaping sequences, this PR is the answer.

It makes the runtime of the `large_escaped` benchmark go down from ~5s to ~3.5s, and with valgrind, from 26GIr to 20GIr

Note: based on #1886, only the 4 last commits are of interest here

Pull-Request: #1896
Reviewed-by: Jean Privat <jean@pryen.org>

27 files changed:
.gitmodules [new file with mode: 0644]
README.md
benchmarks/json/Makefile [new file with mode: 0644]
benchmarks/json/bench_json.sh [new file with mode: 0755]
benchmarks/json/inputs/multiply_gov.sh [new file with mode: 0755]
benchmarks/json/inputs/multiply_twitter.sh [new file with mode: 0755]
benchmarks/json/scripts/c_parser.c [new file with mode: 0644]
benchmarks/json/scripts/json_ext.rb [new file with mode: 0644]
benchmarks/json/scripts/json_parse.go [new file with mode: 0644]
benchmarks/json/scripts/json_pure.rb [new file with mode: 0644]
benchmarks/json/scripts/nit_adhoc_utf_noropes.nit [new file with mode: 0644]
benchmarks/json/scripts/nit_adhoc_utf_ropes.nit [new file with mode: 0644]
benchmarks/json/scripts/nitcc_parser.nit [new file with mode: 0644]
benchmarks/json/scripts/python.py [new file with mode: 0644]
benchmarks/json/thirdparty/ujson4c [new submodule]
lib/core/text/abstract_text.nit
src/compiler/abstract_compiler.nit
src/highlight.nit
src/loader.nit
src/model/mmodule.nit
src/model/model.nit
src/model/model_views.nit [new file with mode: 0644]
src/model/model_visitor.nit
src/test_model_visitor.nit
src/testing/testing_doc.nit
tests/sav/nitx_args3.res
tests/sav/test_model_visitor_args1.res

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..aa7c8cc
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "ujson4c"]
+       path = benchmarks/json/thirdparty/ujson4c
+       url = https://github.com/esnme/ujson4c
index 0df1fcc..a382d3e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -57,4 +57,9 @@ To have your environment automatically configured at login, just source it with
 
     $ . misc/nit_env.sh install
 
-More information: <http://www.nitlanguage.org>
+
+Information, contacts and help:
+
+* Website <http://www.nitlanguage.org>
+* Issues <https://github.com/nitlang/nit/issues>
+* Chatroom <https://gitter.im/nitlang/nit>
diff --git a/benchmarks/json/Makefile b/benchmarks/json/Makefile
new file mode 100644 (file)
index 0000000..1c05dd2
--- /dev/null
@@ -0,0 +1,2 @@
+all:
+       ./bench_json.sh
diff --git a/benchmarks/json/bench_json.sh b/benchmarks/json/bench_json.sh
new file mode 100755 (executable)
index 0000000..1e00026
--- /dev/null
@@ -0,0 +1,128 @@
+#!/bin/bash
+# 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.
+
+# Shell script to bench json parsers over different documents
+
+source ../bench_common.sh
+source ../bench_plot.sh
+
+## CONFIGURATION OPTIONS ##
+
+# Default number of times a command must be run with bench_command
+# Can be overrided with 'the option -n'
+count=5
+
+## HANDLE OPTIONS ##
+
+function init_repo()
+{
+       echo "Preparing submodules"
+       git submodule update --init
+       echo "Submodules ready"
+       mkdir -p inputs
+       echo "Preparing data for benchmarks"
+       if [ ! -e inputs/large_escaped.json ]; then
+               echo "Downloading file 1/4"
+               wget -O inputs/large_escaped.json https://github.com/seductiveapps/largeJSON/blob/master/100mb.json?raw=true
+       fi
+       echo "File 1/4 ready"
+       if [ ! -e inputs/magic.json ]; then
+               echo "Downloading file 2/4"
+               wget -O inputs/magic.json http://mtgjson.com/json/AllSets-x.json
+       fi
+       echo "File 2/4 ready"
+       if [ ! -e inputs/big_twitter.json ]; then
+               echo "Downloading file 3/4"
+               wget -O inputs/twitter.json https://github.com/miloyip/nativejson-benchmark/raw/master/data/twitter.json
+               cd inputs
+               ./multiply_twitter.sh
+               rm twitter.json
+               cd ..
+       fi
+       echo "File 3/4 ready"
+       if [ ! -e inputs/big_gov_data.json ]; then
+               echo "Downloading file 4/4"
+               wget -O inputs/gov_data.json https://edg.epa.gov/data.json
+               cd inputs
+               ./multiply_gov.sh
+               rm gov_data.json
+               cd ..
+       fi
+       echo "File 4/4 ready"
+}
+
+function usage()
+{
+       echo "run_bench: ./bench_json.sh [options]"
+       echo "  -v: verbose mode"
+       echo "  -n count: number of execution for each bar (default: $count)"
+       echo "  -h: this help"
+}
+
+stop=false
+while [ "$stop" = false ]; do
+       case "$1" in
+               -v) verbose=true; shift;;
+               -h) usage; exit;;
+               -n) count="$2"; shift; shift;;
+               *) stop=true
+       esac
+done
+
+init_repo
+
+mkdir -p out
+
+echo "Compiling engines"
+
+echo "C JSON Parser"
+
+gcc -O2 -I thirdparty/ujson4c/src -I thirdparty/ujson4c/3rdparty/ thirdparty/ujson4c/3rdparty/ultrajsondec.c scripts/c_parser.c -o scripts/c_parser -lm
+
+echo "Go JSON Parser"
+
+go build -o scripts/json_parse scripts/json_parse.go
+
+echo "Nit/NitCC Parser"
+
+nitc --semi-global scripts/nitcc_parser.nit -o scripts/nitcc_parser
+
+echo "Nit/Ad-Hoc UTF-8 Parser, No Ropes"
+
+nitc --semi-global scripts/nit_adhoc_utf_noropes.nit -o scripts/nit_adhoc_utf_noropes
+
+echo "Nit/Ad-Hoc UTF-8 Parser, With Ropes"
+
+nitc --semi-global scripts/nit_adhoc_utf_ropes.nit -o scripts/nit_adhoc_utf_ropes
+
+declare -a script_names=('C' 'Python 3' 'Python 2' 'Go' 'Nit Ad-hoc UTF-8 No Ropes' 'Nit Ad-hoc UTF-8 + Ropes' 'Ruby ext')
+declare -a script_cmds=('./scripts/c_parser' 'python3 scripts/python.py' 'python2 scripts/python.py' './scripts/json_parse' './scripts/nit_adhoc_utf_noropes' './scripts/nit_adhoc_utf_ropes' 'ruby scripts/json_ext.rb')
+
+for script in `seq 1 ${#script_cmds[@]}`; do
+       echo "Preparing res for ${script_names[$script - 1]}"
+       prepare_res "./out/${script_names[$script - 1]}.dat" "${script_names[$script - 1]}" "${script_names[$script - 1]}"
+       for file in inputs/*.json; do
+               fname=`basename $file .json`
+               bench_command $file "Benching file $file using ${script_cmds[$script - 1]} parser" ${script_cmds[$script - 1]} $file
+       done;
+done;
+
+rm scripts/nitcc_parser
+rm scripts/json_parse
+rm scripts/c_parser
+rm scripts/nit_adhoc_utf_noropes
+rm scripts/nit_adhoc_utf_ropes
+
+plot out/bench_json.gnu
diff --git a/benchmarks/json/inputs/multiply_gov.sh b/benchmarks/json/inputs/multiply_gov.sh
new file mode 100755 (executable)
index 0000000..03eeac4
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+# 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.
+
+echo "[" > big_gov_data.json
+for i in $(seq 10); do
+  test "$i" != "1" && echo "," >> big_gov_data.json
+    cat gov_data.json >> big_gov_data.json
+done
+echo "]" >> big_gov_data.json
diff --git a/benchmarks/json/inputs/multiply_twitter.sh b/benchmarks/json/inputs/multiply_twitter.sh
new file mode 100755 (executable)
index 0000000..00619ae
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+# 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.
+
+echo "[" > big_twitter.json
+for i in $(seq 100); do
+  test "$i" != "1" && echo "," >> big_twitter.json
+    cat twitter.json >> big_twitter.json
+done
+echo "]" >> big_twitter.json
diff --git a/benchmarks/json/scripts/c_parser.c b/benchmarks/json/scripts/c_parser.c
new file mode 100644 (file)
index 0000000..dfaf745
--- /dev/null
@@ -0,0 +1,48 @@
+#include "ujdecode.c"
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+// Gets the byte size of a file
+int file_byte_size(char* path)
+{
+       int f = open(path, O_RDONLY);
+       if(f == -1)
+               return -1;
+       struct stat s;
+       int ln = 0;
+       if(!fstat(f, &s))
+               ln = s.st_size;
+       close(f);
+       return ln;
+}
+
+int main(int argc, char** argv)
+{
+       if(argc == 1) {
+               printf("Usage: ./c_parser file\n");
+               exit(1);
+       }
+
+       int fl_sz = file_byte_size(argv[1]);
+       char* input = malloc(fl_sz);
+
+       FILE* ifl = fopen(argv[1], "r");
+       if(ifl == NULL) {
+               printf("Error: cannot read file %s, are you sure permissions are set correctly ?\n", argv[1]);
+               exit(2);
+       }
+       int rd = fread(input, 1, fl_sz, ifl);
+       assert(rd == fl_sz);
+
+       void *state;
+
+       UJObject obj = UJDecode(input, fl_sz, NULL, &state);
+
+       free(input);
+       UJFree(state);
+}
diff --git a/benchmarks/json/scripts/json_ext.rb b/benchmarks/json/scripts/json_ext.rb
new file mode 100644 (file)
index 0000000..5c87062
--- /dev/null
@@ -0,0 +1,4 @@
+require 'json/ext'
+
+txt = IO.read(ARGV.first)
+my_hash = JSON.parse(txt)
diff --git a/benchmarks/json/scripts/json_parse.go b/benchmarks/json/scripts/json_parse.go
new file mode 100644 (file)
index 0000000..949a908
--- /dev/null
@@ -0,0 +1,20 @@
+package main
+
+import "io/ioutil"
+import "encoding/json"
+import "os"
+import "fmt"
+
+func main() {
+       if len(os.Args) == 1 {
+               fmt.Println("Usage ./json_parse file")
+               os.Exit(-1)
+       }
+       dat, err := ioutil.ReadFile(os.Args[1])
+       if err != nil { panic(err) }
+
+       var obj interface{}
+
+       jsonerr := json.Unmarshal(dat, &obj)
+       if jsonerr != nil { panic(jsonerr) }
+}
diff --git a/benchmarks/json/scripts/json_pure.rb b/benchmarks/json/scripts/json_pure.rb
new file mode 100644 (file)
index 0000000..2630cfd
--- /dev/null
@@ -0,0 +1,4 @@
+require 'json/pure'
+
+txt = IO.read(ARGV.first)
+my_hash = JSON.parse(txt)
diff --git a/benchmarks/json/scripts/nit_adhoc_utf_noropes.nit b/benchmarks/json/scripts/nit_adhoc_utf_noropes.nit
new file mode 100644 (file)
index 0000000..025a2e4
--- /dev/null
@@ -0,0 +1,18 @@
+# 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.
+
+import json::string_parser
+
+var text = args.first.to_path.read_all_bytes.to_s
+var json = text.parse_json
diff --git a/benchmarks/json/scripts/nit_adhoc_utf_ropes.nit b/benchmarks/json/scripts/nit_adhoc_utf_ropes.nit
new file mode 100644 (file)
index 0000000..75e1046
--- /dev/null
@@ -0,0 +1,21 @@
+# 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.
+
+import json::string_parser
+
+var f = new FileReader.open(args.first)
+
+var text = f.read_all
+f.close
+var json = text.parse_json
diff --git a/benchmarks/json/scripts/nitcc_parser.nit b/benchmarks/json/scripts/nitcc_parser.nit
new file mode 100644 (file)
index 0000000..f09b746
--- /dev/null
@@ -0,0 +1,18 @@
+# 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.
+
+import json
+
+var text = args.first.to_path.read_all
+var json = text.parse_json
diff --git a/benchmarks/json/scripts/python.py b/benchmarks/json/scripts/python.py
new file mode 100644 (file)
index 0000000..b8417e4
--- /dev/null
@@ -0,0 +1,5 @@
+import sys
+import json
+
+json_data=open(sys.argv[1]).read()
+data = json.loads(json_data)
diff --git a/benchmarks/json/thirdparty/ujson4c b/benchmarks/json/thirdparty/ujson4c
new file mode 160000 (submodule)
index 0000000..e14f3fd
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit e14f3fd5207fe30d1bdea723f260609e69d1abfa
index 0b07edb..fc8ec5b 100644 (file)
@@ -995,6 +995,65 @@ abstract class Text
                return s.plain_to_s
        end
 
+       # Return the Levenshtein distance between two strings
+       #
+       # ~~~
+       # assert "abcd".levenshtein_distance("abcd") == 0
+       # assert "".levenshtein_distance("abcd")     == 4
+       # assert "abcd".levenshtein_distance("")     == 4
+       # assert "abcd".levenshtein_distance("xyz")  == 4
+       # assert "abcd".levenshtein_distance("xbdy") == 3
+       # ~~~
+       fun levenshtein_distance(other: String): Int
+       do
+               var slen = self.length
+               var olen = other.length
+
+               # fast cases
+               if slen == 0 then return olen
+               if olen == 0 then return slen
+               if self == other then return 0
+
+               # previous row of distances
+               var v0 = new Array[Int].with_capacity(olen+1)
+
+               # current row of distances
+               var v1 = new Array[Int].with_capacity(olen+1)
+
+               for j in [0..olen] do
+                       # prefix insert cost
+                       v0[j] = j
+               end
+
+               for i in [0..slen[ do
+
+                       # prefix delete cost
+                       v1[0] = i + 1
+
+                       for j in [0..olen[ do
+                               # delete cost
+                               var cost1 = v1[j] + 1
+                               # insert cost
+                               var cost2 = v0[j + 1] + 1
+                               # same char cost (+0)
+                               var cost3 = v0[j]
+                               # change cost
+                               if self[i] != other[j] then cost3 += 1
+                               # keep the min
+                               v1[j+1] = cost1.min(cost2).min(cost3)
+                       end
+
+                       # Switch columns:
+                       # * v1 become v0 in the next iteration
+                       # * old v0 is reused as the new v1
+                       var tmp = v1
+                       v1 = v0
+                       v0 = tmp
+               end
+
+               return v0[olen]
+       end
+
        # Copies `n` bytes from `self` at `src_offset` into `dest` starting at `dest_offset`
        #
        # Basically a high-level synonym of NativeString::copy_to
index 626607e..905812e 100644 (file)
@@ -1659,7 +1659,7 @@ abstract class AbstractCompilerVisitor
        # This is used for the legacy FFI
        fun add_extern(mmodule: MModule)
        do
-               var file = mmodule.location.file.filename
+               var file = mmodule.filepath
                file = file.strip_extension(".nit")
                var tryfile = file + ".nit.h"
                if tryfile.file_exists then
index 424824f..59e5370 100644 (file)
@@ -986,7 +986,7 @@ redef class TComment
        redef fun make_tag(v)
        do
                var res = super
-               if not parent isa ADoc then
+               if is_loose then
                        res.add_class("nc_c")
                end
                return res
index c0891a1..6df4d61 100644 (file)
@@ -1011,9 +1011,6 @@ redef class ModelBuilder
 end
 
 redef class MModule
-       # The path of the module source
-       var filepath: nullable String = null
-
        # Force the parsing of the module using `modelbuilder`.
        #
        # If the module was already parsed, the existing ASI is returned.
@@ -1037,6 +1034,7 @@ redef class MModule
 
                # build the mmodule
                nmodule.mmodule = self
+               self.location = nmodule.location
                modelbuilder.build_a_mmodule(mgroup, nmodule)
 
                modelbuilder.parsed_modules.add self
index 6aa937e..7710bb2 100644 (file)
@@ -81,6 +81,9 @@ class MModule
        # The group of module in the package if any
        var mgroup: nullable MGroup
 
+       # The path of the module source, if any
+       var filepath: nullable String = null is writable
+
        # The package of the module if any
        # Safe alias for `mgroup.mpackage`
        fun mpackage: nullable MPackage
@@ -93,7 +96,7 @@ class MModule
        redef var name: String
 
        # The origin of the definition
-       var location: Location
+       var location: Location is writable
 
        # Alias for `name`
        redef fun to_s do return self.name
index b9d6aa2..3115286 100644 (file)
@@ -119,7 +119,17 @@ redef class Model
        end
 end
 
-# An OrderedTree that can be easily refined for display purposes
+# An OrderedTree bound to MEntity.
+#
+# We introduce a new class so it can be easily refined by tools working
+# with a Model.
+class MEntityTree
+       super OrderedTree[MEntity]
+end
+
+# A MEntityTree borned to MConcern.
+#
+# TODO remove when nitdoc is fully merged with model_collect
 class ConcernsTree
        super OrderedTree[MConcern]
 end
@@ -374,7 +384,7 @@ class MClass
 
        # The short name of the class
        # In Nit, the name of a class cannot evolve in refinements
-       redef var name: String
+       redef var name
 
        # The canonical name of the class
        #
@@ -583,7 +593,7 @@ class MClassDef
 
        # Internal name combining the module and the class
        # Example: "mymodule#MyClass"
-       redef var to_s: String is noinit
+       redef var to_s is noinit
 
        init
        do
@@ -1277,7 +1287,7 @@ class MGenericType
 
        # The short-name of the class, then the full-name of each type arguments within brackets.
        # Example: `"Map[String, List[Int]]"`
-       redef var to_s: String is noinit
+       redef var to_s is noinit
 
        # The full-name of the class, then the full-name of each type arguments within brackets.
        # Example: `"core::Map[core::String, core::List[core::Int]]"`
@@ -1299,7 +1309,7 @@ class MGenericType
                return res.to_s
        end
 
-       redef var need_anchor: Bool is noinit
+       redef var need_anchor is noinit
 
        redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual)
        do
@@ -1669,7 +1679,7 @@ class MNullableType
                self.to_s = "nullable {mtype}"
        end
 
-       redef var to_s: String is noinit
+       redef var to_s is noinit
 
        redef var full_name is lazy do return "nullable {mtype.full_name}"
 
@@ -1723,7 +1733,7 @@ end
 # The is only one null type per model, see `MModel::null_type`.
 class MNullType
        super MType
-       redef var model: Model
+       redef var model
        redef fun to_s do return "null"
        redef fun full_name do return "null"
        redef fun c_name do return "null"
@@ -1749,7 +1759,7 @@ end
 # Semantically it is the singleton `null.as_notnull`.
 class MBottomType
        super MType
-       redef var model: Model
+       redef var model
        redef fun to_s do return "bottom"
        redef fun full_name do return "bottom"
        redef fun c_name do return "bottom"
@@ -1885,7 +1895,7 @@ class MParameter
        super MEntity
 
        # The name of the parameter
-       redef var name: String
+       redef var name
 
        # The static type of the parameter
        var mtype: MType
@@ -1939,7 +1949,7 @@ abstract class MProperty
        var intro_mclassdef: MClassDef
 
        # The (short) name of the property
-       redef var name: String
+       redef var name
 
        # The canonical name of the property.
        #
@@ -2310,7 +2320,7 @@ abstract class MPropDef
 
        # Internal name combining the module, the class and the property
        # Example: "mymodule#MyClass#mymethod"
-       redef var to_s: String is noinit
+       redef var to_s is noinit
 
        # Is self the definition that introduce the property?
        fun is_intro: Bool do return isset mproperty._intro and mproperty.intro == self
@@ -2413,7 +2423,7 @@ end
 # Note this class is basically an enum.
 # FIXME: use a real enum once user-defined enums are available
 class MClassKind
-       redef var to_s: String
+       redef var to_s
 
        # Is a constructor required?
        var need_init: Bool
diff --git a/src/model/model_views.nit b/src/model/model_views.nit
new file mode 100644 (file)
index 0000000..3a88f45
--- /dev/null
@@ -0,0 +1,265 @@
+# 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.
+
+module model_views
+
+import model_visitor
+
+# Provide a configurable view to a model.
+#
+# This can be useful when you need to filter some mentities from a model
+# like private or fictive.
+#
+# TODO doc usage
+class ModelView
+       super ModelVisitor
+
+       # The model to view through `self`.
+       var model: Model
+
+       # MPackages visible through `self`.
+       var mpackages: Set[MPackage] is lazy do
+               var mpackages = new HashSet[MPackage]
+               for mpackage in model.mpackages do
+                       if not accept_mentity(mpackage) then continue
+                       mpackages.add mpackage
+               end
+               return mpackages
+       end
+
+       # MGroups visible through `self`.
+       var mgroups: Set[MGroup] is lazy do
+               var mgroups = new HashSet[MGroup]
+               for mpackage in mpackages do
+                       for mgroup in mpackage.mgroups do
+                               if not accept_mentity(mgroup) then continue
+                               mgroups.add mgroup
+                       end
+               end
+               return mgroups
+       end
+
+       # MModules visible through `self`.
+       var mmodules: Set[MModule] is lazy do
+               var mmodules = new HashSet[MModule]
+               for mmodule in model.mmodules do
+                       if not accept_mentity(mmodule) then continue
+                       mmodules.add mmodule
+               end
+               return mmodules
+       end
+
+       # MClasses visible through `self`.
+       var mclasses: Set[MClass] is lazy do
+               var mclasses = new HashSet[MClass]
+               for mclass in model.mclasses do
+                       if not accept_mentity(mclass) then continue
+                       mclasses.add mclass
+               end
+               return mclasses
+       end
+
+       # MClassDefs visible through `self`.
+       var mclassdefs: Set[MClassDef] is lazy do
+               var mclassdefs = new HashSet[MClassDef]
+               for mclass in mclasses do
+                       for mclassdef in mclass.mclassdefs do
+                               if not accept_mentity(mclassdef) then continue
+                               mclassdefs.add mclassdef
+                       end
+               end
+               return mclassdefs
+       end
+
+       # MProperties visible through `self`.
+       var mproperties: Set[MProperty] is lazy do
+               var mproperties = new HashSet[MProperty]
+               for mproperty in model.mproperties do
+                       if not accept_mentity(mproperty) then continue
+                       mproperties.add mproperty
+               end
+               return mproperties
+       end
+
+       # MPropdefs visible through `self`.
+       var mpropdefs: Set[MPropDef] is lazy do
+               var mpropdefs = new HashSet[MPropDef]
+               for mproperty in mproperties do
+                       for mpropdef in mproperty.mpropdefs do
+                               if not accept_mentity(mpropdef) then continue
+                               mpropdefs.add mpropdef
+                       end
+               end
+               return mpropdefs
+       end
+
+       # Lists all MEntities visible through `self`.
+       var mentities: Set[MEntity] is lazy do
+               var res = new HashSet[MEntity]
+               res.add_all mpackages
+               res.add_all mgroups
+               res.add_all mmodules
+               res.add_all mclasses
+               res.add_all mclassdefs
+               res.add_all mproperties
+               res.add_all mpropdefs
+               return res
+       end
+
+       private fun init_visitor(v: ModelVisitor) do
+               v.min_visibility = self.min_visibility
+               v.include_fictive = self.include_fictive
+               v.include_empty_doc = self.include_empty_doc
+               v.include_attribute = self.include_attribute
+               v.include_test_suite = self.include_test_suite
+       end
+
+       # Searches MEntities that match `name`.
+       fun mentities_by_name(name: String): Array[MEntity] do
+               var res = new Array[MEntity]
+               for mentity in mentities do if mentity.name == name then res.add mentity
+               return res
+       end
+
+       # Looks up a MEntity by its full `namespace`.
+       #
+       # Usefull when `mentities_by_name` returns conflicts.
+       #
+       # Namespaces must be of the form `package::core::module::Class::prop`.
+       fun mentities_by_namespace(namespace: String): Array[MEntity] do
+               var v = new LookupNamespaceVisitor(namespace)
+               init_visitor(v)
+               for mpackage in mpackages do
+                       v.enter_visit(mpackage)
+               end
+               return v.results
+       end
+
+       # Build an concerns tree with from `self`
+       fun to_tree: MEntityTree do
+               var v = new ModelTreeVisitor
+               init_visitor(v)
+               for mpackage in mpackages do
+                       v.enter_visit(mpackage)
+               end
+               return v.tree
+       end
+end
+
+class LookupNamespaceVisitor
+       super ModelVisitor
+
+       var namespace: String
+
+       private var parts: Array[String] is lazy do return namespace.split_with("::")
+
+       var results = new Array[MEntity]
+
+       redef fun visit(mentity) do mentity.accept_namespace_visitor(self)
+end
+
+class ModelTreeVisitor
+       super ModelVisitor
+
+       var tree = new MEntityTree
+
+       redef fun visit(mentity) do mentity.accept_tree_visitor(self)
+end
+
+redef class MEntity
+
+       # Get a public view of the model
+       fun public_view: ModelView do
+               var view = new ModelView(self.model)
+               view.min_visibility = public_visibility
+               return view
+       end
+
+       # Get a public view of the model
+       fun protected_view: ModelView do
+               var view = new ModelView(self.model)
+               view.min_visibility = protected_visibility
+               return view
+       end
+
+       # Get a public view of the model
+       fun private_view: ModelView do
+               var view = new ModelView(self.model)
+               view.min_visibility = private_visibility
+               return view
+       end
+
+       private fun accept_namespace_visitor(v: LookupNamespaceVisitor) do
+               if v.parts.is_empty then return
+               if name != v.parts.first then return
+               v.parts.shift
+               if v.parts.is_empty then
+                       v.results.add self
+                       return
+               end
+               visit_all(v)
+       end
+
+       private fun accept_tree_visitor(v: ModelTreeVisitor) do end
+end
+
+redef class MPackage
+       redef fun accept_tree_visitor(v) do
+               v.tree.add(null, self)
+               visit_all(v)
+       end
+end
+
+redef class MGroup
+       redef fun accept_tree_visitor(v) do
+               var parent = self.parent
+               if parent != null then
+                       v.tree.add(parent, self)
+               else
+                       v.tree.add(mpackage, self)
+               end
+               visit_all(v)
+       end
+end
+
+redef class MModule
+       redef fun accept_tree_visitor(v) do
+               v.tree.add(mgroup, self)
+               visit_all(v)
+       end
+end
+
+redef class MClass
+       # We don't want to collect classes from full namespace.
+       redef fun accept_namespace_visitor(v) do end
+end
+
+redef class MClassDef
+       redef fun accept_tree_visitor(v) do
+               v.tree.add(mmodule, self)
+               visit_all(v)
+       end
+end
+
+redef class MProperty
+       # We don't want to collect properties from full namespace.
+       redef fun accept_namespace_visitor(v) do end
+end
+
+redef class MPropDef
+       redef fun accept_tree_visitor(v) do
+               v.tree.add(mclassdef, self)
+               visit_all(v)
+       end
+end
index b7a2cd3..da881a0 100644 (file)
@@ -53,7 +53,7 @@ abstract class ModelVisitor
        # If `e` is null, nothing is done.
        fun enter_visit(e: nullable MEntity) do
                if e == null then return
-               if e.is_fictive and not include_fictive then return
+               if not accept_mentity(e) then return
                var old_entity = current_entity
                current_entity = e
                visit(e)
@@ -74,17 +74,68 @@ abstract class ModelVisitor
        # visibility level will be visited.
        var min_visibility: nullable MVisibility = null is writable
 
-       # Is `visibility` acceptable with regard to `min_visibility`?
-       private fun accept_visitibily(visibility: MVisibility): Bool
-       do
-               var min = min_visibility
-               return min == null or min <= visibility
+       # Can we accept this `mentity` in the view regarding its visibility?
+       fun accept_visibility(mentity: MEntity): Bool do
+               return mentity.accept_visibility(min_visibility)
        end
 
        # Include fictive entities?
        #
        # By default, fictive entities (see `MEntity::is_fictive`) are not visited.
        var include_fictive = false is writable
+
+       # Can we accept this `mentity` in the view regarding its fictivity?
+       fun accept_fictive(mentity: MEntity): Bool do
+               if include_fictive then return true
+               return not mentity.is_fictive
+       end
+
+       # Should we accept mentities with empty documentation?
+       #
+       # Default is `true`.
+       var include_empty_doc = true is writable
+
+       # Can we accept this `mentity` regarding its documentation?
+       fun accept_empty_doc(mentity: MEntity): Bool do
+               if include_empty_doc then return true
+               return mentity.mdoc != null
+       end
+
+       # Should we accept nitunit test suites?
+       #
+       # Default is `false`.
+       var include_test_suite = false is writable
+
+       # Can we accept this `mentity` regarding its test suite status?
+       fun accept_test_suite(mentity: MEntity): Bool do
+               if include_test_suite then return true
+               if not mentity isa MModule then return true
+               return not mentity.is_test_suite
+       end
+
+       # Should we accept `MAttribute` instances?
+       #
+       # Default is `true`.
+       var include_attribute = true is writable
+
+       # Can we accept this `mentity` regarding its type?
+       fun accept_attribute(mentity: MEntity): Bool do
+               if include_attribute then return true
+               if mentity isa MAttribute then return false
+               if mentity isa MAttributeDef then return false
+               return true
+       end
+
+       # Should we accept this `mentity` from the view?
+       fun accept_mentity(mentity: MEntity): Bool do
+               if not accept_visibility(mentity) then return false
+               if not accept_fictive(mentity) then return false
+               if not accept_empty_doc(mentity) then return false
+               if not accept_test_suite(mentity) then return false
+               if not accept_attribute(mentity) then return false
+               return true
+       end
+
 end
 
 redef class MEntity
@@ -92,6 +143,8 @@ redef class MEntity
        #
        # See the specific implementation in the subclasses.
        fun visit_all(v: ModelVisitor) do end
+
+       private fun accept_visibility(min_visibility: nullable MVisibility): Bool do return true
 end
 
 redef class Model
@@ -124,13 +177,19 @@ redef class MModule
        # On class importation, nothing is visited (the `MClass` and the `MClassDef` are visited in imported modules).
        redef fun visit_all(v) do
                for x in mclassdefs do
-                       if not v.accept_visitibily(x.mclass.visibility) then return
                        if x.is_intro then v.enter_visit(x.mclass)
                        v.enter_visit(x)
                end
        end
 end
 
+redef class MClass
+       redef fun accept_visibility(min_visibility) do
+               if min_visibility == null then return true
+               return visibility >= min_visibility
+       end
+end
+
 redef class MClassDef
        # Visit all the classes and class definitions of the module.
        #
@@ -139,9 +198,27 @@ redef class MClassDef
        # On property inheritance, nothing is visited (the `MProperty` and the `MPropDef` are visited in inherited classes).
        redef fun visit_all(v) do
                for x in mpropdefs do
-                       if not v.accept_visitibily(x.mproperty.visibility) then return
                        if x.is_intro then v.enter_visit(x.mproperty)
                        v.enter_visit(x)
                end
        end
+
+       redef fun accept_visibility(min_visibility) do
+               if min_visibility == null then return true
+               return mclass.visibility >= min_visibility
+       end
+end
+
+redef class MProperty
+       redef fun accept_visibility(min_visibility) do
+               if min_visibility == null then return true
+               return visibility >= min_visibility
+       end
+end
+
+redef class MPropDef
+       redef fun accept_visibility(min_visibility) do
+               if min_visibility == null then return true
+               return mproperty.visibility >= min_visibility
+       end
 end
index 9dd07e8..3569400 100644 (file)
@@ -25,17 +25,12 @@ class TestModelVisitor
        super ModelVisitor
 
        redef fun visit(e) do
-               if not doc_only or e.mdoc != null then
-                       cpt.inc(e.class_name)
-               end
+               cpt.inc(e.class_name)
                e.visit_all(self)
        end
 
        # Counter of visited entities (by classnames)
        var cpt = new Counter[String]
-
-       # Do the visitor only count entities with a documentation?
-       var doc_only = false
 end
 
 # The body of the specific work.
@@ -47,12 +42,14 @@ do
 
        print "All entities, including fictive ones:"
        var v = new TestModelVisitor
+       v.min_visibility = private_visibility
        v.include_fictive = true
        v.enter_visit(model)
        v.cpt.print_elements(10)
 
        print "All entities:"
        v = new TestModelVisitor
+       v.min_visibility = private_visibility
        v.enter_visit(model)
        v.cpt.print_elements(10)
 
@@ -65,7 +62,21 @@ do
        print "\nAll documented non-private entities:"
        v = new TestModelVisitor
        v.min_visibility = protected_visibility
-       v.doc_only = true
+       v.include_empty_doc = false
+       v.enter_visit(model)
+       v.cpt.print_elements(10)
+
+       print "\nAll public entities:"
+       v = new TestModelVisitor
+       v.min_visibility = public_visibility
        v.enter_visit(model)
        v.cpt.print_elements(10)
+
+       print "\nAll documented public entities:"
+       v = new TestModelVisitor
+       v.min_visibility = public_visibility
+       v.include_empty_doc = false
+       v.enter_visit(model)
+       v.cpt.print_elements(10)
+
 end
index 6bd8f0b..2c0e456 100644 (file)
@@ -276,7 +276,7 @@ class NitUnitExecutor
                end
                var opts = new Array[String]
                if mmodule != null then
-                       opts.add "-I {mmodule.location.file.filename.dirname}"
+                       opts.add "-I {mmodule.filepath.dirname}"
                end
                var cmd = "{nitc} --ignore-visibility --no-color '{file}' {opts.join(" ")} >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
                var res = sys.system(cmd)
index b8ecc63..6bf30a6 100644 (file)
@@ -4,15 +4,15 @@
  \e[1m\e[32mP\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
    \e[1m\e[30mbase_simple3\e[m\e[m
    package base_simple3
-   \e[30mbase_simple3.nit\e[m
+   \e[30mbase_simple3.nit:17,1--66,13\e[m
 
  \e[1m\e[32mG\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
    \e[1m\e[30mbase_simple3\e[m\e[m
    group base_simple3
-   \e[30mbase_simple3.nit\e[m
+   \e[30mbase_simple3.nit:17,1--66,13\e[m
 
  \e[1m\e[32mM\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
    \e[1m\e[30mbase_simple3::base_simple3\e[m\e[m
    module base_simple3
-   \e[30mbase_simple3.nit\e[m
+   \e[30mbase_simple3.nit:17,1--66,13\e[m
 
index 5a930fe..b058aab 100644 (file)
@@ -25,14 +25,28 @@ All entities:
 
 All non-private entities:
  list:
-  MMethodDef: 8 (24.24%)
-  MMethod: 7 (21.21%)
-  MClassDef: 7 (21.21%)
-  MClass: 7 (21.21%)
-  MModule: 1 (3.03%)
-  MGroup: 1 (3.03%)
-  MPackage: 1 (3.03%)
-  Model: 1 (3.03%)
+  MMethodDef: 17 (34.00%)
+  MMethod: 15 (30.00%)
+  MClassDef: 7 (14.00%)
+  MClass: 7 (14.00%)
+  MModule: 1 (2.00%)
+  MGroup: 1 (2.00%)
+  MPackage: 1 (2.00%)
+  Model: 1 (2.00%)
 
 All documented non-private entities:
  list:
+
+All public entities:
+ list:
+  MMethodDef: 14 (31.81%)
+  MMethod: 12 (27.27%)
+  MClassDef: 7 (15.90%)
+  MClass: 7 (15.90%)
+  MModule: 1 (2.27%)
+  MGroup: 1 (2.27%)
+  MPackage: 1 (2.27%)
+  Model: 1 (2.27%)
+
+All documented public entities:
+ list: