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 :
SKIPSEARCHREST
Hiertoe is een procedure L_in(regels) welke het gewenste bewijs schrijft in L_result[L_REST] en in L_result[L_SEARCH] . Voor de handigheid wordt het volgnummer van het gevonden object uit de regels geschreven in L_result[INDEX] als deze 0 is betekend dit dat er geen bewijs gevonden is . Een voorbeeld : Gestel dat bewezen moet worden dat een text moet bevatten de woorden 'naam' 'gevolg' 'toestand' . 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 .


Parser

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 .
Dit is het zelfde als een FSM (knollentuin) n.l. de toestanden zijn de NonTerminals en de overgangen de Terminals .
In de lexicalscanner hebben we een aantal groepen gedefineerd zodat we dat niet weer op parser nieveau hoeven te doen . Deze Terminals zijn TEXT , NUM , PUNC en EOF . Sommige laten de lexicalscanner nog meer terminals bepalen zoals HEX , FLOAT e.d. De rest van de terminals worden geschreven tussen quote's (') . Laten we of een eindig machine kunnen omschrijven zoals in de inleiding is gegeven .
	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 .
V.b. De definitie voor een Hexadecimal getal 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 ) .
Er moet een code zijn die een TEXT,NUM,EOF herkend . Ook een code voor het herkennen van Terminals en een die de NonTerminals aanroept . Iedere code moet instaat zijn te zeggen of de verwachting juist/onjuist is .
Daar toe is de code gedefineerd als : operand,fadres voor de TEXT,NUM,EOF en operand,adres,sadres,fadres voor Terminals en NonTerminals . Het fadres wil zeggen gaan naar dat adres als de verifikatie onjuist is , sadres als het juist is en anders pak het volgende adres .
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 .)
De parser interpreter heeft de volgende instruktie's en ze zijn twee of vier bytes lang .
-- 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 .
 


Coder

Het is wel leuk als je weet dat je invoer juist is , maar meestal wil je dat door de text een andere actie wordt geactiveerd . Hiertoe zijn twee operant's toegevoegd en wel een mogelijk een fout nummer te geven dit door '[ getal ]' wat gegenereerd wordt als exit getal .
Een tweede operand is '( getal )' weergegeven als lit getal welke aangeeft welke code/procedure nu moet worden toegepast .
Er zijn parser's (o.a. REBOL) waarbij je in de parser rule's gelijk een uittevoeren code kunt meegegeven .
Er zijn ook parser's waarin je als uittevoeren code een programma kunt meegeven wat in laten stadium dan vertaald wordt (o.a. BISON) .
 
We gaan een poging doen om de code_generator voor boven genoemde assembler te maken .
Een paar andere toelichtingen zijn . de naam 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 .
De asembler gebruikt een forward_reference d.w.z. dat je een NonTerminal mag gebruiken en pas later beschrijft (c.q. definieerd) . Om de NonTerminals tekunnen referen worden deze namen opgeslagen in een nametable . Deze nametable bevat drie elementen naam , adres waar deze naam gedefinieerd is in de codetabel en gebruik hoevaak de naam gebruikt is .
Hiervoor zijn er een subroutine en twee functie beschikbaar . De functie 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' .
 
(Dit voorbeeld is in Q-Basic gesteld .)
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 .
 

Uitleiding