bent has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,
I'm trying to implement a toy version of Basic using Parse::RecDescent as my first attempt at an interpreter. I can't figure out how to implement a goto. Damian Conway's provided plenty of documentation, and I've looked at merlyn's article... If someone could beat me over the head with the solution I would be most grateful.

Here's what I've got so far (edited to illustrate my problem):
use Parse::RecDescent; $RD_HINT++; $RD_WARN++; #$RD_TRACE++; my $grammar = q{ Start: Expression(s) /\Z/ Expression: LineNum Print | LineNum Goto | <error> Print: /print/i /\"/ /[\s\w]+/ /\"/ { print "$item[3]\n"; } Goto: /goto/i LineNum { print "Seen a goto line $item[2]\n"; # How do I seek back to line $item[2] of # the input? } LineNum: /\d+/ }; my $parser = new Parse::RecDescent($grammar); undef $/; my $text = <>; $parser->Start($text);

Thanks in advance.

Replies are listed 'Best First'.
Re: Goto with Parse::RecDescent
by merlyn (Sage) on Jan 11, 2001 at 12:06 UTC
    You need to make only one pass over your source, and "compile" it to an intermediate represtentation. Then "run" it. The action on a "goto" is then just changing the "line number" you're current executing.

    -- Randal L. Schwartz, Perl hacker

      Hi all again,
      Thanks for the great input. Based on your comments, I've given the code another go. This time I've "compiled" the code into the form (op_name, arglist...), which is then run using an "eval". I've also establised a symbol table hash (which currently holds the lvalue type and value). I'd like to have your opinion on this design. (Oh yeah, the grammar syntax is still very ugly and basic. Needs to be redone.)
      Thanks for your time.
      use Parse::RecDescent; use vars qw(@lines $curline %stash); use strict; $RD_HINT++; $RD_WARN++; #$RD_TRACE++; sub do_print { my $a=shift; print "$a\n"; } sub do_goto { $curline = int(shift)-1; } my $grammar = q{ Start: Expression(s) /\Z/ Expression: LineNum Print { $::lines[int($item{LineNum})]= ["print", [$item{Print}]]; } | LineNum Goto { $::lines[int($item{LineNum})]= ["goto", [$item[2]]]; } | LineNum Ass | <error> Ass: StringAss | NumAss StringAss: Identifier /\$/ /=/ /\"/ String /\"/ { $::stash{$item{Identifier}}{type}="String"; $::stash{$item{Identifier}}{value}=$item{String}; } NumAss: Identifier /=/ Number { $::stash{$item{Identifier}}{type}="Number"; $::stash{$item{Identifier}}{value}=$item{Number}; } Goto: /goto/i LineNum Print: /print/i /\"/ String /\"/ { $item{String}; } | /print/i Identifier /\$/ { if(defined($::stash{$item{Identifier}}{value})) { $::stash{$item{Identifier}}{value}; } else {die "A nasty and horrible death... Bad string identifier.\n" +;} } | /print/i Identifier { if(defined($::stash{$item{Identifier}}{value})) { $::stash{$item{Identifier}}{value}; } else {die "A nasty and horrible death... Bad number identifier.\n" +;} } String: /[\w\s]+/ LineNum: /\d+/ Identifier: /[a-zA-Z][a-zA-Z0-9]*/ Number: /[+-]?\d+(\.\d+)?/ }; my $parser = new Parse::RecDescent($grammar); undef $/; my $text = <>; $parser->Start($text); $curline=0; while(1) { if($lines[$curline]) { my $todo = "do_".${$lines[$curline]}[0]."(\""; my $args = join(', ', @{${$lines[$curline]}[1]}); $todo = $todo.$args."\")"; eval $todo; } last if $curline>$#lines; $curline++; }

      Here are some test files:
      test1.bas
      5 print "This " 10 print "is " 15 print "a " 20 print "test" 25 goto 5

      test2.bas
      5 a$ = "This is a string" 10 print a$ 15 b = 35 20 print b
Re: Goto with Parse::RecDescent
by clemburg (Curate) on Jan 11, 2001 at 17:02 UTC
Re: Goto with Parse::RecDescent
by eg (Friar) on Jan 11, 2001 at 13:20 UTC

    Oh lord, there must be an easier way to do this in Parse::RecDescent, so why can't I see it? I think Merlyn's right - it's better to compile the data into another form rather then try to screw around with it on-the-fly.

    What this does is save every line seen, and, when a GOTO is encountered, it's all pasted it all back into to top of $text (which holds the unparsed text.) There is no error checking to make sure the line to go to actually exists.

    use Parse::RecDescent; $RD_HINT++; $RD_WARN++; my $grammar = q{ Start: Expression(s) /\Z/ Expression: LineNum Print { $thisparser->{_lines}->{$item[1]} = $item[1].' PRINT "'.$item[2] +.'"'; } | LineNum Goto { $text = $item[1] . " GOTO " . $item[2] . $text; foreach ( sort { $b <=> $a } grep { $_ >= $item[2] } keys %{$thi +sparser->{_lines}} ) { $text = $thisparser->{_lines}->{$_} . $text; } } | <error> Print: /print/i /\"/ /[\s\w]+/ /\"/ { print "$item[3]\n"; $item[3]; } Goto: /goto/i LineNum { $item[2]; # print "Seen a goto line $item[2]\n"; # How do I seek back to line $item[2] of # the input? } LineNum: /\d+/ }; my $parser = new Parse::RecDescent($grammar); undef $/; my $text = <DATA>; $parser->Start($text); __END__ 10 print "hello" 20 print "this" 30 print "is" 40 print "a" 50 print "test" 60 print "goodbye" 70 goto 30

    There's a general method or something that I'm missing or have skipped over or have forgotten ...