Verder met de volgende stap: het tekstwindow om turtle-programma's te
kunnen schrijven. Ten eerste moeten we dit window vorm geven: we maken een
window met type scrollende tekst, afbrekend op woordgrenzen, en zoveel
mogelijk ruimte binnen het top-level window vullend. Tevens zorgen we dat dit
window de aandacht krijgt, zodat tekst die getypt wordt hier automatisch in
komt.
$text = $top->Scrolled('Text',
-wrap => "word",
-scrollbars => "se",
-width => 40, -height => 8);
$text->pack(-expand => 1, -fill => 'both');
$text->focus();
Nu de acties om iets met het programma in dit window te doen; tevens de
acties die aan de knoppen hangen. Eerst de simpelste: het creëren van
een nieuw (leeg) programma. We gooien het tekstwindow leeg
($text->delete()), en wissen de laatstgebruikte filenaam (die
bij een eerdere laad- of save-actie ingevuld kan zijn).
my $filename = "Untitled"; # current file name
sub newprog { # erase program
$text->delete('1.0', 'end');
$filename = "Untitled";
$top->title("$title $filename");
}
Het laden van een bestaand programma bevat iets meer code. Ik maak gebruik
van de ingebouwde 'getOpenFile' functie om de gebruiker een file
te laten kiezen. Deze functie heeft wat extra informatie nodig, als file
types en een default filenaam (zonder pad!), zie onderstaand fragment. De
standaard file-extensie die ik voor turtle-programma's heb gekozen is '.tl'.
Na aanroep check ik op de file bestaat (-f operator) en zo niet verschijnt er
een foutmelding met behulp van fail_dialog (zie complete listing
in zip file, bij de help-tekst dialog).
Ik wis nu eerst de oude inhoud van het tekstwindow, en lees dan de nieuwe
file regel voor regel in. En uiteindelijk werk ik de titel van het window
bij, om de huidige filenaam weer te geven.
sub loadprog { # read file into input pane
my $line;
my $filetypes = [ ['Turtle files', ['.tl']], ['All files', ['*']]];
$filename =~ s/.*[\/\\]//; # strip path
$filename = $top->getOpenFile(-title => "Select file to open",
-initialfile => $filename, -filetypes => $filetypes,
-defaultextension => ".tl");
return $fail_dialog->Show() if ! -f $filename;
$text->delete('1.0', 'end'); # first delete old contents
if (open(FILE, $filename) || die "Can't open $filename")
{ while($line = <FILE>) # next read file line by line
{ $text->insert('end', $line);
}
close(FILE);
}
$top->title("$title $filename");
}
Saven is nog haast eenvoudiger: weer de gebruiker vragen naar een file, en
vervolgens de inhoud van het tekstscherm er in een keer in wegschrijven:
sub saveprog { # save input pane to file
my $result;
my $filetypes = [ ['Turtle files', ['.tl']]];
$filename =~ s/.*[\/\\]//; # strip path
$filename = $top->getSaveFile(-title => "Select file to save to",
-initialfile => $filename, -filetypes => $filetypes,
-defaultextension => ".tl");
return $fail_dialog->Show() if !$filename;
$result = $text->get('1.0', 'end');
if(open(OUTPUT, ">$filename") || die "Can't open >$filename")
{ print OUTPUT $result; # save results to file in one go
close(OUTPUT);
}
$top->title("$title $filename");
}
Het meeste werk is het uitvoeren van een programma. Om te beginnen een
vertaaltabel Nederlands naar Engels met de basiscommando's.
my %translation = (
"voor" => "foreach", "vooruit" => "forward",
"als" => "if", "anders" => "else",
"links" => "left", "rechts" => "right",
"huis" => "home", "wis" => "erase",
"green" => "groen", "blauw" => "blue",
"rood" => "red", "geel" => "yellow",
"wit" => "white", "zwart" => "black",
"grijs" => "gray", "spring" => "jump");
De routine 'execline' doet het vertaalwerk en de uitvoering.
De geselecteerde tekst in het tekstwindow wordt opgevraagd (of alle tekst, in
het geval niets geselecteerd was). Vervolgens wordt de 'vertaling'
uitgevoerd: voor alle woorden in de tabel wordt dat woord voor alle gevallen
vervangen (mits het zelfstandig voorkomend). Tevens wordt het vraagteken
vervangen door 'print', en de argumenten van functies omgezet naar de minder
leesbare Perl syntax ('arg3' wordt bijvoorbeeld
'$_[3]').
Dan hebben we nu het turtle-programma omgezet naar een hopelijk geldig
Perl programma, dat we nu met behulp van 'eval' uitvoeren. Er
kan echter van alles zijn misgegaan, en dan willen we een nette foutmelding
geven (hoewel ik niet zo ver ga dat ik de melding naar het nederlands
vertaal). Nu kan er op twee manieren een melding komen:
- Perl snapt het programma maar er gaat iets fout bij het uitvoeren
hiervan (bijvoorbeeld als bij deling door 0). Dit wordt door 'eval'
afgevangen, Perl laat dan de foutmelding achter in
$@
- Er zit een syntaxfout in het programma, Perl stopt met vertalen en
roept de interne foutafhandeling aan. In dat laatste geval vangen wij dat
af door de Perl errorhandler af te vangen (zie onderaan, via
$SIG{__WARN__} = ....) waarbij we de waarschuwing in
$warn zetten.
Door het testen van $@ en @warn zien we dus of
er iets is foutgegaan, en laten een window verschijnen met de foutmelding. Ik
negeer echter de foutmelding dat een routine al eerder gedefinieerd was (op
deze manier kunnen routines meerdere malen gedefinieerd worden, net zo lang
totdat we hem goed hebben) of als de gebruiker op 'Stop' had gedrukt. Ging
alles goed, dan druk ik het resultaat af op de standaard output (die normaal
echter niet in beeld is, maar in Windows kan je kijken op het DOS-scherm dat
geminimaliseerd op de Windows balk aanwezig is).
sub execline {
$top->Busy; # change cursor in 'busy'
my $tags = $text->tagNextrange('sel', '1.0', 'end');
$_ = $text->get('sel.first', 'sel.last') if defined $tags;
$_ = $text->get('1.0', 'end') if !defined $tags; # all if none selected
foreach $word (keys %translation) { s/\b$word\b/$translation{$word}/g; }
s/\?/ print /g;
s/\$arg(\d)\b/\$_[$1]/g;
$stop = 0;
$warn = ""; # used to trap warning messages
@result = eval; # evaluate entered expression
if (!$stop && ($@ || $warn) && !($warn =~ "Subroutine .* redefined at"))
{ $err = $@;
print "Warning = $warn\nError = $err";
$syntax_dialog->configure(-text => "$warn$err");
$syntax_dialog->Show();
}
else
{
print "\neval returned: " if !/^\s*print/;
print @result if !/^print/; # only print if statement didn't
}
$top->Unbusy; # cursor back to normal arrow
}
$SIG{__WARN__} = sub { $warn = $_[0]; }; # trap early warnings
Dat zijn de functies nodig voor het afhandelen van het tekst-gedeelte.
Blijft er nog een deel over: de bewegingen van de schildpad. Ga hiervoor door
naar deel drie.