From 85d6237979f9d6a40ea74b3e419d0ceee38b328f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A1ta=20Hodov=C3=A1n?= Date: Fri, 24 May 2024 13:04:44 +0200 Subject: [PATCH] Support rule level named actions (#221) --- docs/guide/actions.rst | 18 +++++++---- grammarinator/tool/processor.py | 15 ++++++++++ .../codegen/GeneratorTemplate.py.jinja | 6 ++++ tests/grammars/InitAfter.g4 | 30 +++++++++++++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 tests/grammars/InitAfter.g4 diff --git a/docs/guide/actions.rst b/docs/guide/actions.rst index abb2101..ba60cff 100644 --- a/docs/guide/actions.rst +++ b/docs/guide/actions.rst @@ -11,7 +11,7 @@ Actions ------- Actions serve the purpose of defining operations that cannot be expressed -solely through grammar rules. Actions can be defined either anonymously +solely through grammar rules. Actions can be defined either within rule definitions or within the global scope of the grammar (outside of any rule definitions), prefixed with specific directives. @@ -30,15 +30,21 @@ Named Actions ============= Named actions are similar to anonymous actions in that they are also inline -code blocks enclosed in braces. However, instead of placing them inside rule -definitions, named actions are inserted into the global scope. These named -actions allow for the inclusion of custom code that can be utilized across -multiple rules or provide global functionality to the grammar. They are +code blocks enclosed in braces. However, they have a prefix in form of +``@``, where the name part specifies their purpose. There are rule and +global level named actions. Rule level actions are ``init`` and ``after`` +and they define code blocks to be executed before any child node generation +(but after :meth:`~grammarinator.runtime.Listener.enter_rule` gets called) +and after the generation of all children (but before +:meth:`~grammarinator.runtime.Listener.exit_rule` gets called), respectively. +Global level named actions place the code blocks into the global scope. +These named actions allow for the inclusion of custom code that can be utilized +across multiple rules or provide global functionality to the grammar. They are prefixed with specific directives to indicate their purpose. These directives can take one of two values: ``header`` or ``members``. These named actions can be defined for both lexer/parser and combined grammars. -The syntax of named actions looks like: +The syntax of global named actions looks like: .. code-block:: antlr diff --git a/grammarinator/tool/processor.py b/grammarinator/tool/processor.py index 2f173ca..74cfd59 100644 --- a/grammarinator/tool/processor.py +++ b/grammarinator/tool/processor.py @@ -99,6 +99,8 @@ def __init__(self, name, type): self.args = [] self.locals = [] self.returns = [] + self.init = '' + self.after = '' def __str__(self): return f'{super().__str__()}; name: {self.id}' @@ -880,6 +882,19 @@ def build_expr(node, parent_id): rule.args = parse_arg_action_block(node, 'args') rule.locals = parse_arg_action_block(node.localsSpec(), 'locals') rule.returns = parse_arg_action_block(node.ruleReturns(), 'returns') + + for prequel in node.rulePrequel() or []: + rule_action = prequel.ruleAction() + if rule_action: + action_name = str(rule_action.identifier().TOKEN_REF() or rule_action.identifier().RULE_REF()) + if action_name not in ['init', 'after']: + continue + + src = ''.join(str(child) for child in rule_action.actionBlock().ACTION_CONTENT()).strip() + if action_name == 'init': + rule.init = src + elif action_name == 'after': + rule.after = src build_expr(node.ruleBlock(), parent_id) elif isinstance(node, (ANTLRv4Parser.RuleAltListContext, ANTLRv4Parser.AltListContext, ANTLRv4Parser.LexerAltListContext)): diff --git a/grammarinator/tool/resources/codegen/GeneratorTemplate.py.jinja b/grammarinator/tool/resources/codegen/GeneratorTemplate.py.jinja index 3fe1cab..5b46f8f 100644 --- a/grammarinator/tool/resources/codegen/GeneratorTemplate.py.jinja +++ b/grammarinator/tool/resources/codegen/GeneratorTemplate.py.jinja @@ -161,9 +161,15 @@ class {{ graph.name }}({{ graph.superclass }}): {% endif %} with {{ rule.type }}Context(self, '{{ rule.name }}', parent) as rule: current = rule.current + {% if rule.init %} + {{ resolveVarRefs(rule.init) }} + {% endif %} {% for edge in rule.out_edges %} {{ processNode(edge.dst, edge) | indent | indent | indent -}} {% endfor %} + {% if rule.after %} + {{ resolveVarRefs(rule.after) }} + {% endif %} {% for _, k, _ in rule.returns %} current.{{ k }} = local_ctx['{{ k }}'] {% endfor %} diff --git a/tests/grammars/InitAfter.g4 b/tests/grammars/InitAfter.g4 new file mode 100644 index 0000000..d0369fe --- /dev/null +++ b/tests/grammars/InitAfter.g4 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Renata Hodovan, Akos Kiss. + * + * Licensed under the BSD 3-Clause License + * . + * This file may not be copied, modified, or distributed except + * according to those terms. + */ + +/* + * This test checks whether the processing of @init and @after rule + * actions work as expected. + */ + +// TEST-PROCESS: {grammar}.g4 -o {tmpdir} +// TEST-GENERATE: {grammar}Generator.{grammar}Generator -j 1 -o {tmpdir}/{grammar}%d.txt + +grammar InitAfter; + +start : r=wrapped_rule {assert $r.testValue == 'endValue', $r.testValue} ; + +wrapped_rule returns [testValue] +@init { +$testValue = 'startValue' +} +@after { +$testValue = 'endValue' +} +: {assert $testValue == 'startValue', $testValue} A ; +A : 'a' ;