The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Pegex::Pegex::AST;
use Pegex::Base;
extends 'Pegex::Tree';

use Pegex::Grammar::Atoms;

has atoms => Pegex::Grammar::Atoms->new->atoms;
has extra_rules => {};

sub got_grammar {
    my ($self, $got) = @_;
    my ($meta_section, $rule_section) = @$got;
    my $grammar = {
        '+toprule' => $self->{toprule},
        %{$self->{extra_rules}},
        %$meta_section,
    };
    for my $rule (@$rule_section) {
        my ($key, $value) = %$rule;
        $grammar->{$key} = $value;
    }
    return $grammar;
}

sub got_meta_section {
    my ($self, $got) = @_;
    my $meta = {};
    for my $next (@$got) {
        my ($key, $val) = @$next;
        $key = "+$key";
        my $old = $meta->{$key};
        if (defined $old) {
            if (ref $old) {
                push @$old, $val;
            }
            else {
                $meta->{$key} = [ $old, $val ];
            }
        }
        else {
            $meta->{$key} = $val;
        }
    }
    return $meta;
}

sub got_rule_definition {
    my ($self, $got) = @_;
    my ($name, $value) = @$got;
    $name =~ s/-/_/g;
    $self->{toprule} = $name if $name eq 'TOP';
    $self->{toprule} ||= $name;
    return +{ $name => $value };
}

sub got_bracketed_group {
    my ($self, $got) = @_;
    my ($prefix, $group, $suffix) = @$got;
    set_modifier($group, $prefix);
    set_quantity($group, $suffix);
    return $group;
}

sub got_all_group {
    my ($self, $got) = @_;
    my $list = $self->get_group($got);
    die unless @$list;
    return $list->[0] if @$list == 1;
    return { '.all' => $list };
}

sub got_any_group {
    my ($self, $got) = @_;
    my $list = $self->get_group($got);
    die unless @$list;
    return $list->[0] if @$list == 1;
    return { '.any' => $list };
}

sub get_group {
    my ($self, $group) = @_;
    sub get {
        my $it = shift;
        my $ref = ref($it) or return;
        if ($ref eq 'HASH') {
            return($it || ());
        }
        elsif ($ref eq 'ARRAY') {
            return map get($_), @$it;
        }
        else {
            die;
        }
    };
    return [ get($group) ];
}

sub got_rule_part {
    my ($self, $got) = @_;
    my ($rule, $sep) = @$got;
    $rule = set_separator($rule, @$sep) if @$sep;
    return $rule;
}

sub got_rule_reference {
    my ($self, $got) = @_;
    my ($prefix, $ref1, $ref2, $suffix) = @$got;
    my $ref = $ref1 || $ref2;
    $ref =~ s/-/_/g;
    my $node = +{ '.ref' => $ref };
    if (my $regex = $self->atoms->{$ref}) {
        $self->{extra_rules}{$ref} = +{ '.rgx' => $regex };
    }
    set_modifier($node, $prefix);
    set_quantity($node, $suffix);
    return $node;
}

