Merge: Introduce model_visitor
authorJean Privat <jean@pryen.org>
Thu, 22 Oct 2015 00:45:37 +0000 (20:45 -0400)
committerJean Privat <jean@pryen.org>
Thu, 22 Oct 2015 00:45:37 +0000 (20:45 -0400)
As requested, this is a basic visitor framework on models.
Services names are inspired from those in the AST visitor.

Fell free to comment the API or suggest ideas.

Pull-Request: #1776
Reviewed-by: Romain Chanoir <romain.chanoir@viacesi.fr>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

src/model/model_base.nit
src/model/model_visitor.nit [new file with mode: 0644]
src/test_model_visitor.nit [new file with mode: 0644]
src/uml/uml_class.nit
src/uml/uml_module.nit
tests/nitcg.skip
tests/sav/test_model_visitor.res [new file with mode: 0644]
tests/sav/test_model_visitor_args1.res [new file with mode: 0644]
tests/test_model_visitor.args [new file with mode: 0644]

index 8b33f15..a97ede8 100644 (file)
@@ -20,6 +20,7 @@ module model_base
 # The container class of a Nit object-oriented model.
 # A model knows modules, classes and properties and can retrieve them.
 class Model
+       super MEntity
 end
 
 # A named and possibly documented entity in the model.
diff --git a/src/model/model_visitor.nit b/src/model/model_visitor.nit
new file mode 100644 (file)
index 0000000..f6169cf
--- /dev/null
@@ -0,0 +1,141 @@
+# 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.
+
+# Simple visitor framework for Nit models.
+#
+# This module provides the `ModelVisitor` abstract class and
+# refines the classes of the `MEntity` hierarchy to add visiting related methods.
+#
+# A standard approach on complex visitor is to dispatch the `ModelVisitor::visit` on the
+# entities to do specific things.
+#
+# ~~~nitish
+# class FooVisitor
+#   super ModelVisitor
+#   redef fun visit(e) do e.foo_visit(self)
+# end
+#
+# redef class MEntity
+# do
+#   fun foo_vist(v: FooVisitor) do visit_all(v)
+# end
+#
+# redef class MClass
+# do
+#   redef fun foo_visit(v) do
+#      print self
+#      super
+#   end
+# end
+# ~~~
+module model_visitor
+
+import model
+
+# The abstract model visitor template.
+#
+# Specific visitor must implement the `visit` method to perform the work.
+abstract class ModelVisitor
+       # Visit the entity `e`.
+       #
+       # This method setups `current_entity` and call `visit`.
+       # If `e` is null, nothing is done.
+       fun enter_visit(e: nullable MEntity) do
+               if e == null then return
+               var old_entity = current_entity
+               current_entity = e
+               visit(e)
+               current_entity = old_entity
+       end
+
+       # The current visited entity
+       var current_entity: nullable MEntity = null
+
+       # Method to define in specific visitor.
+       #
+       # It should not be called directly but used by `enter_visit`
+       protected fun visit(e: MEntity) is abstract
+
+       # Filter classes and method on the visibility.
+       #
+       # If set, only the classes and method with at least the given
+       # 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
+       end
+end
+
+redef class MEntity
+       # Call `v.enter_visit` on all nested entities.
+       #
+       # See the specific implementation in the subclasses.
+       fun visit_all(v: ModelVisitor) do end
+end
+
+redef class Model
+       # Visit all the packages of the model.
+       redef fun visit_all(v) do
+               for x in mpackages do v.enter_visit(x)
+       end
+end
+
+redef class MPackage
+       # Visit the root group of the package.
+       redef fun visit_all(v) do
+               v.enter_visit(root)
+       end
+end
+
+redef class MGroup
+       # Visit all the subgroups and modules of the group.
+       redef fun visit_all(v) do
+               for x in in_nesting.direct_smallers do v.enter_visit(x)
+               for x in mmodules do v.enter_visit(x)
+       end
+end
+
+redef class MModule
+       # Visit all the classes and class definitions of the module.
+       #
+       # On class introduction, the `MClass` then the `MClassDef` are visited.
+       # On class refinement, only the `MClassDef` is visited (the `MClass` is visited in an imported module).
+       # 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 MClassDef
+       # Visit all the classes and class definitions of the module.
+       #
+       # On property introduction, the `MProperty` then the `MPropDef` are visited.
+       # On property redefinition, only the `MPropDef` is visited (the `MProperty` is visited in an inherited class).
+       # 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
+end
diff --git a/src/test_model_visitor.nit b/src/test_model_visitor.nit
new file mode 100644 (file)
index 0000000..be2899b
--- /dev/null
@@ -0,0 +1,65 @@
+# 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.
+
+# Example of model_visitor
+module test_model_visitor
+
+import test_phase
+import frontend
+import model_visitor
+import counter
+
+# Example visitor that just count kind of entities.
+class TestModelVisitor
+       super ModelVisitor
+
+       redef fun visit(e) do
+               if not doc_only or e.mdoc != null then
+                       cpt.inc(e.class_name)
+               end
+               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.
+# The main entry point is provided by `test_phase`,
+# This function is then automatically (unless errors where found).
+redef fun do_work(mainmodule, given_mmodules, modelbuilder)
+do
+       var model = modelbuilder.model
+
+       print "All entities:"
+       var v = new TestModelVisitor
+       v.enter_visit(model)
+       v.cpt.print_elements(10)
+
+       print "\nAll non-private entities:"
+       v = new TestModelVisitor
+       v.min_visibility = protected_visibility
+       v.enter_visit(model)
+       v.cpt.print_elements(10)
+
+       print "\nAll documented non-private entities:"
+       v = new TestModelVisitor
+       v.min_visibility = protected_visibility
+       v.doc_only = true
+       v.enter_visit(model)
+       v.cpt.print_elements(10)
+end
index 92317a4..aa93740 100644 (file)
@@ -44,7 +44,7 @@ end
 redef class Model
 
        # Generates a UML Class diagram from the entities of a `Model`
