Andreas Rozek  

MokkaScript - ein Interpreter für "simplified Javascript"

Seit der dritten Version von ActionScript verzichtet Adobe ganz auf die "eval"-Funktion, die eigentlich zum Sprachumfang von Javascript gehört. Mitunter ist es aber ganz praktisch, eine solche Funktion zur Hand zu haben. Auf dieser Seite wird deshalb mit "MokkaScript", einem Interpreter für "simplified Javascript", ein Ersatz für die "eval"-Funktion vorgestellt.

Themenübersicht

Folgende Themen stehen derzeit zur Verfügung - klicken Sie einfach auf einen Beitrag Ihrer Wahl:

Actionscript und "eval" - Auslöser der MokkaScript-Entwicklung

Seit der dritten Version von ActionScript verzichtet Adobe ganz auf die "eval"-Funktion, die eigentlich zum Sprachumfang von Javascript gehört. Doch auch in den vorherigen Versionen wurde "eval" nicht so implementiert, wie es der Javascript/ECMAscript-Standard [1] eigentlich vorsieht.

Mitunter ist es aber ganz praktisch, eine solche Funktion zur Hand zu haben:

  • ein Programm zur Visualisierung mathematischer Funktionen kann einen Benutzer damit zur Laufzeit eine Funktion eingeben oder gar programmieren lassen,
  • eine Lernumgebung kann Schüler Teile eines Programmes eingeben lassen, die anschließend direkt ausgeführt werden können oder
  • ein Programm kann mithilfe von "eval" ein PlugIn-Konzept realisieren, welches es dem Benutzer ermöglicht, die Anwendung an seine Bedürfnisse anzupassen.

Aus diesen Gründen wurde mit "MokkaScript" ein Javascript-Interpreter geschaffen, der sich möglichst nahtlos in eine Flash- oder Flex-Umgebung einfügt.

Verfügbare "eval"-Implementierungen (Stand: September 2009)

Es gibt jedoch durchaus Alternativen: die vermutlich erste (öffentliche) Implementierung eines Javascript-Interpreters in Javascript wurde von niemand geringerem als Brendan Eich (dem Entwickler von Javascript) geschrieben: "Narcissus" [2] ist frei als Quelltext verfügbar, wurde jedoch speziell für die Javascript-Implementierung "SpiderMonkey" des Mozilla-/Firefox-Browser entwickelt und läßt sich daher nicht so ohne weiteres unter ActionScript einsetzen.

Dagegen wurde D.eval [3] sehr wohl speziell für Actionscript entwickelt. Leider ist dieser Interpreter jedoch sehr fehlerhaft (versuchen Sie einmal, Zeichenketten miteinander zu vergleichen) und wird zu selten aktualisiert als das man damit vernünftig arbeiten könnte.

Bliebe noch die AS3Eval-Bibliothek [4], die auf dem ECMAScript-Compiler Tamarin beruht und ebenfalls unter ActionScript 3 läuft. Leider ist dem Autor weder der Stand der Bibliothek noch des Compilers bekannt - ansonsten könnte AS3Eval eine wirklich interessante Lösung darstellen.

"Simplified Javascript" - ein Vorschlag von Douglas Crockford

"Simplified Javascript" ist eine strikte Untermenge von Javascript/ECMAscript, die ursprgl. von Douglas Crockford vorgeschlagen und in [5] beschrieben wurde. Sie verzichtet auf einige weniger sauber definierte (bzw. schlicht unnötige) Details aus dem ECMAscript-Standard und sorgt so für etwas weniger fehleranfällige Javascript-Programme.

"Simplified Javascript" unterscheidet sich von ECMAscript 3 in folgenden Punkten:

  • Bezeichner dürfen keine "$"-Zeichen enthalten,
  • Blöcke sind nur als Funktionsrümpfe sowie in "if"-, "switch"-, "for"-, "do"-, "while"- und "try"-Anweisungen erlaubt,
  • "if"- und "for"-Anweisungen müssen mit Blöcken arbeiten,
  • im Bedingungsteil einer "if"-, "do"- oder "while"-Anweisung darf keine Zuweisung stehen,
  • "case"- und "default"-Klauseln in "switch"-Anweisungen müssen entweder leer sein oder mit "break", "continue", "return" oder "throw" abgeschlossen werden,
  • "try"-Anweisungen müssen ohne "finally"-Klausel auskommen,
  • als Ausdrucksanweisungen gelten nur Zuweisungen, Funktions- bzw. Methodenaufrufe und "delete"-Anweisungen,
  • Sprungmarken ("labels") sind nur vor "switch"-, "for"-, "do"- und "while"-Anweisungen erlaubt,
  • auf "return", "break" oder "continue" müssen ")", "}", "case" oder "default" folgen
  • der (ohnehin unnötige) "void"-Operator ist nicht mehr erlaubt,
  • "==" und "!=" sind nicht zulässig, stattdessen sind "===" und "!==" zu verwenden,
  • "++" und "--" sowie "*=", "/=" und "%=" sind nicht mehr erlaubt,
  • die "Operatoren" "in" und "instanceof" sind nicht mehr zulässig,
  • bitweise arbeitende Operatoren sind nicht mehr erlaubt,
  • der ","-Operator ist nur in "for"-Anweisungen zulässig,
  • vor einem RegExp-Literal muß ein "(", "=", ":" oder "," stehen
  • Funktionsanweisungen sind nicht mehr erlaubt, stattdessen sind Funktionsliterale zu verwenden