sub got_quoted_regex {
    my ($self, $got) = @_;
    $got =~ s/([^\w\`\%\:\<\/\,\=\;])/\\$1/g;
    return +{ '.rgx' => $got };
}

sub got_regex_rule_reference {
    my ($self, $got) = @_;
    my $ref = $got->[0] || $got->[1];
    return +{ '.ref' => $ref };
}

sub got_whitespace_maybe {
    my ($self) = @_;
    return +{ '.rgx' => '<_>'};
}

sub got_whitespace_must {
    my ($self) = @_;
    return +{ '.rgx' => '<__>'};
}

sub got_whitespace_must_start {
    my ($self) = @_;
    return +{ '.rgx' => '<__>'};
}

sub got_regular_expression {
    my ($self, $got) = @_;
    if (@$got == 2) {
        my $part = shift @$got;
        unshift @{$got->[0]}, $part;
    }

    my $regex = join '', map {
        if (ref($_)) {
            my $part;
            if (defined($part = $_->{'.rgx'})) {
                $part;
            }
            elsif (defined($part = $_->{'.ref'})) {
                "<$part>";
            }
            else {
                XXX $_;
            }
        }
        else {
            $_;
        }
    } @{$got->[0]};
    $regex =~ s!\(([ism]?\:|\=|\!)!(?$1!g;
    return +{ '.rgx' => $regex };
}

sub got_whitespace_token {
    my ($self, $got) = @_;
    my $token;
    if ($got =~ /^\~{1,2}$/) {
        $token = +{ '.ref' => ('_' x length($got)) };
    }
    elsif ($got =~ /^\-{1,2}$/) {
        $token = +{ '.ref' => ('_' x length($got)) };
    }
    elsif ($got eq '+') {
        $token = +{ '.ref' => '__' };
    }
    else {
        die;
    }
    return $token;
}

sub got_error_message {
    my ($self, $got) = @_;
    return +{ '.err' => $got };
}

sub set_modifier {
    my ($object, $modifier) = @_;
    return unless $modifier;
    if ($modifier eq '=') {
        $object->{'+asr'} = 1;
    }
    elsif ($modifier eq '!') {
        $object->{'+asr'} = -1;
    }
    elsif ($modifier eq '.') {
        $object->{'-skip'} = 1;
    }
    elsif ($modifier eq '+') {
        $object->{'-wrap'} = 1;
    }
    elsif ($modifier eq '-') {
        $object->{'-flat'} = 1;
    }
    else {
        die "Invalid modifier: '$modifier'";
    }
}

sub set_quantity {
    my ($object, $quantity) = @_;
    return unless $quantity;
    if ($quantity eq '?') {
        $object->{'+max'} = 1;
    }
    elsif ($quantity eq '*') {
        $object->{'+min'} = 0;
    }
    elsif ($quantity eq '+') {
        $object->{'+min'} = 1;
    }
    elsif ($quantity =~ /^(\d+)$/) {
        $object->{'+min'} = $1 + 0;
        $object->{'+max'} = $1 + 0;
    }
    elsif ($quantity =~ /^(\d+)-(\d+)$/) {
        $object->{'+min'} = $1 + 0;
        $object->{'+max'} = $2 + 0;
    }
    elsif ($quantity =~ /^(\d+)\+$/) {
        $object->{'+min'} = $1 + 0;
    }
    else {
        die "Invalid quantifier: '$quantity'";
    }
}

sub set_separator {
    my ($rule, $op, $sep) = @_;
    my $extra = ($op eq '%%');
    if (not defined $rule->{'+max'} and not defined $rule->{'+min'}) {
        $rule = {'.all' => [ $rule, {%$sep, '+max' => 1}, ] }
            if $extra;
        return $rule;
    }
    elsif (defined $rule->{'+max'} and defined $rule->{'+min'}) {
        my ($min, $max) = delete @{$rule}{qw(+min +max)};
        $min-- if $min > 0;
        $max-- if $max > 0;
        $rule = {
            '.all' => [
                $rule,
                {
                    '+min' => $min,
                    '+max' => $max,
                    '-flat' => 1,
                    '.all' => [
                        $sep,
                        {%$rule},
                    ],
                },
            ],
        };
    }
    elsif (not defined $rule->{'+max'}) {
        my $copy = {%$rule};
        my $min = delete $copy->{'+min'};
        my $new = {
            '.all' => [
                $copy,
                {
                    '+min' => 0,
                    '-flat' => 1,
                    '.all' => [
                        $sep,
                        {%$copy},
                    ],
                },
            ],
        };
        if ($rule->{'+min'} == 0) {
            $rule = $new;
            $rule->{'+max'} = 1;
        }
        elsif ($rule->{'+min'} == 1) {
            $rule = $new;
        }
        else {
            $rule = $new;
            $min-- if $min > 0;
            $rule->{'.all'}[-1]{'+min'} = $min;
        }
    }
    else {
        if ($rule->{'+max'} == 1) {
            delete $rule->{'+min'};
            $rule = {
                %$rule,
                '+max' => 1,
            };
            if ($extra) {
                $rule = { '.all' => [$rule, {%$sep, '+max' => 1}] };
            }
            return $rule;
        }
        else {
            XXX 'FAIL', $rule, $op, $sep;
        }
    }
    if ($extra) {
        push @{$rule->{'.all'}}, {%$sep, '+max' => 1};
    }
    return $rule;
}

1;