Я не уверен на 100%, что понимаю вашу проблему, но здесь приведена очистка / улучшение ваших двух функций:
my %ops = ( # dispatch table for operations
average => sub {my $acc; $acc += $_ for @_; $acc / @_},
'+' => sub {$_[0] + $_[1]},
'-' => sub {$_[0] - $_[1]},
'*' => sub {$_[0] * $_[1]},
'mod' => sub {$_[0] % $_[1]},
(map {$_ => sub {$_[1] ? $_[0] / $_[1] : undef}} qw (/ div)),
);
sub Evaluate {
my $ex = shift;
print "evaluating: ", Dumper($ex), "\n" if $debug;
my $node_type = $ex->[0];
if ( $node_type eq 'leaf' ) {
print "returning leaf: $$ex[1]\n" if $debug;
return $$ex[1];
}
elsif ( $node_type ne 'internal' ) {
die "Eval: Strange node type '$node_type' when evaluating tree";
}
my $operation = $ex->[1];
my @values = map {Evaluate($_)} @$ex[2 .. $#$ex];
defined or return for @values;
if (my $op = $ops{$operation}) {
return $op->(@values);
} else {
print "operation $operation not found\n";
return undef;
}
}
Здесь большой блок if/elsif
заменен таблицей отправки.Это позволяет вам отделить логику от парсера.Я также заменил переменные $left_value
и $right_value
на массив @values
, что позволяет вашему коду масштабироваться до n-арных операций (например, average
).
Следующая функция Display
также была обновлена для обработки n-арных операций:
my %is_infix = map {$_ => 1} qw( * + / - );
sub Display {
my ($ex, $style) = @_;
my $node_type = $ex->[0];
# if a leaf, then the value is a number
if ( $node_type eq 'leaf' ) {
return $$ex[1];
}
# if not a leaf, then is internal,
if ( $node_type ne 'internal' ) {
die "Display: Strange node type '$node_type' when evaluating tree";
}
# should now have an operation and n arguments
my $operation = $ex->[1];
if ($style and $style eq 'infix') {
my @values = map {Display($_, $style)} @$ex[2 .. $#$ex];
if ($is_infix{$operation}) {
return "$values[0] $operation $values[1]"
} else {
local $" = ', '; # "
return "$operation( @values )"
}
} else { # postfix by default
my @out;
for (@$ex[2 .. $#$ex]) {
if (@out and $_->[0] eq 'internal') {
push @out, ';'
}
push @out, Display($_, $style)
}
return join ' ' => @out, $operation;
}
}
Вы можете вызвать Display
как Display($tree)
или Display($tree, 'postfix')
для постфиксной нотации.И Display($tree, 'infix')
для обозначения инфикса.
ex1 is 42
ex1: 42
ex1: 42
ex2 is 52
ex2: 42 10 +
ex2: 42 + 10
ex3 is 26.5
ex3: 42 10 + 1 average
ex3: average( 42 + 10, 1 )
То, что я считаю, то, что вы ищете.
Наконец, используя ваш первый пример 1 + average(3, 4, 5)
:
my $avg = ['internal', 'average', [leaf => 3], [leaf => 4], [leaf => 5] ];
my $ex4 = ['internal', '+', [leaf => 1], $avg ];
print "ex4 is ", Evaluate($ex4), "\n";
print "ex4: ", Display($ex4), "\n";
print "ex4: ", Display($ex4, 'infix'), "\n";
print "\n";
который печатает:
ex4 is 5
ex4: 1 ; 3 4 5 average +
ex4: 1 + average( 3, 4, 5 )