Post on 03-Feb-2022
Faculteit Ingenieurswetenschappen
Vakgroep Informatietechnologie
Voorzitter: Prof. Dr. Ir. Paul Lagasse
Implementatie van ECMAScript voor
de Parrot Virtuele Machine
door
Mehmet Yavuz Selim Soyturk
Promotor: Prof. Dr. Ir. Herman Tromp
Scriptiebegeleiders: Dr. Kris De Schutter, Bram Adams, David Matthys
Scriptie ingediend tot het behalen van de academische graad van
Licentiaat in de Informatica
Academiejaar 2006–2007
VOORWOORD i
Voorwoord
Ik bedank Prof. Herman Tromp omdat hij mij de kans gegeven heeft om mijn thesis te
schrijven over zo’n interessante thema. Ik bedank Kris Deschutter en Bram Adams voor de
ideeen bij de implementatie van PJS en vooral voor het hulp bij het schrijven van dit werk.
Ik bedank David Matthys voor de tips tijdens de presentaties. En ten laatste bedank ik
mijn familie die mij altijd gesteund hebben tijdens mijn studieleven.
“De auteur geeft de toelating deze scriptie voor consultatie beschikbaar te stellen en delen
van de scriptie te kopieren voor persoonlijk gebruik. Elk ander gebruik valt onder de
beperkingen van het auteursrecht, in het bijzonder met betrekking tot de verplichting de
bron uitdrukkelijk te vermelden bij het aanhalen van resultaten uit deze scriptie.”
Mehmet Yavuz Selim Soyturk, augustus 2007
Implementatie van ECMAScript voor
de Parrot Virtuele Machine
door
Mehmet Yavuz Selim Soyturk
Scriptie ingediend tot het behalen van de academische graad van
Licentiaat in de Informatica
Academiejaar 2006–2007
Promotor: Prof. Dr. Ir. Herman Tromp
Scriptiebegeleiders: Dr. Kris De Schutter, Bram Adams, David Matthys
Faculteit Ingenieurswetenschappen
Universiteit Gent
Vakgroep Informatietechnologie
Voorzitter: Prof. Dr. Ir. Paul Lagasse
Samenvatting
Parrot is een virtuele machine die als doel heeft om een platform te worden waarop ver-schillende dynamische programmeertalen werken. Parrot is nog altijd in ontwikkelingsfase,maar is reeds redelijk bruikbaar. In dit werk hebben we geprobeerd Parrot te evaluerendoor een dynamische programmeertaal voor die virtuele machine te implementeren. Wehebben hiervoor de taal ECMAScript gekozen, een taal die meer bekend is onder de naamJavaScript.
Trefwoorden
Parrot, ECMAScript, JavaScript, virtuele machine, dynamische programmeertaal
Implementation of ECMAScript forthe Parrot Virtual Machine
Mehmet Y. S. Soyturk
Supervisor(s): Herman Tromp, Kris De Schutter, Bram Adams, David Matthys
Abstract— Parrot is a virtual machine that aims to be a platform onwhich different dynamic programming languages run together. Parrot iswork in progress, but it is already usable now. In this work, we tried toevaluate Parrot by implementing a dynamic programming language on topof it. We have chosen ECMAScript for this purpose.
Keywords—Parrot, ECMAScript, JavaScript, virtual machine, dynamicprogramming language
I. I NTRODUCTION
Perl is a programming language created by Larry Wall in theyear 1987. Its main purpose was string manipulation. In thefollowing years the language evolved to a general purpose pro-gramming language.
In 1994, the language got new features and the interpreterof the language is rewritten. The resulting language is calledPerl 5. Later, Perl 5 got other new features such as Unicodesupport and threads as extensions. In 2000, the design processof Perl 6 started, which is a version of Perl which will removethe “historical warts” of the language and which will supportmany features directly.
Currently, there is an experimental implementation of Perl6called Pugs, written in Haskell. The real implementation ofPerl 6 will run on top of a virtual machine which is designedfor it. That virtual machine is called Parrot.
Parrot is intended to be a platform not only for Perl 6, butfor many different dynamic programming languages which willinteroperate efficiently. It will make possible that one languagecan make use of the libraries of another language, or that onecan write parts of an application in different languages.
Parrot is still being developed, but it’s already pretty usable.To evaluate Parrot, we have implemented ECMAScript on topof it. ECMAScript is a dynamic programming language whichis mainly used for scripting in host environments. Per example,JavaScript, an implementation of ECMAScript, is mainly usedfor scripting in web browsers. We called our implementationofECMAScript PJS (Parrot JavaScript).
II. PARROT
A virtual machine is software which forms an abstractionlayer between an application and a computer platform. An ap-plication compiled for a virtual machine can be executed in anyplatform which is supported by the virtual machine. This makesthe application portable between platforms.
Binary code which a virtual machine can directly execute iscalled bytecode. One can write an application for a given virtualmachine in any language which has a compiler that can translatecode of the language to bytecode of the virtual machine.
Parrot is a virtual machine which intends to efficiently exe-cute code of dynamic languages. Other popular virtual machines
such as JVM of Sun or CLR of Microsoft are more aimed tostatic languages. Another important difference between Parrotand JVM / CLR is the fact that Parrot is register based, while theothers are stack based.
Hereafter, we will firstly give a brief summary of importantfeatures of parrot. Then we will give some more informationabout the datatypes of Parrot and finally we summarize ways ofgenerating Parrot bytecode.
A. Important features of Parrot
In that section we give a short listing of important featuresofParrot [1].
Parrot is a register based virtual machine unlike other popularvirtual machines such as JVM or CLR which are stack based.Stack based virtual machines have to push operands in a stackbefore executing many instructions. A register based virtual ma-chine has to give to the instructions only the names of the regis-ters where the operands are stored.
A runcore is the way bytecode is interpreted in Parrot. Par-rot supports multiple runcores. Many of then define how thefetch-decode-execute loop runs. There is also JIT (just in timecompilation) which converts bytecode to native code beforein-terpretation. [2]
Parrot supports features like exceptions, closures, continu-ations or coroutines. Parrot also supports garbage collectionwhich automatically frees unused objects from memory.
Parrot supports different types of threads such as threads thatdon’t communicate with each other, threads that communicatewith each other via the event mechanism, or threads that shareobjects.
Parrot has a centralized event mechanism. Each thread has anevent queue which is thread safe. The thread checks regularly ifthere is an event in the event queue and executes it when needed.
B. Datatypes in Parrot
Parrot has 4 register types: int, num, string and pmc. In aParrot program, the register type of a value is known at compiletime. PMCs (Parrot Magic Cookies) represent complex objectsand allow a more dynamic type system in Parrot. Each PMCstructure contains a reference to a VTABLE structure, whichcontains a table of functions, called vtable-functions. Wecanapply general operations on PMCs with the help of its VTABLE.Thus, PMCs are polymorphic and VTABLEs define classes towhich PMCs belong. A PMC class can be considered as anemulation of C++ classes in C.
Parrot contains many PMC classes such as arrays, int, numand string wrappers, subroutines, closures, etc. Some of themare only used internally in the interpreter and some of them are
exposed to interpreted code. A language implementer can alsodefine a set of PMC classes for his language.
C. Bytecode generation
Parrot has two assembly languages. The first is PASM (Par-rot Assembly) which is nearly a one-to-one mapping of the ex-ecuted instructions. It’s not much used anymore. The secondlanguage is PIR (Parrot Intermediate Representation) which hasmore syntactic sugar and hides some details from the users, suchas calling canventions. PIR also allows one to use symbolic reg-ister names in the place of real registers.
There is also a library in Parrot which defines a set of nodesas a low level representation of a Parrot program (POST, Par-rot Opcode Syntax Tree), and which can convert those nodes toParrot code. Another library defines a set of higher level nodes(PAST, Parrot Abstract Syntax Tree) which can be transformedto POST. Finally, Parrot also has a parser generator called PGE(Parser Grammar Engine) and a tree transformation tool calledTGE (Tree Grammar Engine) which can be used together withPAST and POST to translate code of one language to Parrotbytecode.
III. ECMASCRIPT
ECMAScript is a dynamic programming language which ismainly used for scripting in host environments, such as webbrowsers. We summarize some features of the language whichare important from an implementation point of view.
A. Objects and prototype based inheritance
An object in ECMAScript can be considered as some sort ofhastable which maps strings to other values. The values in thehashtable are called the properties of the object.
ECMAScript does not have class based object orientation sys-tem. An object in ECMAScript can have a prototype (whichis also an object) from which the object inherits all properties.It’s therefore said that ECMAScript has prototype based inheri-tance.
B. Functions
Functions in ECMAScript are higher order. They can bestored in variables or they can be passed to/returned from otherfunctions. They are in fact ECMAScript objects as describedin III-A which can be called. A function can also be used as amethod or as a constructor.
C. The with statement
A with statement allows us to place an object at the top ofan execution context. This means practically that the valueofa variablev in a with block with objecto is firstly looked up asa property ’v’ of objecto, and then looked up in the rest of theexecution context if that property does not exist.
D. The eval function
ECMAScript contains a function calledeval which takes asan argument some code of type string, and which interprets thecode (nearly) as if it was directly written in the place of thecallto theeval function.
E. Exceptions
ECMAScript allows throwing an handling of exceptions withthe help ofthrow andtry-catch-finallystatements.
IV. I MPLEMENTATION OF PJS
We defined the datatypes of ECMAScript, such as number,string, object, function, by PMC classes. The implementation ofmany language constructs such as conditional statements, loops,exceptions, etc. were straightforward.
One problem that we encountered was the inflexibility of thelexical scoping mechanism of Parrot that made the implementa-tion of with statements and theevalfunction diffcult. Therefore,we defined our own scoping mechanism by representing an EC-MAScript context by a datatype that we defined ourself, and bygiving that context to each ECMAScript function as a parameter.
The result is an ECMAScript implementation which can com-plete nearly the half of the Spidermonkey test suite from MozillaFoundation. Many of the failing tests are the result of the incom-plete standard library of PJS.
PJS is currently much slower than Spidermonkey, an EC-MAScript implementation of Mozilla Foundation which makesuse of an interpreter written in C. But Parrot is work in progresswhich will eventually speed up in time, and code that PJS gen-erates is totally unoptimized.
The polymorphic nature of PMCs made interoperation be-tween PJS en other Parrot types possible in many cases, butthere are problems in some cases. Per example, PJS could makecalling Parrot subroutines with ECMAScript syntax possible bydoing typechecking, but the other way around is not directlypossible at the moment (but hopefully will be soon when somebugs in Parrot are resolved).
You can download the source code of PJS athttp://users.fulladsl.be/spb1622/pjs/ .
V. CONCLUSION
We think that the main feature of Parrot is being a platform onwhich different dynamic programming languages interoperate.We think that Parrot succeeded in it partially, but can becomemuch better in time.
REFERENCES
[1] Klaas Jan Stol, On the Architecture of the Parrot Virtual Machine,http://www.perlfoundation.org/parrot/index.cgi?publicationson parrot
[2] Dan Sugalski,Presentation: Implementing an Interpreter(Not any moreavailable on Internet)
INHOUDSOPGAVE v
Inhoudsopgave
Voorwoord i
Overzicht ii
Extended abstract iii
Inhoudsopgave v
Afkortingen viii
1 Inleiding 1
2 Parrot virtuele machine 3
2.1 Algemene eigenschappen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.1 Register-gebaseerd . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.2 Verschillende runcores . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.3 Garbage collection . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.4 Continuations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.5 Uitzonderingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.6 NCI (native call interface) . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.7 Draden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.8 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Parrot datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.1 Primitieve datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.2 PMC’s . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.3 Parrot-klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Dynamische opcodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.4 Code generatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.4.1 PASM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.4.2 PIR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4.3 PGE en TGE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
INHOUDSOPGAVE vi
3 ECMAScript 15
3.1 Datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 Objecten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.4 Scoping en de Environment . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.5 Functies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.6 Methodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.7 Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.8 Het with Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.9 Uitzonderingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.10 De Functie eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.11 Lussen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4 PJS 32
4.1 Entiteiten waaruit PJS bestaat . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2 PMC-klassen voor PJS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.2.1 Primitieve types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.2.2 PjsObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2.3 De environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.2.4 PjsFunction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.2.5 PjsArray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.3 Dynamische opcodes voor PJS . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.4 PIR bibliotheek voor PJS . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5 Vertaling van ECMAScript naar PIR 43
5.1 De environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.2 Primitieve datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.3 Unaire en binaire operaties . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.4 Variabelen, declaratie en toekenning . . . . . . . . . . . . . . . . . . . . . . 45
5.5 Functies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.6 Functieoproep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.7 Oproep van functies als constructors . . . . . . . . . . . . . . . . . . . . . 52
5.8 Keuze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.9 Lussen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.9.1 while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.9.2 for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.9.3 for .. in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.10 with . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.11 eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.12 Uitzonderingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
INHOUDSOPGAVE vii
5.12.1 Exception handling in Parrot . . . . . . . . . . . . . . . . . . . . . 58
5.12.2 try .. catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.12.3 try .. finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
5.12.4 try .. catch .. finally . . . . . . . . . . . . . . . . . . . . . . . . . . 62
5.13 Standaard bibliotheek van PJS . . . . . . . . . . . . . . . . . . . . . . . . 62
6 Besluit en toekomstperspectieven 64
6.1 Compleetheid van PJS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.2 Samenwerking met andere talen . . . . . . . . . . . . . . . . . . . . . . . . 65
6.3 Performantie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.4 Gemak in de implementatie en overdraagbaarheid . . . . . . . . . . . . . . 67
6.5 Algemeen besluit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Bibliografie 68
Lijst van figuren 69
AFKORTINGEN viii
Afkortingen
CLR Common Language Runtime, een virtuele machine van Microsoft
HLL High Level Language, een taal geımplementeerd op Parrot
IEEE Institute of Electrical and Electronics Engineers
JVM Java Virtual Machine, een virtuele machine van Sun
PASM Parrot Assembly , een assembly taal voor Parrot
PGE Parser Grammar Engine, een parser generator voor Parrot
PIR Parrot Intermediate Representation, een assembly taal voor Parrot
PJS Parrot JavaScript , onze ECMAScript implementatie
PMC Parrot Magic Cookie, een lage niveau Parrot object
TGE Tree Grammar Engine, een tool dat helpt bij boomtransformaties
INLEIDING 1
Hoofdstuk 1
Inleiding
Perl is een redelijk oude programmeertaal die gecreeerd is door Larry Wall in het jaar
1987. Het aanvankelijke doel van Perl was de manipulatie van tekst. Door de jaren heen
is de taal gegroeid tot een algemeen bruikbare programmeertaal. In het jaar 1994 werden
nieuwe features toegevoegd naar Perl en de interpreter van de taal werd herschreven. De
nieuwe taal werd dan Perl 5 genoemd.
Perl 5 kreeg in de daaropvolgende jaren nog meer features, zoals Unicode ondersteuning,
draden, enz. als extensies op de bestaande taal. In het jaar 2000 heeft men beslist dat
een nieuwe versie van Perl geschreven gaat worden die sommige problemen van Perl 5 niet
meer vertoont en de later toegevoegde features van Perl 5 rechtstreeks ondersteunt. De
nieuwe taal heeft men Perl 6 genoemd.
Er is momenteel een prototype implementatie van Perl 6 geschreven in de programmeertaal
Haskell. De eigenlijke implementatie zal echter op een virtuele machine draaien die speciaal
ontworpen wordt voor Perl 6. Deze virtuele machine heet Parrot.
Parrot heeft tot doel dat het een platform wordt waar, naast Perl 6, verschillende dyna-
mische programmeertalen zoals Python, Ruby, Scheme of PHP op een efficiente manier
lopen. Dat zal mogelijk maken dat die talen bibliotheken met elkaar kunnen delen of dat
men delen van een programma in verschillende talen kan schrijven. Een ander voordeel is
het feit dat verbeteringen in de virtuele machine (zoals grotere snelheid, lager geheugenge-
bruik, meer stabiliteit) ook de talen geımplementeerd bovenop de virtuele machine beter
maken.
Hoewel Parrot nog altijd in de implementatie fase zit, is het momenteel redelijk bruik-
INLEIDING 2
baar. Om Parrot te evalueren hebben we ECMAScript1 geımplementeerd voor Parrot.
ECMAScript is een dynamische programmeertaal die als scriptingtaal gebruikt kan wor-
den in hosting environments. Bekende voorbeelden ervan zijn JavaScript (JScript) die in
webbrowsers draait, en ActionScript die voor scripting van Flash animaties gebruikt wordt.
We hebben onze ECMAScript implementatie PJS (Parrot JavaScript) genoemd.
In deze scriptie gaan we dus aandacht besteden aan Parrot en aan de implementatie van een
programmeertaal voor Parrot, namelijk ECMAScript. In hoofdstuk 2 bespreken we Parrot
vanuit een theoretisch standpunt. We bespreken de datatypes van Parrot, geven we korte
codevoorbeelden van de assemblytalen, en geven een overzicht van sommige belangrijke
features van Parrot. In hoofdstuk 3 sommen we op wat de ECMAScript datatypes zijn. We
geven dan meer uitleg over hoe waarden zoals objecten, functies en arrays gerepresenteerd
worden. Daarna leggen we uit hoe het scoping mechanisme werkt en hoe functies, methodes
en constructors opgeroepen worden. In hoofdstuk 4 schetsen we de entiteiten waaruit PJS,
onze implementatie van ECMAScript, bestaat. Daarna geven we meer informatie over
de nieuwe datatypes, opcodes en Parrot bibliotheken die we gedefinieerd hebben en die
nodig zijn voor de uitvoering van een PJS programma. In hoofdstuk 5 tonen we hoe we
de belangrijkste ECMAScript-bouwstenen vertaald hebben naar Parrot. In het laatste
hoofdstuk geven we dan als besluit kort uitleg over wat we verwezenlijkt hebben en welke
problemen we tegengekomen zijn bij de implementatie van PJS.
Voor ons was het grootste informatiebron over Parrot de documentatie in de webpagina
van Parrot[1]. Informatie over de werking van ECMAScript werd grotendeels gehaald uit
de ECMA-262 specificatie document[2] van Ecma International.
1Vastgelegd door de ECMA-262 standaard, 3e editie
PARROT VIRTUELE MACHINE 3
Hoofdstuk 2
Parrot virtuele machine
Een virtuele machine is een stuk software dat een abstractielaag vormt tussen een compu-
terplatform en een applicatie. Als gevolg daarvan kan een applicatie die gecompileerd is
voor een bepaalde virtuele machine uitgevoerd worden op alle platformen die de virtuele
machine ondersteunen. Er wordt daarom gezegd dat de gecompileerde code overdraagbaar
is tussen platformen.
Binaire code die een virtuele machine begrijpt, wordt bytecode genoemd. Bytecode bevat,
naast metadata, een sequentie van instructies die de virtuele machine moet uitvoeren. Voor
een gegeven virtuele machine kan men een applicatie schrijven in gelijk welke taal waarvoor
een compiler bestaat die de code van de taal naar de bytecode van de virtuele machine
vertaalt.
Parrot is een virtuele machine die efficiente uitvoering van dynamische programmeerta-
len als doel heeft. Andere populaire virtuele machines zoals JVM van Sun en CLR van
Microsoft zijn meer gericht tot minder dynamische programmeertalen (implementatie van
dynamische programmeertalen voor die virtuele machines is nog altijd mogelijk, en er zijn
voorbeelden van zoals IronPython). Een ander groot verschil tussen Parrot en JVM / CLR
is het feit dat Parrot register gebaseerd is, en de andere virtuele machines stapelgebaseerd
zijn.
In dit hoofdstuk gaan we eerst sommige algemene eigenschappen van Parrot bespreken.
Daarna gaan we informatie geven over de datatypes waarmee Parrot werkt. Tot slot tonen
we enkele manieren om Parrot bytecode te genereren.
2.1 Algemene eigenschappen 4
2.1 Algemene eigenschappen
Hieronder geven we kort een overzicht van de interessante eigenschappen van Parrot. Een
grote deel van deze sectie is gebaseerd op [5].
2.1.1 Register-gebaseerd
De populaire virtuele machines JVM en CLR zijn stapel-gebaseerd. In het algemeen moet
een stapel-gebaseerde machine bij het uitvoeren van instructies de operandi eerst op de
stapel pushen, en het resultaat komt dan op de stapel terecht.
Parrot is niet stapel-gebaseerd, maar register-gebaseerd. Registers zijn genummerde plaat-
sen waar de waarden opgeslagen worden. Instructies in Parrot krijgen in het algemeen als
argumenten registernummers, zodat de instructie kan bepalen waar de operandi zijn en
waar het resultaat terecht moet komen. Omdat men niet de stapel moet manipuleren voor
het uitvoeren van instructies verwacht men dat een register-gebaseerde machine sneller
werkt dan een stapel-gebaseerde machine.
Parrot heeft een rijke instructie set. Er zijn instructies voor assignatie, algemene binaire
bewerkingen, branching, uitzonderingen, debugging, I/O, string manipulatie, object bewer-
kingen, transcendentale wiskundige functies, enz. Parrot laat ook toe dat nieuwe opcodes
aan Parrot toegevoegd worden at runtime door het laden van gedeelde bibliotheken (dll,
so).
2.1.2 Verschillende runcores
Een runcore stelt een manier voor waarop de bytecode uitgevoerd wordt. Parrot onder-
steunt verschillende runcores. Men kan voor het uitvoeren van een parrot programma
kiezen welke runcore Parrot moet gebruiken. De meeste runcores bepalen hoe de fetch-
decode-execute lus van de interpreter gebeurt. Daarnaast is er JIT (just in time compila-
tion) die bytecode eerst converteert naar native code voor het uitvoeren. [7]
2.1 Algemene eigenschappen 5
2.1.3 Garbage collection
Parrot ondersteunt garbage collection. Hiermee worden objecten die niet meer gebruikt
worden automatisch verwijderd uit het geheugen.
Parrot maakt hiervoor gebruik van een mark-and-sweep garbage collector. Wanneer het
geheugen vol is, worden objecten in een initiele verzameling (root set, bv. objecten in Parrot
registers) gemarkeerd als levend. Daarna markeren die objecten recursief alle objecten
waarnaar zij referenties hebben als levend. Uiteindelijk verwijdert de garbage collector de
objecten die niet als levend gemarkeerd zijn.
2.1.4 Continuations
Wanneer een subroutine uitgevoerd wordt, wordt de toestand van deze uitvoering ergens
bijgehouden. De toestand kan bv. het volgende bevatten:
� Wat zijn de waarden van de registers?
� Wat zijn de waarden van de lexicale variabelen?
� Waar moet ik terugkeren?
� ...
Die toestand noemen we de context. Als een vereenvoudigde definitie van continuations
kunnen we zeggen dat een continuation een context is samen met een codelocatie. Stel dat
we in een subroutine een continuation aanmaken met de huidige context en een bepaalde
codelocatie in de huidige subroutine, en dat we die continuation doorgeven aan andere
subroutines. Men kan dan altijd terugkeren naar de eerste subroutine door het nemen van
de continuation, d.w.z. door te herstellen van de huidige context naar de context van de
continuation en door een jump uit te voeren naar de codelocatie van de continuation. Een
continuation laat ons dus toe een soort jump-operatie uit te voeren van de ene subroutine
naar een andere, maar op een veilige manier (omdat de context hersteld wordt).
Het oproepen van subroutines gebeurt in Parrot met behulp van continuations. De oproe-
per maakt eerst een continuation aan die bepaalt waar teruggekeerd moet worden, en geeft
die continuation door aan de opgeroepen subroutine.
2.1 Algemene eigenschappen 6
De programmeertaal C ondersteunt continuations gedeeltelijk door setjmp/longjmp func-
ties. [4]
2.1.5 Uitzonderingen
Parrot heeft een uitzonderingsmechanisme dat grotendeels steunt op continuations. Zo
wordt de taak van de HLL-ontwikkelaars gemakkelijker. De huidige implementatie is nog
niet volledig. Voor meer informatie over hoe de uitzonderingen ongeveer werken, zie sectie
5.12.1.
2.1.6 NCI (native call interface)
Parrot maakt het onmiddellijk oproepen van native code vanuit Parrot mogelijk. Men moet
daarvoor een gedeelde bibliotheek (dll, so) laden vanuit Parrot, en een bibliotheekfunctie
oproepen door middel van een speciale opcode. Stel dat men bv. een OpenGL-functie wil
uitvoeren vanuit Parrot. Men moet gewoon een bestaande OpenGL bibliotheek laden en
de functie uitvoeren. Er is geen nood aan het schrijven van speciale binding code in C.
2.1.7 Draden
Parrot ondersteunt draden. Wanneer een nieuwe draad in Parrot aangemaakt wordt, wordt
een nieuwe interpreter aangemaakt die de taak van de draad uitvoert. Er zijn 3 types
draden:
� Draden die niet met elkaar communiceren.
� Draden die met elkaar communiceren door middel van berichten. Hiervoor wordt
gebruik gemaakt van het event-mechanisme.
� Draden die data met elkaar delen.
2.1.8 Events
Parrot heeft een gecentraliseerd event-mechanisme. Elke draad in Parrot heeft een event
queue die thread safe is. De draad controleert af en toe de event queue en voert een event
in de event queue uit als het nodig is.
2.2 Parrot datatypes 7
2.2 Parrot datatypes
2.2.1 Primitieve datatypes
Parrot heeft 4 primitieve datatypes: int (I), num (N), string (S) en pmc (P). Het primitieve
type van een waarde is bekend at compile-time. Voor elk primitieve type heeft parrot
aparte registers, genummerd vanaf nul: I0, N0, S0, P0, I1, N1, enz. Daarom noemt men
de primitieve types ook registertypes. Het aantal van deze registers is afhankelijk van de
uitgevoerde subroutine en wordt bepaald at compile-time.
Een string is een fundamentele datatype in Parrot. Het is logisch, want het verwerken van
tekst is de belangrijkste taak van Perl, de reden van ontstaan van Parrot. Strings houden
bij welke charset en encoding zij gebruiken. Parrot ondersteunt unicode strings.
2.2.2 PMC’s
PMC staat voor Parrot Magic Cookie. Alle soorten data die niet voorgesteld kunnen
worden door een int, num of string, worden voorgesteld door een PMC. Voor PMC’s is er
een ander dynamisch typesysteem, onafhankelijk van de statische primitieve types.
Codefragment 2.1 geeft op een vereenvoudigde manier weer hoe een PMC datastructuur
er uitziet. Zoals uit het codefragment blijkt, heeft elke PMC een aantal datavelden die
de toestand van de PMC bijhouden. Daarnaast heeft een PMC een referentie naar een
VTABLE structuur die het gedrag van de PMC definieert d.m.v. een vast aantal functies,
vtable-functies genoemd. De relatie tussen een VTABLE en een PMC kan men dus best
vergelijken met de relatie tussen een klasse en een instantie van die klasse. Vanaf nu zullen
we daarom het woord ’PMC-klasse’ gebruiken in plaats van het meer tot implementatie
gerichte woord ’VTABLE’.
De definitie van een PMC-klasse wordt in een bestand met een ’pmc’ extensie geschreven.
Een precompiler vertaalt dat bestand naar C code die de VTABLE structuur van de PMC-
klasse invult met de gedefinieerde vtable-functies. We tonen hieronder een voorbeeld van
een PMC-klasse definitie:
pmclass IntegerWrapper {
/* De v ta b l e−f u n c t i e in i t pmc wordt opgeroepen
2.2 Parrot datatypes 8
na de aanmaak van een PMC van deze k l a s s e . */void in i t pmc (PMC* wrapped ) {
/* PMC pmc val i s een macro dat een van deda tave lden van een PMC g e e f t . */
PMC pmc val (SELF) = wrapped ;}
/* De f i n i t i e van de v t a b l e−f u n c t i e g e t i n t e g e r */INTVAL g e t i n t e g e r ( ) {
PMC* wrapped = PMC pmc val (SELF ) ;
/* VTABLE xxx roep t de v t a b l e−f u n c t i e xxx van eenandere PMC op. Zoa ls meeste f u n c t i e s in Parrot k r i j g tdat f u n c t i e ook de Parrot i n t e r p r e t e r a l s argument. */
return VTABLE get integer (INTERP, wrapped ) ;}
/* . . . andere v t a b l e−f u n c t i e s */}
Een PMC-klasse laat ons dus toe op een algemene wijze te interageren met een PMC zonder
te moeten weten tot welke PMC-klasse het behoort. We kunnen bijvoorbeeld de tekstuele
waarde van een PMC krijgen d.m.v. de vtable-functie get string, numerieke waarde van
het PMC aanpassen d.m.v set number, een element op een bepaalde index krijgen d.m.v.
get pmc keyed, het PMC optellen met een andere d.m.v. ’add’, enz.
Vtable-functies zijn relatief snel omdat ze vast in aantal zijn (ongeveer 250) en ze statisch
gedefinieerd zijn in de VTABLE structuur, maar ze zijn niet voldoende om meer gespeci-
aliseerd gedrag van een PMC te definieren. De vtable-functie find method(string), die een
parrot subroutine1 teruggeeft op basis van een methodenaam als parameter, biedt hier een
oplossing voor.
1Voor een PMC-klasse die in C gedefinieerd wordt, zullen ze wrappers zijn rondom C functies.
2.3 Dynamische opcodes 9
2.2.3 Parrot-klassen
PMC-klassen, die in C gedefinieerd worden, zijn meer gericht tot de Parrot- en HLL2-
ontwikkelaars. Men kan van een Perl 6 eindgebruiker niet verwachten dat hij in C pro-
grammeert om nieuwe klassen te definieren. De PMC-klassen ParrotClass en ParrotOb-
ject bieden hier een oplossing voor: zij beheren d.m.v. vtable-functies als add method,
add attribute, find method, add parent, enz. een meer dynamisch objectsysteem. Zo kan
men at runtime nieuwe (sub)klassen definieren, attributen en methodes toevoegen aan deze
klassen, objecten aanmaken die tot deze klassen behoren, enz. Het is ook op te merken
dat dit objectsysteem op een redelijk transparante manier werkt met de in C gedefinieerde
PMC-klassen. Parrot-klassen kunnen zelfs overerven van PMC-klassen.
2.3 Dynamische opcodes
Men kan de instruction set van Parrot extenderen door middel van dynamische opcodes.
Dynamische opcodes worden in C gedefinieerd en worden gecompileerd naar gedeelde bi-
bliotheken (dll, so). Een Parrot programma kan dan de nieuwe instructies gebruiken na
het opladen van deze bibliotheken.
2.4 Code generatie
In deze sectie bespreken we de verschillende manieren om Parrot bytecode te genereren.
2.4.1 PASM
PASM (Parrot Assembly) was ooit de standaard assembly taal voor Parrot. Het is min of
meer een een-op-een-mapping van de door Parrot uitgevoerde instructies. Maar PASM is
intussen grotendeels vervangen door PIR, een hogere niveau assemblytaal.
Codefragment 2.2 toont een voorbeeld van een PASM programma. In het programma is
een subroutine foo gedefinieerd dat een argument van type string en een argument van type
int accepteert, de argumenten afprint, en 21 teruggeeft. Het programma roept de functie
2HLL: High Level Language, een taal die geımplementeerd wordt op Parrot
2.4 Code generatie 10
foo op met parameters "Hello\n" en 42, en print het resultaat uit op het scherm. Als het
resultaat groter is dan 50, wordt er ook "Niet uitgeprint" uitgeprint op het scherm.
2.4.2 PIR
PIR (Parrot Intermediate Representation) is de huidige assembly taal voor Parrot. Hij is
redelijk van hoog niveau voor een assembly taal.
Codefragment 2.3 toont een voorbeeld van een PIR programma dat equivalent is met het
PASM programma op codefragment 2.2. Er zijn een aantal dingen op te merken:
� Men kan genaamde variabelen declareren. Bij de declaratie moet men het registertype
aangeven. De PIR compiler zorgt dan dat deze variabele een register toegekend krijgt.
� Men kan gebruik maken van symbolische registers zoals $I0 en $S2 door registernamen
te laten voorafgaan door een $-teken. Het registertype zit al ingebakken in de naam
van het symbolische register (de letter na de $-teken). De PIR compiler zorgt dat
het symbolische register een echt register toegekend krijgt.
� Er zijn onbeperkt aantal registers.
� Er is heel wat ’syntactic sugar’ die PIR ook voor mensen hanteerbaar maken, o.a.
betere syntax voor subroutine- en methodeoproep, binaire operatoren, conditional
jumps, enz.
Een belangrijke syntactic sugar is de assignatie operator ’=’. De operatie
left = right
is equivalent met het uitvoeren van een set-instructie:
set left, right
Als de operandi van hetzelfde type zijn, wordt de waarde right gekopieerd in het register
left. Als left en right van verschillende type zijn, wordt er aan conversie gedaan van
de ene type naar de andere. Als left een PMC is en right geen, dan wordt de vtable-
functie set XXX native van left opgeroepen, met XXX vervangen door “string”, “integer”
of “number”, afhankelijk van het type van right. Als left geen PMC is en right wel, dan
2.4 Code generatie 11
wordt de nieuwe waarde van het register left bepaald door de vtable-functie get XXX van
right op te roepen.
En nog:
left = opcode right1, right2, ...
is equivalent met
opcode left, right1, right2, ...
Dit werkt ook met de wiskundige operatoren:
left = right1 + right2
is bijvoorbeeld equivalent met
add left, right1, right2
2.4.3 PGE en TGE
PGE (Parrot Grammar Engine) is een parser generator (vergelijkbaar met Lex en Yacc)
die een syntax definitie converteert naar een parser in PIR. Het resultaat van het uitvoeren
van de parser geeft dan een boomrepresentatie van de geparsete syntax. Door middel van
TGE (Tree Grammar Engine) kan men syntaxbomen transformeren van de ene vorm naar
de andere.
PAST (Parrot Abstract Syntax Tree) is een vooraf gedefinieerde boomstructuur die een
abstract Parrot programma voorstelt. POST (Parrot Opcode Syntax Tree) is een lagere
niveau boomstructuur die dichter ligt bij het PIR niveau.
Er zijn reeds Parrot bibliotheken die PAST kunnen converteren naar POST en POST
kunnen converteren naar PIR. Het ideale geval voor een compiler schrijver is dan dat hij
zijn taal definieert in PGE, en door middel van TGE de abstracte syntax boom van zijn
taal converteert naar PAST.
2.4 Code generatie 12
Codefragment 2.1: Vereenvoudigde structuur van PMC’s
/* Elke PMC wordt v o o r g e s t e l d door deze s t r u c t uu r */struct PMC {
void* data ;/* . . . andere data */struct VTABLE* vtab l e ;
}
/* Elke PMC−k l a s s e h e e f t een VTABLE */struct VTABLE {
int type ;/* . . . andere data */
/* v t a b l e f u n c t i e s */
/* f u n c i n i t t i s bv . een f un c t i e van type vo id (* ) ( vo id ) */f u n c i n i t t i n i t ;f un c d e s t r o y t des t roy ;
func add t add ;f unc sub t sub ;/* . . . */
f u n c g e t s t r i n g t g e t s t r i n g ;f u n c g e t i n t e g e r t g e t i n t e g e r ;f u n c g e t b o o l t g e t boo l ;/* . . . */
func get pmc keyed t get pmc keyed ;f u n c g e t s t r i n g k e y e d t g e t s t r i n g k ey ed ;/* . . . */
f un c type t type ;func f ind method t f ind method ;func add method t add method ;func add paren t t add parent ;/* . . . */
f un c i nvoke t invoke ;
/* . . . */}
2.4 Code generatie 13
Codefragment 2.2: Voorbeeld van PASM code
. pcc sub main sub :set S0 , "Hello\n"
set I0 , 42
# oproepconvent i e sset args "(0, 0)" , S0 , I0get results "(0)" , I1
# foo oproepenfind name P1 , "foo"
invokecc P1print I1
le I1 , 50 , e e n l a b e lprint "Niet uitgeprint"
e e n l a b e l :
# in t e r p r e t e r s toppenend
. pcc sub f oo :get params "(0, 0)" , S0 , I0set returns "(0)" , 21print S0print I0returncc
2.4 Code generatie 14
Codefragment 2.3: Voorbeeld van PIR code
. sub main sub : main. local string s t r # genaamde r e g i s t e rs t r = "Hello\n" # ’ s yn t a c t i c sugar ’ voor a s s i g n a t i e
$ I0 = 42 # symbo l i sch r e g i s t e r
$ I1 = foo ( s t r , $ I0 ) # func t i eoproepprint $ I1
i f I1 <= 50 goto e e n l a b e lprint "Niet uitgeprint"
e e n l a b e l :
. end
. sub f oo.param string s t r.param int i
print s t rprint i
. return (21). end
ECMASCRIPT 15
Hoofdstuk 3
ECMAScript
ECMAScript is een dynamische programmeertaal die voor scripting gebruikt wordt in
een hosting omgeving. De taal werd gestandardiseerd door Ecma International in het
jaar 1997. Deze standaard heet ECMA-262. JavaScript en JScript, die als scriptingtaal
gebruikt worden in webbrowsers, zijn implementaties van ECMAScript. Die talen zijn wel
ouder dan de ECMA-262 standaard en zij vormden de basis van die standaard. Er zijn
momenteel veel andere implementaties, zoals ActionScript die voor scripting van Flash
animaties gebruikt wordt.
In dit hoofdstuk leggen we uit hoe ECMAScript werkt. Eerst sommen we op wat de EC-
MAScript datatypes zijn. We geven dan meer uitleg over hoe waarden zoals objecten,
arrays en functies gerepresenteerd worden. Daarna leggen we uit hoe het scoping mecha-
nisme werkt en hoe functies, methodes en constructors opgeroepen worden. Dan geven we
uitleg over andere belangrijke mogelijkheden van de taal zoals with, uitzonderingen, eval
en for-in lussen.
3.1 Datatypes
ECMAScript heeft vijf primitieve types: Boolean, Number, String, Undefined en Null. Tot
Boolean behoren enkel de waarden true en false. Number representeert dubbele precisie
kommagetallen volgens de IEEE 754 standaard. String representeert een onveranderbare
(immutable) reeks van lettertekens. Tot Undefined behoort enkel de waarde undefined, en
tot Null behoort enkel de waarde null.
Naast de primitieve types is er ook het type Object. Een instantie van dat type kunnen we
3.2 Objecten 16
ruwweg vergelijken met een hashtabel die waarden van het type String mapt naar waarden
van om het even welk ECMAScript type. Als het object o de sleutel s mapt naar de waarde
w, dan noemen we w de property s van het object o of de property van het object o met
naam s.
Een waarde in ECMAScript behoort tot een van deze zes types. Men moet wel opmerken
dat enkel een waarde een vast type heeft in ECMAScript. De variabelen behoren tot geen
enkel type, en kunnen waarden van elk type aannemen.
Een ECMAScript array is een speciaal geval van een object, die zijn property length dy-
namisch aanpast wanneer die properties krijgt met namen die als gehele getallen geparsed
kunnen worden. Een functie is ook een speciaal geval van een object.
3.2 Objecten
Een ECMAScript object is een datastructuur die een aantal properties bevat. Elke property
heeft een naam van het type String en een waarde van om het even welk ECMAScript
type. Men kan at runtime nieuwe properties toevoegen aan een object, of men kan die
verwijderen. In dat opzicht kunnen we een object dus vergelijken met een hashtabel.
Maar een ECMAScript object is meer dan een hashtabel. Elk object heeft een prototype
object (mogelijk null) waarvan het alle properties overerft. Daarom spreekt men over
prototypegebaseerde overerving in ECMAScript.
Properties van een object die onmiddellijk in dat object opgeslagen zijn (niet overgeerfd)
noemen we de eigenlijke properties van dat object.
Figuur 3.1 toont drie ECMAScript objecten: obj, p1, p2. Elk blok op de figuur stelt
een object voor dat een hashtabel bevat die sleutels van type string naar andere waarden
vertaalt. Die hashtabel bevat de eigenlijke properties van een object. In de figuur zien we
dat obj de properties van p1 overerft, en dat p1 de properties van p2 overerft.
Een property p van een object obj wordt op de volgende manier verkregen: p wordt eerst
opgezocht in de hashtabel van obj. Als die hashtabel p niet bevat, wordt p opgezocht in
de hashtabel van het prototype van obj, en de procedure wordt op deze manier herhaald.
Als p nergens in de prototype ketting kan gevonden worden, is de waarde van property p
undefined.
3.2 Objecten 17
Figuur 3.1: Een ECMAScript object met zijn prototype ketting
De ECMAScript syntax voor het verkrijgen van de property p van een object obj is
obj.p
of
obj[’p’]
Bij het aanpassen van de waarde van een property van object obj wordt nooit gekeken naar
het prototype van obj. De nieuwe waarde wordt onmiddellijk geplaatst in de eigenlijke
properties van obj. De eigenlijke properties van de prototypes blijven dus ongewijzigd door
de properties van obj te manipuleren.
Het ECMAScript syntax voor het aanpassen van de waarde van een property is
obj.p = w
of
obj[’p’] = w
Een property van een object kan een aantal attributen hebben. De mogelijke attributen
zijn de volgende:
3.3 Arrays 18
ReadOnly De waarde van de property kan niet gewijzigd worden. Pogingen tot wijzigen
worden genegeerd.
DontDelete De property kan niet verwijderd worden. Een delete operatie geeft false
terug.
DontEnum De property mag niet geenumereerd worden in een for-in lus.
Objecten worden aangemaakt door middel van constructors (zie 3.7) of door middel van
object literals. Onderaan ziet u een voorbeeld van een object literal met twee properties p
en q, met waarden respectievelijk 2 en 3:
{p: 2, q: 3}
3.3 Arrays
Een array in ECMAScript is een speciaal object waarvan de length property automatisch
verandert. De waarde van de length property is het maximum van de propertynamen in
de array die als een natuurlijke getal geparsed kunnen worden.
3.4 Scoping en de Environment
Om een nieuwe variabele te declareren in de huidige scope wordt in ECMAScript gebruik
gemaakt van het keyword var :
var x;
ECMAScript heeft een ander soort scoping mechanisme dan we gewend zijn in andere talen
(zoals Java of C). In ECMAScript definieren codeblokken zoals if-blokken of for-blokken
geen nieuwe scopes. Volgende codevoorbeeld is dus een geldig ECMAScript programma
en geeft als output “5”.
/* s t a r t g l o b a l e scope */// y in g l o b a l e scopevar y = 3 ;
3.4 Scoping en de Environment 19
function f ( ) { /* s t a r t scope voor f */i f ( t rue ) {
// x in scope van fvar x = 5 + y ;
}pr in t ( x ) ;
} /* e inde scope voor f */
/* e inde g l o b a l e scope */
De (bijna) enige codeblok die een nieuwe scope definieert is een functieblok (voor de andere,
zie with (3.8) en catch (3.9)). Variabelen gedeclareerd binnen een functieblok zijn niet
zichtbaar buiten het functieblok, en de variabelen in de omringende omgeving van het
functieblok zijn wel zichtbaar in het functieblok. Omdat ECMAScript geneste functies
toelaat, is dat een interessante eigenschap.
We zullen dit meer verduidelijken met volgend voorbeeld:
var x = 10 ;
function a ( ) {pr in t ( x ) ;
}a ( ) ; // output i s 10
function b ( ) {var y = 50 ;function c ( ) {
pr in t ( y ) ;y++;
}c ( ) ;c ( ) ;
}b ( ) ; // output i s 50 en 51
// Veroorzaakt een u i t z onde r in g omdat y// n i e t g e d e f i n i e e r d i s in dat s cope .
3.4 Scoping en de Environment 20
pr in t ( y ) ;
Een functie onthoudt zijn buitenomgeving zelfs als deze functie op een andere plaats op-
geroepen wordt:
function b ( ) {var y = 50 ;function c ( ) {
pr in t ( y ) ;y++;
}return c ; // c wordt n i e t u i t gevoerd , maar te ruggegeven
}var f = b ( ) ;f ( ) ; // output i s 50f ( ) ; // output i s 51
var g = b ( ) ;g ( ) ; // output i s 50g ( ) ; // output i s 51
Bij elke uitvoering van b wordt in de scope van b een variabele y gedeclareerd en geınitialiseerd
met waarde 50. Daarna wordt een nieuwe functie c gedefinieerd die gebruik maakt van y.
De functie c onthoudt zijn buitenomgeving. Zo’n functie die zijn buitenomgeving onthoudt,
wordt een closure genoemd[3].
Voor de realisatie van zo’n scoping systeem wordt gebruik gemaakt van een datastructuur
die we de environment noemen. Figuur 3.2 toont een voorbeeld van een environment1. In deze figuur zien we dat een environment bestaat uit een ouder-environment en een
scope-object. Het scope-object is een puur ECMAScript object waarvan de properties de
variabelen in de huidige scope bevatten.
Het veld new vars? in de figuur geeft aan of nieuwe variabelen, gedeclareerd door het
var keyword, in de huidige scope (dus in het scope-object van de huidige environment)
mogen terechtkomen. new vars? zal waar zijn voor scopes binnen een functie, en vals
1De ECMA-262 specificatie beschrijft het scoping mechanisme op een andere manier die hetzelfderesultaat geeft als onze beschrijving, maar onze beschrijving sluit meer aan bij de implementatie van PJS(Parrot JavaScript).
3.4 Scoping en de Environment 21
zijn voor scopes in een with- of catch-blok (zie secties 3.8 en 3.9). Als new vars? vals
is, komen nieuwe variabelen terecht in een ouder-environment die dat wel toelaat. De
environments die bij een variabele declaratie de variabele niet in hun scope-object mogen
plaatsen noemen zullen we in het vervolg pseudo-environments noemen.
Aan de top van een environment zien we de globale environment. Globale code (code niet
binnen een functie-, with- of catch-blok) wordt uitgevoerd in deze environment. De globale
environment heeft als scope-object het globale object.
Het opzoeken van de waarde van een variabele v gebeurt dus op de volgende manier:
1. Als een property v bestaat in het scope-object van de huidige environment, is de
waarde van de variabele v de waarde van die property.
2. Anders, herhaal de procedure in de ouder-environment als er een ouder-environment
is.
3. Er is geen ouder-environment. Genereer een uitzondering (variabele v niet gevon-
den!).
Het opzoeken van de waarde van een variabele is iets anders dan het opzoeken van een
property van dat object. Om de waarde van o.x te vinden moeten we eerst de waarde van
variabele o in de huidige environment zoeken. Daarna zoeken we de property x van het
object o in de prototype ketting van o.
Het toekennen van de waarde w aan een variabele v gebeurt op de volgende manier:
1. Als een property v bestaat in het scope-object van de huidige environment, wijzig de
waarde van die property naar w.
2. Anders, herhaal de procedure in de ouder-environment als er een ouder-environment
is.
3. Er is geen ouder-environment. Plaats de property v met waarde w in het scope-object
van de huidige environment (= globale environment).
Merk op dat als er toegekend wordt aan een variabele die in geen enkele scope gedeclareerd
is, de variabele dan terechtkomt in de globale scope. Een voorbeeld:
3.5 Functies 22
function f oo ( ) {bar = 42 ;
}f oo ( ) ; // g l o b a l e v a r i a b e l e ” bar” wordt 42pr in t ( bar ) ; // output : 42
Het declareren van een variabele v gebeurt op de volgende manier:
1. Zolang de huidige environment een pseudo-environment is, ga naar de ouder-environment.
2. Als het scope-object in de huidige environment geen property v heeft, plaats een
property v met waarde undefined in het scope-object.
3.5 Functies
Functies in ECMAScript zijn een speciaal geval van gewone ECMAScript objecten. Zoals
andere ECMAScript objecten hebben zij properties, prototype objecten, enz. Daarnaast
kunnen zij ook opgeroepen worden.
We tonen een voorbeeld van een functie, die daarna opgeroepen wordt:
// hu id i g e environment : p //
// d e f i n i t i e van een f un c t i e f in environment pfunction f (x , y ) {
// hu id i g e environment : q //pr in t (x , y ) ;
}
// oproep van f un c t i e ff (1 , 2 ) ;
Wanneer een functie f gedefinieerd wordt in een environment p, onthoudt de functie f
zijn buiten-environment, namelijk p. Wanneer de functie f opgeroepen wordt, wordt een
nieuwe environment q aangemaakt die als ouder-environment p heeft. Als scope-object
van q wordt een nieuw ECMAScript object gebruikt. q laat nieuwe variabelen toe (new
3.5 Functies 23
vars? = true) zodat in f gedeclareerde variabelen in het scope-object van q terechtkomen.
Omdat f met parameters 1 en 2 opgeroepen wordt, worden in het scope-object van q een
property x met waarde 1 en een property y met waarde 2 geplaatst. Daarna wordt de code
binnen de functiedefinitie uitgevoerd in environment q.
Hier ziet u nog een voorbeeld:
function f (x , y ) {pr in t (x , y ) ;p r i n t ("ARGS" ) ;for (var i =0; i<arguments . l ength ; i++) {
pr in t ( arguments [ i ] ) ;}
}f ( 1 ) ;// output : 1 , undef ined , ARGS, 1
f (1 , 2 , 3 ) ;// output : 1 , 2 , ARGS, 1 , 2 , 3
We zien in het voorbeeld dat het aantal parameters in de declaratie van een functie niet
hoeft overeen te komen met het aantal parameters doorgegeven bij de oproep van de functie.
Niet-doorgegeven parameters krijgen de waarde undefined. Alle doorgegeven parameters
worden ook in een array arguments geplaatst.
ECMAScript laat daarnaast functie-expressies toe. Dit zijn naamloze functiewaarden (in
functionele talen lambda-functies genoemd). Een voorbeeld:
// Naamloze f u n c t i e toegekend aan een v a r i a b e l evar a = function (p) { pr in t (p ) ; } ;
// Een func t i e−d e f i n i t i efunction c a l l f u n c t i o n ( f ) { f ( ) ; }
c a l l f u n c t i o n (// Naamloze f u n c t i e a l s argument doorgegeven aan c a l l f u n c t i o nfunction ( ) {
pr in t ("foo" ) ;}
3.6 Methodes 24
) ;
Methodes en constructors in ECMAScript zijn gewone functies. We kunnen elke functie
als een methode van een object of als een constructor gebruiken.
Functieobjecten krijgen een speciaal prototype property (niet te verwarren met het prototy-
pe van dat object) wanneer zij aangemaakt worden. Deze property bepaalt welk prototype
een instantie van deze functie gaat krijgen (voor meer informatie, zie 3.7). De initiele waar-
de van deze property is een nieuw ECMAScript object dat als prototype Object.prototype
heeft (zie ook figuur figuur 3.3).
3.6 Methodes
Elk ECMAScript functie krijgt intern een this-parameter. De waarde van de this-parameter
kan binnen de functie-blok gekregen worden met de this sleutelwoord.
Een methode is in feite niets anders dan een gewone functie. Het enige verschil is de manier
waarop zij opgeroepen wordt. Wanneer een functie opgeroepen wordt als een property van
een object o, spreken we van een methodeoproep. De functie krijgt dan het object o als
de zogenaamde this-parameter.
Als een functieoproep geen methodeoproep is, wordt als de this-parameter het globale
object doorgegeven.
Onderaan ziet u twee voorbeelden van methodeoproepen:
o b j . f ( ) ;obj [ ’g’ ] ( 3 , 5 ) ;
Maar het volgende voorbeeld bevat geen methodeoproep:
f = obj.meth ;f ( ) ;
Ten slotte tonen we een zinvol voorbeeld van hoe methodes gebruikt kunnen worden:
var punt = {x : 3 , y : 4} ;punt .a f s tand = function ( ) {
3.7 Constructors 25
return Math.sqrt ( t h i s . x * t h i s . x + t h i s . y * t h i s . y ) ;} ;p r i n t ( punt .a f s tand ( ) ) ;
3.7 Constructors
Zoals een methode is een constructor ook niets anders dan een gewone functie. Wanneer
een functieoproep voorafgegaan wordt door een new keyword, wordt de functie opgeroepen
als een constructor.
Wanneer een constructor wordt opgeroepen via new f(x, y) gebeurt het volgende:
1. Een nieuw ECMAScript object wordt aangemaakt door de interpreter. Het prototype
van dat object wordt f.prototype.
2. f wordt opgeroepen met het aangemaakte object als de this-parameter, en argumen-
ten x en y.
3. Als het resultaat r van de functieoproep een object is, is het resultaat van de con-
structoroproep dat object. Anders is het resultaat het aangemaakte object.
Er moet wel opgemerkt worden dat obj.prototype (de property prototype van het object
obj ) totaal verschillend is van het prototype van obj. De eerste is een property van het
object en niets anders, maar de tweede is een intern dataveld in een object dat bepaalt hoe
de properties van een object gevonden worden.
We tonen hieronder een voorbeeld van hoe constructors gebruikt kunnen worden:
function Punt (x , y ) {t h i s . x = x ;t h i s . y = y ;
}Punt .p ro to type .a f s tand = function ( ) {
return Math.sqrt ( t h i s . x * t h i s . x + t h i s . y * t h i s . y ) ;} ;var p = new Punt ( 3 , 5 ) ;var q = new Punt ( 6 , 8 ) ;p r i n t ( p . a f s t and ( ) ) ; // output : 5pr in t ( q . a f s t and ( ) ) ; // output : 10
3.8 Het with Statement 26
We hebben hier een functie Punt die de properties x en y van het this-object initiali-
seert. De interpreter zorgt ervoor dat Punt een property prototype krijgt met een nieuw
ECMAScript object als waarde. Daarna voegen we een nieuwe property afstand toe aan
Punt.prototype met een functie als waarde.
Daarna roepen we de constructor Punt op met waarden 3 en 5. Dat zorgt ervoor dat een
nieuw object wordt aangemaakt dat als prototype Punt.prototype heeft. Dat object wordt
doorgegeven aan de functie Punt als de this-parameter, waardoor dat object de properties
x en y krijgt met waarden 3 en 5. Het object wordt opgeslagen in p.
De variabele q krijgt op dezelfde manier x en y properties met waarden 6 en 8.
Omdat p als prototype Punt.prototype.afstand heeft, kunnen we afstand als een methode
van p oproepen.
3.8 Het with Statement
Het with statement zorgt ervoor dat we de properties van een object kunnen vinden met
de propertynamen alleen. In plaats van
ob j . x = 10 ;p r i n t ( ob j . y ) ;o b j . f ( ) ;
kunnen we dan het volgende schrijven:
with ( obj ) {x = 10 ;p r i n t ( y ) ;f ( ) ;
}
Het kan ook als een importing-mechanisme gebruikt worden. In plaats van
var x = Math.exp ( Math.sqrt ( Math.PI ) ) ;var y = Math.s in (x ) + Math.cos ( x ) ;p r i n t ( y ) ;
kunnen we het volgende schrijven:
with (Math) {var x = exp ( sq r t (PI ) ) ;
3.9 Uitzonderingen 27
var y = s in (x ) + cos (x ) ;}
Dat wordt op de volgende manier verwezenlijkt:
// beg in environment evar x = 10 ;
with (Math) { // beg in environment wvar y = sq r t (PI + x ) ;
} // einde environment wpr in t ( y ) ;
// einde environment e
De code start in environment e. Wanneer de uitvoering het with-blok bereikt, wordt er
een nieuwe pseudo-environment w aangemaakt die e als ouder-environment en Math als
scope-object heeft.
Wanneer de variabelen sqrt en PI dan in de environment w geevalueerd worden, worden
ze eerst gezocht in het scope-object van de environment w (namelijk Math) waarin ze
gevonden worden. Maar de variabele x wordt niet gevonden in Math. Het wordt dan
opgezocht in een de hogere environment e, die evalueert naar 10.
Omdat de environment w een pseudo-environment is, wordt de variabele y gedefinieerd in
de environment e, en niet w. Daarom kan men de variabele y ook buiten het with-blok
bereiken.
3.9 Uitzonderingen
ECMAScript ondersteunt uitzonderingen. Uitzonderlijke situaties worden gesignaleerd
d.m.v. throw, en uitzonderingen kunnen opgevangen worden d.m.v. try-catch, try-finally
en try-catch-finally blokken. De werking is gelijkaardig aan die van de welbekende pro-
grammeertaal Java, behalve dat:
� men (zoals bij C++) elke waarde kan werpen als uitzondering, en niet alleen waarden
van een speciale uitzonderingtype.
� er maximaal een catch-blok per try-blok bestaat. Dat catch-blok vangt elke geworpen
uitzondering op, en niet alleen uitzonderingen van een bepaald type zoals in Java.
3.10 De Functie eval 28
We tonen een voorbeeld van een try-catch-finally blok:
var f i l e = null ;try {
f i l e = open ("foo.txt" ) ;p r i n t ( f i l e . r e a dCon t e n t s ( ) ) ;
} catch ( e ) {pr in t ("Uitzondering gebeurd: " + e ) ;
} f ina l ly {i f ( f i l e != null )
f i l e . c l o s e ( ) ;}
We merken op dat de variabele e alleen in de catch-blok beschikbaar is. Wanneer een
uitzondering geworpen wordt en de uitvoering van het programma de catch-blok bereikt,
wordt een nieuwe pseudo-environment aangemaakt als kind van de huidige environment.
De nieuwe environment krijgt een nieuw ECMAScript object als scope-object. Dat scope-
object krijgt een property met naam e dat de geworpen uitzondering als waarde heeft.
Daarna wordt de catch-blok uitgevoerd in die environment.
3.10 De Functie eval
Wanneer de functie eval opgeroepen wordt in een environment e met een parameter s van
het type String, wordt s geevalueerd in de environment e als ECMAScript code. De laatste
geevalueerde expressie wordt dan teruggegeven door eval. Voorbeeld:
// d e f i n i e e r t een f un c t i e ’ f ’eva l ("function f() { print(’foo’); }" ) ;
// f un c t i e ’ f ’ i s nu be s ch i k baar in de hu id i g e environmentf ( ) ; // output : foo
var s = "3 + 5" ;p r i n t ( eva l ( s ) ) ; // output : 8
function bar ( ) {eva l ("var x = 20" ) ;
3.11 Lussen 29
pr in t ( x ) ; // output : 20}bar ( ) ;p r i n t ( x ) ; // u i t zonder ing , want x n i e t g edec l a r e e rd in deze env .
Om de kracht van eval aan te tonen, geven we een laatste voorbeeld waarin een closure
aangemaakt wordt d.m.v. eval:
function myEval ( code ) {return eva l ( code ) ;
}var i n j e c t = ’var n=0; var f=function(){return n++}; f’ ;var t e l l e r = myEval ( i n j e c t ) ;
p r i n t ( t e l l e r ( ) ) ; // 0pr in t ( t e l l e r ( ) ) ; // 1pr in t ( t e l l e r ( ) ) ; // 2
3.11 Lussen
ECMAScript heeft for- en while-lussen zoals we ze kennen in de programmeertalen Ja-
va en C++. Daarnaast heeft ECMAScript for-in-lussen die enumereerbare property-
namen (zie 3.2) van objecten enumereren. Onderstaande voorbeeld geeft als output:
x -> 10, y -> 20,
var obj = new Object ( ) ;ob j . x = 10 ;ob j . y = 20 ;
for ( i in obj ) {pr in t ( i + " -> " + obj [ i ] + ’, ’ ) ;
}
PJS 32
Hoofdstuk 4
PJS
PJS staat voor Parrot JavaScript, onze implementatie van ECMAScript voor Parrot. In
dat hoofdstuk geven we informatie over de entiteiten waaruit PJS bestaat. In het volgende
hoofdstuk geven we dan meer informatie hoe de eigenlijke vertaling gebeurt.
4.1 Entiteiten waaruit PJS bestaat
Figuur 4.1: Verschillende entiteiten van PJS
4.2 PMC-klassen voor PJS 33
Figuur 4.1 toont de verschillende entiteiten van PJS die nodig zijn voor de uitvoering
van een ECMAScript programma. Het figuur toont drie lagen. De basislaag wordt ge-
vormd door PMC-klassen en dynamische opcodes. Beide bestaan uit C code, gecompileerd
naar native code onder de vorm van shared libraries (so, dll). Daarboven staat de PIR-
bibliotheek (gecompileerd naar Parrot bytecode), waarvan de uitvoering van ECMAScript
af en toe gebruik maakt (zie sectie 4.4). In de bovenste laag staat gecompileerde EC-
MAScript code. In het linker deel van de bovenste laag zien we de entiteiten die de
ECMAScript standaard bibliotheek voorstelt, die uitgevoerd moet worden voordat andere
ECMAScript code uitgevoerd wordt. In het rechter deel zien we gecompileerde code van
het ECMAScript programma die we uitvoeren.
De compilatie van ECMAScript code naar PIR code gebeurt d.m.v. de dynamische opcode
pjs compile. Deze opcode krijgt ECMAScript code als argument en produceert PIR code als
resultaat. We gaan gebruik maken van deze opcode om de eval -functie te implementeren.
De compiler zelf werd geschreven in C, met behulp van flex en bison.
In de volgende secties leggen we de verschillende delen waaruit PJS bestaat verder uit.
4.2 PMC-klassen voor PJS
Zoals reeds vermeld heeft ECMAScript vijf primitieve types: Number, String, Boolean,
Undefined, Null. Daarnaast heeft ECMAScript het type Object om objecten voor te stellen.
Parrot heeft vier registertypes: int, num, string en pmc. Parrot heeft ook PMC-klassen
zoals Integer, Float, String, Array, etc. Er moet beslist worden hoe een ECMAScript
datatype voorgesteld zal worden in Parrot. Mogelijke opties voor het ECMAScript-type
Number zijn bijvoorbeeld:
1. gebruik het registertype num
2. gebruik de PMC-klasse Float dat reeds gedefinieerd is in Parrot
3. implementeer een nieuwe PMC-klasse voor het ECMAScript-type Number
ECMAScript heeft geen statische typecontrole. Wanneer we in ECMAScript een functie f
oproepen, weten we niet welk type het resultaat gaat hebben. Maar om met het resultaat
te kunnen werken, moeten we die waarde in een Parrot register steken. Daarom moeten we
het meest algemene registertype pmc kiezen voor het opslaan van een ECMAScript-waarde,
en we elimineren dus optie 1.
4.2 PMC-klassen voor PJS 34
Het gedrag van een ECMAScript-waarde van het type number is niet altijd dezelfde als
dat van een pmc van het type Float. Als voorbeeld nemen we de optelling van een getal en
een string: Float genereert hiervoor een uitzondering, terwijl in ECMAScript het resultaat
de concatenatie van beide operandi moet zijn. Een Float accepteert ook geen deling door
nul en genereert in dat geval een uitzondering, terwijl het resultaat Infinity of NaN moet
worden in ECMAScript.
Figuur 4.2: PMC-klassen voor ECMAScript datatypes
Daarom kiezen we optie 3 en implementeren we de ECMAScript datatypes d.m.v. PMC-
klassen. Figuur 4.2 toont de klassenhierarchie van deze klassen. Zoals we zien in de figuur
erven PjsFunction en PjsArray over van PjsObject. PjsString erft over van de standaard
PMC-klasse String, en PjsNumber erft over van de standaard PMC-klasse Float.
4.2.1 Primitieve types
De primitieve ECMAScript types hebben we geımplementeerd als PMC-klassen met vol-
gende namen: PjsNumber, PjsString, PjsBoolean, PjsNull, PjsUndefined.
Voor de primitieve ECMAScript-types moeten we een aantal operaties definieren d.m.v.
vtable-functies. Dit zijn o.a.
4.2 PMC-klassen voor PJS 35
� conversieoperatoren:
– get integer: converteer de waarde naar een int
– get number: converteer de waarde naar een num
– get string: converteer de waarde naar een string
– get bool: geef de logische waarde als een int
� algemene unaire/binaire operatoren:
– is equal: controle voor gelijkheid aan een andere waarde
– add: tel op bij een andere waarde en geef het resultaat terug
– subtract
– multiply
– logical not
– bitwise and
– cmp: vergelijk het object met een andere en geef -1, 0 of 1 terug
– etc.
We geven een voorbeeld van hoe de optelling gedefinieerd wordt voor de PjsNumber type:
pmclass PjsNumberh l l Pjsextends Float {
PMC* add (PMC* value , PMC* dest ) {MMD PjsNumber : { // wanneer ’ va lue ’ a l s type PjsNumber h e e f t
// v e r z e k e r dat ’ d e s t ’ van type PjsNumber i s . . .i f ( dest−>vtable−>base type != dynpmc PjsNumber ) {
// . . . door een t ype conve r s i eVTABLE morph(INTERP, dest , dynpmc PjsNumber ) ;
}// t e l de waarden op , en s l a he t r e s u l t a a t op in de s t// d .m. v . de v t a b l e−f u n c t i e s e t number na t i v eVTABLE set number native (INTERP, dest ,
VTABLE get number (INTERP, SELF) +
4.2 PMC-klassen voor PJS 36
VTABLE get number (INTERP, value ) ) ;return dest ;
}MMD PjsString : {
STRING* s t r = VTABLE get string (INTERP, SELF ) ;STRING* s t r 2 = VTABLE get string (INTERP, value ) ;i f ( dest−>vtable−>base type != dynpmc PjsString ) {
VTABLE morph(INTERP, dest , dynpmc PjsString ) ;}VTABLE set str ing native (INTERP, dest ,
s t r i n g c on c a t (INTERP, s t r , s t r2 , 0 ) ) ;return dest ;
}MMD PjsBoolean : {
// . . .}MMD PjsNull : {
// . . .}MMD PjsUndefined : {
// . . .}MMDDEFAULT: {
// . . .}
}
// andere v t a b l e−f u n c t i e s . . .}
We geven dan een voorbeeld van een PIR-programma dat gebruik maakt van onze PMC-
types:
# laad de PMC d e f i n i t i e s. loadlib ’pjs_group’
. sub main# Maak een nieuw PjsNumber aan.
4.2 PMC-klassen voor PJS 37
$P0 = new . PjsNumber
# Geef he t de waarde 3 . 14 .# Intern wordt de v t a b l e−f u n c t i e s e t number na t i v e# met argument 3.14 opgeroepen op $P0.$P0 = 3.14
# Maak een nieuwe P j sS t r ing aan.$P1 = new . P j sS t r ing
# Geef he t de waarde ’ foo ’ .# Intern wordt de v t a b l e−f u n c t i e s e t s t r i n g n a t i v e# met argument ’ foo ’ opgeroepen op $P0.$P1 = ’foo’
# Maak een nieuwe PjsUndefined aan.$P2 = new . PjsUndef ined
# Intern wordt de v t a b l e−f u n c t i e add# met argument $P1 ( va lue ) en $P2 ( de s t ) opgeroepen op $P0.$P2 = $P0 + $P1
print $P2. end
4.2.2 PjsObject
Een ECMAScript object implementeren we ook d.m.v. een PMC-klasse.
Elk PMC heeft een dataveld van het type PMC* en een dataveld van het type void*. In
de implementatie van PjsObject gebruiken we het eerste dataveld om het prototype-object
van ons ECMAScript-object op te slaan. In het tweede dataveld plaatsen we een hashtabel
die de eigenlijke properties van onze object zal bevatten.
We implementeren de vtable-functies get pmc keyed, set pmc keyed en exists keyed( str)
zoals beschreven in sectie 3.2. We implementeren ook de vtable-functies get attr str en
set attr str op dezelfde manier als get pmc keyed, set pmc keyed.
4.2 PMC-klassen voor PJS 38
De vtable-functies get pmc keyed en set pmc keyed bepalen hoe geındexeerde toegang op
een object gebeurt (zoals foo[’bar’] = 10). Zo kunnen we een property van een bepaald
ECMAScript object bereiken met vierkante-haakje-syntax:
# $P2 k r i j g t de waarde van ’ foo [” bar ” ] ’ o f ’ f o o . b a r ’
# vind de waarde van de v a r i a b e l e foo$P0 = pjs f ind lex ’foo’ , env
$P1 = new . P j sS t r ing$P1 = ’bar’
# get pmc keyed van $P0 wordt opgeroepen met parameter $P1# en opges lagen in $P2$P2 = $P0 [ $P1 ]
De vtables-functies get attr str en set attr str worden gebruikt om respectievelijk attribu-
ten van een object te verkrijgen en te wijzigen (zoals foo.bar = 10).
get pmc keyed/set pmc keyed en get attr str/set attr str hebben dezelfde betekenis in EC-
MAScript, maar we implementeren toch beide zodat ook andere HLL’s op een correcte
manier met een ECMAScript object kunnen werken.
4.2.3 De environment
Parrot heeft een lexicaal scoping mechanisme dat toelaat om sommige Parrot subroutines te
markeren zodat ze in een nieuwe lexicale scope uitgevoerd worden wanneer zij opgeroepen
worden. Men kan ook aanduiden dat een subroutine genest is in een andere, zodat men
closures kan vormen.
Bij het uitvoeren van elke subroutine maakt Parrot dan instanties van de PMC-klasse
LexInfo en LexPad, die de variabelen in de huidige scope gaan bijbouden. Hun functie
is gelijkaardig aan die van een scope-object in ECMAScript. Parrot laat toe dat een
bepaalde HLL zijn eigen PMC-klassen aanwijst in plaats van LexInfo en LexPad, maar
omdat Parrot geen controle geeft over de manipulatie van de environment zelf, maakt dat
de implementatie van de with-statement en de eval-functie moeilijk.
4.2 PMC-klassen voor PJS 39
Daarom gebruiken we als environment van ECMAScript een eigen datastructuur. Een
Parrot subroutine die een vertaling is van een ECMAScript functie, bevat een extra pa-
rameter om de environment door te geven waarin de subroutine uitgevoerd zal worden.
De ECMAScript variabelen, gebruikt in die functie, worden opgezocht/opgeslagen in deze
environment.
De environment implementeren we niet als een nieuwe PMC-klasse: er zijn te veel operaties
en er zijn niet genoeg aantal passende vtable-functies om die te kunnen implementeren.
We kunnen die operaties implementeren als methodes, maar dat is niet efficient genoeg.
We gebruiken daarom een simpele array-pmc dat een vaste lengte heeft (FixedPMCArray)
als de environment. De array heeft volgende elementen:
0. Het scope-object van de environment
1. De ouder environment
2. Het variabele-object van de environment
3. Het globale object
4. Werd de environment gecreeerd voor een with-blok?
In de plaats van bij te houden of huidige environment wel of niet een pseudo-environment
is, houden we als de tweede element van de environment het scope-object van de eerste
(ouder)environment dat geen pseudo-environment is. Dat noemen we dan een variabele-
object.
We implementeren de operaties op de environment d.m.v. een aantal dynamische opcodes:
pjs new lex(env, varname) Declareer een variabele varname in env
pjs new lex with flags(env, varname, flags) Declareer een variabele varname in env
met bepaalde property attributen (zoals DontDelete).
pjs find lex(env, varname) Zoek de variabele varname op in env
pjs find lex and base(env, varname) Zoek de variabele varname op in env en geef de
waarde ervan als resultaat. Geef ook als resultaat het scope-object waarin varname
gevonden werd indien het scope-object tot een with-blok behoort, of het globale
object in het andere geval. Dat wordt gebruikt bij het oproepen van functies om
samen met de functie zelf ook het this-object te vinden.
4.2 PMC-klassen voor PJS 40
pjs store lex(value, env, varname) Wijzig de waarde van de variabele varname naar
value in de environment env
pjs get scope object(env) Geef het scope-object van env
pjs new scope from object(obj) Maak een nieuwe environment aan die geen ouder
heeft (globale environment), en gebruik obj als het scope- en variabele-object.
pjs new subscope(env) Maak een nieuwe environment aan als kind van env. Gebruik
een nieuw ECMAScript object als het scope- en variabele-object.
pjs augment scope chain with(env, obj) Maak een nieuwe pseudo-environment aan
als kind van env en gebruik obj als het scope-object. Als variabele-object wordt dat
van env gebruikt omdat de nieuwe environment tot een with-blok behoort.
4.2.4 PjsFunction
PjsFunction is een subklasse van PjsObject. Het heeft bijkomende datavelden outer env en
pir sub. outer env houdt bij in welke environment de functie aangemaakt werd. De functie
zal in een kinderenvironment van outer env uitgevoerd worden wanneer die opgeroepen
wordt. Daarnaast houdt de functie een referentie naar de pir subroutine die de vertaling
is van de ECMAScript-code van deze functie.
4.2.5 PjsArray
PjsArray is een subklasse van PjsObject. Het heeft een bijkomend veld dat een Resi-
zablePMCArray bevat. ResizablePMCArray is een standaard PMC-klasse die een array
voorstelt waarvan de lengte dynamisch aanpasbaar is.
De vtable-functies set pmc keyed en get pmc keyed uit PjsObject zijn overridden. Ze zor-
gen er nu ook voor dat als een property een naam heeft die als integer geparsed kan
worden, de property dan in de ResizablePMCArray terechtkomt. Bovendien weerspiegelt
de property ’length’ nu de lengte van de ResizablePMCArray.
4.3 Dynamische opcodes voor PJS 41
4.3 Dynamische opcodes voor PJS
Naast de opcodes om de environment te beheren zijn er nog volgende dynamische opcodes
voor PJS:
pjs to primitive(value) Als value een PjsObject is, geef de primitieve waarde ervan
terug door valueOf en toString methodes ervan te proberen op te roepen. Als value
reeds een primitieve waarde is, geef dan gewoon value terug.
pjs compile(jscode, eval?) Compileer de ECMAScript-code in jscode naar PIR-code en
geef de PIR-code terug. eval? is een vlag die bepaalt of de gegenereerde PIR-code
gebruikt wordt voor de eval functie.
4.4 PIR bibliotheek voor PJS
Als vertaling van sommige basisoperaties van ECMAScript zijn soms tientallen lijnen PIR
code nodig. Voor een functieoproep bijvoorbeeld moeten volgende zaken gedaan worden:
1. Controleer of de functie een ECMAScript functie is. Als dat niet het geval is, roep
het op als een gewone PIR subroutine.
2. Maak een nieuwe environment aan als kind van de buiten-environment van de functie.
3. Maak een nieuw ECMAScript object aan, steek alle argumenten in dat object, en
steek dat object in de aangemaakte environment met naam arguments.
4. Roep uiteindelijk de subroutine op die behoort tot onze functie, en geef hem als
environment de aangemaakte environment.
Het opnieuw genereren van al deze logica voor alle functieoproepen heeft een aantal nadelen:
� De compilercode wordt moeilijker te onderhouden
� Lezen/debuggen van de gegenereerde PIR code wordt moeilijker
� Gegenereerde bytecode is groter
4.4 PIR bibliotheek voor PJS 42
Daarom worden zulke operaties als aparte PIR subroutines in de PIR bibliotheek gedefini-
eerd. Wanneer ECMAScript code uitgevoerd wordt, wordt er eerst verzekerd dat de PIR
bibliotheek geladen is.
Men kan zich afvragen waarom sommige operaties als dynamische opcodes en anderen als
subroutines in de PIR bibliotheek geımplementeerd zijn. Soms zijn beide goed, maar er
zijn verschillen:
+ Dynamische opcodes worden gedefinieerd in C. Zij zijn dus niet begrensd tot wat
PIR kan doen.
+ Dynamische opcodes worden sneller uitgevoerd.
+ De oproep van een dynamische opcode kost veel minder dan de oproep van een
subroutine.
- PIR code is meestal leesbaarder, korter en veiliger dan C voor de manipulatie van
Parrot data.
- Het oproepen van een PIR subroutine vanuit C code is mogelijk, maar problematisch.
VERTALING VAN ECMASCRIPT NAAR PIR 43
Hoofdstuk 5
Vertaling van ECMAScript naar PIR
De compiler van PJS werd geschreven in C. Eerst wordt ECMAScript code geconverteerd
naar een abstracte syntax boom via een parser die we geschreven hebben met behulp van
Flex en Bison. Daarna wordt de boom een keer recursief doorlopen om meer informatie
te halen uit de syntax boom, zoals “In welke functie en in welke codeblok staat deze/dit
expressie/statement?”, “Wat zijn de binnenfuncties van deze functie?”, “Welke lus gaat dit
break-statement afbreken?”. Daarna wordt de boom nog een keer doorlopen om uiteindelijk
PIR code te genereren.
In het vervolg leggen we uit hoe we verschillende expressies/statements van de program-
meertaal ECMAScript vertaald hebben naar PIR. Aan het einde geven we uitleg over de
standaard bibliotheek van PJS.
5.1 De environment
Elke ECMAScript functie wordt vertaald naar een PIR subroutine. Globale code wordt
ook vertaald naar een subroutine.
De vertaalde subroutine krijgt een parameter met naam env 0 voor de environment waarin
de uitvoering van ECMAScript zal gebeuren. Elke geneste with- en catch-blok maakt een
kinder-environment van de huidige environment aan, en de nieuwe environment krijgt een
naam waarvan het getal eentje hoger is dan zijn ouder. Een catch-blok juist binnen een
functieblok heeft bv. env 1, en een with-blok daarin heeft env 2 als environment.
In de voorbeelden hieronder wordt als de huidige environment altijd env 0 gebruikt, maar
5.2 Primitieve datatypes 44
men moet niet vergeten dat het getal achter env afhankelijk is van de plaats van de
vertaalde expressie/statement.
5.2 Primitieve datatypes
Het aanmaken van primitieve ECMAScript datatypes gebeurt als volgt.
Een Number met waarde 3.14:
$P0 = new . PjsNumber# Roept se t number na t i v e v t a b l e−f u n c t i e# van $P0 op met argument 3 . 14 .$P0 = 3.14
Een String met waarde ’foo’:
$P0 = new . P j sS t r ing# Roept s e t s t r i n g n a t i v e v t a b l e−f u n c t i e# van $P0 op met argument ’ foo ’ .$P0 = ’foo’
Een boolean met waarde true:
$P0 = new . PjsBoolean# Roept s e t i n t e g e r n a t i v e v t a b l e−f u n c t i e# van $P0 op met argument 1 .$P0 = 1
Een null en een undefined:
$P0 = new . P j sNul l$P1 = new . Undefined
5.3 Unaire en binaire operaties
Meeste unaire en binaire operaties zijn gedefinieerd als vtable-functies van de eerste ope-
rand. Men moet wel ervoor zorgen dat het resultaatregister een nieuwe pmc bevat. Het
uitvoeren van de operatie zorgt ervoor dat de resultaat-pmc het juiste type krijgt.
5.4 Variabelen, declaratie en toekenning 45
Meeste wiskundige operaties vereisen ook dat de operandi primitieve waarden zijn. Een EC-
MAScript object moet dus eerst naar een primitieve waarde geconverteerd worden (d.m.v.
de methodes valueOf en toString van dat object).
Als voorbeeld nemen we de optelling van twee waarden die in registers $P0 en $P1 staan:
# Verzeker dat $P0 en $P1 geen o b j e c t z i j n$P0 = pjs to primitive $P0$P1 = pjs to primitive $P1
# ecreer een nieuwe waarde a l s r e s u l t a a t$P2 = new . PjsUndef ined
# De v tab l e−f u n c t i e ’ add ’ van $P0 wordt opgeroepen met parameters# $P1 en $P2. Dat z o r g t ervoor dat $P2 e e r s t he t j u i s t e type k r i j g t# en daarna he t r e s u l t a a t k r i j g t .$P2 = $P0 + $P1
5.4 Variabelen, declaratie en toekenning
Voor de evaluatie, declaratie en toekenning van variabelen maken we gebruik van de dy-
namische opcodes die beschreven zijn in sectie 4.2.3. We geven als voorbeeld een stuk
ECMAScript code samen met de vertaling naar PIR:
var x = 3 . 5 ;som = a + x ;
De vertaling:
# dec l a r e e r ’ x ’ met a t t r i b u u t DontDeletepjs new lex with flags @env 0 , ’x’ , 4
# s l a 3.5 op a l s ’ x ’$P1 = new . PjsNumber , ’3.5’
pjs store lex $P1 , @env 0 , ’x’
# laad a en x in r e g i s t e r s $P2 en $P3$P2 = pjs f ind lex @env 0 , ’a’
5.5 Functies 46
$P3 = pjs f ind lex @env 0 , ’x’
# doe de o p t e l l i n g$P2 = pjs to primitive $P2$P3 = pjs to primitive $P3$P1 = new . PjsUndef ined$P1 = $P2 + $P3
# s l a he t r e s u l t a a t op a l s ’som ’pjs store lex $P1 , @env 0 , ’som’
5.5 Functies
Code van elke functie wordt vertaald naar een PIR subroutine. Die subroutine krijgt naast
de eigenlijke functieparameters ook de volgende parameters:
@this: De this-waarde actief tijdens de uitvoering van de functie
@env 0: De environment waarin de functie uitgevoerd gaat worden
@dyn env: De environment van de caller. Die parameter is alleen nodig voor de eval -
functie.
In het begin van de subroutine worden de eigenlijke parameters van de functie gestoken in
env 0. We geven een voorbeeld van een functie en de vertaling naar PIR:
function f ( a , b ) {// code
}
De vertaling:
## func t i on f. sub @func 0 0 : anon
.param pmc @this
.param pmc @env 0
.param pmc @dyn env
5.5 Functies 47
# Parameter a ( o p t i on e e l ).param pmc par@a : op t i ona l
# Een v l a g d i e aangee f t o f# parameter a d e g e l i j k doorgegeven werd# ( dat z a l n i e t he t g e va l z i j n a l s f# zonder parameters opgeroepen wordt ) .
.param int has@a : o p t f l a g
.param pmc par@b : op t i ona l
.param int has@b : o p t f l a g
# De o v e r t o l l i g e parameters komen h i e r t e r e c h t ..param pmc @rest : s lu rpy
. local pmc @undefined@undefined = new . PjsUndef ined
# Niet−doorgegeven parameters k r i j g e n de waarde unde f ined .i f has@a goto @de fau l t va l 0par@a = @undefined
@de fau l t va l 0 :i f has@b goto @de fau l t va l 1par@b = @undefined
@de fau l t va l 1 :
# Steek de doorgegeven parameters in de environment# met a t t r i b u u t DontDe le te .
pjs new lex with flags @env 0 , ’a’ , 4pjs store lex par@a , @env 0 , ’a’
pjs new lex with flags @env 0 , ’b’ , 4pjs store lex par@b , @env 0 , ’b’
# HIER KOMT DE VERTALING VAN DE FUNCTIECODE
# De d e f a u l t returnwaarde van een f un c t i e i s unde f ined .. return ( @undefined )
. end
5.5 Functies 48
De :anon-pragma zorgt ervoor dat de subroutine alleen beschikbaar is in de compile unit,
en niet geexporteerd wordt in heel Parrot met de naam @func 0 0.
De globale code van een ECMAScript programma wordt ook op dezelfde manier vertaald
naar een subroutine met naam @func 0. De functies gedefinieerd in de globale code worden
vertaald naar subroutines @func 0 0, @func 0 1, @func 0 2... en functies die daarin genest
zijn worden vertaald naar subroutines @func 0 0 0, @func 0 0 1, @func 0 1 0, enz.
Een ECMAScript functie is echter meer dan een PIR subroutine. Een ECMAScript functie
is ook een ECMAScript object, en zij moet ook de environment bijhouden waarin zij
aangemaakt werd. We tonen hier hoe de definitie van functie f in de vorige voorbeeld
vertaald wordt:
# Introduceer een nieuwe v a r i a b e l e f in de hu id i g e environment# met a t t r i b u u t DontDe le te .
pjs new lex with flags @env 0 , ’f’ , 4
# Geef de subrou t ine d i e de v e r t a l i n g i s van f .. const .Sub @func 0 0 = ’@func_0_0’
# Maak een ECMAScript f u n c t i e aan met# a l s bui tenenvironment de hu id i g e environment.
$P1 = c r e a t e f un c t i o n ( @func 0 0 , @env 0 )
# Steek de f u n c t i e in de v a r i a b e l e f .pjs store lex $P1 , @env 0 , ’f’
create function is een subroutine in de PIR bibliotheek en doet het volgende (minder
belangrijke code werd verwijderd):
. sub c r e a t e f un c t i o n.param pmc p i r sub.param pmc outer env
. local pmc j s f un c , prototype
. local pmc ob j ec t pro to type , f unc t i on pro to type
# Maak een nieuw f u n c t i e o b j e c t aan dat z i j n v e r t a a l d e# subrou t ine en z i j n bu i t en environment k en t .
5.6 Functieoproep 49
j s f u n c = new . PjsFunction , p i r subj s f u n c . s e t o u t e r e n v ( outer env )
# Vind Ob j e c t . p r o t o t y p e$P0 = pjs f ind lex outer env , ’Object’
ob j e c t p ro to type = $P0 [ ’prototype’ ]
# Maak een nieuw o b j e c t aan a l s de proto type−proper ty# van ons f u n c t i e o b j e c t en zorg ervoor dat dat o b j e c t# Ob j e c t . p r o t o t y p e a l s p ro to t ype h e e f t .
prototype = new . PjsObjectp ro to type . ’setProto’ ( ob j e c t p ro to type )j s f u n c [ ’prototype’ ] = prototype
# Het f u n c t i e o b j e c t z e l f h e e f t Func t i on .pro to type a l s p r o t o t y p e .$P0 = pjs f ind lex outer env , ’Function’
f unc t i on pro to type = $P0 [ ’prototype’ ]j s f u n c . ’setProto’ ( f unc t i on pro to type )
# Geef he t aangemaakte f u n c t i e o b j e c t t e r u g .. return ( j s f u n c )
. end
5.6 Functieoproep
Om een functie of methode op te roepen moeten we volgende stappen uitvoeren:
1. Controleer of het hier wel degelijk gaat over een ECMAScript functieoproep. Anders,
gebruik de PIR-conventies voor de oproep zodat samenwerking tussen talen mogelijk
is.
2. Maak een nieuwe environment aan als kind van de buiten-environment van de opge-
roepen functie.
3. Maak een nieuw ECMAScript object aan, steek alle argumenten in dat object, en
steek dat object in de zopas aangemaakte environment met naam arguments.
5.6 Functieoproep 50
4. Roep uiteindelijk de subroutine op die behoort tot onze functie, en geef hem als
environment de zopas aangemaakte environment.
Een probleem hier is het bepalen wat het this-argument van de oproep gaat zijn. Als de
opgeroepen functie bekomen werd als een property toegang naar een object (zoals obj.f()
of obj[3]()), dan is het duidelijk dat de this-parameter dat object zal zijn.
Als de functie bekomen werd door een variabele te evalueren, dan wordt als het this-object
in de meeste gevallen het globale object gebruikt, maar niet altijd:
var printX = function ( ) {pr in t ( t h i s . x ) ;
} ;var x = "globale x" ;
var obj = {} ;ob j . x = "obj.x" ;ob j .p r in tX = printX ;
with ( obj )printX ( ) ; // output : o b j . x
printX ( ) ; // output : g l o b a l e x
Hoewel in het vorige voorbeeld printX in de with-statement niet bekomen werd door een
property-toegangsexpressie, wordt het toch als een methode van obj opgeroepen omdat het
als een property van obj gevonden wordt. Daarom gebruiken we de dynamische opcode
pjs find lex and base ook om de this-waarde te vinden bij het zoeken van printX in de
environment (zie sectie 4.2.3).
De volgende subroutine in de PIR bibliotheek wordt gebruikt om een functie op te roepen:
. sub p j s c a l l f u n c t i o n# De th i s−waarde g e b r u i k t b i j he t# u i t voe ren van de opgeroepen f un c t i e
.param pmc t h i s
# De opgeroepen f un c t i e.param pmc f unc t i on
5.6 Functieoproep 51
# Environment van de c a l l e r.param pmc dyn env
# De parameters van de func t i eoproep ( in een array ).param pmc params : s lu rpy
# Kijk o f de f u n c t i e wel d e g e l i j k een ECMAScript f u n c t i e i s .# Anders wordt he t a l s een gewone PIR subrou t ine opgeroepen.
$ I0 = i sa funct ion , ’PjsFunction’
i f $ I0 goto i s P j sFunc t i on# roep op a l s een gewone PIR subrou t ine
$P0 = func t i on ( params : f l a t ). return ($P0)
i s P j sFunc t i on :. local pmc subrout ine , enc env , new env
# Maak een nieuwe environment a l s k ind van# de bui tenenvironment van onze f u n c t i e .
enc env = fun c t i o n . g e t ou t e r e nv ( )new env = pjs new subscope enc env
# Maak een nieuw ’ arguments ’ o b j e c t aan in de nieuwe# environment en s t e e k a l l e parameters in dat o b j e c t .
add arguments ( params , funct ion , new env )
# Neem de subrou t ine d i e behoor t t o t onze f u n c t i e# en roep he t u i t e i n d e l i j k op.
subrout ine = f u n c t i o n . g e t p i r s u b ( ). return subrout ine ( th i s , new env , dyn env , params : f l a t )
. end
obj.f(3.0, 5.0) wordt op de volgende manier vertaald:
# Evalueer de parameters .$P0 = new . PjsNumber$P0 = 3 .0
5.7 Oproep van functies als constructors 52
$P1 = new . PjsNumber$P1 = 5 .0
# Steek in $P2 de waarde van de v a r i a b e l e ’ ob j ’ .$P2 = pjs f ind lex @env 0 , ’obj’
# Vind o b j . f$P3 = $P0 [ ’f’ ]
# Roep de f un c t i e op met ’ ob j ’ a l s t h i s−waarde ,# en 3.0 en 5.0 a l s parameters .
$P4 = p j s c a l l f u n c t i o n ($P2 , $P3 , @env 0 , $P0 , $P1)
f(3.0, 5.0) wordt op de volgende manier vertaald:
# Evalueer de parameters .$P0 = new . PjsNumber$P0 = 3 .0$P1 = new . PjsNumber$P1 = 5 .0
# Steek in $P3 de waarde van de v a r i a b e l e ’ f ’ ,# en in $P2 het t h i s−o b j e c t van f .
$P3 = pjs find lex and base @env 0 , ’f’ , $P2
# Roep de f un c t i e f op met 3 .0 en 5.0 a l s parameters .$P4 = p j s c a l l f u n c t i o n ($P2 , $P3 , @env 0 , $P0 , $P1)
5.7 Oproep van functies als constructors
Een constructoroproep is gelijkaardig aan een functie-oproep. Maar deze keer wordt een
nieuw ECMAScript object aangemaakt, dat als prototype de prototype property van de
opgeroepen functie heeft. Dat object wordt als het this-object van de functie-oproep ge-
bruikt.
Als de functie een object teruggeeft, wordt dat object als het resultaat van de constructor-
oproep gebruikt. Anders wordt het pas aangemaakte object gebruikt als het resultaat.
5.7 Oproep van functies als constructors 53
new f(3.0, 5.0) wordt dan op de volgende manier vertaald:
# Evalueer de parameters .$P0 = new . PjsNumber$P0 = 3 .0$P1 = new . PjsNumber$P1 = 5 .0
# Steek in $P2 de waarde van de v a r i a b e l e ’ f ’ .$P2 = pjs f ind lex @env 0 , ’f’
# Roep de f un c t i e f op a l s een c on s t r u c t o r .$P3 = pjs invoke new ($P2 , @env 0 , $P0 , $P1)
Hieronder vindt u de definitie van de subroutine pjs invoke new :
. sub pj s invoke new# Funct ie dat opgeroepen wordt
.param pmc f unc t i on
# Environment van de c a l l e r.param pmc dyn env
# De e i g e n l i j k e parameters van de cons t ruc toroproep.param pmc params : s lu rpy
. local pmc enc env , new env
# Maak een nieuwe environment a l s k ind van# de bui tenenvironment van onze f u n c t i e .
enc env = fun c t i o n . g e t ou t e r e nv ( )new env = pjs new subscope enc envadd arguments ( params , funct ion , new env )
. local pmc proto , newobject
# Maak een nieuwe ECMAScript o b j e c t aannewobject = new ’PjsObject’
# Zorg ervoor dat he t p ro to t ype van he t nieuwe o b j e c t de waarde
5.8 Keuze 54
# van de ’ p ro to t ype ’ proper ty van onze f u n c t i e k r i j g t .proto = func t i on [ ’prototype’ ]n ewob jec t . s e tProto ( proto )
# Neem de subrou t ine d i e behoor t t o t onze f u n c t i e en roep# het op , met a l s t h i s−waarde he t pas aangemaakte o b j e c t .
. local pmc subrout ine , returnValsubrout ine = f u n c t i o n . g e t p i r s u b ( )returnVal = subrout ine ( newobject , new env , dyn env , params : f l a t )
# Kijk o f he t r e s u l t a a t van de oproep een o b j e c t was.$ I0 = i sa returnVal , ’PjsObject’
i f $ I0 goto wasObject. return ( newobject )
wasObject :. return ( returnVal )
. end
5.8 Keuze
Hieronder vindt u een ECMAScript programma met een if-else-blok en zijn vertaling als
voorbeeld:
i f (<COND>)<DEEL A>
else<DEEL B>
$P1 = <eva luee r COND>unless $P1 goto ELSE@0<eva luee r DEEL A>
goto END IF@0ELSE@0:
<eva luee r DEEL B>
END IF@0 :
5.9 Lussen 55
5.9 Lussen
5.9.1 while
Hieronder ziet u hoe de vertaling van een while-lus gebeurt:
while(<COND>)<BLOK>;
CONTINUE@0:$P1 = <eva luee r COND>unless $P1 goto BREAK@0<eva luee r BLOK>
goto CONTINUE@0BREAK@0:
5.9.2 for
Hieronder ziet u hoe de vertaling van een for-lus gebeurt:
for(<INIT>; <COND>; <NEXT>)<BLOK>;
<eva luee r INIT>
goto FIRSTLOOP@0CONTINUE@0:
<eva luee r NEXT>FIRSTLOOP@0:
$P0 = <eva luee r COND>unless $P0 goto BREAK@0<eva luee r BLOK>
goto CONTINUE@0BREAK@0:
5.9.3 for .. in
Hieronder ziet u hoe de vertaling van een for-in-lus gebeurt:
5.10 with 56
for ( x in <OBJ>)<BLOK>;
$P0 = <eva luee r OBJ># Geef een i t e r a t o r van <OBJ> d ie a l l e enumereerbare# propertynamen in <OBJ> enumereert .
$P0 = i ter $P1
CONTINUE@0: # <f o r in>
# Kijk o f er nog propertynamen z i j n .unless $P1 goto BREAK@0
# Steek de vo lgende propertynaam in de v a r i a b e l e ’ x ’ .$S2 = sh i f t $P1$P2 = new . P j sS t r ing$P2 = $S2pjs store lex $P2 , @env 0 , ’x’
# Evalueer de b l o k .<eva luee r BLOK>
goto CONTINUE@0BREAK@0:
5.10 with
Het with-statement maakt een nieuwe kinderenvironment van de huidige environment aan
dat als scope-object een bepaald ECMAScript object gebruikt. De code binnen het with-
blok gebruikt dan de nieuwe environment als de huidge environment.
Hieronder vindt u een voorbeeld van hoe de vertaling gebeurt:
with ( obj ) {x = 10 ;
}x = 20 ;
5.11 eval 57
# Evalueer o b j .$P1 = pjs f ind lex @env 0 , ’obj’
# Maak een kinderenvironment aan# dat ons o b j e c t a l s scope−o b j e c t g e b r u i k t .
. local pmc @env 1@env 1 = pjs augment scope chain with @env 0 , $P1
# BEGIN with−b l o k
# De nieuwe environment wordt in he t with−b l o k g e b r u i k t .$P2 = new . PjsNumber , ’10’
pjs store lex $P2 , @env 1 , ’x’
# EINDE with−b l o k
# Vanaf h i e r wordt weer de oude environment g e b r u i k t .$P1 = new . PjsNumber , ’20’
pjs store lex $P1 , @env 0 , ’x’
5.11 eval
Parrot laat toe dat men at runtime nieuwe PIR subroutines toevoegt aan het systeem.
Wanneer we een stuk ECMAScript code moeten evalueren in een environment env, dan
compileren we eerst deze code d.m.v. de dynamische opcode pjs compile naar PIR code.
De gecompileerde PIR code bevat subroutine definities als vertaling van de functies in de
ECMAScript code, samen met een subroutine die de vertaling is van de globale code. We
kunnen nu aan Parrot de gegenereerde PIR code laten evalueren, wat ons de subroutine
geeft die de code van de geevalueerde expressie voorstelt. Wanneer we nu die subroutine
oproepen met argument env, is onze ECMAScript code geevalueerd.
5.12 Uitzonderingen 58
5.12 Uitzonderingen
5.12.1 Exception handling in Parrot
Een exception handler is een soort continuation die de controle krijgt wanneer we hem
uitvoeren. Parrot houdt een stapel van exception handlers bij. Men kan een nieuwe
exception handler toevoegen in de stapel door de push eh instructie uit te voeren, en men
kan een exception handler van de stapel verwijderen met behulp van de clear eh opcode.
Men kan ook merktekens (dat zijn integer waarden) plaatsen op de stapel van exception
handlers door middel van de opcode push mark. Met pop mark kan men dan alle exception
handlers van de stapel verwijderen tot een bepaald merkteken.
Wanneer een uitzonderlijke situatie optreedt, neemt parrot de laatste exception handler
weg van de stapel en voert hem uit.
Men kan een nieuwe uitzondering werpen door middel van de opcode throw. Men kan ook
de laatste opgevangen uitzondering opnieuw werpen door middel van de opcode rethrow.
We tonen hieronder een PIR programma dat een uitzondering opvangt:
. sub : maindee l (7 , 0)# output : Probeer t t e de l en# Uit zonder ing : d e l i n g door 0
dee l (10 , 2)# output : Probeer t t e de l en# 5# Succes
. end
. sub dee l.param int a.param int b
push eh opvangprint "Probeert te delen"
5.12 Uitzonderingen 59
$ I0 = a / bprint $ I0
print "Succes"
clear ehgoto e inde
opvang :say "Uitzondering: deling door 0"
e inde :. end
Nadat we een exception handler toegevoegd hebben in de stapel van exception handlers,
proberen we een deling uit te voeren. Nadat we de deling met succes uitgevoerd hebben,
moeten we de stapel van exception handlers herstellen: het met succes uitvoeren van een
operatie laat de toestand van de stapel van exception handlers best ongewijzigd. Wan-
neer de deling faalt, neemt Parrot de exception handler opvang van de stapel weg, en de
uitvoering gaat naar opvang.
5.12.2 try .. catch
De vertaling van een try-catch-blok gebeurt zoals in het vorige voorbeeld. Maar deze keer
moeten we de opgevangen uitzondering in het catch-blok in een ECMAScript environment
steken. Omdat de variabele voor de opgevangen uitzondering niet zichtbaar mag zijn
buiten het catch-blok, gebruiken we in het catch-blok een nieuwe environment als kind van
de huidige environment.
try {<TRY BLOK>
}catch ( e ) {
<CATCH BLOK>
}
De vertaling naar PIR:
push eh CATCH@0<eva luee r TRY BLOK>
5.12 Uitzonderingen 60
clear ehgoto END TRY CATCH@0
CATCH@0:. local pmc @exc# Steek de opgevangen excep t i on in @exc.. get results (@exc )
# Maak een nieuw o b j e c t aan dat de opgevangen# excep t i on a l s proper ty ’ e ’ h e e f t .$P2 = new . PjsObject$P2 [ ’e’ ] = @exc
# Maak een nieuwe environment a l s k ind van de huid ige ,# dat he t nieuwe o b j e c t a l s scope−o b j e c t g e b r u i k t .. local pmc @env 1@env 1 = pjs augment scope chain with @env 0 , $P2
<eva luee r CATCH BLOK met @env 1 a l s environment>END TRY CATCH@0:
5.12.3 try .. finally
In een try-finally blok wordt eerst geprobeerd code in het try-blok uit te voeren. Daarna
wordt altijd het finally-blok uitgevoerd, onafhankelijk van wat er in het try-blok gebeurd
is. Een try-finally-blok heeft succes alleen als het try-blok en het finally-blok met succes
uitgevoerd zijn.
Hieronder ziet u hoe de vertaling van een try-finally-blok gebeurt:
try {<TRY BLOK>
}f ina l ly {
<FINALLY BLOK>
}
De vertaling in PIR:
5.12 Uitzonderingen 61
# Plaa t s een merkteken in de s t a p e l van excep t i on handlers ,# samen met de excep t i on hand ler van f i n a l l y .pushmark 0push eh FINALLY HANDLER@0
# Hou b i j o f he t try−b l o k een excep t i on g en e r e e r t .. local int @has except ion 0@has except ion 0 = 0
<eva luee r TRY BLOK>
# Het try−b l o k met succes u i t g e v o e r d . Ga naar de f i n a l l y −b l o k .goto FINALLY@0
FINALLY HANDLER@0:. local pmc @exc# Plaa t s de opgevangen u i t z onde r in g in @exc.. get results (@exc )
# Het try−b l o k h e e f t een excep t i on gegeneree rd .@has except ion 0 = 1
FINALLY@0:# Hers t e l de s t a p e l van excep t i on hand l e r s .popmark 0
<eva luee r FINALLY BLOK>
# Als de try−b l o k een excep t i on gegenereerd had , werp dat# excep t i on opnieuw.unless @has except ion 0 goto END TRY FINALLY@0rethrow @exc
END TRY FINALLY@0:
5.13 Standaard bibliotheek van PJS 62
5.12.4 try .. catch .. finally
Een try-catch-finally-blok wordt vertaald als een try-catch blok binnen een try-finally blok.
De volgende twee stukken ECMAScript code zijn immers equivalent:
try {<TRY BLOK>
}catch ( e ) {
<CATCH BLOK>
}f ina l ly {
<FINALLY BLOK>
}
try {try {
<TRY BLOK>
}catch ( e ) {
<CATCH BLOK>
}}f ina l ly {
<FINALLY BLOK>
}
5.13 Standaard bibliotheek van PJS
Voordat een ECMAScript-programma uitgevoerd wordt, wordt een globaaal object en een
globale environment aangemaakt. Dan wordt in de globale environment het ECMAScript-
programma uitgevoerd dat de standaard bibliotheek van ECMAScript voorstelt. Pas daar-
na kunnen we het eigenlijke programma uitvoeren in de globale environment.
De standaard bibliotheek zorgt ervoor dat de standaardwaarden (functies, constructors,
objecten, primitieve waarden) zoals String, Number, Object, parseInt, NaN, undefined,
Math, etc. in de globale environment geınstalleerd worden.
5.13 Standaard bibliotheek van PJS 63
PJS laat inline PIR-code binnen ECMAScript-code toe. Dat maakt mogelijk dat men
lagere niveau operaties kan uitvoeren in PJS, en dat is uiterst hulpzaam bij het schrijven van
de standaard bibliotheek. Inline PIR komt tussen /**PIR en END*/. We tonen hieronder
een voorbeeld van hoe we een functie print kunnen definieren:
pr in t ("foo" , 42 , "bar" ) ;function pr in t ( x ) {
i f ( x != undef ined ) {/**PIR
# We zoeken x in @env 0 ( en n i e t @env 1 ) omdat we# n i e t binnen een with− o f catch−b l o k z i j n .. l o c a l pmc xx = p j s f i n d l e x @env 0 , ’ x ’p r i n t x
END*/}
}pr in t ("foo" ) ;p r i n t ( 1 0 ) ;
BESLUIT EN TOEKOMSTPERSPECTIEVEN 64
Hoofdstuk 6
Besluit en toekomstperspectieven
In dit werk hebben we een dynamische taal, namelijk ECMAScript, geımplementeerd voor
de Parrot virtuele machine als evaluatie van die virtuele machine. De implementatie hebben
we PJS genoemd.
6.1 Compleetheid van PJS
PJS implementeert een grote deel van ECMAScript behalve de standaard bibliotheek.
Het geımplementeerde deel van de standaard bibliotheek bevat maar een klein deel van
constructors, functies en andere standaard variabelen.
We hebben PJS getest met de JavaScript Test Library van Mozilla Foundation. PJS
slaagt in 48% van de 928 tests. Er blijkt dat minstens 316 van de tests falen wegens de
onvolledigheid van de ECMAScript standaard bibliotheek1. Dat betekent dat maximaal
20% van de tests falen wegens een gebrek aan de implementatie zelf van PJS.
Enkele oorzaken tot falingen zijn:
� Vlottende kommagetallen of grote gehele getallen worden op een verkeerde manier
omgezet naar string.
1 We gaan ervan uit dat een ReferenceError of een TypeError het resultaat is van de onvolledigestandaard bibliotheek. Een ReferenceError wordt gegenereerd wanneer men een onbestaande variabele wilevalueren. Een TypeError wordt meestal gegenereerd wanneer men een undefined of null als een objectwil gebruiken (zoals property toegang, methode oproep). Onbestaande properties van objecten evaluerennaar undefined.
6.2 Samenwerking met andere talen 65
� Een functieoproep met zeer groot aantal argumenten veroorzaakt een segmentation
fault.
� De switch-statement heeft geen correcte implementatie
� De operator ’>>>’ heeft geen correcte implementatie
� etc.
We hebben de C code (compiler, PMC-klassen, dynamische opcodes) zoveel mogelijk op
een van platform onafhankelijke manier geschreven, maar we hebben PJS alleen uitgetest
op een Linux systeem.
De code van PJS, de testbibliotheek en de output van de falende tests zijn te vinden op
http://users.fulladsl.be/spb1622/pjs/.
6.2 Samenwerking met andere talen
De eerste probleem voor de samenwerking tussen de talen is hoe fundamentele waarden
(zoals getallen, strings) van een bepaalde taal gebruikt worden in een andere taal. Het
polymorfisme van PMC’s (d.m.v. de vtable-functies zoals get number, set number, add,
multiply, enz.) bieden hier slechts een gedeeltelijke oplossing voor. Bijvoorbeeld, de typeof
operator van ECMAScript uitgevoerd met een PMC van een vreemde type faalt omdat
die PMC geen methode getJsType definieert, of de tekstuele representatie van een Float is
verschillend van die van een PjsNumber.
Men kan vanuit PJS attributen van vreemde objecten bereiken/aanpassen met de ’dot
syntax’ (zoals obj.foo, attribuut foo van obj), en omgekeerd. Dit was mogelijk wegens
het polymorfisme van PMC’s met behulp van de vtable-functies get attr str en set attr str.
Geındexeerde toegang (zoals x[i]) was op een analoge manier mogelijk met behulp van
de vtable-functies get pmc keyed en set pmc keyed.
Omdat een PJS functie een wrapper is rondom een Parrot subroutine, gebeurt het oproepen
van een PJS functie niet op een gebruikelijke manier. PJS kan wel Parrot subroutines
oproepen met de gewone functie-oproep syntax door eerst te kijken naar het type van
de opgeroepene. Het rechtstreeks oproepen van een PJS functie vanuit PIR code is ook
6.3 Performantie 66
mogelijk, maar de implementatie ervan is fragiel. Het kan in de toekomst gemakkelijk
geımplementeerd worden als een bug in Parrot opgelost wordt.
Het oproepen van een methode van een Parrot object vanuit PJS is mogelijk door type-
checking van het object. Het rechtstreeks oproepen van een methode van een PJS object
vanuit PIR is ook mogelijk met een fragiele implementatie.
6.3 Performantie
We hebben geen doorgedreven benchmarks gemaakt om de snelheid van PJS te meten,
maar het blijkt in het algemeen 10 tot 30 keer trager te zijn dan Spidermonkey2 voor code
zonder functieoproep. Met enkele handmatige transformaties konden we die tijd verlagen
tot een factor 2 door sommige verbeteringen te doen in de gegenereerde code, zoals:
� constanten zoals string literals en number literals niet altijd opnieuw aanmaken.
� lokale variabelen alleen in registers houden en niet in de ECMAScript environment
steken als dat mogelijk is.
� de unboxing instructie to primitive die uitgevoerd wordt voor de uitvoering van elke
binaire operatie, om ECMAScript objecten naar primitieve waarden om te zetten,
weglaten en de unboxing uitvoeren wanneer het nodig is.
Ook een functieoproep blijkt een zeer dure operatie te zijn die de uitvoeringstijd van PJS
50 keer trager kan maken dan Spidermonkey. Een van de oorzaken ervan is het feit dat
we te veel gebruik maken van slurpy parameters/flat argumenten bij een functieoproep (cf.
varargs).
PJS doet momenteel geen optimalisatie bij het genereren van code. We hopen dat in de
toekomst PJS veel sneller kan lopen met een geoptimaliseerde runtime en codegeneratie en
met verbeteringen in Parrot.
2Een ECMAScript interpreter van Mozilla Foundation, geschreven in C. Wordt ook gebruikt in dewebbrowser Mozilla Firefox
6.4 Gemak in de implementatie en overdraagbaarheid 67
6.4 Gemak in de implementatie en overdraagbaarheid
We kunnen het implementatiegemak moeilijk vergelijken met een andere virtuele machine
omdat wij geen ervaring ermee hebben.
We denken dat de implementatie van een rechtstreekse interpreter voor ECMAScript zelf
een voor de hand liggendere taak zou zijn dan de omweg van Parrot te nemen. Parrot heeft
ons wel veel geholpen met het schrijven van een overdraagbare3 taal. Parrot stelt ons een
overdraagbare bibliotheek ter beschikking (als bytecode) om de standaard bibliotheek van
PJS te implementeren. Hoewel we heel wat C code geschreven hebben in de implementatie
van PJS, konden we de code redelijk overdraagbaar houden. Het is immers de taak van de
Parrot codebase om de platform afhankelijke problemen op te lossen.
We moesten een niet te kleine deel van ons tijd steken in het onderzoek van Parrot. Be-
staande documentatie van Parrot is momenteel te technish en gaat meestal uit van de
voorkennis van de lezer. Parrot heeft meer documentatie nodig onder de vorm van boeken
of tutorials.
6.5 Algemeen besluit
We konden in ongeveer een jaar tijd een min of meer werkende implementatie van EC-
MAScript schrijven voor Parrot. Het resultaat is niet een te performante taal, maar er zijn
nog veel optimalisatiemogelijkheden en we verwachten dat Parrot gaat verbeteren in de
toekomst. Volgens ons is het belangrijkste van wat Parrot te bieden heeft is een platform
waar verschillende programmeertalen met elkaar kunnen samenwerken. Parrot biedt hier
gedeeltelijke oplossingen voor, en we hopen dat het in de toekomst zal verbeteren.
3We hebben PJS enkel getest in een Linux platform, maar het overdragen naar een andere platformmoet mogelijk zijn
BIBLIOGRAFIE 68
Bibliografie
[1] Parrot documentatie.
http://www.parrotcode.org/docs/.
[2] Ecma International. Standard ecma-262.
http://www.ecma-international.org/publications/standards/Ecma-262.htm.
[3] Wikipedia, Closure (computer science).
http://en.wikipedia.org/wiki/Closure %28computer science%29. revisie van 9
augustus 2007.
[4] Wikipedia, Continuation.
http://en.wikipedia.org/wiki/Continuation. revisie van 28 juli 2007.
[5] Klaas-Jan Stol. On the Architecture of the Parrot Virtual Machine, januari 2006.
http://www.perlfoundation.org/parrot/index.cgi?publications on parrot.
[6] Klaas-Jan Stol en Ewoud Werkman. Software patterns in the parrot system.
http://www.perlfoundation.org/parrot/index.cgi?publications on parrot.
[7] Dan Sugalski. Presentatie: Implementing an interpreter, juni 2004.
(niet meer beschikbaar op Internet, gedownload op oktober 2006).
LIJST VAN FIGUREN 69
Lijst van figuren
3.1 Een ECMAScript object met zijn prototype ketting . . . . . . . . . . . . . 17
3.2 Een ECMAScript environment . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.3 “prototype” property van functies . . . . . . . . . . . . . . . . . . . . . . . 31
4.1 Verschillende entiteiten van PJS . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2 PMC-klassen voor ECMAScript datatypes . . . . . . . . . . . . . . . . . . 34