Da der Autor nicht für alle oben genannten Einschränkungen die zugehörigen Gründe nachvollziehen kann, erweitert MokkaScript diese Untermenge wieder ein wenig:
  • Bezeichner dürfen wieder das "$"-Zeichen enthalten,
  • "continue" wird weiterhin unterstützt (sorry, Douglas!)
  • die "try"-Anweisung darf weiterhin eine "finally"-Klausel besitzen,
  • es werden weitere in-situ-Operatoren zugelassen (nämlich "*=", "/=" und "%=") und
  • die Infix-Operatoren "in" und "instanceof" sind weiterhin zulässig.

Desweiteren unterstützt der MokkaScript-Parser die unter ActionScript üblichen, mit einem Doppelpunkt (":") eingeleiteten Typ-Angaben hinter Variablen- und Funktionsdeklarationen - diese haben jedoch keinerlei Auswirkung auf die Ausführung des Javascript-Quelltextes und werden einfach ignoriert.

Im Idealfall sollte ein (vollständig mit Typ-Spezifikationen versehener) MokkaScript-Quelltext unverändert auch als ActionScript-Quelltext akzeptiert werden - und (mit Ausnahme der schnelleren Ausführung) auch dieselben Ergebnisse liefern.

MokkaScript-Syntax

MokkaScript hält sich im Großen und Ganzen an die von Douglas Crockford ausgesprochenen Empfehlungen und weicht nur im Detail von der in [5] vorgeschlagenen Syntax ab. Da die in dem genannten Buch verwendeten Syntaxdiagramme nicht jedermanns Sache sind, soll an dieser Stelle die Syntax noch einmal als EBNF (Extended Backus-Naur-Form) dargestellt werden.

Die untenstehende Darstellung ist - streng genommen - nicht vollständig, stattdessen wird einer klaren Darstellung Vorzug gegenüber mathematischer Präzision und Vollständigkeit gegeben.

Das Format der Syntax-Darstellung entspricht den üblichen Gepflogenheiten:

  • Terminal-Symbole werden in doppelten Hochkommata notiert,
  • senkrechte Striche trennen Alternativen voneinander (es darf also entweder der linke oder der rechte Term auftreten, wobei jeder Term selbst wieder eine Alternative sein darf),
  • eckige Klammern kennzeichnen optionale Terme (die 0- oder 1-mal auftreten dürfen),
  • geschweifte Klammern kennzeichnen Terme, die beliebig häufig (auch 0-mal) auftreten dürfen,
  • runde Klammern dienen lediglich der Gruppierung.

Darüber hinaus markieren Ellipsen ("...") zumeist Wertebereiche, aus denen genau ein Wert auftreten darf. Lediglich in Kommentar- und Zeichenketten-Definitionen steht die Ellipse als Platzhalter für alle Zeichen, die innerhalb des Kommentars bzw. der Zeichenkette stehen dürfen.

Lexikalische Grammatik

Die "lexikalische Grammatik" beschreibt die Zerlegung eines Quelltextes in einzelne "Tokens", auf deren Basis die "syntakische Grammatik" später den Aufbau eines gültigen Programmes definiert. Im Gegensatz zur syntaktischen Grammatik wird "Weißraum" in der lexikalischen Grammatik explizit notiert.

whitespace
:=
space | tab | eol | line-comment | block-comment
line-comment
:=
"//" ... eol
block-comment
:=
"/*" ... "*/"

boolean
:=
"true" | "false"

number
:=
mantissa [exponent] | hexadecimal-number |
"NaN" | "Infinity"
mantissa
:=
integer ["." {digit}]
exponent
:=
("e" | "E") ["+" | "-"] integer
integer
:=
"0" | (("1"..."9") {digit})
digit
:=
"0"..."9"

