src/indexing: introduce `code_index`
authorAlexandre Terrasa <alexandre@moz-code.org>
Tue, 19 Jun 2018 20:07:44 +0000 (16:07 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Tue, 19 Jun 2018 20:22:13 +0000 (16:22 -0400)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

src/indexing/code_index.nit [new file with mode: 0644]
src/indexing/tests/test_code_index.nit [new file with mode: 0644]

diff --git a/src/indexing/code_index.nit b/src/indexing/code_index.nit
new file mode 100644 (file)
index 0000000..765fad0
--- /dev/null
@@ -0,0 +1,204 @@
+# 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.
+
+# An index that contains Nit code
+#
+# Model entities are indexed by their ANode.
+#
+# Vectorization is based on model usage such as:
+# * modules importation
+# * classes spcialization and refinement
+# * methods calls and refinements
+#
+# Example:
+# ~~~nitish
+# # Create the index
+# var index = new CodeIndex(toolcontext)
+# for mentity in mentities do
+#      index.index_mentity(mentity)
+# end
+#
+# # Match a piece of code
+# var matches = index.match_code("print \"Hello, World!\"")
+# for match in matches do
+#      print match
+# end
+# ~~~
+module code_index
+
+import vsm
+import semantize
+import parser_util
+
+# Index for Nit doc
+class CodeIndex
+       super VSMIndex
+
+       redef type DOC: CodeDocument
+
+       # ToolContext used to parse pieces of code
+       var toolcontext: ToolContext
+
+       # Index `mentity`
+       fun index_mentity(mentity: MEntity) do
+               var terms = vectorize_mentity(mentity)
+               var doc = new CodeDocument(mentity, terms)
+               index_document(doc, false)
+       end
+
+       # Match `code` with the index
+       fun match_code(code: String): Array[IndexMatch[DOC]] do
+               var node = parse_code(code)
+               if node == null then return new Array[IndexMatch[DOC]]
+               return match_node(node)
+       end
+
+       # Match `node` with the index
+       fun match_node(node: ANode): Array[IndexMatch[DOC]] do
+               var vector = vectorize_node(node)
+               return match_vector(vector)
+       end
+
+       # Parse a piece of code
+       private fun parse_code(code: String): nullable AModule do
+               # Try to parse code
+               var node = toolcontext.parse_something(code)
+               if not node isa AModule then return null
+
+               # Load code into model
+               var mbuilder = toolcontext.modelbuilder
+               mbuilder.load_rt_module(null, node, "tmp")
+               mbuilder.run_phases
+               return node
+       end
+
+       # Transform `node` in a Vector
+       private fun vectorize_node(node: ANode): Vector do
+               var visitor = new CodeIndexVisitor
+               visitor.enter_visit(node)
+               return visitor.vector
+       end
+
+       # Transform `mentity` in a Vector
+       private fun vectorize_mentity(mentity: MEntity): Vector do
+               var node = toolcontext.modelbuilder.mentity2node(mentity)
+               if node == null then return new Vector
+               return vectorize_node(node)
+       end
+end
+
+# A specific document for mentities code
+class CodeDocument
+       super Document
+       autoinit(mentity, terms_count)
+
+       # MEntity related to this document
+       var mentity: MEntity
+
+       redef var title = mentity.full_name is lazy
+
+       redef var uri = mentity.location.to_s is lazy
+end
+
+# Code index visitor
+#
+# Used to build a VSM Vector from a Nit ANode.
+private class CodeIndexVisitor
+       super Visitor
+
+       var vector = new Vector
+
+       redef fun visit(node) do
+               node.accept_code_index_visitor(self)
+       end
+end
+
+redef class ANode
+       private fun accept_code_index_visitor(v: CodeIndexVisitor) do
+               visit_all(v)
+       end
+end
+
+redef class AStdImport
+       redef fun accept_code_index_visitor(v) do
+               var mmodule = self.mmodule
+               if mmodule != null then
+                       v.vector.inc "import#{mmodule.full_name}"
+               end
+       end
+end
+
+redef class AStdClassdef
+       redef fun accept_code_index_visitor(v) do
+               var mclassdef = self.mclassdef
+               if mclassdef != null then
+                       if not mclassdef.is_intro then
+                               v.vector.inc "redef#{mclassdef.full_name}"
+                               v.vector.inc "redef#{mclassdef.mclass.full_name}"
+                       end
+               end
+               visit_all(v)
+       end
+end
+
+redef class ASuperPropdef
+       redef fun accept_code_index_visitor(v) do
+               var mtype = self.n_type.mtype
+               if mtype isa MClassType then
+                       v.vector.inc "super#{mtype.mclass.intro.full_name}"
+                       v.vector.inc "super#{mtype.mclass.full_name}"
+               end
+       end
+end
+
+redef class APropdef
+       redef fun accept_code_index_visitor(v) do
+               var mpropdef = self.mpropdef
+               if mpropdef != null then
+                       if not mpropdef.is_intro then
+                               v.vector.inc "redef#{mpropdef.mproperty.intro.full_name}"
+                               v.vector.inc "redef#{mpropdef.mproperty.full_name}"
+                       end
+               end
+               visit_all(v)
+       end
+end
+
+redef class ASendExpr
+       redef fun accept_code_index_visitor(v) do
+               var callsite = self.callsite
+               if callsite != null then
+                       var args = callsite.signaturemap.as(not null).map.length
+                       v.vector.inc "call#{callsite.mpropdef.full_name}({args})"
+                       v.vector.inc "call#{callsite.mpropdef.mproperty.full_name}({args})"
+                       v.vector.inc "call#{callsite.mpropdef.mclassdef.full_name}({args})"
+                       v.vector.inc "call#{callsite.mpropdef.mclassdef.mclass.full_name}({args})"
+               end
+               visit_all(v)
+       end
+end
+
+redef class ANewExpr
+       redef fun accept_code_index_visitor(v) do
+               var callsite = self.callsite
+               if callsite != null then
+                       var args = callsite.signaturemap.as(not null).map.length
+                       v.vector.inc "call#{callsite.mpropdef.full_name}({args})"
+                       v.vector.inc "call#{callsite.mpropdef.mproperty.full_name}({args})"
+                       v.vector.inc "new#{callsite.mpropdef.mclassdef.full_name}({args})"
+                       v.vector.inc "new#{callsite.mpropdef.mclassdef.mclass.full_name}({args})"
+               end
+               visit_all(v)
+       end
+end
diff --git a/src/indexing/tests/test_code_index.nit b/src/indexing/tests/test_code_index.nit
new file mode 100644 (file)
index 0000000..b2a8f2e
--- /dev/null
@@ -0,0 +1,74 @@
+# 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 test_code_index is test
+
+import code_index
+import frontend
+
+class TestCodeIndex
+       test
+
+       # CodeIndex used in tests
+       var test_index: CodeIndex is noinit
+
+       # Initialize test variables
+       #
+       # Must be called before test execution.
+       # FIXME should be before_all
+       fun build_test_env is before do
+               var test_path = "NIT_TESTING_PATH".environ.dirname
+               var test_src = test_path / "../../../tests/test_prog"
+
+               # build model
+               var toolcontext = new ToolContext
+               var model = new Model
+               var modelbuilder = new ModelBuilder(model, toolcontext)
+               var mmodules = modelbuilder.parse_full([test_src])
+               modelbuilder.run_phases
+               toolcontext.run_global_phases(mmodules)
+
+               # create index
+               var index = new CodeIndex(toolcontext)
+               for mmodule in mmodules do
+                       index.index_mentity(mmodule)
+               end
+               test_index = index
+               modelbuilder.paths.add test_src
+       end
+
+       fun test_find1 is test do
+               var query = "import game\n"
+               var matches = test_index.match_code(query)
+               assert matches.first.document.mentity.full_name == "test_prog::test_prog"
+       end
+
+       fun test_find2 is test do
+               var query = "import game\nimport rpg\n"
+               var matches = test_index.match_code(query)
+               assert matches.first.document.mentity.full_name == "test_prog::game"
+       end
+
+       fun test_find3 is test do
+               var query = "import game\nclass MyGame\nsuper Game\nredef fun start_game do end\nend\n"
+               var matches = test_index.match_code(query)
+               assert matches.first.document.mentity.full_name == "test_prog::game_examples"
+       end
+
+       fun test_find_error is test do
+               var query = "error"
+               var matches = test_index.match_code(query)
+               assert matches.is_empty
+       end
+end