Module Eval
Verfasst: 23.09.2015 20:42
Ab und zu ist es für ein Programm doch interessant, wenn der Benutzer eine Rechnung eingeben kann, anstatt eines festen wertes. Dummerweise ist keine Funktion in PureBasic vorgesehen, das dies übernimmt. Die Lösungen die ich anfangs gefunden habe, waren alle ziemlich alt und nicht mehr wirklich lauffähig und mich hat einfach die Aufgabe gereizt.
Besondernheiten meines Codes:
Aufgerufen wird das ganze mit Eval::D("<rechnungsstring>") oder Eval::I("<rechnungsstring>") jenachdem ob man einen Double oder Integer zurück bekommen will. Achtung, wenn man Integer wählt, dann werden auch alle Zahlen und Zwischenschritte als Integer interpretiert - Rundungsfehler sind da also vorprogrammiert.
Sollte ein Fehler in String gefunden werden, wird bei Integer 0, bei Double nan() zurückgegeben. Ob und welcher Fehler festgestellt wird, kann man mit GetError() feststellen. Wer lieber einen Text will, kann ErrorText(error) benutzen.
Threadsicher ist der Code nicht, man sollte also tunlichst vermeiden gleichzeitig in Hauptprogramm und in Thread auf die Eval-Routinen zuzugreifen.
Was kann die Integer-Berechnung?
Die Operatoren und die Prioliste orientiert sich an Purebasic. Neu ist nur ^ für Power (hoch). Keine Ahnung warum das bei PureBasic fehlt.
Folgende Funktionen sind drin: Random(max), Random2(min,max) - Achtung umgekehrte Reihenfolge wie in PB! -, sqr(i), pow(i,i), log(i), log10(i), abs(i), exp(i), mod(i,i), sign(i)
Was Double?
Bei Double fallen die Operatoren ~, <<, >>, &, !, | weg, also alles bitweise. Dafür sind folgende Konstanten definiert: pi, infinity, nan, e. Zusätzlich gibts auch ein paar zusätzliche Funktionen: acos, acosh, asin, asinh, atan, atan2, atanh, cos, cosh, degree, int, radian, sin, sinh, tan, tanh, round (nur ein parameter!).
Bei den Konstanten darf man kein # davor schreiben, sondern so: "sin(pi)".
Wie füge ich Konstanten hinzu?
Für die Integer und Double-Funktionen gibt es eigene Konstanten-Listen. Mit AddConstantI(<name.s>,<value.i>) fügt man eine Integer-Konstante hinzu. Als Namen sind nur Buchstaben, Ziffern und "_" erlaubt, darf aber nicht mit einer Zahl beginnen. Für Double heißt der Befehl analog AddConstandD(<name.s>,<value.d>).
Wie füge ich neue Funktionen hinzu?
Einfach eine Procedure schreiben, wobei der Typ mit denen von Eval übereinstimmen muss. Eine Double-Funktion muss sowohl als Parameter als auch als Rückgabewert den Type Double verwenden. Bis zu drei Parameter sind aktuell möglich. Die Procedure übergibt man mit AddFunctionD(<name.s>,<Anzahl der Parameter>,<@function()>), analog für Integer AddFunctionI(<name.s>,<Anzahl der Parameter>,<@function()>).
Beispiel:
würde einen "IF"-Befehl für die Integer-Berechnung hinzufügen.
Falls bei der Berechnung festgestellt wird, das illegale Parameter übergeben wurden, kann man mit SetError(error.i) einen Fehler melden.
Ich hätte gerne eine Float-Berechnung, wie mach ich das?
in Zeile 1004 gibt es zwei unschuldige Aufrufe: evalit(i) und evalit(d). Die Berechnungroutinen sind als Macros vorhanden und so universell geschrieben. Mit evalit(<typ>) kann man hier einfach weitere Typen außer d und i hinzufügen oder entfernen. Ein evalit(f) würde die Routinen für float hinzufügen. Aufruf ist dann mit eval::f().
Man muss aber vorher in ModulDeclare die Funktionen f(), AddConstantF(),AddFunctionF() deklarieren (Zeile 29), fertig.
Danksagung
Geht an Little John aus den englischen Forum. Ein Teil der Test sind von ihn entliehen. Genauso die Hinweise mit ^ waren sehr praktisch.
Besondernheiten meines Codes:
- Gibt eine Double oder Integer Wert (andere Typen sind schnell hinzugefügt).
- Eigene Funktionen und Konstanten können eingefügt werden, ohne das man die Eval-Routine kapieren muss.
- Funktion verhält sich wie PB es compilen würde, Prio-Listen wie bei PB - inklusive Klammernregeln.
Aufgerufen wird das ganze mit Eval::D("<rechnungsstring>") oder Eval::I("<rechnungsstring>") jenachdem ob man einen Double oder Integer zurück bekommen will. Achtung, wenn man Integer wählt, dann werden auch alle Zahlen und Zwischenschritte als Integer interpretiert - Rundungsfehler sind da also vorprogrammiert.
Sollte ein Fehler in String gefunden werden, wird bei Integer 0, bei Double nan() zurückgegeben. Ob und welcher Fehler festgestellt wird, kann man mit GetError() feststellen. Wer lieber einen Text will, kann ErrorText(error) benutzen.
Threadsicher ist der Code nicht, man sollte also tunlichst vermeiden gleichzeitig in Hauptprogramm und in Thread auf die Eval-Routinen zuzugreifen.
Was kann die Integer-Berechnung?
Die Operatoren und die Prioliste orientiert sich an Purebasic. Neu ist nur ^ für Power (hoch). Keine Ahnung warum das bei PureBasic fehlt.
Code: Alles auswählen
Priority Level | Operators
---------------+---------------------
10 (high) | () und Funktionen
9 | ~, -
8 | ^ (power)
7 | <<, >>, %, !
6 | |, &
5 | *, /
4 | +, -
3 | >, >=, <, <=, =, <>
2 | Not
1 (low) | And, Or, XOr
Was Double?
Bei Double fallen die Operatoren ~, <<, >>, &, !, | weg, also alles bitweise. Dafür sind folgende Konstanten definiert: pi, infinity, nan, e. Zusätzlich gibts auch ein paar zusätzliche Funktionen: acos, acosh, asin, asinh, atan, atan2, atanh, cos, cosh, degree, int, radian, sin, sinh, tan, tanh, round (nur ein parameter!).
Bei den Konstanten darf man kein # davor schreiben, sondern so: "sin(pi)".
Wie füge ich Konstanten hinzu?
Für die Integer und Double-Funktionen gibt es eigene Konstanten-Listen. Mit AddConstantI(<name.s>,<value.i>) fügt man eine Integer-Konstante hinzu. Als Namen sind nur Buchstaben, Ziffern und "_" erlaubt, darf aber nicht mit einer Zahl beginnen. Für Double heißt der Befehl analog AddConstandD(<name.s>,<value.d>).
Wie füge ich neue Funktionen hinzu?
Einfach eine Procedure schreiben, wobei der Typ mit denen von Eval übereinstimmen muss. Eine Double-Funktion muss sowohl als Parameter als auch als Rückgabewert den Type Double verwenden. Bis zu drei Parameter sind aktuell möglich. Die Procedure übergibt man mit AddFunctionD(<name.s>,<Anzahl der Parameter>,<@function()>), analog für Integer AddFunctionI(<name.s>,<Anzahl der Parameter>,<@function()>).
Beispiel:
Code: Alles auswählen
Procedure.i ifi(a.i,b.i,c.i)
If a
ProcedureReturn b
EndIf
ProcedureReturn c
EndProcedure
eval::AddFunctionI("if",3,@ifi())
Falls bei der Berechnung festgestellt wird, das illegale Parameter übergeben wurden, kann man mit SetError(error.i) einen Fehler melden.
Ich hätte gerne eine Float-Berechnung, wie mach ich das?
in Zeile 1004 gibt es zwei unschuldige Aufrufe: evalit(i) und evalit(d). Die Berechnungroutinen sind als Macros vorhanden und so universell geschrieben. Mit evalit(<typ>) kann man hier einfach weitere Typen außer d und i hinzufügen oder entfernen. Ein evalit(f) würde die Routinen für float hinzufügen. Aufruf ist dann mit eval::f().
Man muss aber vorher in ModulDeclare die Funktionen f(), AddConstantF(),AddFunctionF() deklarieren (Zeile 29), fertig.
Danksagung
Geht an Little John aus den englischen Forum. Ein Teil der Test sind von ihn entliehen. Genauso die Hinweise mit ^ waren sehr praktisch.