I want to parse input but when I'm in a particular top-level rule, save
the text that was parsed instead of executing the actions, to do so
instead later. I've got a solution but I'd like a sanity check.

I can explain it in terms of some adventure game commands.  (That's not my
application, but it's exactly equivalent and more entertaining.  I cut my
gaming teeth on VAX/VMS DUNGEON and it's colored my thinking ever since.)
So I want to be able to "Kill troll with axe", and "Open window", but
also, "Tell robot open door" where there is a subset of commands I can
execute that the robot can also execute.

So for a "tell <actor> <command>" statement I want to save <command> and
pass that text to the action for 'tell' where it decides what to do with
it (maybe setting the global actor and then calling the parser again).  I
found Parse::RecDecent::Consumer and came up with the below, which does
what I want, but I thought I'd ask here for comments on improving it. 
I want to minimize the impact upon the grammar for executing regular
commands caused by having to be able to handle them as delegated commands
also.

#!/usr/local/bin/perl
use strict;
use warnings;

use Parse::RecDescent;
use Parse::RecDescent::Consumer 'Consumer';

# $::RD_TRACE = 1;
my $parser = Parse::RecDescent->new(join '', <DATA>);

for ( 'open window',
      'kill troll with sword',
      'tell robot kill thief with knife',
      'tell troll drop sack',
      'tell thief open door' )
{
  print "$_ : ";
  defined $parser->start($_) or print "Error\n";
}

sub Parse::RecDescent::do_open { print "OPEN(@_)\n"; }

sub Parse::RecDescent::do_drop { print "DROP(@_)\n"; }

sub Parse::RecDescent::do_attack
{
  my @args = map { ref $_ ? @$_ : $_ } @_;
  print "KILL(", join (', ', @args), ")\n";
}

sub Parse::RecDescent::do_tell
{
  print "TELL($_[0] => '$_[1]')\n"
}

__END__

{ $::SAVING = 0 }

start : tell | immediate

tell : 'tell' actor slave_command
        { do_tell($item{actor}, $item{slave_command}); $::SAVING = 1 }

immediate: ( slave_command | master_command ) { $::SAVING = 0 }

slave_command : <rulevar: $C>
slave_command : { $C = Consumer($text) ; 1}
                (drop | attack)
                { $C->($text); }

master_command : open     # Only I can open stuff

actor   : 'robot' | 'thief' | 'troll'

drop   : 'drop' object
          <defer: do_drop($item[2]) unless $::SAVING >

object : 'sack' | 'bottle' | weapon

attack : 'kill' actor ('with' weapon)(?)
         <defer: do_attack(@item[2,3]) unless $::SAVING >

weapon : 'knife' | 'sword'

open   : 'open' orifice
         <defer: do_open($item[2]) unless $::SAVING >

orifice : 'door' | 'window'


$ ./telltest
open window : OPEN(window)
kill troll with sword : KILL(troll, sword)
tell robot kill thief with knife : TELL(robot => ' kill thief with knife')
tell troll drop sack : TELL(troll => ' drop sack')
tell thief open door : Error


-- 
Peter Scott
http://www.perlmedic.com/

Reply via email to