Perl5 の適当な手書きパーザで、簡単な数式をパースしてみる

パーザジェネレータを使うのは情弱だ、そんな風なことをいう kazuho さんもいる今日このごろなので、手書きでパーズしてみようとおもってみました。
Perl5 で適当にパースしたい場合は、こんな風に書くのが楽なのではないでしょうか。もっとスマートに書く方法もあるかもしらんですが。

my $src = "11*3+2/2";
use Data::Dumper;

say Dumper(parse($src));
exit;

sub parse {
    my $src = shift;
    my ($rest, $ret) = expression($src);
    if ($rest) {
        die "Parse failed: $rest";
    }
    $ret;
}

sub expression {
    my $src = $_[0];
    {
        my $c = $src;
        ($c, my $lhs) = term($c) or goto n1;
        ($c) = match($c, '+') or goto n1;
        ($c, my $rhs) = expression($c) or goto n1;
        return ($c, [$lhs, '+', $rhs]);
    }
n1:
    {
        my $c = $src;
        ($c, my $lhs) = term($c) or goto n2;
        ($c) = match($c, '-') or goto n2;
        ($c, my $rhs) = expression($c) or goto n2;
        return ($c, [$lhs, '-', $rhs]);
    }
n2:
    {
        my $c = $src;
        ($c, my $ret) = term($c) or goto err;
        return ($c, $ret);
    }
err:

    die "Parse Error";
}

sub term {
    my $src = shift;
    {
        my $c = $src;
        ($c, my $lhs) = primary($c) or goto n1;
        ($c) = match($c, '*') or goto n1;
        ($c, my $rhs) = term($c) or goto n1;
        return ($c, [$lhs, '*', $rhs]);
    }
n1:
    {
        my $c = $src;
        ($c, my $lhs) = primary($c) or goto n2;
        ($c) = match($c, '/') or goto n2;
        ($c, my $rhs) = term($c) or goto n2;
        return ($c, [$lhs, '/', $rhs]);
    }
n2:
    {
        my $c = $src;
        ($c, my $ret) = primary($c) or goto err;
        return ($c, $ret);
    }
err:
    die "Parse failed";
}

sub match {
    my ($c, $word) = @_;
    $word = quotemeta($word);
    $c =~ s/^\s*//;
    $c =~ s/^$word//
        or return ();
        warn "O";
    return ($c);
}

sub primary {
    local $_ = shift;
    s/^([1-9][0-9]*)//;
    ($_, $1);
}

【追記】
このコードは - とかが左結合になってなくてダメです。

Published: 2012-04-13(Fri) 01:56