Zaphod / Narcissus
JavaScript VM in JavaScript

von Philipp Hirch


Seminar Zaphod / Narcissus | Inhaltsverzeichnis | Einleitung | Interpreter | Narcissus | Beispiel | Fazit | Quellenverzeichnis

Beispiel

Als Beispiel für eine Anpassung der Syntax, schauen wir uns einmal an, wie wir der Plus-Operation, eine polnische Notation hinzufügen.

Beispiel:
A = 2 + 6 + 3;

soll zu
A = pp 2 6 3; oder
pp A 2 6 3;
werden.

Der Aufbau der neuen Syntax soll es ermöglichen eine Liste von Zahlen aufzusummieren, und die Summe zurückzugeben, oder in eine Variable zu speichern, welche als erster Parameter übergeben wird.

Um diese Änderungen umzusetzen müssen wir uns Gedanken machen, was wir da erstellen wollen. Das erste Beispiel entspricht mit seinen Aufbau einer Expression. Das zweite entspricht jedoch mehr einen Statement.

Nach dem wir wissen was wir hinzufügen wollen, können wir mit der Umsetzung beginnen.

Zuerst über legen wir uns ein Symbol für unsere neue Operation. Der einfachheitshalber nehmen wir die Zeichenfolge „pp“ für Polnisch-Plus. Legen wir also in der jsDefs.js ein neues Schlüsselwort an, damit der Tokenizer unser neues Symbol auch kennt.

var tokens = [
	…
     "while", "with", "pp"
 ];

Schauen wir uns nun die Änderungen im Parser an. Beginnen wir hier mit der Implementierung des Statements. Dank des simplen Aufbaus des „recursive descent parser“ müssen wir nur in der Switch-Anweisung des Parsers einen weiteren Fall für die Polnische-Notation hinzufügen, in welchen wir lediglich den ersten Parameter auf einen Identifier(Variable) und die restlichen auf Number(Zahlen) prüfen. Es wäre hier auch möglich auch auf Statements und weitere Variablen zu prüfen, dies wird hier aber der Übersichtlichkeit weggelassen.


t= der Tokenizer
tt = der aktuelle Token
    function Statement(t, x) {
        ...
        switch (tt) {
	...
          case PP:
			n = new Node(t, { params: [] });
			if(!t.mustMatch(IDENTIFIER))
				throw t.newSyntaxError("Missing Variable");
			n.push(new Node(t));			
			while (t.match(NUMBER)) {
				n2 = new Node(t);
				n.params.push(n2);
			}
            		t.mustMatch(SEMICOLON);
			return n;
	...
	

Kümmern wir uns nun um die Expression-Darstellung. Diese ist etwas unübersichtlich, da wir uns hier auch um die Priorität der Operation kümmern müssen. Auch hier machen wir es uns einfach und setzen es auf die gleiche Stufe wie Klammern. Also zu den hier „PrimaryExpressions“ genannten. Der Rest ist gleich der Statement-Darstellung.

n = aktueller Knoten aus dem Programmbaum 
    function PrimaryExpression(t, x) {
		…
         		 case PP:
			n = new Node(t, { params: [] });			
			while (t.match(NUMBER)) {
				n2 = new Node(t);
				n.params.push(n2);
			}
			break;
		...

Nun sollte unser Interpreter unsere neue Funktion erkennen und richtig im Programmbaum erstellen.

Kümmern wir uns nun um die Funktionalität. Die „execute“-Funktion, aus der jsExec.js, besteht auch hier wieder nur aus einer Switch-Anweisung, welche beim Traversieren des Programmbaum aufgerufen wird.

n = aktueller Knoten aus dem Programmbaum 
          case PP:
			c = n.params;
			v = 0;
			for(var i=0; i < c.length ; i++){
				v += getValue(execute(c[i], x));
			}
			if(n.children.length){
				r = execute(n.children[0], x);
				putValue(r, v, n.children[0]);
			}
            break;


Seminar Zaphod / Narcissus | Inhaltsverzeichnis | Einleitung | Interpreter | Narcissus | Beispiel | Fazit | Quellenverzeichnis