You may not want anything like what I showed being used, but I've just dug up the raw Perl for my previous handler (in case I didn't make it clear, I'd converted it to a self-contained executable, and
that was the reason why I couldn't easily modify the interior rules).
While I won't put the specifics of the getting web-pages and such here (it'd depend on your style with whatever language you want to implement it in, anyway), I think that you can probably take the following as pseudo-code, if you
wanted to pinch any conceptual ideas from it. (I recall being quite happy with the looping and logic constructs.) Not that it's particularly brilliant, but sounds like (if you're going to be "rolling your own") you'd be having to work something out not unlike this, anyway.
sub InstructHandle { my @Instructs = @INSTS;
INSTRUCT: while (@Instructs || @LOOP) {
unless (@Instructs) { @Instructs = @LOOP }
my $Instruct = shift @Instructs;
$Instruct=~s/^\s+//;
# Comments and spacer lines
if ($Instruct=~m/^#/) { next INSTRUCT; }
if ($Instruct=~m/^\s*$/) { next INSTRUCT; }
#print "Instruction: '$Instruct'\n"; next INSTRUCT;
## Inward
if ($Instruct=~m/^SET (.*)\|(.+)\|(.*)$/) { Instruct_SET($1,$2,$3); next INSTRUCT; }
if ($Instruct=~m/^DEC (.*)\|(.+)\|(.*)$/) { Instruct_DEC($1,$2,$3); next INSTRUCT; }
if ($Instruct=~m/^CLEAR (.*)\|(.+)$/) { Instruct_CLEAR($1,$2); next INSTRUCT; }
if ($Instruct=~m/^GET (.+)\|(.+)\|(.*)$/) { Instruct_GET($1,$2,$3); next INSTRUCT; }
if ($Instruct=~m/^LOAD (.+)\|(.*)$/) { Instruct_LOAD($1,$2); next INSTRUCT; }
## Process
if ($Instruct=~m/^IDENTIFY (.*)\|(.+)\|(.+)$/) { Instruct_IDENTIFY($1,$2,$3); next INSTRUCT; }
if ($Instruct=~m/^TEST (.*)\|(\S+) ([<=>]+) (\S+)$/) { Instruct_TEST($1,$2,$3,$4); next INSTRUCT; }
if ($Instruct=~m/^CONVERT (.*)\|(.+)\|(.+)\|(.*)$/) { Instruct_CONVERT($1,$2,$3,$4); next INSTRUCT; }
if ($Instruct=~m/^FORMAT (.*)\|(.+)\|(.+)$/) { Instruct_FORMAT($1,$2,$3); next INSTRUCT; }
## Display
if ($Instruct=~m/^INFORM (.+)$/) { Instruct_INFORM($1); next INSTRUCT; }
# if ($Instruct=~m/^PRINT$/) { Instruct_PRINT(); next INSTRUCT; }
# if ($Instruct=~m/^PRINT (.*)$/) { Instruct_PRINT($1); next INSTRUCT; }
## Outward
if ($Instruct=~m/^SAVE (.*)$/) { Instruct_SAVE($1); next INSTRUCT; }
## Nonlinear Control
if ($Instruct=~m/^FAILURE REPORTS \|(.*)$/) { Instruct_INFORM($1."\n\t/!\\ $ERROR") if $WFAIL; next INSTRUCT }
if ($Instruct=~m/^FAILURE QUITS \|(.*)$/) { Instruct_QUIT($1) if $WFAIL; next INSTRUCT }
if ($Instruct=~m/^LOOP (\w+) START$/) { my $loopname = $1;
print "\t(i) Entering loop '$loopname'\n";
# First, store away any current @LOOP.
push @STACK, [@LOOP] if @LOOP; @LOOP = ();
# Now, separate out the inner loop and store the remainder of the @Instructs and this loopname
SUB: while (@Instructs) { my $SubInstruct = shift @Instructs;
unless ($SubInstruct=~m/^LOOP $loopname END$/) { push @LOOP, $SubInstruct; next SUB }
push @STACK, [@Instructs]; @Instructs = ();
push @STACK, $loopname;
next INSTRUCT; # Which will take head of any known @LOOP instructions
} # END SUB: while (@Instructs)
die "\t/!\\ No LOOP $loopname END encountered before end of Instructs\n";
} # END if ($Instruct=~#LOOP $loopname START#)
if ($Instruct=~m/^FAILURE ENDS LOOP (.+) \|(.*)$/) { my ($loopname,$msg) = ($1,$2); if ($WFAIL) {
Instruct_INFORM($msg); $WFAIL = 0; @LOOP = ();
while (@STACK) { my ($instarray,$loopid) = (shift @STACK,shift @STACK); # loopname and remaining instructions in container
if ($loopid eq $loopname) { @Instructs = @$instarray; @LOOP = shift @STACK if (@STACK); next INSTRUCT }
shift @STACK if @STACK; # now unnecessary @LOOP storage
} # END while (@STACK)
die "\t/!\\ De-STACKing error while exiting loop $loopname!\n";
} next INSTRUCT } # END if ($Instruct=~#FAILURE ENDS LOOP $loopname#)
# if ($Instruct=~m/^COUNT (\w+) SET (\d+)$/) { my ($countname,$val) = ($1,$2); $COUNT{$countname}=$val; next INSTRUCT; }
## Error Trap
die "\t/!\\ Unknown I: [$Instruct]\n";
} # END while (@Instructs)
} # END sub InstructHandle
Note some comments that I never expunged from testing/alternative implementations...
However, there's also (I'm pleased to see!) a decent compliment of
explanatory comments and labels, too, which I must say I don't always bother with for code that I was never thinking I'd share! Also, while I could have written the code tighter, it looks like I
was going for readable instead. How well I did, I leave up to you.
You should of course realise that any given "Instruct_FOO(...)" is of course the 'handler' function, for the outputs (if any[1]) of the relevant regexp that came to be tested true, with @LOOP and @STACK and various other variables being elsewhere-defined globals ("Bad Starver, don't use global variables, Starver!") to contain state information. I
think they're largely self-explanatory.
Oh, one more thing I used, was for the "places where a {variable} would be added back in". Each Instruct_FOO() modified their params, so that they had the ability to pre-process these incoming parameters and resolve such assignments before getting on with business. Given that the the SET functions pushed their assignments into a hash called %WVARS (i.e. $WVARS{'name'}='value', in the manner of Perl code), this was done with the following method to Interpolate (wrong word, but it worked for me at the time) the values...
sub Interpolate { for my $i (0..$#_) { while ($_[$i]=~m/\{(\w+)\}/) {
my $var0 = $1;
die "\t/!\\ Interpolation could not find var [$var0]!\n" unless exists($WVARS{$var0});
my $var1 = $WVARS{$var0};
$_[$i]=~s/\{$var0\}/$var1/;
print "\t(i) Interpolation: [{$var0}]=>[$var1] ==> [$_[$i]]\n" if ($ILEVEL>1);
} } } # END sub Interpolate
I squashed the block-delimiters together a little, for some reason, it operates directly on the essentially called-by-reference
implicit array of @_ (i.e. $_[0] through to $_[last]), and I don't like the variable names I used, but it should still otherwise be readable[2]!
[1] In these functions, even though you can't see, I have pipe-delimited the input parameters. Often the very first (between the full FOO-statement and the first pipe delimiter) I've reserved for a character such as "?", "1", "+" or whatever, which (according to the context of the called FOO-statement) would say "I expect a true/false out of this", "write-once only", "append to existing" or whatever. But probably none of that is of any use to you.
[2] The most obscure part of this might be that $ILEVEL is the "Inform Level, which was set by me at development time", which here gives an equivalent to an "if debug>0"-like control over how much automatic output there is over and above the
scripted INFORM messages! I had different bits functions respond to different $ILEVELs, while working on this. I forget if there was any logic behind those relative values, however.
Absolutely
fatal errors, I appear to have left as a standard "die" 'print comment and terminate' instruction in the bare code, though.