Implementatie van ECMAScript voor de Parrot Virtuele Machine

78
Faculteit Ingenieurswetenschappen Vakgroep Informatietechnologie Voorzitter: Prof. Dr. Ir. Paul Lagasse Implementatie van ECMAScript voor de Parrot Virtuele Machine door Mehmet Yavuz Selim Soyt¨ urk 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

Transcript of Implementatie van ECMAScript voor de Parrot Virtuele Machine

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 ] + ’, ’ ) ;

}

3.11 Lussen 30

Figuur 3.2: Een ECMAScript environment

3.11 Lussen 31

Figuur 3.3: “prototype” property van functies

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