package Regex::CodeGen::IMCC; # Copyright (C) 2002-2006, The Perl Foundation. # $Id: IMCC.pm 21249 2007-09-13 06:33:24Z paultcochrane $ use Regex::Ops::Tree (); # For mark() use base 'Regex::CodeGen'; use strict; use warnings; my $fail_label = Regex::Ops::Tree::mark('FAIL'); sub init_context { my ( $self, $ctx ) = @_; $ctx->{rx_match} ||= 'rx_match'; $ctx->{rx_stack} ||= 'rx_stack'; $ctx->{rx_ptmp} ||= 'rx_ptmp'; $ctx->{rx_tmp} ||= 'rx_itmp'; $ctx->{rx_pos} ||= 'rx_pos'; $ctx->{rx_len} ||= 'rx_len'; $ctx->{rx_input} ||= 'rx_input'; $self->SUPER::init_context($ctx); } sub pushop { "push" } sub popop { "pop" } sub output_match_succeeded { return ( 'set ["!POS"], ', 'set ["!RESULT"], 1', 'add , , -1', 'set ["0";1], ' ); } sub output_match_failed { return ( 'set ["!POS"], ', 'set ["!RESULT"], 0', 'set ["0";1], -2' ); } sub value { my $name = shift; return '' if $name eq 'pos' || $name eq ''; return '' if $name eq 'tmp' || $name eq ''; return '' if $name eq 'ptmp' || $name eq ''; return $name; } sub dbgoto { my ( $self, $label ) = @_; return () unless $self->{DEBUG}; return () unless $self->{DEBUG_SUPPORT}; return ("bsr $label"); } ############### SIMPLE OUTPUT ############## sub output_terminate { return ""; } sub output_advance { my ( $self, $distance, $failLabel ) = @_; $failLabel = $self->output_label_use($failLabel); return ( "add , $distance # pos++", "gt , , $failLabel # past end of input?", 'set ["0";0], # group 0 start := pos' ); } sub output_increment { my ( $self, $distance, $failLabel ) = @_; die "invalid distance" if $distance =~ /[^\d\-]/; return () if $distance == 0; my $comment; if ( $distance == 1 ) { $comment = "pos++"; } elsif ( $distance == -1 ) { $comment = "pos--"; } elsif ( $distance > 0 ) { $comment = "pos += $distance"; } elsif ( $distance < 0 ) { $comment = "pos -= " . ( -$distance ); } return ("add , $distance # $comment"); } sub output_add { my ( $self, $var, $arg1, $arg2 ) = @_; my $realvar = value($var); return "add $realvar, $arg1, $arg2" if defined($arg2); return "add $realvar, $arg1"; } sub output_sub { my ( $self, $var, $amount ) = @_; $amount = 1 if !defined $amount; my $realvar = value($var); return "sub $realvar, $amount"; } sub output_set { my ( $self, $reg, $value ) = @_; $reg = value($reg); return "set $reg, $value"; } sub output_print { my ( $self, $what ) = @_; $what = value($what); return ("print $what"); } sub output_test { my ( $self, $test, $val1, $val2, $dest ) = @_; $val1 = value($val1); $val2 = value($val2); return "$test $val1, $val2, " . $self->output_label_use($dest); } sub output_eq { my ( $self, $val1, $val2, $dest ) = @_; $self->output_test( 'eq', $val1, $val2, $dest ); } sub output_ne { my ( $self, $val1, $val2, $dest ) = @_; $self->output_test( 'ne', $val1, $val2, $dest ); } sub output_lt { my ( $self, $val1, $val2, $dest ) = @_; $self->output_test( 'lt', $val1, $val2, $dest ); } sub output_le { my ( $self, $val1, $val2, $dest ) = @_; $self->output_test( 'le', $val1, $val2, $dest ); } sub output_gt { my ( $self, $val1, $val2, $dest ) = @_; $self->output_test( 'gt', $val1, $val2, $dest ); } sub output_ge { my ( $self, $val1, $val2, $dest ) = @_; $self->output_test( 'ge', $val1, $val2, $dest ); } sub output_if { my ( $self, $reg, $dest ) = @_; $reg = value($reg); return "if $reg, " . $self->output_label_use($dest); } sub output_unless { my ( $self, $reg, $dest ) = @_; $reg = value($reg); return "unless $reg, " . $self->output_label_use($dest); } sub output_check { my ( $self, $needed, $failLabel, $lenvar ) = @_; $lenvar ||= ""; my $fail = $self->output_label_use($failLabel); if ( $needed eq "1" ) { return "ge , $lenvar, $fail # need $needed more chars"; } elsif ( $needed eq "0" ) { return (); } else { return "sub , $lenvar, # need $needed more chars", "lt , $needed, $fail"; } } sub output_match { my ( $self, $code, $failLabel ) = @_; my $comment = Regex::Ops::Tree::isplain($code) ? " # match '" . chr($code) . "'" : ""; my @ops = ( "ord , , # tmp = INPUT[pos]", "ne , $code, " . $self->output_label_use($failLabel) . $comment, ); if ( $self->{DEBUG} ) { push @ops, 'print "matched(' . chr($code) . ') at "'; push @ops, 'print rx_pos'; push @ops, 'print "\n"'; } return @ops; } sub output_classmatch { my ( $self, $incexc, $failLabel ) = @_; my $passLabel = $self->{state}->genlabel("pass_charclass"); my @ops = ("ord , , # tmp = INPUT[pos]"); my $fail = $self->output_label_use($failLabel); my $pass = $self->output_label_use($passLabel); while (@$incexc) { my $first = shift(@$incexc); my $last = shift(@$incexc); if ( defined($last) ) { push @ops, "lt , $first, $fail" unless $first == 0; push @ops, "lt , $last, $pass"; } else { push @ops, "ge , $first, $pass"; } } push @ops, "branch $fail"; push @ops, $self->output_label_def($passLabel); return @ops; } sub output_initgroup { my ( $self, $group ) = @_; return ( "new , \"MatchRange\" # new group \"$group\"", "set [\"$group\"], " ); } sub output_setstart { my ( $self, $group, $value ) = @_; $value = value($value); return qq!set ["$group";0], $value # open group $group!; } sub output_setend { my ( $self, $group, $value, $adj ) = @_; $value = value($value); my @ops; if ($adj) { push @ops, "add , $value, $adj"; $value = ""; } push @ops, qq!set ["$group";1], $value # close group $group!; return @ops; } sub output_getstart { my ( $self, $reg, $group ) = @_; $reg = value($reg); return qq!set $reg, ["$group";0] # get group $group start!; } sub output_getend { my ( $self, $reg, $group ) = @_; $reg = value($reg); return qq!set $reg, ["$group";1] # get group $group end!; } sub output_delete { my ( $self, $n ) = @_; return qq!set ["$n";1], -2 # delete group $n!; } sub output_atend { my ( $self, $failLabel ) = @_; my $fail = $self->output_label_use($failLabel); return ( $self->dbprint("At end: % >= %?\n"), "lt , , $fail # at end?" ); } sub output_pushmark { my ($self) = @_; my @ops; if ( $self->{DEBUG} ) { push @ops, ( qq(print "PUSHED ) . ( @_ > 1 ? $_[1] : "mark" ) . qq(\\n") ); } push @ops, $self->pushop . " , -1 # pushmark"; return @ops; } sub output_pushindex { my ( $self, $reg ) = @_; $reg = value( defined($reg) ? $reg : 'pos' ); return $self->output_pushint($reg); } sub output_pushint { my ( $self, $reg, $db_desc ) = @_; $reg = value($reg); if ( $self->{DEBUG} ) { my $desc = $db_desc ? " ($db_desc)" : ""; return ( "set , ", $self->pushop . " , $reg", $self->dbprint("PUSHED[\%<>] INT: \%<$reg>$desc\n"), ); } return $self->pushop . " , $reg"; } sub output_save { my ( $self, $reg ) = @_; $reg = value($reg); return ("save $reg"); } sub output_restore { my ( $self, $reg ) = @_; $reg = value($reg); return ("restore $reg"); } sub output_refresh { my ( $self, $reg ) = @_; $reg = value($reg); return ( "restore $reg", "save $reg" ); } use vars qw($DEBUG_LABEL); sub output_popindex { my $self = shift; my ( $reg, $fallback ); if ( @_ == 1 ) { ( $reg, $fallback ) = ( 'pos', @_ ); } elsif ( @_ == 2 ) { ( $reg, $fallback ) = @_; } elsif ( @_ == 0 ) { die "Must always have fallback defined!"; } else { die "Too many arguments to popindex!"; } $reg = value($reg); my @ops = ( $self->popop . " , # popindex" ); if ( $self->{DEBUG} ) { push @ops, 'print "POPPED: "', "print ", 'print "\n"'; } # FIXME: Still have extra copy in many cases push @ops, "eq , -1, " . $self->output_label_use($fallback) . " # was a mark?"; push @ops, "set $reg, # nope, set pos := popped index" unless $reg eq ''; return @ops; } sub output_peekindex { my $self = shift; my ( $reg, $fallback ); if ( @_ == 1 ) { ( $reg, $fallback ) = ( 'pos', @_ ); } elsif ( @_ == 2 ) { ( $reg, $fallback ) = @_; } elsif ( @_ == 0 ) { die "Must always have fallback defined!"; } else { die "Too many arguments to popindex!"; } $reg = value($reg); return ( "set , [-1\] # peekindex", "eq , -1, " . $self->output_label_use($fallback) . " # was a mark?", "set $reg, # nope, set pos := popped index" ); } sub output_popint { my ( $self, $reg, $db_desc ) = @_; $reg = value($reg); if ( $self->{DEBUG} ) { my $desc = $db_desc ? " ($db_desc)" : ""; return ( "set , ", $self->popop . " $reg, ", $self->dbprint("POPPED[\%<>] INT: \%<$reg>$desc\n"), ); } else { return ( $self->popop . " $reg, # popint" ); } } sub output_substr { my ( $self, $dest, $src, $offset, $len ) = @_; return ("substr $dest, $src, $offset, $len"); } sub output_length { my ( $self, $dest, $string ) = @_; return ("length $dest, $string"); } sub output_arg { my ( $self, $name, $type, $value ) = @_; $value = value($value); $DB::single = 1 unless length($value); return ".arg $value"; } sub output_param { my ( $self, $name, $type, $reg ) = @_; $reg = value($reg); return ".param $type $reg"; } sub output_return { my ( $self, $rettype, $retval ) = @_; $retval = value($retval); return ( ".return $retval", "ret" ); } sub output_declare { my ( $self, $var, $type ) = @_; return (".local $type $var"); } sub output_rule_def { my ( $self, $name, $L_trymatch, $L_backup, $num_groups, $startup ) = @_; my $trymatch = $self->output_label_use($L_trymatch); my $backup = $self->output_label_use($L_backup); my @ops = split( /\n/, <<"END"); .sub _$name .param int .param string .param int .param pmc .local pmc .local pmc .local int .local int = new "Match" ["!INPUT"] = ["!GROUPS"] = $num_groups length , # cache the length in END push @ops, $self->output( $startup, $self->{ctx} ); push @ops, split( /\n/, <<"END"); if goto $trymatch goto $backup END return @ops; } sub output_rule_end { my ( $self, $name ) = @_; return ( "end", ".end # End of rule $name" ); } sub output_rule_pass { my ( $self, $name ) = @_; return ( $self->output_match_succeeded(), ".return ()" ); } sub output_rule_fail { my ( $self, $name ) = @_; return ( $self->output_match_failed(), ".return ()" ); } sub output_call_setup { my ( $self, $name, $uid ) = @_; return ".local pmc $uid"; } sub output_call { my ( $self, $name, $mode, $uid ) = @_; return split( /\n/, <<"END"); $uid = _$name($mode, , , ) = $uid\['!POS'] END } sub output_call_result { my ( $self, $uid, $name, $fail ) = @_; my $fail_label = $self->output_label_use($fail); my @ops; if ( defined $name ) { push @ops, "['$name'] = $uid"; } return ( @ops, " = $uid\['!RESULT']", "unless , $fail_label" ); } sub output_code { my ( $self, $code ) = @_; # Assume, for now, that the code is PIR code return ( "# START EMBEDDED PIR CODE", split( /\n/, substr( $code, 1, -1 ) ), "# END EMBEDDED PIR CODE" ); } 1; # Local Variables: # mode: cperl # cperl-indent-level: 4 # fill-column: 100 # End: # vim: expandtab shiftwidth=4: