diff --git a/doc/source/conf.py b/doc/source/conf.py index 714f012..42b3641 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -58,9 +58,9 @@ # built documents. # # The short X.Y version. -version = u'1.3' +version = u'1.4' # The full version, including alpha/beta/rc tags. -release = u'1.3' +release = u'1.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index e833432..c594ebe 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -169,3 +169,34 @@ is provided with: with open('mycode') as f: # 'start' is the default rule name used to start parsing model = parser.parse(f.read(), rule_name='start') + +When using the ``NodeWalker`` class to traverse the model (built with the +``ModelBuilderSemantics`` class), those two functions are usefull: + +.. code-block:: python + + from link.utils.grammar import codegenerator, adopt_children, find_ancestor + from grako.model import ModelBuilderSemantics + + + with open('grammar.bnf') as f: + module = codegenerator('mydsl', 'MyDSL', f.read()) + + parser = module.MyDSLParser(semantics) + + with open('mycode') as f: + # 'start' is the default rule name used to start parsing + model = parser.parse(f.read(), rule_name='start') + +Use this before calling the NodeWalker in order to have nodes parent member set: + +.. code-block:: python + + adopt_children(model._ast, parent=model) + +When traversing the model, you may need to get informations about a parent node: + + pnode = find_ancestor(node, 'ParentNode') + + if pnode is not None: + # parent node was found diff --git a/link/utils/__init__.py b/link/utils/__init__.py index 0893146..adac240 100644 --- a/link/utils/__init__.py +++ b/link/utils/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = '1.3' +__version__ = '1.4' CONF_BASE_PATH = 'link/utils' diff --git a/link/utils/grammar.py b/link/utils/grammar.py index eb10283..e1cfe54 100644 --- a/link/utils/grammar.py +++ b/link/utils/grammar.py @@ -1,6 +1,8 @@ from grako.parser import GrakoGrammarGenerator from grako.codegen import pythoncg +from grako.util import Mapping +from grako.model import Node from six import exec_ import imp @@ -43,3 +45,58 @@ def codegenerator(modname, prefix, grammar): sys.modules[modname] = module return module + + +def adopt_children(ast, parent=None): + """ + Make sure that grako Nodes have the parent property set correctly. + + :param ast: Grako AST + :type ast: grako.model.Node, grako.util.Mapping or list + + :param parent: Parent Node + :type parent: grako.model.Node or None + """ + + childset = set() + + if isinstance(ast, Node) and ast not in childset: + if isinstance(parent, Node): + ast._parent = parent + childset.add(ast) + + elif isinstance(ast, Mapping): + for c in ast.values(): + adopt_children(c, parent=parent) + + elif isinstance(ast, list): + for c in ast: + adopt_children(c, parent=parent) + + for child in childset: + adopt_children(child._ast, parent=child) + + +def find_ancestor(node, classname): + """ + Find first node's ancestor which match class' name. + + :param node: Grako Node + :type node: grako.model.Node + + :param classname: Class' name + :type classname: str + + :returns: Node's ancestor or None if not found + :rtype: grako.model.Node + """ + + pnode = node.parent + + while pnode is not None: + if pnode.__class__.__name__ == classname: + break + + pnode = pnode.parent + + return pnode diff --git a/link/utils/test/grammar.py b/link/utils/test/grammar.py index a5521d9..038ae75 100644 --- a/link/utils/test/grammar.py +++ b/link/utils/test/grammar.py @@ -3,13 +3,14 @@ from b3j0f.utils.ut import UTCase from unittest import main -from link.utils.grammar import codegenerator +from link.utils.grammar import codegenerator, adopt_children, find_ancestor +from grako.model import ModelBuilderSemantics, Node from grako.exceptions import FailedToken import sys -class TestCodeGenerator(UTCase): - def test_module(self): +class TestGrammar(UTCase): + def test_codegenerator(self): mod = codegenerator('mydsl', 'MyDSL', 'dsl = "DSL" ;') self.assertEqual(mod.__name__, 'mydsl') @@ -23,6 +24,33 @@ def test_module(self): with self.assertRaises(FailedToken): parser.parse('dsl', rule_name='dsl') + def test_adopt_children(self): + mod = codegenerator('mydsl', 'MyDSL', ''' + subnode::SubNode = v:"DSL" ; + dsl::RootNode = sub:{ subnode }+ ; + ''') + + parser = mod.MyDSLParser(semantics=ModelBuilderSemantics()) + model = parser.parse('DSL', rule_name='dsl') + self.assertIsInstance(model, Node) + + adopt_children(model._ast, parent=model) + self.assertIs(model.sub[0].parent, model) + + def test_find_ancestor(self): + mod = codegenerator('mydsl', 'MyDSL', ''' + subsubnode::SubSubNode = v:"DSL" ; + subnode::SubNode = sub:subsubnode ; + dsl::RootNode = sub:subnode ; + ''') + + parser = mod.MyDSLParser(semantics=ModelBuilderSemantics()) + model = parser.parse('DSL', rule_name='dsl') + adopt_children(model._ast, parent=model) + pnode = find_ancestor(model.sub.sub, 'RootNode') + + self.assertIs(pnode, model) + if __name__ == '__main__': main()