Introductie in Vertaler bouw
Inhoud
Inleiding
(Versie juli 2004 M.S.Ter Haseborg)Mijn bedoeling is om aldatgene wat ik ervaren heb met het be/verwerken van texten eens op een rijtje te zetten . Dit onstond doordat ik een kompjoeter wil vertellen om wat voor mij te doen ofwel iets uittevoeren . Behalve in het tegenwoordige muisklik tijdperk is het nog steeds nodig een kompjoeter opdrachten te geven . De opdrachten bestaan uit texten welke in een bepaalde vorm worden gelezen en uitgevoerd . In onze spreek en schrijf taal is de inhoud van wat we vertelen vaak afhankelijk van de omgeving waarin het wordt gelezen of gesproken . (context afhangkelijk) Machine's (dus ook kompjoeter) kunnen daar slecht mee overweg , of anders gezegt wij kunnen deze machine's nog maar moeilijk leren hoe met hun omgeving omtegaan . Daarom beperkt men zich tot een taal konstruktie die niet afhankelijk is van zijn omgeving . (context vrij) Om het leven nog simpler temaken ('t is als moeilijk genoeg) wordt de taal gekonstrueerd als een Eindige Machine . Deze taal bestaat uit twee elementen een Terminal en een NonTerminal . Een Terminal is een gewenst te herkennen stuk text (of teken) zoals 'procedure' . Een NonTerminal is naam voor een reeks achter elkaar verwachte Terminal('s) gevolgt door mogelijk andere NonTerminals . Bv. " 'procedure' naam '(' parameters ')' " . Hierin zijn naam en parameters NonTerminals en 'procedure' '(' en ') de Terminals . Een rij van deze Terminal's en NonTerminals is een uitspaak(en) ook wel regel(s) genoemd . Bekijk nu of een gegeven invoer text voldoet aan een gegeven uitspraak . Dit kan alleen maar juist of onjuist zijn . Een programma (functie) genaamt controleer met invoer gegevens TEXT en REGELS geeft als antwoord juist indien de TEXT geschreven is volgens de REGELS anders is het fout . ( resultaat=controleer(TEXT,REGELS) ) Het betekend ook dat alle NonTerminal ergens in de uitspraak alleen Terminals moet hebben anders is de uitspraak niet bewijsbaar . Dus naam en parameters moeten gegeven zijn in een Terminal vorm . D.w.z. naam = een_Letter gevolgd_door_een_of_meerdere_letters ofwel [a-zA-Z][a-zA-Z]* . Mum was dit nu weer . Evenzo goed dat je regels nodig hebt om je uitspraken te kontroleren op juistheid heb je ook regels om de regels te maken . ('tja het ei of de kip) Er zijn nogal wat notatie's voor het maken van regels bedacht . De meesten gaan er vanuit om zomin mogelijk teken zoveel mogelijk te zeggen . Dit leid meestal tot nogal criptische texten . Maar goed een aantal moet wel . De volgende gebruik ik meestal : regel = expressie . expressie = 'Terminal' | NonTerminal expressie . NonTerminal = 'Terminal' | NonTerminal . Het '|' is heeft een 'of' betekenis dus iet als "karel | piet" wil zeggen "karel OF piet" is juist . regel = expressie . expressie = 'Terminal' NonTerminal [ 'Terminal' NonTerminal ] . NonTerminal = 'Terminal' | NonTerminal . Het gegeven tussen '[' en ']' mag 0 of meerdere keren voorkomen de uitspraak is altyd juist . (In RegExp wordt vaak gebruikt [ ]? is 0 of 1 keer , [ ]+ is 1 of meer , [ ]* is 0 of meer ) Verder zou ik zo min mogelijk (liefst helemaal niet) uitspraken gebruiken waarin een NonTerminal zichzelf gebruikt . Dit heten recursieve aanroepen , de meesten kunnen vervangen worden door een herhaalde aanroep door middel van [ ] . Zo kun je herkennen van een getal in een text schrijven als : cyfer = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' . getal = cyfer [ cyfer ] . bv. "3421" is goed maar "342.456" is fout (is een float number) Een hexadecimaal getal als hexcyfer = cyfer | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' . hexgetal = '#' hexcyfer [ hexcyfer ] . bv. "#ff456" is goed maar "#FF456" is fout ook "ff456" is fout (mist startsymbol #) (In notatie voor RegExpr heet dit : getal = [0-9]+ , hexgetal = #[0-9a-f]+ ) Een andere mogelijkheid is : nummmer = hexcyfer [hexcyfer] | getal . Dit betekend dat voordat gesteld kan worden of de uitspraak juist is ALLE alternatieven moeten worden bekeken . Als 1 juist is is de uitspraak juist . Gegeven : 45ff67 ^ |-- getal uitspraak onjuist (nl. f behoort niet tot de getal uitspraak) ^ |-- hexgetal juist (er zijn geen tekens meer) Deze vorm kan moeilijk zijn daar de bewijsvoering moet beschikken over 'backtracking' of 'paralel verwerking' . Omdit te vermeiden wordt een z.g.n. startsymbol gebruikt . nummer = getal | hexgetal . Geeft als het gegeven niet begint met een '#' moet het een getal zijn anders moet het hexgetal zijn .
Lexical scanner
Het eerste wat bepaald moet worden is hoe de text gelezen moet worden . Ik heb er voor gekozen dit tedoen in een afzonderlijke procedures die feitelijk een buffer openen en na gebruik sluiten . Ofschoon tegenwoordig vaak hele bestanden in gelezen worden ga ik dit niet doen . Dit om de procedures te kunnen gebruiken op machines met kleine geheugens . Ik lees daarom 1 regel welke , indien de scanning niet af is , wordt verlengd met een volgende regel . De veronderstelling hierbij is dat het bestand gescheiden wordt door een of ander EOL teken . (EOL = End Of Line) De tweede veronderstelling is dat de scanner de behandelde text uit het buffer verwijderd . Hiertoe zijn twee functie's gedefineerd . De openings functie is L_init(text) welke een logisch waarde TRUE geeft als de text een bewerkbare text is . De text kan zijn een zin (string) of een file . Een bewerkbare text is een die min 1 character bevat uit de ASCII reeks (0 tot en met 255) . Al het andere wordt als niet bewerkbaar beschouwd z.g.n EOF . (EOF=End Of File) De tweede functie is een afsluiting L_end() deze geeft een logische waarde TRUE als er nog andere text te behandelen is . Dit klinkt vreemd maar het is mogelijk om tijdens een scan een tweede (of volgende) bestanden te bewerken . Dit betekend als het laatste bestand wordt gesloten verder gegaan moet worden met de vorige . (b.v. geeft dit de mogelijk tot include file's) Het tweede is te bepalen hoe de scanner moet gaan werken . In het simpleste geval is dit een procedure die het buffer splits in drie delen . Een SKIP , SEARCH en een REST deel .
Buffer : |
|
regel = { "naam","gevolg","toestand" }
text = "toestand = naam -> gevolg"
L_init(text) --> result TRUE , text naar buffer
L_in(regel) --> result is (L_SKIP)"" ,(L_SEARCH)"toestand",(L_INDEX) 3
L_in(regel) --> result is (L_SKIP)" = " ,(L_SEARCH)"naam" ,(L_INDEX) 1
L_in(regel) --> result is (L_SKIP)" -> ",(L_SEARCH)"gevolg" ,(L_INDEX) 2
L_in(regel) --> result is (L_SKIP)"" ,(L_SEARCH)"" ,(L_INDEX) 0
L_end() --> result FALSE
(Deze methode wordt ook toegpast in SNOBOL als text BREAK(regel) SPAN(regel) BREAK(regel) SPAN(regel) BREAK(regel) SPAN(regel) :S(oke)F(fout)
)
Een methode die ook de SKIP definieerd geeft dat de vrijheid van de scanner kleiner wordt of wel de regels
preciser worden . Hiertoe wordt een SKIP set gedefinieerd m.b.v. L_any(skip_set) . We willen dat de
spatie en de = en - > worden genegeerd . Voeg dan toe L_any(" -=>") het resultaat is gelijk aan
bovenstaand voorbeeld . Doch als we opgeven L_any(" -=") dan gaat het in de 3e test fout omdat de '-'
wordt gelezen maar niet '>' doch verwacht wordt "gevolg" .
Een laatste zeer strenge methode is dat de scanner zelf aangeeft wat het volgende symbool is . Hiertoe is de
scanner verteld wat deze wel en niet kan analiseren . De implementatie hiervan is vaak sneller als die van de
voorgenoemde methoden . Je kunt deze vorm van scanner beschrijven in termen van de voorgaande methoden .
Zie hier de code . Het resultaat staat in L_result[TOKEN] en L_result[IDENT] . Het L_result[TOKEN] bevat
een getal 1 t/m 5 wat voorsteld 1=text , 2=getal , 3=punctuatie , 4=eol , 5=eof .
De waarde staat in L_result[IDENT] . Deze vorm is vrij oud , werd toegepast in het PES programma (~1984) .
Nog een opmerking wat de regels betreft , in het voorbeeld zie je een vorm van reg.expr. nl :
sequence ident={"ABCDEFGHIJKLNMOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLNMOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"}
sequence num ={"0123456789","0123456789"}
sequence punctuation = {"?/()#$*-+:=<>&{}[]@!,;'.^\"",{}}
(Het spraakgebrek waarin dit geschreven is heet PEU(=Porteble Euphoria) een public version van Euphoria geschreven in C onder dos/window/linux )
Het equivalent in reg.expr. voor ident is [A-Za-z][A-Za-z0-9_]* voor num is [0-9]+ dit wordt hier weer geven
als altyd twee set's volledig uitgeschreven . Daarom staat num er ook als [0-9][0-9]* .
Hier een voorbeeld welke een HTML (bron) file afbeeld als een in kleur gezette HTML file .
Het idee is dat alle commentaar rood wordt de opening tag's blauw en de sluit tag's in groen .
De versie die gebruikt maakt van de eerst genoemde methode staat hier ter vergelijking
staat hier een versie die gebruikt maakt van de tweede beschreven methode .
(Als je het leuk vindt kun je hier de oplossing zien uit geschreven in SNOBOL)
Misschien gelooft u het niet maar hier staat het resultaat . Wil je het origineel zien dan klik hier .
Een voorbeeld waarbij je zelf de scanner dirigeerd is deze , waarin gevraagd is om free format van een file te lezen .
Hiertoe is een routine readf() gemaakt welke als inhoud de scanner gebruikt om free format te lezen .
Voor de geinterresseerde is dit de bron text van de eigenlijk lexical scanner .
End Of File(=EOF) in de Lexicalscanner zoals hier gebracht wordt de EOF als Token -1 weergegeven .
In de oudere systemen ontbrak het vaak aan een eenduidige manier om een EOF te bepalen , vandaar dat hiervoor vaak een speudo EOF
in de vorm van '-*' aan het begin van een regel werd gebruikt . Ofschoon deze vorm uniek ingebruikt is is zij lastig in hiervoor
gegeven vorm van een lexicalscanner te maken . (kan wel !) Het heeft overigen een voordeel n.l. dat deze manier absoluut O.S. onafhankelijk is .
In de huidige systemen is , voor zover ik weet , altyd wel een functie die een EOF kan detecteren hetgeen mij rechtvaardigd om het oude systeem
te verlaten en de EOF tezien als een Token en geeft als resultaat in SEARCH 1 teken een control D (eot) . De keuze hiervoor leek mij logisch
gezien de betekenis die het heeft in de ASCII set (afkomstig uit de tyd van de teletype's) n.l. end of tape . ^D is afkomstig uit de Unix/Linux
wereld , een andere wereld VAX/DOS/WINDOWS gebruikt ^Z (sub) . Naast het EOF wordt soms ook wel de kreet End of Medium(=EOM) gebruikt .
Dit komt voort uit een meervoudige EOF definitie in het gebruikte bestand .
End Of Line(=EOL) kent iedereen ook wel als 'return' , 'terug_wagen_nieuwe_regel' , 'enter' . De afbeelding in de text file is afhankelijk van het O.S. . In DOS/WINDOWS is dit carriege_return linefeed (\r\n) in Unix/Linux is dit een linefeed(\n) . Naast deze onduidelijk hoe het O.S. het EOL beschouwt blijken ook de programmeertalen er afzonderlijke ideeen op na tehouden . B.v. Fortran leest een regel text in een array maar zet er nooit een EOL in , terwijl in C de EOL wordt mee gelezen (zelfs afhankelijk van het O.S.) . Ofschoon geen echte programmeer taal wil ik ter volledigheid nog HTML noemen welke helemaal niet kijkt naar een EOL , nog naar meerdere spatie's achter elkaar . (behalve in <pre> <code> e.d.) Om enige eenduidigheid te creeeren ga ik er van uit dat iedere text regel wordt beindigd met een Line Feed (\n , ascii waarde= 10) . Of je de EOL nu een eigen Token geeft of een Punctuatie noemt is naar mijn gevoel een beetje om het even .
Een Parser is niets anders dan een programma dat verifieerd of de gedane uitspraken juist zijn binnen een gegeven aantal regels . (syntax rules) Een geven regel kan afhankelijk zijn van andere regels maar deze dient eindig tezijn . Dat betekend dat er uiteindelijk een regel is die eenduidig zegt juist of onjuist .
Parser
rules : beschrijving . beschrijving : defentry ':' defrule '.' beschrijving . defentry : TEXT . defrule : defexpr defalter . defalter : '|' defexpr defalter | . defexpr : deftok defexpr . deftok : TEXT | NUM | term . term : ''' TEXT ''' .Iedere Terminal geeft bij verwerking een juist of onjuist als resultaat (vergeleken met de invoer) . Als het resultaat juist is wordt het volgende element uit de regel bekeken . Is het resultaat onjuist dan wordt gezocht of er nog een alternatief in de regel staat en deze bekeken . Zijn er geen alternativen meer dan wordt hetresultaat aan de NonTerminal gegeven en keert terug naar waar de aanvraag vandaan gekomen is .
HexNum : '#' hexgetal . Uitgaande van de regel main geldt : ->rules->defentry->TEXT->':'->defrule->defexpr->deftok->term->defexpr->deftok->TEXT ->defexpr->deftok/onjuist->defalter/onjuist->defalter/leeg(=juist)->'.' Dus deze regel is herkent door rules . ->beschrijving-> nu de volgende regels : hexgetal : hexrest hexgetal . hexrest : N | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' . Er moet een teken zijn wat verteld dat er niet meer te verifieren text komt anders eindigd rules altyd als onjuist . Voeg een EOF toe aan de regel rules dan is de totale uitspraak ook juist .Er moet een programma zijn waar je de regel's zoals rules ingeeft en programma maakt dat andere regels verifieerd of ze voldoen aan de regels in rules . Dus iets als : code = maak( rules ) ; resultaat = controleer( text, code ) .
Een idee hoe je code zou kunnen definieeren :
rules : CALL beschrijving s(.+1)f(.+1)
EXIT
beschrijving : CALL defentry s(.+1)f(exitbeschrijving)
TERM ':' s(.+1)f(exitbeschrijving)
CALL defrule s(.+1)f(exitbeschrijving)
TERM '.' f(exitbeschrijving)
CALL beschrijving s(.)f(exitbeschrijving)
exitbeschrijving : RET
Je kunt je voorstellen dat dit niet bepaald een optimale code is .
Maar het dient dan ook maar als voorbeeld want het herhaald oproepen van een routine is niet erg efficient .
Een vorm van Loop zou beter zijn .
beschrijving : CALL defentry s(.+1)f(exitbeschrijving)
TERM ':' s(.+1)f(exitbeschrijving)
CALL defrule s(.+1)f(exitbeschrijving)
TERM '.' f(exitbeschrijving)
repeat : CALL defentry s(.+1)f(exitrepeat)
TERM ':' s(.+1)f(exitrepeat)
CALL defrule s(.+1)f(exitrepeat)
TERM '.' f(exitrepeat)
exitrepeat : REPT s(repeat)
exitbeschrijving : RET
Deze vorm impliceerd dat beschrijving als defentry ':' defrule '.' [ defentry ':' defrule '.' ]
.
Hiermee is duidelijk dat ook [ .. ] kan worden gemaakt .
Een wat gekompliseerdere uitdrukking is de break : '(' terminal [ | terminal ] ')'
welke bedoeld is vanaf het huidige punt te lezen tot aan een van de terminal .
Deze vorm wordt voornamelijk gebruikt om ingeval van een fout een volgend start symbool(terminal) tevinden .
Mijn persoonlijke mening is dat je na een fout NIET moet proberen door te analyseren .
(Wirth doet dit in Pascal en levert een doos foutmeldingen op , welke na een korrectie van de eerste er al een heleboel zijn verdwenen .)
-- assembler code
-- opr = 0 ; exit, <result> : 0=einde , anders fout_nummer .
-- opr = 1 ; lit , <result> :
-- opr = 2 ; punc, <fail> : token == punc , textstring = teken .
-- opr = 3 ; text, <fail> : token == text , textstring = text .
-- opr = 4 ; num , <fail> : token == num , textstring = nummer.
-- opr = 5 ; term, <term> , <succes> <fail>
-- opr = 6 ; break, <end_break>
-- opr = 7 ; repeat,<begin_repeat>
-- opr = 8 ; call, <routine>, <succes>, <fail>
-- opr = 9 ; rte, 0 : return from stack ,set suc=true , <suc>
-- opr =10 ; ret, 0 : return from stack ,if suc <suc> else <fail>
-- opr =11 ; bre, <adres> : set suc=true , <adres>
-- opr =12 ; brf, <fail> : if suc <pc+1> else <adres>
-- opr =13 ; brs, <succes> : if suc <adres> else <pc+1>
De instruktie slaan we op in een array van 1 tot n genaamt ProgramCode.
En we hebben nodig een ProgramCounter PC genaamt en iets wat op een stack lijkt .
Behalve dat we moeten uitschrijven wat iedere instruktie moet doen moet deze aan de interpreter vertellen of deze moet continueren of stoppen .
Interpreter luidt als volgt :
pc=1
opr=ProgramCode(pc) pc+=1
while exec(opr)=true do opr=Program(pc) pc+=1 end
Oke alles kan slimmer maar dit geeft naar mijn idee aan wat de bedoeling is .
De enige opr
die als antwoord een false geven zijn exit
en lit
.
Oh bedenk dat deze true/false iets anders is dan of de verifikatie van de invoer regel naar rules juist of onjuist is !
Als de verifikatie klopt komt de parser terug met exit 0
als het niet klopt dan is de waarde welke exit
geen 0 . (als je het tenminste goed geprogrameerd hebt)
Als je er nog bent dan kun je hier de bron code aanschouwen .
Het bestaat uit vijf delen a) declaratie's b)loader voor de ProgramCounter c) disassembler d) assembler instruktie's e) parser . Het resultaat van dit geworstel kun je hier lezen .
exit getal
.
lit getal
welke aangeeft welke code/procedure nu moet worden toegepast .
lpc
staat voor last_progam_posistion en de naam npc
staat voor iets als next_program_position .
De tabel waarin de executeble code staat heet codetab
en de tabel waarin de namen van alle van de Terminal's staan heet termtab
en is berijkbaar door de functie totermtab
welke de Terminal opslaat en teruggeeft op welke positie deze is opgeslagen .
nametable
.
Deze nametable
bevat drie elementen naam
, adres
waar deze naam gedefinieerd is in de codetabel en gebruik
hoevaak de naam gebruikt is .
adrestoname
geeft een naam een adres .
De functie adresofname
geeft het adres van de naam . En adresnametab
voegt een naam toe als die nog niet bestaat en mocht die wel bestaan dan verhoogt hij het gebruik
als resultaat geeft hij het adres
van de naam . Mocht deze nog niet zijn gedefinieerd dan is adres
een '-(negative)nametabel_index' .
SELECT CASE opr
CASE 0: PRINT #2, "foutje in coder": codelen = 0
CASE 1: PRINT #2, "einde code gen."
IF codelen THEN codelen = npc - 2
CASE 2: PRINT #2, "decl "; text$
CASE 3: PRINT #2, "entry "; text$: lpc = npc: progname$ = text$
adrestoname adresnametab(text$), npc
'invullen van forwerd ref's
i = 1
DO
IF codetab(i) = 8 THEN
codetab(1 + i) = adresofname(codetab(i + 1))
i = i + 2
ELSEIF codetab(i) = 5 THEN
i = i + 2
END IF
i = i + 2
LOOP UNTIL i >= npc
CASE 4: codetab(npc) = 10: codetab(npc + 1) = 0
PRINT #2, npc, "return": npc = npc + 2:
PRINT #2, TAB(40); "$"; lpc; "="; npc
'resolve forward ref's (tussen lpc en npc-2 ) alleen Fail
FOR i = lpc TO npc - 2
IF codetab(i) = -lpc THEN codetab(i) = npc
NEXT
lpc = npc
CASE 5: codetab(npc) = 3: codetab(npc + 1) = -lpc
PRINT #2, npc, "string f($"; lpc; ")": npc = npc + 2
CASE 6: codetab(npc) = 2: codetab(npc + 1) = -lpc
PRINT #2, npc, "teken f($"; lpc; ")": npc = npc + 2
CASE 7: codetab(npc) = 4: codetab(npc + 1) = -lpc
PRINT #2, npc, "integer f($"; lpc; ")": npc = npc + 2
CASE 8: codetab(npc) = 0: codetab(npc + 1) = Value
PRINT #2, npc, "error"; Value: npc = npc + 2
CASE 9: PRINT #2, "{begin loop}"
loopbg = npc: loopend = lpc: lpc = npc
CASE 10: codetab(npc) = 7: codetab(npc + 1) = loopbg
PRINT #2, npc, "loop s("; loopbg; ")"
PRINT #2, TAB(40); "$"; lpc; "="; npc
'resolve forward ref's (tussen loopbg en npc) alleen fail
FOR i = loopbg TO npc STEP 2
IF codetab(i + 1) = -lpc THEN codetab(i + 1) = npc
NEXT
lpc = loopend: npc = npc + 2
CASE 11: codetab(npc) = 1: codetab(npc + 1) = Value
PRINT #2, npc, "code"; Value: npc = npc + 2
CASE 12: 'BKS scan
codetab(npc) = 6: codetab(npc + 1) = -npc
PRINT #2, npc, "scan s($"; npc; ")"
scanpc = npc: npc = npc + 2: scanend = lpc: lpc = 0
CASE 13: 'end scan
PRINT #2, "$"; lpc; "="; scanpc
'resolve forward ref's (mogen alleen call en term instr. zijn)
codetab(lpc + 3) = scanpc
PRINT #2, TAB(40); "$"; scanpc; "="; npc
'resolve forward ref of scan instr.
codetab(scanpc + 1) = npc
PRINT #2, TAB(40); "{end scan}": scanpc = 0: lpc = scanend
CASE 14: 'term
codetab(npc) = 5: codetab(npc + 1) = totermtab(text$)
IF scanpc THEN
IF lpc THEN
PRINT #2, "$"; lpc; "="; npc
'resolve forward ref's (mogen alleen call en term instr. zijn)
codetab(lpc + 3) = npc
END IF
lpc = npc
PRINT #2, npc, "term "; text$; " f($"; lpc; ")s("; scanpc; ")"
codetab(npc + 2) = scanpc: codetab(npc + 3) = -lpc
ELSE
PRINT #2, npc, "term "; text$; " s("; npc + 4; ")f($"; lpc; ")"
codetab(npc + 2) = npc + 4: codetab(npc + 3) = -lpc
END IF
npc = npc + 4
CASE 15: 'call
codetab(npc) = 8: codetab(npc + 1) = adresnametab(text$)
IF scanpc THEN
IF lpc THEN
PRINT #2, "$"; lpc; "="; npc
'resolve forward ref's (mogen alleen call en term instr. zijn)
codetab(lpc + 3) = npc
END IF
lpc = npc
PRINT #2, npc, "call "; text$; " f($"; lpc; ")s("; scanpc; ")"
codetab(npc + 2) = scanpc: codetab(npc + 3) = -lpc
ELSE
PRINT #2, npc, "call "; text$; " s("; npc + 4; ")f($"; lpc; ")"
codetab(npc + 2) = npc + 4: codetab(npc + 3) = -lpc
END IF
npc = npc + 4
CASE 16: 'dot
IF lpc = npc THEN
codetab(npc) = 9: codetab(npc + 1) = 0
PRINT #2, npc, "returnSucc"
ELSE
codetab(npc) = 10: codetab(npc + 1) = 0
PRINT #2, npc, "return"
PRINT #2, TAB(40); "$"; lpc; "="; npc
'resolve forward ref's ( tussen lpc en npc) alleen Fail
FOR i = lpc TO npc
IF codetab(i) = -lpc THEN codetab(i) = npc
NEXT
END IF
npc = npc + 2
CASE ELSE: PRINT #2, "Ill. code ("; opr; ")"
END SELECT
De PRINT statement's geven de uitvoer zoals in het het resultaat is te lezen .