{#
 # Copyright (c) 2020-2025 Renata Hodovan, Akos Kiss.
 #
 # Licensed under the BSD 3-Clause License
 # <LICENSE.rst or https://opensource.org/licenses/BSD-3-Clause>.
 # This file may not be copied, modified, or distributed except
 # according to those terms.
 #}

{% macro resolveVarRefs(src) %}
{{ src | substitute('\$(?P<var_name>\\w+)', 'local_ctx[\'\\g<var_name>\']') -}}
{% endmacro %}


{% macro processVariableNode(node, inedge) %}
local_ctx['{{ node.name }}']{% if node.is_list %}.append(current.last_child){% else %} = current.last_child{% endif %}

{% endmacro %}


{% macro processActionNode(node, inedge) %}
{{ resolveVarRefs(node.src) }}
{% endmacro %}


{% macro processLambdaNode(node, inedge) %}
pass
{% endmacro %}

{% set py_keywords = [
    'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'case', 'class',
    'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if',
    'import', 'in', 'is', 'lambda', 'match', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
    'try', 'while', 'with', 'yield'
] %}

{% macro py_escape(name) %}
{%- if name in py_keywords -%}{{ name }}_{%- else -%}{{ name }}{%- endif -%}
{% endmacro %}


{% macro processRuleNode(node, inedge) %}
{% if inedge.reserve != 0 %}
self._reserve({{ inedge.reserve }}, self.{{ py_escape(node.id | join('_')) }}, {% if inedge.args %}{% for _, k, v in inedge.args %}{% if k %}{{ k }}={% endif %}{{ resolveVarRefs(v) }}, {% endfor %}{% endif %}parent=current)
{% else %}
self.{{ py_escape(node.id | join('_')) }}({% if inedge.args %}{% for _, k, v in inedge.args %}{% if k %}{{ k }}={% endif %}{{ resolveVarRefs(v) }}, {% endfor %}{% endif %}parent=current)
{% endif %}
{% endmacro %}


{% macro processCharsetNode(node, inedge) %}
current.src += self._model.charset(current, {{ node.idx }}, {{ graph.name }}._charsets[{{ node.charset }}])
{% endmacro %}


{% macro processLiteralNode(node, inedge) %}
current.src += '{{ node.src | escape_string }}'
{% endmacro %}


{% macro processQuantifierNode(node, inedge) %}
with QuantifierContext(rule, {{ node.idx }}, {{ node.start }}, {{ node.stop }}, {{ graph.name }}._quant_sizes[{{ node.min_size }}], {{ inedge.reserve }}) as quant{{ node.idx }}:
    while quant{{ node.idx }}():
        with QuantifiedContext(rule):
            current = rule.current
            {% for edge in node.out_edges %}
            {{ processNode(edge.dst, edge) | indent | indent | indent -}}
            {% endfor %}
current = rule.current
{% endmacro %}


{% macro processAlternationNode(node, inedge) %}
with AlternationContext(rule, {{ node.idx }}, {{ graph.name }}._alt_sizes[{{ node.min_sizes }}], {{ inedge.reserve }}, {% if node.conditions is sequence %}({{ resolveVarRefs(node.conditions | join(', ')) }}){% else %}{{ graph.name }}._alt_conds[{{ node.conditions }}]{% endif %}) as alt{{ node.idx }}:
    current = rule.current
    {% set simple_lits, simple_rules = node.simple_alternatives() %}
    {# In case of alternations with simple literals or rules, the selected option doesn't need to care about reserved tokens, since they have no siblings to spare budget for. #}
    {% if simple_lits and simple_rules %}
    choice{{ node.idx }} = alt{{ node.idx }}()
    alt_src = [{% for lit in simple_lits %}{% if lit is not none %}'{{ lit | escape_string }}'{% else %}None{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}][choice{{ node.idx }}]
    alt_rule = [{% for rule in simple_rules %}{% if rule is not none %}self.{{ py_escape(rule | join('_')) }}{% else %}None{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}][choice{{ node.idx }}]
    if alt_src is not None:
        current.src += alt_src
    else:
        alt_rule(parent=current)
    {% elif simple_lits %}
    current.src += [{% for lit in simple_lits %}'{{ lit | escape_string }}'{% if not loop.last %}, {% endif %}{% endfor %}][alt{{ node.idx }}()]
    {% elif simple_rules %}
    [{% for rule in simple_rules %}self.{{ py_escape(rule | join('_')) }}{% if not loop.last %}, {% endif %}{% endfor %}][alt{{ node.idx }}()](parent=current)
    {% else %}
    choice{{ node.idx }} = alt{{ node.idx }}()
    {% for edge in node.out_edges if not edge.dst.is_lambda_alternative %}
    {{ 'if' if loop.index0 == 0 else 'elif' }} choice{{ node.idx }} == {{ edge.dst.idx }}:
        {{ processNode(edge.dst, edge) | indent | indent -}}
    {% endfor %}
    {% endif %}
current = rule.current
{% endmacro %}


{% macro processAlternativeNode(node, inedge) %}
{% for edge in node.out_edges %}
{{ processNode(edge.dst, edge) -}}
{% endfor %}
{% endmacro %}


{% macro processNode(node, inedge) %}
{% set processors = {
    'QuantifierNode': processQuantifierNode,
    'UnlexerRuleNode': processRuleNode,
    'UnparserRuleNode': processRuleNode,
    'ImagRuleNode': processRuleNode,
    'CharsetNode': processCharsetNode,
    'LiteralNode': processLiteralNode,
    'AlternationNode': processAlternationNode,
    'AlternativeNode': processAlternativeNode,
    'ActionNode': processActionNode,
    'LambdaNode': processLambdaNode,
    'VariableNode': processVariableNode,
    }
%}
{{ processors[node.__class__.__name__](node, inedge) -}}
{% endmacro %}


# Generated by Grammarinator {{ version }}

from math import inf
from typing import Callable, ClassVar
from grammarinator.runtime import *

{% if graph.superclass != 'Generator' %}
if __name__ is not None and '.' in __name__:
    from .{{ graph.superclass }} import {{ graph.superclass }}
else:
    from {{ graph.superclass }} import {{ graph.superclass }}


{% endif %}

{%- if graph.header %}
{{ graph.header }}
{% endif -%}


class {{ graph.name }}({{ graph.superclass }}):

    {% for rule in graph.imag_rules %}
    def {{ py_escape(rule.id | join('_')) }}(self, parent: ParentRule | None = None) -> Rule:
        with UnlexerRuleContext(self, '{{ rule.id | join('_') }}', parent) as rule:
            current = rule.current
        return current
    {% endfor %}

    {%- if graph.members %}
    {{ graph.members | trim | indent }}
    {% endif %}

    {% for rule in graph.rules %}
    def {{ py_escape(rule.id | join('_')) }}(self, {% for t, k, v in rule.args %}{{ k }}{% if t %}: {{ t }}{% endif %}{% if v %} = {{ resolveVarRefs(v) }}{% endif %}, {% endfor %}parent: ParentRule | None = None) -> Rule:
        {% if rule.labels or rule.args or rule.locals or rule.returns %}
        local_ctx = {
            {%- for _, k, _ in rule.args -%}
            '{{ k }}': {{ k }}{% if not loop.last %}, {% endif %}
            {% endfor %}{% if rule.args and (rule.locals or rule.returns or rule.labels) %}, {% endif %}
            {% for _, k, v in (rule.locals + rule.returns) -%}
            '{{ k }}': {% if v %}{{ resolveVarRefs(v) }}{% else %}None{% endif %}{% if not loop.last %}, {% endif %}
            {% endfor %}{% if (rule.locals or rule.returns) and rule.labels %}, {% endif %}
            {% for name, is_list in rule.labels.items() -%}
            '{{ name }}': {% if is_list %}[]{% else %}None{% endif %}{% if not loop.last %}, {% endif %}
            {% endfor -%}
        }
        {% endif %}
        with {{ rule.type }}Context(self, {% if rule.trampoline %}'{{ rule.id[0] }}'{% else %}'{{ rule.name }}'{% endif %}, parent{% if rule.type == 'UnlexerRule' and (rule.name,) in graph.immutables %}, True{% endif %}) as rule:
            current = rule.current
            {% if rule.init %}
            {{ resolveVarRefs(rule.init) | indent | indent | indent }}
            {% endif %}
            {% for edge in rule.out_edges %}
            {{ processNode(edge.dst, edge) | indent | indent | indent -}}
            {% endfor %}
            {% if rule.after %}
            {{ resolveVarRefs(rule.after) | indent | indent | indent }}
            {% endif %}
            {% for _, k, _ in rule.returns %}
            current.{{ k }} = local_ctx['{{ k }}']
            {% endfor %}
        {% if rule.trampoline %}
        current.remove()
        current = current.children[0].children[0]
        current.remove()
        if parent:
            parent += current
        {% endif %}
        return current

    {% endfor %}

    _default_rule: ClassVar[Callable[['{{ graph.name }}', ParentRule | None], Rule]] = {{ py_escape(graph.default_rule) }}

    _rule_sizes: ClassVar[dict[str, RuleSize]] = {
        {% for rule in graph.rules %}
        '{{ rule.id | join('_') }}': RuleSize({{ rule.min_size.depth }}, {{ rule.min_size.tokens }}),
        {% endfor %}
    }

    _alt_sizes: ClassVar[tuple[tuple[RuleSize, ...], ...]] = (
        {% for alt_size in graph.alt_sizes %}
        ({% for size in alt_size %}RuleSize({{ size.depth }}, {{ size.tokens }}){% if not loop.last %}, {% endif %}{% endfor %}),  # {{ loop.index0 }}
        {% endfor %}
    )

    _alt_conds: ClassVar[tuple[tuple[float, ...], ...]] = (
        {% for alt_cond in graph.alt_conds %}
        ({{ alt_cond | join(', ') }}),  # {{ loop.index0 }}
        {% endfor %}
    )

    _quant_sizes: ClassVar[tuple[RuleSize, ...]] = (
        {% for size in graph.quant_sizes %}
        RuleSize({{ size.depth }}, {{ size.tokens }}),  # {{ loop.index0 }}
        {% endfor %}
    )

    _charsets: ClassVar[tuple[tuple[int, ...], ...]] = (
        {% for charset in graph.charsets %}
        Generator._charset(({% for range in charset %}({{ "0x%x, 0x%x" % (range[0], range[1]) }}), {% endfor %})),  # {{ loop.index0 }}
        {% endfor %}
    )
{# Ensure newline at end of file #}
