in reply to Complex dispatch table

Seems pretty cool to me. I just wrote a node on programming with data structures which might give you food for thought.

Alternative ways to do it:

If you are thinking of developing a complex system, then at some point you might need to structure your language more, rather than just adding lots of commands with different patterns (sooner or later, you're likely to accidentally have 2 patterns that both match, especially if you load patterns from modules). How you do this is up to you. It might not be a bad idea to use the standard Unix form command [-option [argument...] ...] [command-argument...] and maybe even use one of the Getopt modules to parse the command line?

(Thinking some more) You have two choices:

syntax: centralised/delegated
semantics: centralised/delegated

Where centralised means "handled in the parser" and "delegated" means "handled in the module". I suggest centralised syntax, as much as possible, and delegated semantics. This combines predictability with flexibility. For example, given the input

foo -b baz boot.txt bop.txt

your central parser decides if it is a valid command, then passes it to the module (which has previously registered a "foo" command with the parser). The module decides what to do. All modules inherit from a Module class which provides an overridable method for deciding what to do. (E.g. "call the subroutine foo with the arguments parsed into a perl data structure as follows".)

What I am suggesting has some advantages. Your program could maintain state: e.g. "cd /foo" followed by "ls" prints something different from "ls" on its own. You would implement this by having the module which provides "cd" and "ls" use method calls on an object rather than just subroutine calls.

However, it all depends on how regular the commands are - which depends on what exactly you are trying to do. If everything is very regular, perhaps you can have centralised semantics too and just dispatch to particular subroutines, as now. OTOH, if you want a complex language, you might want to think of a parsing module like Parse::RecDescent

dave hj~

Replies are listed 'Best First'.
Re: Re: Complex dispatch table
by castaway (Parson) on Feb 20, 2003 at 10:51 UTC
    Thanks for the thoughts.
    I guess it's not always easy to give tips without knowing what the thing is being used for. I purposely just wanted tips on the code and not its surroundings, but I'll tell you a little bit more anyway.
    It's not about external command-line arguments, but commands typed into the 'shell', so I guess Getopt wouldnt help much (unless I can use that internally as well?) and isnt really the sort of syntax I need.
    You'll have to enlighten me on 'semantics' I'm afraid, I think I know what you mean with 'syntax' .. ;)

    It's actually a Telnet Client for playing muds with.. With commands similar to zMUD, ie '#VAR myvar {2}' '#TRIGGER {sometext} {#colour red}' to mention a couple, this is the stuff and commands that are getting parsed. I'm already using Parse::RecDescent to parse complex scripts, but am trying to avoid using it for simple commands, which seems to be working so far.

    C.

      I don't know if you can use Getopts modules on a string, but I bet there's an option somewhere. Take a look.

      Semantics means "meaning", more or less. The semantics of a statement are going to be what it actually does. The syntax is just how the statement is structured. So you have to think about how the commands you are using are related. Perhaps some commands have a context (a "state"). Some commands might be "like" others (so you can use inheritance to specify their semantics: derive behaviour from a base class, modify it in the child class). Et cetera.

      At the moment, you are basically assuming "all commands are semantically different" - they each get their own subroutine or module method call. You're also assuming "all commands are syntactically the same", but only in that they can be parsed by regexes. You could increase the level of syntactic sameness, and this might save you time typing out different regexes and format messages.

      Here's a rough example:

      package main; # ... my $command; eval { $command = CommandFactory->create ($input_string); # returns the r +ight subclass of Command }; #trap non-existent commands if ($@) {printandprompt("command not recognized");} if ($command->parse_syntax) { # check for syntax errors eval { $command->execute; }; # trap semantic errors, e.g. "user doesn't exist" if ($@) { print $@; # maybe die if need be } } else { printandprompt $command->syntax_string; } package CommandFactory; sub create { my $class = shift; my $str = shift; my ($cmd, @args) = split $str; my $cmd_class = "Command::" . ucfirst ($cmd); return $cmd_class->new(@args); } package Command; use vars qw/$SYNTAX/; # ... sub new { my $class = shift; my @args = @_; my $self = {args => \@args}; bless $self, $class; } sub _syntax { my $self = shift; no strict 'refs'; my $class = ref ($self) || $self; return ${"$class::SYNTAX"}; } sub parse_syntax { my $self = shift; # check $self->{args} against $self->_syntax # then parse the arguments into $self fields # return false on error # alternatively "die" and catch the exception } package Command::Msg; use Command; use base 'Command'; use vars qw/$SYNTAX/; $SYNTAX = { options => { player => { required => 1, arg => 'string', } loudness => { required => 0, arg => 'int' } } arguments => { min => 1, } }; sub execute { my $self = shift; # send $self->{msg} to $self->{player} with $self->{loudness} }

      dave hj~

        That looks interesting .. Thanks..

        C.

      so I guess Getopt wouldnt help much (unless I can use that internally as well?)
      Untested code ahead (must take daughter to school):
      use Text::ParseWords; use GetOpt::Std; @ARGV = shellwords($textin); my $cmd = shift @ARGV; my %opts; getopts('oif:', \%opts);