octal-number
:=
"0" octal-digit {octal-digit}
octal-digit
:=
"0"..."7"

hexadecimal-number
:=
"0x" hex-digit {hex-digit}
hex-digit
:=
"0"..."9" | "a"..."f" | "A"..."F"

string
:=
double-quote ... double-quote |
single-quote ... single-quote
escape-sequence
:=
"\"" | "\'" | "\\" | "\/" | "\b" | "\f" | "\n" | "\r" | "\t" |
"\u" hex-digit hex-digit hex-digit hex-digit

Syntaktische Grammatik

Im Gegensatz zur lexikalischen Grammatik wird "Weißraum" in der syntaktischen Grammatik nicht explizit erwähnt, sondern darf zwischen allen genannten Symbolen in beliebiger Menge auftreten.

program
:=
[statements] eof

statements
:=
statement {";" statement}
statement
:= var-statement | expression-statement | function-statement |
if-statement | switch-statement |
for-statement | do-statement | while-statement |
try-statement | disruptive-statement

var-statement
:= "var" var-declaration {"," var-declaration}
var-declaration := identifier ["=" expression]

expression-statement :=
"delete" expression qualification |
qualified-name "="} [qualified-name in-situ-operator] expression |
qualified-name invocation
qualified-name
:= identifier [{invocation | qualification} qualification]

function-statement
:=
"function" identifier "(" [parameter-list] ")" block
 
if-statement
:= "if" "(" expression ")" block ["else" block]

switch-statement
:= [label] "switch" "(" expression ")"
"{" {case-clause} [default-clause] {case-clause} "}"
case-clause
:= "case" expression ":" [switch-chunk]
default-clause := "default" ":" [switch-chunk]
switch-chunk
:=
[statements ";"] disruptive-statement ";"
 
for-statement
:= [label] for-next-statement | for-in-statement
for-next-statement
:= "for" "(" [for-initializer] ";" [expression] ";" for-incrementor ")"
[block]
for-initializer
:= comma-statement {"," comma-statement}
for-incrementor
:= comma-statement {"," comma-statement}
comma-statement
:=
"var" var-declaration | expression-statement
 
for-in-statement
:= "for" "(" ["var"] identifier "in" expression ")" block

do-statement
:= [label] "do" block "while" "(" expression ")"
while-statement
:= [label] "while" "(" expression ")" block

disruptive-statement
:= break-statement | continue-statement |
return-statement | throw-statement
break-statement := "break" [identifier]
continue-statement := "continue" [identifier]
return-statement := "return" [expression]
throw-statement := "throw" expression

try-statement
:= "try" block ["catch" "(" identifier ")" block] ["finally" block]

block
:= "{" [statements] "}"

expression-statement
:= assignment | object-invocation | object-deletion

expression
:= literal | identifier | "(" expression ")" |
prefix-operator expression |
expression infix-operator expression |
expression "?" expression ":" expression |
expression invocation | expression qualification |
"new" expression invocation |
"delete" expression qualification

literal
:= boolean | number | string | regexp-literal |
object-literal | array-literal | function-literal |
"null" | "undefined"

object-literal
:= "{" object-entry {"," object-entry} "}"
object-entry
:= (identifier | string) ":" expression

array-literal
:= "[" [expression {"," expression}] "]"

function-literal
:= "function" [identifier] parameter-list block
parameter-list
:= "(" identifier {"," identifier} ")"

identifier := (letter | "_" | "$") {letter | digit | "_" | "$"}

prefix-operator
:= "+" | "-" | "!" | “typeof"
infix-operator
:= "*" | "/" | "%" |
"+" | "-" | "instanceof" | "in" |
"<" | "<=" | ">=" | ">" | "===" | "!==" |
"||" | "&&"
in-situ-operator
:= "+=" | "-=" | "*=" | "/=" | "%="

object-creation
:= "new" object-invocation
object-deletion
:= "delete" expression {qualification}
object-invocation
:= name {qualification} {invocation}

invocation
:= "(" [expression {"," expression}] ")"
qualification
:= "." identifier | "[" expression "]"

label
:=
identifier ":"

Eigenschaften und Grenzen der derzeitigen MokkaScript-Implementierung

Als Ersatz für "eval" wurde MokkaScript so konstruiert, daß es sich möglichst nahtlos in eine bestehende Flash- oder Flex-Umgebung einpaßt. So kann man beispielsweise von einem MokkaScript-Makro aus problemlos auf die Objekte und Funktionen der Umgebung zugreifen - MokkaScript nutzt dasselbe (Paket-spezifische) "Global"-Objekt und dieselben in Javascript eingebauten Prototypen wie jedes andere Javascript-Programm auch.