-       fun tpl_class(ctx: ToolContext, main: MModule): Writable do
+       redef fun tpl_class(ctx, main) do
                var t = new Template
                for i in mclasses do
                        if not ctx.private_gen and i.visibility != public_visibility then continue
index 6e80325..eda98e3 100644 (file)
@@ -42,7 +42,7 @@ end
 
 redef class Model
        # Returns a UML package diagram of `main`
-       fun tpl_module(ctx: ToolContext, main: MModule): Writable do
+       redef fun tpl_module(ctx, main) do
                return main.tpl_module(ctx, main)
        end
 end
index d8429ff..cc9df0a 100644 (file)
@@ -4,4 +4,5 @@ test_neo
 test_phase
 test_parser
 test_highlight
+test_model_visitor
 ^nit
diff --git a/tests/sav/test_model_visitor.res b/tests/sav/test_model_visitor.res
new file mode 100644 (file)
index 0000000..fd8dbb5
--- /dev/null
@@ -0,0 +1,2 @@
+Usage: [OPTION]... <file.nit>...
+Use --help for help
diff --git a/tests/sav/test_model_visitor_args1.res b/tests/sav/test_model_visitor_args1.res
new file mode 100644 (file)
index 0000000..1b30918
--- /dev/null
@@ -0,0 +1,26 @@
+All entities:
+ list:
+  MMethodDef: 17 (30.35%)
+  MMethod: 15 (26.78%)
+  MClassDef: 7 (12.50%)
+  MClass: 7 (12.50%)
+  MAttributeDef: 3 (5.35%)
+  MAttribute: 3 (5.35%)
+  Model: 1 (1.78%)
+  MGroup: 1 (1.78%)
+  MPackage: 1 (1.78%)
+  MModule: 1 (1.78%)
+
+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%)
+
+All documented non-private entities:
+ list:
diff --git a/tests/test_model_visitor.args b/tests/test_model_visitor.args
new file mode 100644 (file)
index 0000000..5f3aa2c
--- /dev/null
@@ -0,0 +1 @@
+base_simple3.nit