Skip to content
martok edited this page Jan 14, 2016 · 9 revisions

oder: Wie rechnet Pi?

Umgebung

Um den Kernel von Pi (also Tau) verwenden zu können, muss lediglich eine Instanz von TMathSystem erstellt und eingerichtet zu werden. Dazu sind mehrere Dinge notwendig: eine Instanz von IOutputProvider, welcher die Ausgaben erhält, der Kernel selbst sowie Packages, welche Funktionen zur verfügung stellen.

Einfaches Beispiel

fOutput:= TOutputStdOut.Create;
MathS:= TMathSystem.Create(fOutput);
with MathS do begin
  RegisterPackage(TPackageTrig.Create);
  RegisterPackage(TPackageElementary.Create);
  RegisterPackage(TPackageNumerical.Create);
  // ...
end;

Ablauf

Zum Ausführen eines Befehls sind mehrere Schritte notwendig, alle passieren in TMathSystem:

  • Run(string): Ruft Eval() auf und gibt das Ergebnis aus
  • Eval(string): Ruft Parse() auf, prüft die Expression auf Verwendbarkeit und führt sie aus "Verwendbarkeit" bedeutet, dass die Context-Verändernden Funktionen new() und drop() nur auf oberster Ebene stehen dürfen.
  • Parse(string): Implementiert einen 3-Schritt-Parser.
    1. Tokenize. Der Eingabestring wird in Tokens zerlegt (Operator, Zahl, String, Funktionsaufruf, (runde/eckige/geschweifte)Klammer auf, (runde/eckige/geschweifte)Klammer zu)
    2. Fold. Erzeugt aus nebeneinander liegenden Elementen den Expression-Baum, indem Operatoren, Parameter etc eingesammelt werden
    3. CheckResult. Prüft, ob nur noch genau ein Token übrig ist - nämlich das Wurzelelement des Expression-Baums.

Expressions: IExpression

Nach dem Parsen wird der Ausdruck in einem Baum von Ausdrücken dargestellt. Jeder Knoten besitzt eine Liste von Argumenten, welche Unterknoten darstellen. Dazu werden weitere Interfaces implementiert, welche die konkrete Art des Knotens festlegen. Es existieren folgende Interfaces: ISymbolReference (Symbolische Referenzen, also Variablennamen), IFunctionCall (Funktionsaufrufe), IExpressionAtom (Atomare ausdrücke, also Werte).

IExpression definiert eine Funktion Evaluate(Context: TContext), welche die Expression in einem bestimmten Kontext ausführt und das Ergebnis als neue Expression zurückgibt. Expressions sind immer "immutable", also nicht veränderbar. Soll etwas modifiziert werden, muss eine neue Expression erzeugt werden, Unterknoten können aber dank Referenzzählung wiederverwendet werden.

Kontexte: IContext

Kontexte stellen eine Umgebung für die Auswertung dar. Sie sind über das Interface IContext implementiert. Unterste Ebene ist der "Constants"-Kontext, der von weiteren überlagert werden kann. Die jeweils oberste Ebene ist der Ankerpunkt. Unter Anderem können auch in Funktionen private Kontexte erstellt werden, um z.B. Parameter für Expressions verändern zu können. Dazu existiert auch die Eigenschaft Silent, die die "Reassigned Variable"-Meldungen unterdrückt sowie eine Funktion "Bake", welche eine statische Kopie aller Variablen des Kontexts und der übergeordneten Variablen erstellt.

ctx:= TContext.Create(Context.System, Context);
try
  ctx.Silent:= true;
  //mit dem Kontext arbeiten, z.B. ctx.Define, expression.Evaluate(ctx) usw.
finally
  FreeAndNil(ctx);
end;

Werte: IExpressionAtom

Werte werden über "atomare", also unteilbare Ausdrücke dargestellt. Das bezieht sich darauf, dass ihre Auswertung nicht vom Kontext abhängt und keine Unterknoten existieren. Mehrere abgeleitete Interfaces lassen Rückschlüsse auf den Datentyp zu:

  • IValueUnassigned: nicht zugewiesen, z.B. wenn kein Rückgabewert existiert
  • IValueNull: NULL-Wert
  • IValueNumber: einfache Zahlen
  • IValueDimension: Zahlen mit physikalischer Einheit über einer Dimensionsbasis
  • IValueString: Strings
  • IValueList: Listen

Weitere Packages können zusätzliche, domainspezifische Datentypen definieren; so stellt z.B. das Graphing-Package seine Plots als IPlotObject dar.

Es existieren mehrere Typen von Listen: Bereiche, die durch Anfang, Ende und Schrittweite abgebildet werden und direkt keine Daten speichern und normale Listen, die weitere Ausdrücke enthalten (und deswegen genau genommen keine Atome sind).

Zur Darstellung in der Ausgabe implementieren Ausdrücke die Funktion AsString, welche das gewünschte Format übergeben bekommt:

  • STR_FORM_STANDARD: Ausgabeformat, z.B. für interaktive Konsole
  • STR_FORM_INPUT: Formatierung wie Eingabe (z.B. a=3+4)
  • STR_FORM_FULL: Volle Eingabe, Operatoren durch Funktionen ersetzt (z.B. _assign(a,_plus(3,4))
  • STR_FORM_DUMP: Kontenname (z.B. _plus())
  • STR_FORM_TYPEOF: Kontentyp (z.B. Call, ValueInteger etc.)
  • STR_FORM_EXPORT: Ausgabe für Export in Textbasierte Formate. Meistens identisch Input, außer Escapezeichen (z.B. 42.2E5, Ein String etc.)

Funktionen: TE_FunctionCall & TFunctionPackage

Funktionen in sogenannten Packages organisiert.

(Infix)Operatoren werden vom Parser in Funktionsaufrufe übersetzt. Per Konvention beginnen Funktionen, die einen Operator implementieren, mit einem Unterstrich (_plus()). Operatoren können mit der Funktion RegisterAsInfix des Kernels zur Laufzeit definiert werden. Möchte ein Package Operatoren zur Verfügung stellen, kann es diese in einer Funktion OnImport des Packages registrieren. Es werden 5 Parameter übergeben: der Operator, ein Präzedenzwert größer 0 (höchste Präzedenz), Optionen sowie Packageinstanz und Funktionsname der Implementation. Optionen sind an Mathematica angelehnt:

  • ooUnary: Operator ist unär, hat also keine linke Seite
  • ooUnparsed: Operator wird vom Parser nicht umgesetzt, wird aber als Token verarbeitet
  • ooFlat: Operator ist assoziativ und sammelt die Baumstruktur ein _mult(1,_mult(2,3)) wird zusammengefasst zu _mult(1,2,3)
  • ooFlatAssociative: Operator ist auch distributiv, Klammern werden beim ooFlat-Auswerten aufgelöst
  • ooUnpackInArguments: Funktion wird aufgelöst und ihre Argumente zu dieser verschoben, wenn sie das einzige Argument einer anderen Funktion ist: _mult(_comma(1,2,3)) wird _mult(1,2,3)
  • ooHoldPackedArguments: Unterdrückt explizit ooUnpackedInArguments seiner Argumente
  • ooEndsStatement: Kann am Ende eines Ausdrucks stehen, also keine rechte Seite haben (nicht das Gleiche wie ein Postfix)