Interpreter implementieren: Typensystem
-
In meinem noch unfertigen Compiler hab ich's so: Es gibt eine Typliste, in der alle vorhandenen Typen und die darauf möglichen Operatoren und Funktionen gespeichert sind. Beim Erstellen der Typliste kommen die nativen Typen rein.
Eine 'Expression' kann bei mir ein Operator, Funktionsaufruf, konstanter Wert (eben bool/string/int/...) oder ein Variablen-/Konstantenname sein. Findet der Parser ein konstantes Literal, wird der Name des Typs (hartkodiert), und, als String, der konstante Wert als Expression gespeichert.
Beim Evaluieren wird der Typname bzw die Typinformationen zurückgegeben.Mit einer Typliste (oder auch einem einfachen enum, falls du nur fixe Datentypen hast) würdest du dir die doppelten Klassen ersparen, weil eine Expression einfach die Typ-Information für die Konstante und der Validator die Typ-Informationen aus der Typliste hat.
Da aber Konrads Programmierkenntnisse >>>> Badestrands Programmierkenntnisse, kann mein Post auch totaler Müll sein, nimm's mir dann nicht übel
-
Headhunter schrieb:
spontan würde ich genau das machen, was du vorschlägst: Value ist eine Unterklasse von Expression. Im simpelsten Fall ist ein Ausdruck ja genau ein Wert.
Im Moment habe ich das aus folgendem Grund nicht so: 'Value' hat ja eine Unterklasse namens 'Callable' und die hat einfach keine Entsprechung im Syntaxbaum. 'Callable' ist ja einfach eine abstrakte Beschreibung eines aufrufbaren Elements, im Syntaxbaum kann das verschiedene Formen annehmen.
Wenn Value kein Untertyp von Expression ist, müsste irgendwo in deinem Parser auch zweimal der Code zum einlesen von Values enthalten sein...
Nein, wieso sollte? Die ganze 'Value'-Hierarchie taucht im Parser ja gar nicht auf. Der entsprechende Teil des Parsers könnte z.B. so aussehen (der Parser ist noch nicht geschrieben, aber das ist ja trivial als Recursive-Descent-Parser):
private Expression Literal() { try { if (Scanner.Lookahead is NumberToken) return new Number(((NumberToken)Scanner.Lookahead).Value); if (Scanner.Lookahead is StringToken) return new String(((StringToken)Scanner.Lookahead).Value); if (Scanner.Lookahead is BoolToken) return ((BoolToken)Scanner.Lookahead).Value ? True.Instance : False.Instance; if (Scanner.Lookahead is QuoteChar) return new QuotedExpression(Expression()); return null; } finally { Scanner.Consume(); } }
Ich sehe jetzt nicht, wieso ich das zweimal schreiben müssen sollte.
Tipp: Schau dir einfach mal ein EBNF Diagramm deiner lispähnlichen Sprache an, und übersetze das ganz naiv in dein Klassenmodell.
Das ist ja geschehen. Am Anfang meines Klassenmodells stand ein EBNF-Modell, das eigentlich genau der 'Expression'-Klassenhierarchie entspricht (plus Syntaxelemente wie öffnende und schließende Klammern).
Badestrand schrieb:
Eine 'Expression' kann bei mir ein Operator, Funktionsaufruf, konstanter Wert (eben bool/string/int/...) oder ein Variablen-/Konstantenname sein. Findet der Parser ein konstantes Literal, wird der Name des Typs (hartkodiert), und, als String, der konstante Wert als Expression gespeichert.
Hmm. Ich weiß nicht. Das ist natürlich ebenfalls möglich, aber da finde ich die Unterklassen dann doch flexibler. Bei mir entfällt ja das Speichern des Typennamens, weil der bereits durch die entsprechende Unterklasse von 'Value' gegeben ist. Und den Wert muss ich auch nicht als String speichern sondern kann das Dank der Unterklassen streng typisiert speichern.
Da aber Konrads Programmierkenntnisse >>>> Badestrands Programmierkenntnisse
Selbst wenn das so wäre: Manchmal hat man einfach nen Brett vor'm Kopf. Zum Beispiel habe ich mit einem Freund ewig daran gesessen, diese Klassenhierarchie zu etablieren, weil uns einfach nicht in den Kopf wollte, wie der Zusammenhang zwischen einer Liste und einem Aufruf ist (in Lisp *sind* Listen gewissermaßen Aufrufe). Uns ist z.B. erst sehr spät aufgefallen, dass wir die Unterklasse 'Macro' brauchen, um lazy evaluation (also verzögerte Auswertung) zu implementieren. Anders könnte man ja nicht mal ein Lambda definieren.
-
Hallo,
das Konzept von Callable war mir vorher nicht bekannt. Welche Syntaxkonstrukte werden damit abgebildet? Lambdadefinitionen und Standard Funktionsaufrufe?
Entweder ist das Modell unlogisch, oder ich habe selber ein Brett vor KopfIch vermisse bei dir eine Klasse "Statement"...
Eine Expression sollte iirc rekursiv aufgebaut sein - eine Expression kann Unterexpressions erhalten; ist das in deinem Code abgebildet? Vielleicht kannst du ja Callable in den linken Teil des Sourcetrees verschieben und von Atom erben lassen?
-
Mach eine Klasse Literal welche von Expression erbt. Diese nimmt ein Value als Konstruktorparameter. Beim Aufruf von eval gibt sie einfach diesen zurück.
Expression und Value solltest du IMO als separate Hierarchien lassen.
-
Headhunter schrieb:
Hallo,
das Konzept von Callable war mir vorher nicht bekannt. Welche Syntaxkonstrukte werden damit abgebildet? Lambdadefinitionen und Standard Funktionsaufrufe?
Na wie gesagt: Es handelt sich dabei nicht um ein Syntaxkonstrukt sondern um einen Wert, und zwar auf einen beliebigen Wert, der auf etwas Aufrufbares verweist. Es gibt verschiedene Ausdrücke, die einen solchen Wert erzeugen können, dazu gehören auch Lambdas. Standard-Funktionsaufrufe gibt es in Wisp (und soweit ich weiß auch in Lisp) nicht.
Hier mal drei Beispiele für 'Callables' (jeweils fett dargestellt):
# Identitätsfunktion: ([b](lambda (x) x)[/b] 42) (define id (lambda (x) x)) ([b]id[/b] 42) ([b](if (< 23 42) + -)[/b] 11 13 )
Ich vermisse bei dir eine Klasse "Statement"...
Was soll diese Klasse repräsentieren?
Eine Expression sollte iirc rekursiv aufgebaut sein - eine Expression kann Unterexpressions erhalten; ist das in deinem Code abgebildet?
Ja, schau doch ins Diagramm. Ein Ausdruck ist entweder ein Atom oder eine Liste von Unterausdrücken.
-
Ben04 schrieb:
Mach eine Klasse Literal welche von Expression erbt. Diese nimmt ein Value als Konstruktorparameter. Beim Aufruf von eval gibt sie einfach diesen zurück.
Mit anderen Worten: Ich lasse im Prinzip alles so wie es ist, nur dass ich die Unterklassen von 'Literal' weglasse und deren Semantik – ohne strenge Typisierung – in die Oberklasse 'Literal' bewege? Wäre ne Möglichkeit. Ich schaue nachher mal, ob das Probleme gibt. Glaube ich aber eher nicht, bisher machen die Unterklassen das eh alle genauso, nur dass ich hier eben strengere Typentrennung habe (die aber zugegeben nicht wirklich nötig ist).
-
Mit deinem derzeitigen Design setzt du die Kerneigenschaft von Lisp, Code als Daten zu betrachten, nicht wirklich um.
Mein Vorschlag waere, eine Klasse Reader zu erstellen, die eine Lisp-Form einliest und in ein Objekt der Klasse Expression umwandelt. Simple Typen kann der Reader sofort erkennen (Symbole, Strings, Zahlen).
Daraus folgt, dass du Lambda, Callable etc. auch von Expression erben lassen solltest: Die Repraesentierung von Code und Daten ist in Lisp aequivalent.
-
Doktor Prokt schrieb:
Mit deinem derzeitigen Design setzt du die Kerneigenschaft von Lisp, Code als Daten zu betrachten, nicht wirklich um.
… was ursprünglich gar nicht so gedacht war (S-Expressions vs. M-Expressions).
Mein Vorschlag waere, eine Klasse Reader zu erstellen, die eine Lisp-Form einliest und in ein Objekt der Klasse Expression umwandelt. Simple Typen kann der Reader sofort erkennen (Symbole, Strings, Zahlen).
Hmm, was hat der Reader (= Parser?) mit dem Interpreter zu tun? Das ist doch eigentlich komplett unabhängig voneinander. Ich habe ja wie gesagt zur Zeit auch noch keinen Parser sondern ich baue mir die Ausdrücke im Moment in C#-Konstrukten zusammen.
Daraus folgt, dass du Lambda, Callable etc. auch von Expression erben lassen solltest: Die Repraesentierung von Code und Daten ist in Lisp aequivalent.
Gut, nur: wie ist 'Callable' in der Grammatik repräsentiert? Es kann ja sowohl sein, dass ein Bezeichner auf ein Callable verweist, als auch, dass (wie in meinem Beispiel vorhin) ein komplexer Ausdruck ein Callable ergibt. Das ist problematisch, denn ich weiß nicht, wo ich 'Callable' in der Hierarchie einordnen soll: Es ist ja ganz offensichtlich nicht immer ein atomarer Ausdruck, *kann* aber einer sein. Umgekehrt ist nicht jeder atomarer Ausdruck callable.
Das ist übrigens in „klassischem“ Lisp ganz anders geregelt, soweit ich das verstehe. Und zwar ist das Aufruf-Dings in Lisp der gesamte geklammerte Ausdruck, während es bei mir der erste Teil eines geklammerten Ausdrucks ist. Ich habe es anders gemacht, weil der Parsebaum dadurch wesentlich einfacher wird (oder ich habe ein Brett vor'm Kopf).
-
Konrad Rudolph schrieb:
Doktor Prokt schrieb:
Mit deinem derzeitigen Design setzt du die Kerneigenschaft von Lisp, Code als Daten zu betrachten, nicht wirklich um.
… was ursprünglich gar nicht so gedacht war (S-Expressions vs. M-Expressions).
Ja, bis Lisp 1.5 oder so. Das hat aber nichts mit modernen Lisp-Dialekten zu tun.
Hmm, was hat der Reader (= Parser?) mit dem Interpreter zu tun? Das ist doch eigentlich komplett unabhängig voneinander. Ich habe ja wie gesagt zur Zeit auch noch keinen Parser sondern ich baue mir die Ausdrücke im Moment in C#-Konstrukten zusammen.
Nein, der Reader in Lisp-Systemen ist etwas mehr als nur ein Parser. S-Expressions werden praktisch nicht mehr geparst, da der Programmierer den Parse-Baum ja schon eingibt. Der Reader sollte wie gesagt einige einfache Typen sofort erkennen koennen, wie zB Zahlen und Symbole.
Also bekommt der Reader eine Lisp-Form uebergeben und macht daraus ein Expression-Objekt. Dieses Expression-Objekt sollte sinnigerweise baumartig aufgebaut sein, damit du die tatsaechliche Lisp-Form auch adaequat repraesentierst. Hierzu eignet sich deine List-Klasse.Gut, nur: wie ist 'Callable' in der Grammatik repräsentiert?
Gar nicht. Warum sollte es?
Das ist problematisch, denn ich weiß nicht, wo ich 'Callable' in der Hierarchie einordnen soll: Es ist ja ganz offensichtlich nicht immer ein atomarer Ausdruck, *kann* aber einer sein.
Unter welchen Umstaenden kann ein Callable-Objekt ein atomarer Ausdruck sein?
-
Doktor Prokt schrieb:
Konrad Rudolph schrieb:
Doktor Prokt schrieb:
Mit deinem derzeitigen Design setzt du die Kerneigenschaft von Lisp, Code als Daten zu betrachten, nicht wirklich um.
… was ursprünglich gar nicht so gedacht war (S-Expressions vs. M-Expressions).
Ja, bis Lisp 1.5 oder so. Das hat aber nichts mit modernen Lisp-Dialekten zu tun.
Gut, das hat Wisp auch nicht.
Hmm, was hat der Reader (= Parser?) mit dem Interpreter zu tun? Das ist doch eigentlich komplett unabhängig voneinander. Ich habe ja wie gesagt zur Zeit auch noch keinen Parser sondern ich baue mir die Ausdrücke im Moment in C#-Konstrukten zusammen.
Nein, der Reader in Lisp-Systemen ist etwas mehr als nur ein Parser. S-Expressions werden praktisch nicht mehr geparst, da der Programmierer den Parse-Baum ja schon eingibt. Der Reader sollte wie gesagt einige einfache Typen sofort erkennen koennen, wie zB Zahlen und Symbole.
Also bekommt der Reader eine Lisp-Form uebergeben und macht daraus ein Expression-Objekt. Dieses Expression-Objekt sollte sinnigerweise baumartig aufgebaut sein, damit du die tatsaechliche Lisp-Form auch adaequat repraesentierst. Hierzu eignet sich deine List-Klasse.Ah okay, alles klar (hoffentlich:). Das heißt, dieser Reader nimmt eine Umstrukturierung des Baumes vor? Ich meine, wenn ich in Lisp den Ausdruck „(+ 1 2)“ habe, dann entspricht das ja diesem Baum:
• /|\ / | \ + 1 2
wohingegen der AST (zumindest beim Interpreter-Muster) so aussähe:
+ / \ / \ 1 2
Gut, nur: wie ist 'Callable' in der Grammatik repräsentiert?
Gar nicht. Warum sollte es?
Na ja, weil die GoF davon spricht, dass jede Klasse im Interpreter-Muster einem Element der Grammatik entspricht.
Das ist problematisch, denn ich weiß nicht, wo ich 'Callable' in der Hierarchie einordnen soll: Es ist ja ganz offensichtlich nicht immer ein atomarer Ausdruck, *kann* aber einer sein.
Unter welchen Umstaenden kann ein Callable-Objekt ein atomarer Ausdruck sein?
Na im obigen Beispiel wird es durch ein Atom (fett) repräsentiert: „(+ 1 2)“.
-
Konrad Rudolph schrieb:
Ah okay, alles klar (hoffentlich:). Das heißt, dieser Reader nimmt eine Umstrukturierung des Baumes vor? Ich meine, wenn ich in Lisp den Ausdruck „(+ 1 2)“ habe, dann entspricht das ja diesem Baum:
• /|\ / | \ + 1 2
wohingegen der AST (zumindest beim Interpreter-Muster) so aussähe:
+ / \ / \ 1 2
Wie du den Baum im einzelnen repraesentierst, ist nicht so furchtbar entscheidend. In meinem (Spielzeug)-Lisp ist es ein Binaerbaum.
Na im obigen Beispiel wird es durch ein Atom (fett) repräsentiert: „(+ 1 2)“.
Ok, darum muss sich der Reader aber nicht kuemmern. Der Reader sieht hier lediglich eine Liste aus drei Symbolen und erzeugt eine solche ohne den Ausdruck auszuwerten. Wenn jetzt spaeter ein call() oder ein eval() auf der Liste aufgerufen werden sollte, dann kann die Liste nachschauen, ob sich in der Symboltabelle unter dem Symbol + eine Funktion verbirgt. Diese Funktion (das eigentliche "Callable") kann dann aufgerufen werden.
Wenn du das so implementierst, verstehe ich nicht, warum du eine Entsprechung fuer Callable in der Grammatik brauchst.
-
Konrad Rudolph schrieb:
Mit anderen Worten: Ich lasse im Prinzip alles so wie es ist, nur dass ich die Unterklassen von 'Literal' weglasse und deren Semantik – ohne strenge Typisierung – in die Oberklasse 'Literal' bewege?
Genau. Ich habe damit an sich nur gute Erfahrungen gemacht. Allerdings habe ich bis jetzt nur Interpreter für C-artige Dialekte geschrieben...
Ich kenne mich bei Lisp nicht wirklich aus allerdings von dem was ich bis jetzt gesehen habe wäre es wahrscheinlich das beste da beide Hierarchien zu vereinigen.
-
Doktor Prokt schrieb:
Na im obigen Beispiel wird es durch ein Atom (fett) repräsentiert: „(+ 1 2)“.
Ok, darum muss sich der Reader aber nicht kuemmern. Der Reader sieht hier lediglich eine Liste aus drei Symbolen und erzeugt eine solche ohne den Ausdruck auszuwerten. Wenn jetzt spaeter ein call() oder ein eval() auf der Liste aufgerufen werden sollte, dann kann die Liste nachschauen, ob sich in der Symboltabelle unter dem Symbol + eine Funktion verbirgt.
Ja, so mache ich es ja zur Zeit auch.
Wenn du das so implementierst, verstehe ich nicht, warum du eine Entsprechung fuer Callable in der Grammatik brauchst.
Wie gesagt: Ich habe mich da am „Design Patterns“-Buch orientiert.
Nur jetzt habe ich nach wie vor das Problem, dass ich nicht weiß, wo 'Callable' in der Hierarchie stehen soll. Es ist ja ein 'Value', aber kein 'Literal'. Andererseits sind aber alle 'Values' ein Literal.
Schau Dir doch bitte nochmal die eingangs von mir gepostete Hierarchie an. Wenn ich jetzt 'Value' in die 'Expression'-Hierarchie einbaue, wo kommt es dann hin? Unter 'Atom'? Geht nicht, 'Callable' ist ja kein Atom. Über 'Atom'? Geht nicht, denn 'Identifier' ist kein 'Value'.
Den einzigen Ausweg, den ich momentan sehe, wäre, die abstrakten Basisklassen zu Interfaces zu machen aber irgendwie gefällt mir das nicht.
-
Konrad Rudolph schrieb:
Schau Dir doch bitte nochmal die eingangs von mir gepostete Hierarchie an. Wenn ich jetzt 'Value' in die 'Expression'-Hierarchie einbaue, wo kommt es dann hin? Unter 'Atom'? Geht nicht, 'Callable' ist ja kein Atom. Über 'Atom'? Geht nicht, denn 'Identifier' ist kein 'Value'.
Ueber Atom klingt gut. Die Annahme, dass Identifier kein Value ist, ist weniger gut, denn warum sollte sich hinter einem Symbol, wenn dieses ausgewertet wird, nicht wieder ein Symbol verstecken koennen? Es ist doch wichtig, dass Symbole (Identifier bei Wisp), "first-class objects" sind.
Den Sinn von Literal habe ich hingegen noch nicht verstanden. Warum ist das in der Klassenhierarchie notwendig (im Moment leitet ja eh nur Number davon ab)?
-
Wie wäre es mit folgendem: (Pseudo-Code)
class Value{ Value eval(Runtime); string to_string(); bool equals(Value); } class ConstantSymbol:Value{ Value eval(Runtime){return this;} } class Integer:ConstantSymbol{ int n; } class String:ConstantSymbol{ string str; } class Symbol:Value{ Value eval(Runtime rt){ return rt.var.lookup(identifier); } string identifier; } class List:Value{ Value eval(Runtime rt){ vector<Value>args = elements.rest; ... preprocessor transformations on args... tansform(args, Value::eval); return rt.func.exec(elements.first, args); } vector<Value>elements; } class VarManager{ void set(string identifier, Value val){ ... } Value lookup(string identifer) { ...throw if not defined... } } class FuncManager{ void set(Value func_name, ...some callback construct...); Value exec(Value func_name, vector<Value>args) { ...throw if not defined, dispach otherwise...} } class Runtime{ VarManager var; FuncManager func; } int main(){ string code = ...load code...; Value tree = parse(code); Runtime rt; rt.func.set(Symbol("set"), ...something with a reference to rt.var...); ... other buildin functions... string result = tree.eval().to_string; ...output result... }
-
Doktor Prokt schrieb:
Konrad Rudolph schrieb:
Schau Dir doch bitte nochmal die eingangs von mir gepostete Hierarchie an. Wenn ich jetzt 'Value' in die 'Expression'-Hierarchie einbaue, wo kommt es dann hin? Unter 'Atom'? Geht nicht, 'Callable' ist ja kein Atom. Über 'Atom'? Geht nicht, denn 'Identifier' ist kein 'Value'.
Ueber Atom klingt gut. Die Annahme, dass Identifier kein Value ist, ist weniger gut, denn warum sollte sich hinter einem Symbol, wenn dieses ausgewertet wird, nicht wieder ein Symbol verstecken koennen? Es ist doch wichtig, dass Symbole (Identifier bei Wisp), "first-class objects" sind.
Na ja, „Values“ sind bei mir eben nur Werte, ein Bezeichner hingegen verweist lediglich auf einen Wert, selbst ist er keiner. Aber das liegt ja eben daran, dass ich bisher zwischen M- und S-Ausdrücken unterschieden habe.
Den Sinn von Literal habe ich hingegen noch nicht verstanden. Warum ist das in der Klassenhierarchie notwendig (im Moment leitet ja eh nur Number davon ab)?
Äh? Nein, da leiten auch 'String', 'Bool' und 'QuotedExpression' von ab. Ein Literal ist halt quasi eine Konstante, die im Code steht. Eben genau die Bedeutung, die dieses Wort normalerweise im Zusammenhang mit Programmiersprachen hat.
-
Mir ist da ein wenig zu viel ist-ein im Spiel, wo ich mich fragen muss, ob es sinnvoll ist.
Ein Literal oder ein String ist eine Expression bei dir. Und String ist gleichzeitig noch eine andere Klasse, die von Value erbt.
Gut in 2 Namespaces lässt sich das sauber trennen, dennoch frag ich mich, wo dann der Unterschied ist. Ähnliches gilt für Number oder List.Ich denke auch das sich der (Parse oder Expression)Baum evtl. ganz gut als Kompositum abbilden lässt, kann das jetzt nicht ganz deinem Diagramm entnehmen, ob dem schon so ist.
-
phlox81 schrieb:
Ein Literal oder ein String ist eine Expression bei dir. Und String ist gleichzeitig noch eine andere Klasse, die von Value erbt.
Gut in 2 Namespaces lässt sich das sauber trennen, dennoch frag ich mich, wo dann der Unterschied ist. Ähnliches gilt für Number oder List.Eben, ich benutze zwei verschiedene Namensbereiche und im Moment sind das zwei verschiedene Dinge. Und auch die ist-ein-Beziehung ist dadurchaus sinnvoll. Ein String-Ausdruck ist-ein Literal. Und ein String-Wert ist-ein Wert.
Aber wenn ich das ganze eingliedere, dann wird sich das ja eh ändern.
Ich denke auch das sich der (Parse oder Expression)Baum evtl. ganz gut als Kompositum abbilden lässt, kann das jetzt nicht ganz deinem Diagramm entnehmen, ob dem schon so ist.
Da kann ich nicht ganz folgen. Wie soll das über Komposition gehen? Der Witz am Interpreter-Muster ist doch schon, dass man die Auswertungsregeln unterscheidet, indem man Spezialisierungen eines allgemeinen Musters ausfertigt. Komposition wird nur zur Zusammensetzung benutzt (also eine Liste besteht aus Unterausdrücken).
Komposition ist in dem Diagramm übrigens durch die braunen Pfeile ausgedrückt.
Nochmal zur Erinnerung:
http://madrat.net/temp/Wisp Interpreter.png
-
Konrad Rudolph schrieb:
Ich denke auch das sich der (Parse oder Expression)Baum evtl. ganz gut als Kompositum abbilden lässt, kann das jetzt nicht ganz deinem Diagramm entnehmen, ob dem schon so ist.
Da kann ich nicht ganz folgen. Wie soll das über Komposition gehen? Der Witz am Interpreter-Muster ist doch schon, dass man die Auswertungsregeln unterscheidet, indem man Spezialisierungen eines allgemeinen Musters ausfertigt. Komposition wird nur zur Zusammensetzung benutzt (also eine Liste besteht aus Unterausdrücken).
Komposition ist in dem Diagramm übrigens durch die braunen Pfeile ausgedrückt.
ok. Also kann ich davon ausgehen, das die Klasse Expression intern eine Liste von Expression* Objekten hält, bzw. eine List-klassen Instanz derer.
Nächste Vererbungsebene, du definierst die Klasse Atom und List. Atom soll wohl atomare, nicht weiter aufspaltbare Elemente in der Expression abbilden.
Frage mich ob die Vererbung dann nicht umgekehrt sinnvoller wäre, über Vererbung einzuschränken halte ich für weniger Sinnvoll als über sie zu erweitern.
Von Atom erben schließlich über Literal die Klassen Number,String, und QuotedExpression. Was ist QuotedExpression? Ein "foo" oder ( 1 + 2 )?
Ersteres würde ja sinn machen, letzteres der Basisklasse Atom wieder sprechen.phlox
-
phlox, ich glaube Du verstehst das Interpreter-Entwurfsmuster nicht. Die von Dir vorgeschlagene Vererbungshierarchie würde einfach nicht funktionieren. Der Witz ist ja gerade, dass sich einfach jedes Wisp-Programm als 'Expression'-Objekt auffassen lässt und ich kann 'Eval' darauf ausführen. Und je nachdem, wie das Programm konktet aussieht, wird es dann korrekt evaluiert, weil die verschiedenen Sprachkomponenten eben wissen, wie sie selbst evaluiert werden müssen.
Zur QuotedExpression: Dabei handelt es sich um einen normalen Audruck, der aber als Literal gekennzeichnet wurde. Dadurch wird dieser Ausdruck nicht ausgeführt sondern repräsentiert Daten. Das sieht dann – analog zu Lisp – so aus:
'(1 2 3)
Ohne das „'“ am Anfang ginge das so nicht, denn der Interpreter erwartet, dass das erste Symbol in einer Liste etwas Ausführbares ist und nicht, wie in diesem Fall, ein Wert.