Desweiteren können die innerhalb eines MokkaScript-Makros erstellten Funktionen unmittelbar als Event-Handler eingesetzt werden - der Aufruf von MokkaScript-Funktionen unterscheidet sich in nichts vom Aufruf anderer Funktionen (und Methoden).

Leider sorgt genau dieser Komfort jedoch auch dafür, daß sich MokkaScript derzeit eher weniger für die Implementierung von Plugins oder ähnlichen Komponenten eignet, die innerhalb einer möglichst hermetisch geschlossenen "Sandbox" ablaufen sollen. Hier ist derzeit noch Vorsicht geboten - in einer späteren Version soll MokkaScript auch eine entsprechende Sandbox mitbringen.

Anleitung für den Einsatz von MokkaScript in eigenen Programmen

Vorbemerkung: die folgende Beschreibung geht davon aus, daß Sie "MokkaScript" innerhalb des "Flex Builder" [6] von Adobe einsetzen. Falls Sie für Ihre Entwicklungen eine andere Umgebung verwenden, müssen Sie die Anleitungen entsprechend anpassen.

Grundsätzlich gibt es drei verschiedene Verfahren für die "MokkaScript"-Integration in eigene "Flex Builder"-Projekte: Sie können

  • den Quelltext der "MokkaScript"-Klasse in Ihr Projekt integrieren,
  • die fertig übersetzte "MokkaScript"-Klasse in das Projekt einbinden oder
  • einen Verweis auf das "MokkaScript"-Projekt anlegen.

Das erstgenannte Verfahren lohnt sich nur, falls Sie vorhaben, den "MokkaScript"-Interpreter speziell für Ihr vorliegendes Projekt zu modifizieren. Die zweitgenannte Methode ist die einfachste und bietet sich an, wenn Sie sich weder für den Quelltext von "MokkaScript" interessieren noch vorhaben, diesen jemals zu verändern. Falls Sie die Klasse jedoch an eigene Bedürfnisse anpassen und anschließend mehrfach in Ihren Projekten einsetzen möchten, empfiehlt sich die letztgenannte Vorgehensweise.

Im folgenden werden zunächst die Methoden 2 und 3 genauer beschrieben. Anschließend erfahren Sie, wie Sie "MokkaScript" von Ihrem Programm aus aufrufen können.

Einbinden der fertig übersetzten "MokkaScript"-Klasse

Das Einbinden der fertig übersetzten Klasse in ein eigenes Projekt ist denkbar einfach:

  1. laden Sie die Datei MokkaScript.swc auf Ihren Rechner herunter (legen Sie sie in ein Verzeichnis Ihrer Wahl),
  2. legen Sie (sofern noch nicht geschehen) Ihr neues "Flex Builder"-Projekt an,
  3. öffnen Sie über das Menü "Project" -> "Properties" den "Project Properties"-Dialog und wählen Sie darin die Rubrik "Flex Build Path",
  4. aktivieren Sie den Reiter "Library Path",
  5. klicken Sie auf "Add SWC..." und führen Sie den "Flex Builder" zu der zuvor heruntergeladenen SWC-Datei.

Nach einem Klick auf "Ok" und dem Verlassen des Dialoges steht Ihnen "MokkaScript" zur Verfügung.

Verweis auf ein "MokkaScript"-Projekt

Das Einbinden des "MokkaScript"-Interpreter über einen Verweis auf das zugehörige Projekt ist ebenfalls äußerst einfach:

  1. laden Sie die Datei MokkaScript.zip auf Ihren Rechner herunter und entpacken Sie sie in ein Verzeichnis Ihrer Wahl - am besten dorthin, wo auch Ihre anderen FlexBuilder-Projekte liegen,
  2. laden Sie das Projekt in den FlexBuilder,
  3. legen Sie (sofern noch nicht geschehen) Ihr neues "Flex Builder"-Projekt an,
  4. öffnen Sie über das Menü "Project" -> "Properties" den "Project Properties"-Dialog und wählen Sie darin die Rubrik "Project References",
  5. da Sie das "MokkaScript"-Projekt zuvor geladen haben, sollte es in der Liste der angebotenen Projekt auftauchen - setzen Sie vor das Projekt einen Haken,

Nach einem Klick auf "Ok" und dem Verlassen des Dialoges steht Ihnen "MokkaScript" zur Verfügung.

Aufruf des "MokkaScript"-Interpreter

