3 # Copyright (C) 2003-2007, The Perl Foundation.
7 use vars
qw(@builtins @keywords);
12 @builtins = qw( abs asc atn
27 lbound lcase$ left$ len loc lof log lpos ltrim$
28 mid$ mkd$ mkdmbf$ mki$ mkl$ mks$ mksmbf$
29 peek pen play pmap point pos
31 sadd screen seek setmem sgn sin spc sqr
32 stick str$ strig string$
34 ubound ucase$ val varptr varptr$ varseg
36 @keywords = qw( access alias any append as
37 base beep binary bload bsave byval
38 call calls absolute interrupt base chain circle clear
39 close cls color com common const
40 data declare def fn seg defdbl defint deflng defsng defstr
41 dim do loop double draw
42 else elseif end endif environ erase error exit
43 field files for next function
45 if then input integer ioctl is
47 let line input list local locate lock unlock long loop
51 off on com error key pen play strig timer gosub goto com
53 paint palette pcopy pen play poke preset print using
55 random randomize read redim rem reset restore resume
57 screen seek select case shared shell single sleep
58 sound static stop sub swap system step
61 timer troff tron type to
62 uevent unlock until using
64 wait while wend width window write
69 print "Upcoming: $type[0] $syms[0]\n";
70 print "Current : $type[1] $syms[1]\n";
71 print "Previous: $type[2] $syms[2]\n";
74 sub isbuiltin
{ # Built in functions
75 return 0 unless defined $_[0];
76 return 1 if ( grep /^\Q$_[0]\E$/i, @builtins );
82 # print "Isuserfunc $_[0] and $funcname..";
83 return 0 unless defined $_[0];
84 return 0 if $funcname and $funcname eq $_[0]; # We're processing this, don't count!
85 if ( grep /^\Q$_[0]\E$/i, keys %functions ) {
97 #print STDERR "Looking up $_[0]$seg...\n";
98 $_ = ( grep /^\Q$_[0]$seg\E$/i, keys %arrays );
105 return ( isbuiltin
( $_[0] ) or isuserfunc
( $_[0] ) or isarray
( $_[0] ) );
109 return 1 if ( grep /^\Q$_[0]\E$/i, @keywords );
114 my ( $op, $next ) = @_;
116 #print STDERR "Precedence with '$op' and '$next'\n";
118 return 5 if ( $op eq "and" );
119 return 5 if ( $op eq "eqv" );
120 return 5 if ( $op eq "imp" );
121 return 5 if ( $op eq "or" );
122 return 5 if ( $op eq "xor" );
123 return 7 if ( $op eq "not" );
124 return 10 if ( $op eq "=" );
125 return 10 if ( $op eq ">=" );
126 return 10 if ( $op eq "<=" );
127 return 10 if ( $op eq "<>" );
128 return 10 if ( $op eq ">" );
129 return 10 if ( $op eq "<" );
130 return 15 if ( $op eq "," );
131 return 20 if ( $op eq "+" );
132 return 20 if ( $op eq "-" );
133 return 30 if ( $op eq "mod" );
134 return 40 if ( $op eq '\\' );
135 return 50 if ( $op eq "*" );
136 return 50 if ( $op eq "/" );
137 return 60 if ( $op eq "^" );
138 return 70 if ( $op eq "." );
140 return 99 if ( $op eq "UNARYMINUS" );
142 return 100 if ( isbuiltin
$op);
143 return 100 if ( isuserfunc
$op);
144 return 100 if ( isarray
$op
145 and ( ( $next and $next eq "(" ) or !$next ) );
147 return 0; # Not an operator
153 if ( $type eq "N" ) {
163 my ( $a1, $a2, $result ) = @_;
164 if ( $result =~ /S/ ) {
165 return ( "\tconcat $result, $a2, $a1", $result );
168 return ( "\t$result = $a1 + $a2", $result );
172 return ( "\t$_[2] = $_[1] - $_[0]", $_[2] );
175 return ( "\t$_[2] = $_[0] * $_[1]", $_[2] );
178 return ( "\t$_[2] = $_[1] / $_[0]", $_[2] );
181 my ( $a1, $a2, $result, $op ) = @_;
182 $op = "eq" unless $op;
185 return ( <<CODE, $result );
187 $op $a2, $a1, EQ_$eqnum
193 my ( $a1, $a2, $result, $op, $ot1, $ot2 ) = @_;
198 return ( <<CODE, $result );
200 eq $a1, $ot1, EQ_$eqnum
201 eq $a2, $ot2, EQ_$eqnum
207 my ( $a1, $a2, $result, $op, $ot1, $ot2 ) = @_;
212 return ( <<CODE, $result );
215 ne $a1, $ot1, EQ_$eqnum
216 ne $a2, $ot2, EQ_$eqnum
217 branch EQ_${eqnum}_false
218 EQ_$eqnum: set $result, 1.0
219 EQ_${eqnum}_false: noop
223 my ( $a1, $a2, $result, $op, $ot1, $ot2 ) = @_;
227 return ( <<CODE, $result );
229 eq $a1, $ot1, TRUE_${eqnum}
232 TRUE_${eqnum}: set $result, 1.0
235 die "$a1,$a2,$result,$op,$ot1,$ot2\n";
238 my ( $a1, $a2, $result, $op, $ot1, $ot2 ) = @_;
243 return ( <<CODE, $result );
246 eq $a1, $ot1, EQ_${eqnum}_a
249 eq $a2, $ot2, EQ_$eqnum
251 EQ_$eqnum: ne $result, 2.0, EQ_${eqnum}_end
253 EQ_${eqnum}_end: noop
257 my ( $a1, $a2, $result, $op, $ot1, $ot2 ) = @_;
262 return ( <<CODE, $result );
265 eq $a1, $ot1, EQ_${eqnum}_a
268 eq $a2, $ot2, EQ_$eqnum
270 EQ_$eqnum: eq $result, 0.0, EQ_${eqnum}_ok
272 branch EQ_${eqnum}_end
273 EQ_${eqnum}_ok: set $result, 1.0
274 EQ_${eqnum}_end: noop
278 my ( $a1, $a2, $result, $op, $ot1, $ot2 ) = @_;
283 ( $a1, $a2, $ot1, $ot2 ) = ( $a2, $a1, $ot2, $ot1 );
284 return ( <<CODE, $result );
287 eq $a1, $ot1, EQ_${eqnum}_end
288 ne $a2, $ot2, EQ_${eqnum}_end
290 EQ_${eqnum}_end: noop
295 my ( $a1, $a2, $result ) = @_;
296 return ( <<CODE, $result );
297 cmod $result, $a2, $a1
304 '<=' => sub { &{ $opsubs{"="} }( @_[ 0 .. 2 ], "le" ) },
305 '>=' => sub { &{ $opsubs{"="} }( @_[ 0 .. 2 ], "ge" ) },
306 '<>' => sub { &{ $opsubs{"="} }( @_[ 0 .. 2 ], "ne" ) },
307 '<' => sub { &{ $opsubs{"="} }( @_[ 0 .. 2 ], "lt" ) },
308 '>' => sub { &{ $opsubs{"="} }( @_[ 0 .. 2 ], "gt" ) }
314 #print STDERR "In RPN Convert...\n";
316 my ( @stack, @stream );
318 foreach my $item (@expr) {
319 die "Expression too complex at line $sourceline" if $i++ > 100; # Arbitrary.
320 my ( $sym, $type ) = @
$item;
322 #print "Got a $sym...\n";
328 push @stream, pop @stack while ( @stack and $stack[-1]->[0] ne "(" );
333 if ( $type eq "STRING"
334 or not precedence
( $sym, exists $expr[ $i + 1 ] ?
$expr[ $i + 1 ]->[0] : "NOTARR" ) )
336 push @stream, $item; # Operands, etc..
340 #print "It's an op!\n";
346 while ( @stack and precedence
( $stack[-1]->[0] ) >= precedence
( $item->[0] ) ) {
347 push @stream, pop @stack;
352 push @stream, reverse @stack;
354 #print STDERR "Outta RPN convert\n";
361 # Do the fixup. Unary minus, functions, etc.
364 for my $t ( 0 .. @foo - 1 ) {
365 my ( $unary, $argthing ) = ( 0, 0 );
366 my ( $prev, $this, $next ) = (
367 ( ( $t - 1 >= 0 ) ?
$foo[ $t - 1 ] : undef ),
368 $foo[$t], ( ( $t + 1 <= $#foo ) ?
$foo[ $t + 1 ] : undef )
370 if ( $this->[0] eq '-' and $this->[1] eq "PUN" ) {
371 if ( !defined $prev->[0] or $prev->[0] eq "(" ) {
374 elsif ( precedence
( $prev->[0], $next->[0] )
375 and not isarray
( $prev->[0] ) )
379 elsif ( iskeyword
( $syms[PREV
] ) and not isbuiltin
( $syms[PREV
] ) ) {
385 and hasargs
( $prev->[0] ) # This works, but damned if I know why.
386 #and $next->[0] ne ")"
390 # print "Argthing $prev->[0]\n";
393 if ( $this->[0] eq 'not' and $this->[1] ne "STRING" ) {
395 @expr, [ "0.0", "INT" ], # Cheating, making not a binary op
402 push( @expr, [ "-1.0", "INT" ], [ "*", "PUN" ] );
406 # Sadly, IMCC wants INTs to be INTs.
407 if ( $this->[1] eq "INT" ) {
411 if ( $this->[1] eq "BARE" ) {
412 $this->[0] = changename
( $this->[0] );
415 push( @expr, $foo[$t] );
417 # Functions, array lookups, and builtins are converted
420 # and commas become a low-precedence unary operator that means
421 # "Push the top of the stack onto the function's call stack"
422 # No-arg funcs are simply left alone.
424 push( @expr, [ "STARTARG", "STARTARG" ] );
435 goto PROCEXP_NOFEED
if $opts{lhs
};
436 goto PROCEXP_NOFEED
if $opts{nofeed
};
441 $parens++ if ( $syms[CURR
] eq "(" );
442 $parens-- if ( $syms[CURR
] eq ")" );
444 #print "Read $syms[CURR]...";
445 last if ( not $parens
446 and $syms[CURR
] eq "="
452 $type[CURR
] eq "STMT"
453 or $type[CURR
] eq "COMP"
454 or $type[CURR
] eq "COMM"
456 $type[CURR
] eq "BARE"
457 and ( iskeyword
( $syms[CURR
] )
458 and !isbuiltin
( $syms[CURR
] ) )
463 and not $opts{ignorecomma
}
464 and ( $syms[CURR
] eq ',' and $type[CURR
] ne "STRING" ) );
465 last if ( $syms[CURR
] eq ';' );
466 push( @expr, [ $syms[CURR
], $type[CURR
] ] );
467 if ( ( isbuiltin
( $syms[CURR
] ) or isuserfunc
( $syms[CURR
] ) )
468 and $syms[NEXT] ne "("
469 and $type[CURR
] ne "STRING" )
471 push( @expr, [ "(", "PUN" ] ); # Make sure no-arg funcs have at
472 push( @expr, [ ")", "PUN" ] ); # least token parenthesis...
477 $currentexpr = join( ' ', map { $_->[0] } @expr );
482 my ( $code, $optype, $sym, $type, $oldresult ) = @_;
485 if ( $type ne "RESULT" ) {
486 if ( $type =~ /STRING|INT|FLO|BARE/ ) {
488 if ( $type =~ /STRING/ ) {
492 if ( $type =~ /BARE/ ) {
493 if ( $sym =~ s/\$$/_string/ ) {
499 $main::code
{$main::seg
}->{declarations
}->{$sym} = 1
500 unless $main::code
{$main::seg
}->{declarations
}->{$sym};
504 elsif ( $type eq "STARTARG" ) {
508 die "Bad type for $sym? in expression '$currentexpr'";
517 my ( $code, $optype, $work ) = @_;
519 return unless @
$work;
522 while ( $$work[-1]->[0] ne "STARTARG" ) {
523 my $item = pop @
$work;
524 my $a1 = pushthing
( $code, $optype, @
$item );
525 push @args, [ $a1, @
$item ];
529 #push @$code, qq{\t.arg $_->[0]\t\t# $_->[0]};
531 pop @
$work; # REmove startarg tag...
532 return ( scalar @args, @args );
536 my ( $func, $extra ) = @_;
537 if ( $extra and $extra->[2] eq "STRING" ) {
540 if ( $func =~ /\$$/ ) {
548 sub generate_code
{ # Will return a result register, or something.
549 my ( $lhs, @stream ) = @_;
555 foreach my $token (@stream) {
556 my ( $sym, $type, $op ) = @
$token;
558 #print "Dealing with $sym $type $op\n";
564 next if ( $sym eq "," ); # Commas get ignored, args to stack
565 my ( $ac, @args, $extern, $pir_args );
566 if ( isarray
($sym) and $lhs ) {
567 ( $ac, @args ) = pushargs
( \
@code, \
$optype, \
@work );
568 $pir_args = join( ",", map { $_->[0] } ( reverse @args ) );
569 $pir_args = ",$pir_args" if $pir_args;
571 $optype = optype_of
($extern);
572 goto NEST_ARRAY_ASSIGN
if (@work); # Ugly, yeah sue me.
573 push @code, qq{\t_ARRAY_ASSIGN
("$extern$seg",INSERT NEW VALUE HERE
,$ac$pir_args)};
574 return ( "~Array", "$optype", @code );
576 elsif ( hasargs
($sym) ) {
577 ( $ac, @args ) = pushargs
( \
@code, \
$optype, \
@work );
578 $pir_args = join( ",", map { $_->[0] } ( reverse @args ) );
579 $pir_args = ",$pir_args" if $pir_args;
581 $optype = optype_of
($extern);
582 my ( $calling_code, @return_params );
583 if ( isarray
($sym) ) {
589 qq{\t\
$$optype$retcount = _ARRAY_LOOKUP_
$optype("$extern$seg",$ac$pir_args)};
590 push @work, [ "result of $extern()", "RESULT", "\$$optype$retcount" ];
592 elsif ( isbuiltin
($sym) ) {
593 $extern =~ s/\$/_string/g;
594 $extern =~ tr/a-z/A-Z/;
595 push @code, qq{\
$$optype$retcount = _BUILTIN_
$extern($ac$pir_args)};
596 push @work, [ "result of $extern()", "RESULT", "\$$optype$retcount" ];
599 $extern =~ s/\$/_string/g;
600 $extern =~ tr/a-z/A-Z/;
602 $calling_code = "(%s) = _USERFUNC_${extern}_run($ac$pir_args)";
603 push @work, [ "result of $extern()", "RESULT", "\$$optype$retcount" ];
606 # External functions return their arguments,
607 # except for PMC types. Figure if you want to locally
608 # modify those, go ahead. This simulates pass-by-reference.
609 foreach my $arg (@args) {
610 next if $arg->[0] =~ /^\$P\d+$/;
611 if ( $arg->[2] eq "BARE" ) {
612 push @return_params, $arg->[0];
615 push @return_params, "\$" . optype_of
( $arg->[0], $arg ) . $retcount++;
618 if (@return_params) {
619 push @code, sprintf( $calling_code, join( ",", @return_params ) );
622 push @code, sprintf( $calling_code, '' );
629 my ( $op1, $op2 ) = ( pop @work, pop @work );
630 my ( $a1, $a2, $ot1, $ot2 );
631 $ot1 = $ot2 = $optype;
632 $a1 = pushthing
( \
@code, \
$ot1, @
$op1 );
633 $a2 = pushthing
( \
@code, \
$ot2, @
$op2 );
635 if ( exists $opsubs{$sym} ) {
636 if ( !ref $opsubs{$sym} ) {
637 die "No op code yet for $sym\n";
640 my ( $code, $return ) =
641 &{ $opsubs{$sym} }( $a1, $a2, "\$$optype$retcount", "", $ot1, $ot2 );
642 ($optype) = $return =~ /([N|S])/;
647 die "Op $sym not implemented?";
649 push @work, [ "($op1->[0] $sym $op2->[0])", "RESULT", "\$$optype$retcount" ];
656 $result = pushthing
( \
@code, \
$optype, @
$_ );
659 return ( $result, $optype, @code );
662 sub build_assignment
{
663 my ( $left, $leftexpr, $right, $rightexpr, $righttype ) = @_;
666 if ( $left =~ /^\w+$/ ) {
667 if ( $left =~ /(_percent|_amp)$/ ) {
668 my $ti = "\$I" . ++$retcount;
669 my $tn = "\$N" . ++$retcount;
671 ( @
$rightexpr, "\tset $ti, $right\t# Truncate", "\tset $tn, $ti", "\t$left = $tn",
676 # Simple a=expr case.
685 s/INSERT NEW VALUE HERE/$right/g for @
$leftexpr;
686 s/--TYPE--/$righttype/g for @
$leftexpr;
688 @ass = ( @
$rightexpr, @
$leftexpr, );
696 %opts = %{ $_[0] } if @_;
697 my ( @expr, @stream, @left, $whole );
698 my ( $assignto, $result );
703 if ( $opts{assign
} ) {
705 #print STDERR "Assign\n";
707 @expr = get_expression
(%opts); # Get expression tokens
708 $whole .= join( ' ', map { $_->[0] } @expr );
709 @expr = fixup
(@expr); # Repair unary -, functions, etc...
710 @stream = convert_to_rpn
(@expr); # Get infix into RPN
711 ( $assignto, $type, @left ) =
712 generate_code
( $opts{lhs
}, @stream ); # Generate PASM code stream
713 feedme
(); # Eat the =
717 @expr = get_expression
(%opts); # Get expression tokens
718 $whole .= join( ' ', map { $_->[0] } @expr );
719 @expr = fixup
(@expr); # Repair unary -, functions, etc...
720 @stream = convert_to_rpn
(@expr); # Get infix into RPN
721 ( $result, $type, @stream ) = generate_code
( 0, @stream ); # Generate PASM code stream
723 @stream = build_assignment
( $assignto, \
@left, $result, \
@stream, $type );
726 elsif ( $opts{stuff
} ) {
728 #print STDERR "Stuff\n";
732 # Do the left-hand side
733 @expr = get_expression
(%opts); # Get expression tokens
734 #print STDERR Dumper(\@expr);
735 $whole .= join( ' ', map { $_->[0] } @expr );
736 @expr = fixup
(@expr); # Repair unary -, functions, etc...
737 @stream = convert_to_rpn
(@expr); # Get infix into RPN
738 #print STDERR "Stream:", join(' ', map { $_->[0] } @stream), "\n";
739 ( $assignto, $type, @left ) =
740 generate_code
( $opts{lhs
}, @stream ); # Generate PASM code stream
741 #print STDERR "Left: @left \n";
743 if ( $opts{choose
} ) {
744 $opts{stuff
} =~ s/X/$type/g;
747 # The rhs was passed in
748 @stream = build_assignment
( $assignto, \
@left, $opts{stuff
}, [], $type );
754 #print STDERR "Extract\n";
755 @expr = get_expression
(%opts); # Get expression tokens
756 $whole .= join( ' ', map { $_->[0] } @expr );
757 @expr = fixup
(@expr); # Repair unary -, functions, etc...
758 @stream = convert_to_rpn
(@expr); # Get infix into RPN
759 ( $result, $type, @stream ) = generate_code
( 0, @stream ); # Generate PASM code stream
763 ( "\t#\n", "\t# Evaluating $whole\n", "\t# Result in $result of type $type\n", @stream );
764 return ( $result, $type, @stream );
775 $name =~ s/(%|!|\#|&)$/$lookup{$1}/e;
776 $name =~ tr/A-Z/a-z/;
783 # cperl-indent-level: 4
786 # vim: expandtab shiftwidth=4: