Interpreter implementieren: Typensystem



  • 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.



  • Konrad Rudolph schrieb:

    Ä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.

    Hmm, achja. Dann solltest du aber Literal und Identifier zu einer Klasse Symbol zusammenfassen. Ein String kann dann ja von List erben oder du erzeugst eine weitere Klasse Sequence, die von Expression erbt, von der dann List, String und evtl. weitere Sequenztypen erben koennen.



  • Hi Dr. P.,

    so langsam freunde ich mich damit an. Wobei ich es wohl eher so machen werde, dass 'QuotedExpression' nicht eine Expression enthält sondern eine List, und zusammen mit 'String' von einer Klasse 'Sequence' abgeleitet wird. Gut, damit ich solche Änderungen machen kann, muss ich das Projekt jetzt aber erstmal auf eine Versionskontrolle umstellen. 😉



  • Konrad Rudolph schrieb:

    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.

    Ja mag sein das ich das Interpretermuster nicht unbedingt verinnerlicht habe 😉 Oder einfach da was mit dem Value verwechsle.
    Trotzdem bin ich der meinung das es einen Unterschied macht, ob du nun eine Expression oder eine SingleExpression/AtomExpression hast, und ich der Meinung bin, das für es Sinnvoller währe die Expression von SingleExpression oder einer Basisklasse ExpressionBase abzuleiten, anstatt Atom von nicht-ATomic basisklassen abzuleiten.
    Wobei ich hier davon ausgehe, das es Unterschiede im Interface dieser Klassen geben könnte, wenn da nur eine Eval Methode ist, ist das natürlich wurscht.

    phlox



  • So, vielen Dank nochmal an alle. Die Umstellung ist jetzt wie von Doktor Prokt vorgeschlagen erfolgt und es funktioniert genauso wie vorher, nur dass ich weniger Klassen brauche.



  • Auch wenn dein Problem gelöst scheint, hier noch einmal ein paar Anmerkungen:

    ➡ Ich mag durchaus falsch liegen, kann mir aber beim besten Willen nicht vorstellen dass das Interpreter Pattern allzu geeignet ist einen [L/W]isp-Interpreter zu implementieren.

    ➡ Mir erschließt sich der Sinn deiner Value-Vererbungslinie nicht ganz so. Selbst wenn du darauf bestehst, erscheint es mir sinnvoller einfach alle Typen die ein Value sein können mit einem entsprechendem Interface auszustatten.

    So wie's derzeit in deinem Diagramm abgebildet ist geht's auf jeden Fall nicht:

    (let ((odd? (lambda (n) (if (= n 0) #f (not (even? (- n 1))))))
          (even? (lambda (n) (if (= n 0) #t (not (odd? (- n 1)))))))
      (if (even? 43) 'even 'oddness))
    

    ➡ Apropos quote. Wozu QuotedExpression? Die Klasse würde ich ganz rausschmeißen und dafür schlicht den Parser ein wenig umbauen:

    if (Scanner.Lookahead is QuoteChar)
      return new List().append(theQuoteSymbol).append(readList());
    

    Sprich das Apostroph ist nur ein wenig syntactic sugar der 'expr in (quote expr*)* umwandelt.

    Mit dem Backtick kannst du ähnlich verfahren.

    ➡ Wenn du auf Closures nicht verzichten willst, denk dran deinen Lambdas das entsprechende Environment (Context?) mitzugeben.

    ➡ Bezüglich Literal etc. habe ich eurer Diskussion vielleicht einfach nicht folgen können, wenn du mit Literal allerdings schlicht 'String', 'Bool' und so meinst, quasi alles was in C mit Literal bezeichnet wird, dann
    - solltest du Literal besser nicht mit Identifier zusammenfassen,
    - ist ein Identifier/Symbol ein Literal und desweiteren
    - ist Literal synonym zu Atom.



  • 'finix schrieb:

    ➡ Ich mag durchaus falsch liegen, kann mir aber beim besten Willen nicht vorstellen dass das Interpreter Pattern allzu geeignet ist einen [L/W]isp-Interpreter zu implementieren.

    Wieso nicht? Der Interpreter läuft eigentlich bereits spitze. Allerdings ist Wisp ≠ Lisp. Ich kann gar kein Lisp (klar, einfache Dinge kann ich lesen und auch verstehen).

    ➡ Mir erschließt sich der Sinn deiner Value-Vererbungslinie nicht ganz so. Selbst wenn du darauf bestehst, erscheint es mir sinnvoller einfach alle Typen die ein Value sein können mit einem entsprechendem Interface auszustatten.

    So wie's derzeit in deinem Diagramm abgebildet ist geht's auf jeden Fall nicht:

    (let ((odd? (lambda (n) (if (= n 0) #f (not (even? (- n 1))))))
          (even? (lambda (n) (if (= n 0) #t (not (odd? (- n 1)))))))
      (if (even? 43) 'even 'oddness))
    

    Stimmt, als Interface wär's auch gegangen. Allerdings sehe ich nicht, wo sich Dein Code und mein Klassendiagramm widersprechen. Ich habe zwar noch nicht versucht, eine mutuelle Rekursion zu implementieren (ich teste das morgen mal, jetzt habe ich leider keine Zeit mehr), aber prinzipiell sollte das klappen.

    ➡ Apropos quote. Wozu QuotedExpression? Die Klasse würde ich ganz rausschmeißen und dafür schlicht den Parser ein wenig umbauen:

    if (Scanner.Lookahead is QuoteChar)
      return new List().append(theQuoteSymbol).append(readList());
    

    Sprich das Apostroph ist nur ein wenig syntactic sugar der 'expr in (quote expr*)* umwandelt.

    Hmm, Mist. Daran habe ich gar nicht gedacht. Ursprünglich wäre das in meinen Gedanken auch gar nicht gegangen, weil die Argumente vor Aufruf einer Funktion evaluiert wurden. Das ist jetzt allerdings nicht mehr überall so (ich unterscheide zwischen Macros, welche ihre Argumente nicht evaluieren und keinen eigenen Ausführungskontext erzeugen und Funktionen, welche beides tun).

    Mit dem Backtick kannst du ähnlich verfahren.

    Welchen Backticks?

    ➡ Wenn du auf Closures nicht verzichten willst, denk dran deinen Lambdas das entsprechende Environment (Context?) mitzugeben.

    Ja, erfolgt sowieso.

    ➡ Bezüglich Literal etc. habe ich eurer Diskussion vielleicht einfach nicht folgen können, wenn du mit Literal allerdings schlicht 'String', 'Bool' und so meinst, quasi alles was in C mit Literal bezeichnet wird, dann
    - solltest du Literal besser nicht mit Identifier zusammenfassen,
    - ist ein Identifier/Symbol ein Literal und desweiteren
    - ist Literal synonym zu Atom.

    Ja. 'Literal' habe ich inzwischen vollkommen rausgeschmissen.



  • Konrad Rudolph schrieb:

    [Interpreter Pattern]

    Wieso nicht? Der Interpreter läuft eigentlich bereits spitze. Allerdings ist Wisp ≠ Lisp. Ich kann gar kein Lisp (klar, einfache Dinge kann ich lesen und auch verstehen).

    War lediglich 'ne ad hoc Intuition. Kann, wie gesagt, auch komplett falsch liegen.

    Hat auch nichts speziell mit Lisp oder so zu tun. Ich stelle mir bloß vor dass das Interpreter Pattern bei dieser Komplexität keinerlei Vorteile bringt, die Sache eher noch verkompliziert. Und natürlich die Fixierung auf direkte Interpretation des AST.

    Konrad Rudolph schrieb:

    Stimmt, als Interface wär's auch gegangen. Allerdings sehe ich nicht, wo sich Dein Code und mein Klassendiagramm widersprechen. Ich habe zwar noch nicht versucht, eine mutuelle Rekursion zu implementieren (ich teste das morgen mal, jetzt habe ich leider keine Zeit mehr), aber prinzipiell sollte das klappen.

    Ungeschicktes Beispiel meinerseits, konnte's als Unreg leider nicht editieren. Ich meinte eigentlich bloß dass dir ein SymbolValue fehlt.

    Btw, wie hast du denn deine Funktionsaufrufe implementiert? Mutuelle Rekursion sollte doch eigentlich kein Problem darstellen.

    Konrad Rudolph schrieb:

    Hmm, Mist. Daran habe ich gar nicht gedacht. Ursprünglich wäre das in meinen Gedanken auch gar nicht gegangen, weil die Argumente vor Aufruf einer Funktion evaluiert wurden.

    Ich dachte das wäre klar - der ganze Sinn von quote ist ja das die Argumente nicht evaluiert werden. Mir ging's nur darum dass ' eigentlich nichts besonderes ist, oder sein muss.

    Wisp>(car ''foo)
    quote
    

    Konrad Rudolph schrieb:

    Das ist jetzt allerdings nicht mehr überall so (ich unterscheide zwischen Macros, welche ihre Argumente nicht evaluieren und keinen eigenen Ausführungskontext erzeugen und Funktionen, welche beides tun).

    Weise Entscheidung 😉 Denk dran dass es neben quote und Makros noch ein paar weitere SpecialForms wie z.B. if gibt.

    Konrad Rudolph schrieb:

    Mit dem Backtick kannst du ähnlich verfahren.

    Welchen Backticks?

    Den Backticks für backquote/quasiquote.

    Konrad Rudolph schrieb:

    ➡ Wenn du auf Closures nicht verzichten willst, denk dran deinen Lambdas das entsprechende Environment (Context?) mitzugeben.

    Ja, erfolgt sowieso.

    Wusste nicht ob es in deinem Diagramm nur ausgeblendet war, oder du nicht dran gedacht hast oder so.



  • 'finix schrieb:

    Hat auch nichts speziell mit Lisp oder so zu tun. Ich stelle mir bloß vor dass das Interpreter Pattern bei dieser Komplexität keinerlei Vorteile bringt, die Sache eher noch verkompliziert. Und natürlich die Fixierung auf direkte Interpretation des AST.

    Ach, so kompliziert ist das gar nicht. Ob es effizient ist, ist natürlich nochmal ne ganz andere Frage, darum mache ich mir dann später Gedanken. 😉

    Außerdem, was wäre denn eine (einfachere) Alternative? Enweder ich müsste das ganze in eine Bytecodeform übertragen und einen Interpreter für diesen Bytecode schreiben oder ich kompiliere das ganze in eine existierende Sprache. Auch nicht unbedingt einfacher. Die wohl einfachste Möglichkeit wäre, das ganze per 'System.Linq.Expression' in .NET-Lambdas zu überführen, zu kompilieren und auszuführen. Aber genau diesen Part wollte ich ja mal selbst bauen. 😉

    Evtl. hänge ich da irgendwann mal einen 'Compile'-Interpreter ran, der genau das macht, und vergleiche dann die Resultate.

    Zum Glück ist das ganze auch eher akademisch, sodass nicht das Überleben von Ariane-Raketen von der Effizienz meines Interpreters abhängt.

    Ungeschicktes Beispiel meinerseits, konnte's als Unreg leider nicht editieren. Ich meinte eigentlich bloß dass dir ein SymbolValue fehlt.

    Du meinst sowas wie '#t' und '#f'? Ja, wie gesagt, Wisp ist nicht Lisp.

    Btw, wie hast du denn deine Funktionsaufrufe implementiert? Mutuelle Rekursion sollte doch eigentlich kein Problem darstellen.

    Tut sie auch nicht. Im Moment kopiere ich für den Funktionsaufruf einfach den aktuellen Kontext, rufe für jeden Parameter eine 'RebindArgument'-Methode auf und gut is'. Das ganze ist zwar ebenfalls nicht besonders effizient aber sollte sich das als kritisch herausstellen, kann man das ganze lokal anpassen.

    Konrad Rudolph schrieb:

    Denk dran dass es neben quote und Makros noch ein paar weitere SpecialForms wie z.B. if gibt.

    Wieso sollte man 'if' nicht als Makro implementieren? Bei mir sieht die aktuelle Implementierung so aus:

    [Macro("if", NumberOfArguments = 3)]
    public static Value MacroIf(Context context, IEnumerable<Expression> arguments) {
        var cond = arguments.First().Eval(context) as Bool;
        var true_part = arguments.Skip(1).First();
        var false_part = arguments.Last();
    
        if (cond == null)
            throw new InvalidOperationException("Invalid type. List must yield boolean value.");
    
        return (cond is True ? true_part : false_part).Eval(context);
    }
    


  • Konrad Rudolph schrieb:

    Ungeschicktes Beispiel meinerseits, konnte's als Unreg leider nicht editieren. Ich meinte eigentlich bloß dass dir ein SymbolValue fehlt.

    Du meinst sowas wie '#t' und '#f'? Ja, wie gesagt, Wisp ist nicht Lisp.

    Nein, mit Symbol meine ich das was bei dir unter Identifier läuft.
    (Und wenn ich nicht weiß wie Wisp genau aussehen soll halte ich mich "an Lisp angelehnt" - ist ja auch nicht schlimm, denke ich.)

    Konrad Rudolph schrieb:

    Im Moment kopiere ich für den Funktionsaufruf einfach den aktuellen Kontext, rufe für jeden Parameter eine 'RebindArgument'-Methode auf und gut is'.

    Da musst du eigentlich nichts kopieren. Gib deinem Context einfach einen baseContext mit:

    Context.GetValue(identifier):
      value = self.symbolTable[identifier]
      return value ? value : self.baseContext.GetValue(identifier)
    
    Lambda.Invoke(args):
      frame = new Context()
      frame.bind(self.parameters, map(Eval, args))
      frame.baseContext = self.context
      currentContext = frame
      return evalBody()
    

    Konrad Rudolph schrieb:

    Wieso sollte man 'if' nicht als Makro implementieren? Bei mir sieht die aktuelle Implementierung so aus:

    Wenn es möglich ist spricht natürlich nichts dagegen 'if' als Makro zu implementieren. Aber deine aktuelle Implementation sollte sich auch bei deiner Terminologie nicht wirklich Makro nennen, oder?



  • 'finix schrieb:

    Konrad Rudolph schrieb:

    Ungeschicktes Beispiel meinerseits, konnte's als Unreg leider nicht editieren. Ich meinte eigentlich bloß dass dir ein SymbolValue fehlt.

    Du meinst sowas wie '#t' und '#f'? Ja, wie gesagt, Wisp ist nicht Lisp.

    Nein, mit Symbol meine ich das was bei dir unter Identifier läuft.

    Ach so. Aber sowas gibt es doch.

    (Und wenn ich nicht weiß wie Wisp genau aussehen soll halte ich mich "an Lisp angelehnt" - ist ja auch nicht schlimm, denke ich.)

    Ja, ist schon prinzipiell die richtige Annahme. 😉

    [Zum Context:]
    Da musst du eigentlich nichts kopieren. Gib deinem Context einfach einen baseContext mit:

    Ja, das ist natürlich wesentlich besser.

    Konrad Rudolph schrieb:

    Wieso sollte man 'if' nicht als Makro implementieren? Bei mir sieht die aktuelle Implementierung so aus:

    Wenn es möglich ist spricht natürlich nichts dagegen 'if' als Makro zu implementieren. Aber deine aktuelle Implementation sollte sich auch bei deiner Terminologie nicht wirklich Makro nennen, oder?

    Hmm, da sehe ich den Einwand jetzt nicht. Ein Macro ist bei mir einfach ein 'Callable', bei dem die Argumente nicht pauschal vor dem Aufruf evaluiert werden, und wo kein neuer Aufrufkontext erstellt worden ist. Innerhalb des Makros kann das natürlich nachgeholt werden.


Anmelden zum Antworten