#!/usr/bin/perl use strict; use warnings; my $expr = Expr->new( q[ (File::Spec > 0.80 || Cwd) && {OSNAME} == 'linux' ] ); print $expr->eval() ? 'true' : 'false'; print "\n"; ############################################################ package Expr; use strict; use warnings; sub new { my $class = shift; my $expr = shift; my $lexer = Expr::Lexer->new( $expr ); $lexer->eval(); my $self = { lexer => $lexer, }; return bless( $self, $class ); } sub eval { my $self = shift; my $expr = $self->parse(); return $expr->eval(); } sub parse { my $self = shift; my %op_precedence = ( RELOP => 20, LOP => 10, ); my( @term_stack, @op_stack ); while ( my($token, $ttype) = $self->{lexer}->next() ) { if ( $ttype eq 'NAME' ) { my $expr = Expr::Symbol::Name->new( $token ); push( @term_stack, $expr ); } elsif ( $ttype eq 'VARIABLE' ) { my $expr = Expr::Symbol::Variable->new( $token ); push( @term_stack, $expr ); } elsif ( $ttype eq 'STRING' ) { my $expr = Expr::Literal::String->new( $token ); push( @term_stack, $expr ); } elsif ( $ttype eq 'NUMBER' ) { my $expr = Expr::Literal::Number->new( $token ); push( @term_stack, $expr ); } elsif ( $ttype eq 'RELOP' || $ttype eq 'LOP' ) { if ( @op_stack ) { my $prev_prec = $op_precedence{$op_stack[-1]->[1]}; my $curr_prec = $op_precedence{$ttype}; if ( $prev_prec == $curr_prec ) { $self->reduce( \@term_stack, \@op_stack ); push( @op_stack, [ $token, $ttype ] ); } elsif ( $prev_prec > $curr_prec ) { $self->reduce( \@term_stack, \@op_stack ) while @op_stack; push( @op_stack, [ $token, $ttype ] ); } else { push( @op_stack, [ $token, $ttype ] ); } } else { push( @op_stack, [ $token, $ttype ] ); } } elsif ( $ttype eq 'EOF' ) { $self->reduce( \@term_stack, \@op_stack ) while @op_stack; } elsif ( $ttype eq 'BEGIN_GROUP' ) { my $expr = $self->parse(); push( @term_stack, $expr ); } elsif ( $ttype eq 'END_GROUP' ) { $self->reduce( \@term_stack, \@op_stack ) while @op_stack; return pop( @term_stack ); } else { die "Unexpected token type: $ttype\n"; } } return pop( @term_stack ); } sub reduce { my $self = shift; my $term_stack = shift; my $op_stack = shift; my $op = pop( @$op_stack )->[0]; my $rhs = pop( @$term_stack ); my $lhs = pop( @$term_stack ); my $expr = Expr::BinaryOp->new( $lhs, $op, $rhs ); push( @$term_stack, $expr ); } 1; ############################################################ package Expr::Lexer; use strict; use warnings; sub new { my $class = shift; my $expr = shift; $expr =~ s/^\s+//; $expr =~ s/\s+$//; my $self = { expr => $expr, tokens => [], }; return bless( $self, $class ); } sub eval { my $self = shift; my @token_desc = ( { re => qr/(\()/, ttype => 'BEGIN_GROUP', }, { re => qr/(\))/, ttype => 'END_GROUP', }, { re => qr/(>=)/, ttype => 'RELOP', }, { re => qr/(>)/, ttype => 'RELOP', }, { re => qr/(<=)/, ttype => 'RELOP', }, { re => qr/(<)/, ttype => 'RELOP', }, { re => qr/(==)/, ttype => 'RELOP', }, { re => qr/(!=)/, ttype => 'RELOP', }, { re => qr/(&&)/, ttype => 'LOP', }, { re => qr/(\|\|)/, ttype => 'LOP', }, { re => qr/(\^\^)/, ttype => 'LOP', }, { re => qr/([[:alpha:]]\w+(?:::\w+)*(?=\W|$))/, ttype => 'NAME', }, { re => qr/{(\w+)}/, ttype => 'VARIABLE', }, { re => qr/(\d+(?:\.\d+)*|\d+\.|\.\d+)/, ttype => 'NUMBER', }, { re => qr/'([^']*)'/, ttype => 'STRING', }, { re => qr/"([^"]*)"/, ttype => 'STRING', }, ); my $text = $self->{expr}; while (length( $text )) { foreach my $token ( @token_desc ) { if ( $text =~ /^$token->{re}/ ) { push( @{$self->{tokens}}, [ $1, $token->{ttype} ] ); $text =~ s/^$token->{re}\s*//; } } } push( @{$self->{tokens}}, [ 'EOF', 'EOF' ] ); } sub next { my $self = shift; return @{$self->{tokens}} ? @{shift( @{$self->{tokens}} )} : (); } sub peek { my $self = shift; return @{$self->{tokens}} ? @{$self->{tokens}[0]} : (); } 1; ############################################################ package Expr::BinaryOp; use strict; use warnings; sub new { my $class = shift; my $self = { lhs => shift, op => shift, rhs => shift, }; return bless( $self, $class ); } sub eval { my $self = shift; my $lhs = $self->{lhs}; my $lval = $lhs->eval(); my $rhs = $self->{rhs}; my $rval = $rhs->eval(); my $op = $self->{op}; # strings if ( $op eq '==' ) { $op = 'eq' if $lhs->isa( 'Expr::Literal::String' ) || $rhs->isa( 'Expr::Literal::String' ); } elsif ( $op eq '!=' ) { $op = 'ne' if $lhs->isa( 'Expr::Literal::String' ) || $rhs->isa( 'Expr::Literal::String' ); } if ( $op eq '^^' ) { # special case return ($lval || $rval) && !($lval && $rval); } else { my $result; eval "\$result = \$lval $op \$rval"; return $result; } } 1; ############################################################ package Expr::Symbol; sub new { my $class = shift; my $symbol = shift; return bless( \$symbol, $class ); } sub eval { die "Not implemented.\n" } 1; ############################################################ package Expr::Symbol::Variable; use base qw(Expr::Symbol); use strict; use warnings; sub eval { my $self = shift; my $symtab = Expr::SymbolTable->instance(); return $symtab->lookup( $$self ); } 1; ############################################################ package Expr::Symbol::Name; use base qw(Expr::Symbol); use strict; use warnings; sub eval { my $self = shift; require Versions; my $vers; eval { $vers = Versions->from_module( $$self ) }; return $vers ? $vers : 0; } 1; ############################################################ package Expr::Literal; use strict; use warnings; sub new { my $class = shift; my $symbol = shift; return bless( \$symbol, $class ); } sub eval { my $self = shift; return $$self; } 1; ############################################################ package Expr::Literal::String; use base qw(Expr::Literal); 1; ############################################################ package Expr::Literal::Number; use base qw(Expr::Literal); 1; ############################################################ package Expr::SymbolTable; use strict; use warnings; my $singleton; sub instance { my $class = shift; return $singleton if defined( $singleton ); my $self = { OSNAME => $^O, }; $singleton = bless( $self, $class ); return $singleton; } sub lookup { my $self = shift; my $name = shift; return $self->{$name}; } sub set { my $self = shift; my $name = shift; my $value = shift; return $self->{$name} = $value; } sub unset { my $self = shift; my $name = shift; return delete $self->{$name}; } 1;