Die zentrale Funktion für die Auswertung eines Ausdruckes lautet:

  MokkaScript.ValueOf(SourceCode,GlobalObject)

Darin stehen

  • SourceCode für eine Zeichenkette mit dem zu interpretierenden Quelltext
  • GlobalObject für ein Javascript-Objekt mit den von MokkaScript zu benutzenden globalen Variablen

Vor der Ausführung des Quelltextes schreibt MokkaScript alle von Javascript standardmäßig bereitgestellten globalen Werte, Objekte und Funktionen in das "GlobalObject", so daß beim Aufruf problemlos auch ein leeres Objekt übergeben werden kann. Eine kleine Anmerkung am Rande: MokkaScript stellt selbst eine "eval"-Funktion zur Verfügung - bei Bedarf kann der Interpreter also seinerseits Javascript-Quelltext interpretieren.

Vom Javascript-Programm nicht behandelte Ausnahmen werden an das aufrufende Programm durchgereicht. Innerhalb des "MokkaScriptDemonstrator" wird der Aufruf von "ValueOf" deshalb in eine "try"-Anweisung eingebettet:

  try {
ResultView.text = "";
    var Result:* = MokkaScript.ValueOf(CodeEntry.text,GlobalObject);
  } catch (Signal:*) {
    ResultView.text += String(Signal);
  };

Verfügbare Dateien

Folgende Dateien können von hier aus auf den eigenen Rechner heruntergeladen werden:

Bekannte Probleme

Dies ist die erste, noch nicht ganz vollständige Version des MokkaScript-Interpreters. Folgende Einschränkungen sollten Sie beachten:

  • syntaktisch korrekte Javascript-Programme sollten eigentlich erfolgreich analysiert und korrekt ausgeführt werden - syntaktisch fehlerhafte Programme können jedoch (derzeit noch) zu "ungewöhnlichen" Fehlermeldungen führen,
  • es ist derzeit noch nicht möglich, über Paketpfade auf (explizit oder implizit) importierte Klassen und Objekte zuzugreifen - diese Funktionalität ist für eine spätere Version des Interpreter geplant,
  • die von "simplified Javascript" vorgegebenen syntaktischen Einschränkungen werden derzeit noch nicht alle kontrolliert - folglich können Sie derzeit(!) noch Javascript-Programme ausführen lassen, die eigentlich nicht zulässig wären...

Literaturhinweise

[1]
ECMAscript
(siehe http://www.ecmascript.org/)

ECMAscript ist die standardisierte Form der ursprünglich von Brendan Eich entwickelten Sprache "Javascript". Derzeit dürften sich die meisten Implementierungen noch an der dritten Fassung des Standards orientieren - soeben (d.h. im Dezember 2009) ist jedoch die fünfte Fassung (eine vierte gibt es nicht!) erschienen.

[2]
Brendan Eich
mozilla/js/narcissus
(siehe http://mxr.mozilla.org/mozilla/source/js/narcissus/)

Narcissus ist ein von Brendan Eich höchstselbst entwickelter, quell-offener Interpreter für Javascript, der ebenfalls in Javascript geschrieben ist.

[3]
RIA One
The D.eval API
(siehe http://www.riaone.com/products/deval/index.html)

D.eval ist ebenfalls ein in ActionScript geschriebener, kostenloser Javascript-Interpreter, der als Ersatz für die fehlende "eval"-Funktion in AS3 dienen kann.

[4]
Metal Hurlant
AS3 Eval Library
(siehe http://eval.hurlant.com/)

Die AS3 Eval Library ist ein auf dem Tamarin Compiler aufsetzender Javascript-Interpreter für ActionScript.

[5]
Douglas Crockford
Javascript: The Good Parts
O'Reilly Media, December 2008

In seinem (auch auf Deutsch erschienenen) Buch analysiert Douglas Crockford (Entwickler des JSON-Dateiformates und Mitglied des Standardisierungsgremiums für ECMAscript 5) Syntax und Semantik der Sprache Javascript/ECMAscript (noch in der dritten Fassung) und erläutert, welche Eigenschaften der Sprache nützlich sind und welche man tunlichst meiden sollte.

[6]
Adobe Flex Builder
(siehe http://www.adobe.com/de/products/flex/)

Der "Flex Builder" ist eine auf Eclipse aufsetzende Entwicklungsumgebung für Flex- (und Flash-)Anwendungen von Adobe. Obwohl nicht kostenlos, ist der "Flex Builder" (vor allem für Adobe-Verhältnisse) erstaunlich preisgünstig.


http://www.Rozek.de/MokkaScript/index_de.html Stand: 23.12.2009