{#
 # Copyright (c) 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 %}.push_back(static_cast<ParentRule*>(current)->last_child()){% else %} = static_cast<ParentRule*>(current)->last_child(){% endif %};
{% endmacro %}


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


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

{% set cpp_keywords = [
    'NULL','alignas', 'alignof', 'and', 'and_eq', 'asm', 'atomic_cancel', 'atomic_commit', 'atomic_noexcept',
    'auto', 'bitand', 'bitor', 'bool', 'break', 'case', 'catch', 'char', 'char8_t', 'char16_t',
    'char32_t', 'class', 'compl', 'concept', 'const', 'consteval', 'constexpr', 'constinit',
    'const_cast', 'continue', 'co_await', 'co_return', 'co_yield', 'decltype', 'default', 'delete',
    'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float',
    'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'noexcept',
    'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'private', 'protected', 'public', 'register',
    'reinterpret_cast', 'requires', 'return', 'short', 'signed', 'sizeof', 'static', 'static_assert',
    'static_cast', 'struct', 'switch', 'synchronized', 'template', 'this', 'thread_local', 'throw',
    'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void',
    'volatile', 'wchar_t', 'while', 'xor', 'xor_eq'
] %}

{% macro cpp_escape(name) %}
{%- if name in cpp_keywords -%}{{ name }}_{%- else -%}{{ name }}{%- endif -%}
{% endmacro %}


{% macro processRuleNode(node, inedge) %}
{% if inedge.reserve != 0 %}
_reserve({{ inedge.reserve }}, this, &{{ graph.name }}::{{ cpp_escape(node.id | join('_')) }}, {% if inedge.args %}{% for _, _, v in inedge.args %}{{ resolveVarRefs(v) }}, {% endfor %}{% endif %}current);
{% else %}
{{ cpp_escape(node.id | join('_')) }}({% if inedge.args %}{% for _, _, v in inedge.args %}{{ resolveVarRefs(v) }}, {% endfor %}{% endif %}current);
{% endif %}
{% endmacro %}


{% macro processCharsetNode(node, inedge) %}
static_cast<UnlexerRule*>(current)->src += _model->charset(current, {{ node.idx }}, {{ graph.name }}::_charsets[{{ node.charset }}]);
{% endmacro %}


{% macro processLiteralNode(node, inedge) %}
static_cast<UnlexerRule*>(current)->src += "{{ node.src | escape_string }}";
{% endmacro %}


{% macro processQuantifierNode(node, inedge) %}
{
    QuantifierContext quant{{ node.idx }}(rule, {{ node.idx }}, {{ node.start }}, {{ node.stop | replace('inf', 'INT_MAX') }}, {{ graph.name }}::_quant_sizes[{{ node.min_size }}], {{ inedge.reserve }});
    while (quant{{ node.idx }}()) {
        {
            QuantifiedContext quantified(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) %}
{
    AlternationContext alt{{ node.idx }}(rule, {{ node.idx }}, {{ graph.name }}::_alt_sizes[{{ node.min_sizes }}], {{ inedge.reserve }}, {% if node.conditions is sequence %}{ {% for cond in node.conditions %}static_cast<double>({{ resolveVarRefs(cond) }}){% if not loop.last %}, {% endif %}{% endfor %}}{% else %}{{ graph.name }}::_alt_conds[{{ node.conditions }}]{% endif %});
    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 %}
    size_t choice{{ node.idx }} = alt{{ node.idx }}();
    const char* alt_src = std::vector<const char*>{ {% for lit in simple_lits %}{% if lit is not none %}"{{ lit | escape_string }}"{% else %}nullptr{% endif %}{% if not loop.last %}, {% endif %}{% endfor %} }[choice{{ node.idx }}];
    RuleFn alt_rule = std::vector<RuleFn>{ {% for rule in simple_rules %}{% if rule is not none %}&{{ graph.name }}::{{ cpp_escape(rule | join('_')) }}{% else %}nullptr{% endif %}{% if not loop.last %}, {% endif %}{% endfor %} }[choice{{ node.idx }}];
    if (alt_src) {
        static_cast<UnlexerRule*>(current)->src += alt_src;
    } else {
        (this->*alt_rule)(current);
    }
    {% elif simple_lits %}
    static_cast<UnlexerRule*>(current)->src += std::vector<std::string>{ {% for lit in simple_lits %}"{{ lit | escape_string }}"{% if not loop.last %}, {% endif %}{% endfor %} }[alt{{ node.idx }}()];
    {% elif simple_rules %}
    std::vector<RuleFn> options{ {% for rule in simple_rules %}&{{ graph.name }}::{{ cpp_escape(rule | join('_')) }}{% if not loop.last %}, {% endif %}{% endfor %} };
    RuleFn fn = options[alt{{ node.idx }}()];
    (this->*fn)(current);
    {% else %}
    size_t choice{{ node.idx }} = alt{{ node.idx }}();
    switch (choice{{ node.idx }}) {
    {% for edge in node.out_edges if not edge.dst.is_lambda_alternative %}
        case {{ edge.dst.idx }}:
            {{ processNode(edge.dst, edge) | indent | indent | indent }}            break;
    {% 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 }}

#ifndef GRAMMARINATOR_GENERATED_{{ graph.name | upper }}_HPP
#define GRAMMARINATOR_GENERATED_{{ graph.name | upper }}_HPP

#include <climits>
#include <map>
#include <string>
#include <vector>

#undef EOF

#include "grammarinator/runtime/Generator.hpp"

{% if graph.superclass != 'Generator' %}
#include "{{ graph.superclass }}.hpp"


{% endif %}

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

using namespace grammarinator;
using namespace grammarinator::runtime;
using namespace grammarinator::tool;

class {{ graph.name }} : public {{ graph.superclass }}
{
public:
    explicit {{ graph.name }}(Model* model=new DefaultModel(), const std::vector<Listener*>& listeners={}, const RuleSize& limit=RuleSize::max()) : {{ graph.superclass }}(model, listeners, limit) {}

    {% for rule in graph.imag_rules %}
    virtual Rule* {{ cpp_escape(rule.id | join('_')) }}(Rule *parent = nullptr) {
        UnlexerRuleContext rule(this, "{{ rule.id | join('_') }}", parent);
        return rule.current();
    }
    {% endfor %}

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

    {% for rule in graph.rules %}
    {% if rule.options.get('virtual', graph.options.get('virtual', 'false')) == 'true' %}virtual {% endif %}Rule* {{ cpp_escape(rule.id | join('_')) }}({% for t, k, v in rule.args %}{{ t }} {{ k }}{% if v %}={{ resolveVarRefs(v) }}{% endif %}, {% endfor %}Rule *parent = nullptr) {
        {% if rule.labels or rule.args or rule.locals or rule.returns %}
        struct {
            {% for t, k, _ in rule.args %}
            {{ t }} {{ k }};
            {% endfor %}
            {% for t, k, v in (rule.locals + rule.returns) %}
            {{ t }} {{ k }};
            {% endfor %}
            {% for name, is_list in rule.labels.items() %}
            {%+ if is_list %}std::vector<{% endif %}Rule*{% if is_list%}>{% endif %} {{ name }};
            {% endfor %}
        } local_ctx {
            {% for t, k, _ in rule.args %}
            .{{ k }} = {{ k }},
            {% endfor %}
            {% for t, k, v in (rule.locals + rule.returns) %}
            {%+ if v %} .{{ k }} = {{ resolveVarRefs(v) }},{% endif %}
            {% endfor %}
            {% for name, is_list in rule.labels.items() %}
            {%+ if is_list %}.{{ name }} = {},{% endif %}
            {% endfor %}
        };
        {% endif %}
        {{ rule.type }}Context rule(this, "{% if rule.trampoline %}{{ rule.id[0] }}{% else %}{{ rule.name }}{% endif %}", parent{% if rule.type == 'UnlexerRule' and (rule.name,) in graph.immutables %}, true{% endif %});
        Rule* current = rule.current();
        {% for edge in rule.out_edges %}
        {{ processNode(edge.dst, edge) | indent | indent -}}
        {% endfor %}
        {# FIXME: missing rule.returns #}
        {% if rule.trampoline %}
        current->remove();
        current = static_cast<ParentRule*>(static_cast<ParentRule*>(current)->children[0])->children[0];
        current->remove();
        if (parent)
            static_cast<ParentRule*>(parent)->add_child(current);
        {% endif %}
        return current;
    }

    {% endfor %}

    using RuleFn = Rule* ({{ graph.name }}::*)(Rule *);

    inline static const std::string _default_rule = "{{ graph.default_rule }}";

    inline static const std::unordered_map<std::string, RuleFn> _rule_fns = {
        {% for rule in graph.rules %}
        {%+ if rule.args %}// {% endif %}{ "{{ rule.id | join('_') }}", &{{ graph.name }}::{{ cpp_escape(rule.id | join('_')) }} },
        {% endfor %}
    };

    inline static const std::unordered_map<std::string, RuleSize> _rule_sizes = {
        {% for rule in graph.rules %}
        { "{{ rule.id | join('_') }}", RuleSize({{ rule.min_size.depth }}, {{ rule.min_size.tokens }}) },
        {% endfor %}
    };

    inline static const std::vector<std::vector<RuleSize>> _alt_sizes = {
        {% 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 %}
    };

    inline static const std::vector<RuleSize> _quant_sizes = {
        {% for size in graph.quant_sizes %}
        RuleSize({{ size.depth }}, {{ size.tokens }}),  // {{ loop.index0 }}
        {% endfor %}
    };

private:
    inline static const std::vector<std::vector<double>> _alt_conds = {
        {% for alt_cond in graph.alt_conds %}
        { {{ alt_cond | join(', ') }} },  // {{ loop.index0 }}
        {% endfor %}
    };

    inline static const std::vector<std::vector<std::string>> _charsets = {
        {% for charset in graph.charsets %}
        Generator::_charset({ {% for range in charset %}{ {{ "0x%x, 0x%x" % (range[0], range[1]) }} }{% if not loop.last%}, {% endif %}{% endfor %} }),  // {{ loop.index0 }}
        {% endfor %}
    };

}; // class {{ graph.name }}

#endif // GRAMMARINATOR_GENERATED_{{ graph.name | upper }}_HPP
{# Ensure newline at end of file #}
