Entwickler-Ecke

Open Source Units - Parser für mathematische Ausdrücke (inkl. Var. und Fkt.)


delfiphan - So 20.02.05 14:29
Titel: Parser für mathematische Ausdrücke (inkl. Var. und Fkt.)
tyParser.pas update:
* Ich hab das ganze noch weiter optimiert, sodass der tyParser Compiler in fast jedem Fall den schnelleren Code erzeugt als der Delphicompiler!! :D
* Funktionen arccot,abs,round,trunc,frac,heaviside,sign hinzugefügt
* Detaillierte Fehlermeldung bei Syntaxfehlern
* Optional: Interface wrapper (siehe Source für Info)
* Bugfix im Cotangens
* Der tyParser Compiler kann neu 3 verschiedene Funktionstypen erzeugen:

Delphi-Quelltext
1:
2:
3:
 ExprFuncR = function(var Args : array of Extended) : Extended;
 ExprFunc1V = function(const x : Extended) : Extended;       
 ExprFunc2V = function(const x,y : Extended) : Extended;


Downloads:
Downloadlink: tyParser.pas [http://www.tyberis.com/download/tyParser.pas] (35kb)
FarbMix Parser-Demo: FarbMix.exe [http://www.tyberis.com/download/FarbMix.exe] (199kb)

Beispielanwendung:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Var
 SinC : IExpr1V;
begin
  SinC := compileStr1V('sin(x)/x',['x']);

  if SinC.compiled then
   ShowMessage(FloatToStr( SinC.Eval(1.2) ));

  // (SinC muss nicht explizit zerstört werden)
end

// Weitere Beispiele mit mehreren Variablen in der Source. 
// Für absolute Top-Performance kann man auch eine direkte Funktion generieren lassen, siehe dazu Beispiele in der Source.


PS: Hab das Freeware Programm TyMathPlotter [http://www.delphi-forum.de/viewtopic.php?p=227018#227018] gestern auf die Beine gestellt: Als weiteres Demo des Parsers und als Tool um mathematische Plots zu zeichnen. Man findet zwar viele solche Programme im Netz; hier aber eine "fast and easy" Variante. Man kann damit sehr schnell, sehr einfach, schöne Plots generieren und diese dann als Vektorgraphik direkt ins Word kopieren.

========================================
========================================
//Edit: Hier noch der ursprüngliche Post mit anderen Infos
Mathe-Parser mit vollständigem Syntaxcheck. Bei einem Syntaxfehler wird "NaN" zurückgegeben.

Genaue Erklärung des (ein bisschen vereinfachten) Parsers im Forum unter
http://www.delphi-forum.de/topic_Taschenrechner+Problem_36933.html

Diese Version wertet auch Funktionen (sqrt, sin, cos, ...) und Konstanten wie e und pi aus. Ausserdem können userdefinierte Variablen benutzt werden.

Beispiele:

Delphi-Quelltext
1:
2:
3:
evalMath('1+2*3');
evalMath('1+a^2*sin(x)', ['x',10'a',2]);
if isNaN(evalMath('(1+2')) then ShowMessage('Syntaxfehler');


mathparser.pas

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393:
394:
395:
396:
397:
398:
399:
400:
401:
402:
403:
404:
405:
406:
407:
408:
409:
410:
411:
412:
413:
414:
415:
416:
417:
418:
419:
420:
421:
422:
423:
424:
425:
426:
427:
428:
429:
430:
431:
432:
433:
434:
435:
436:
437:
438:
439:
440:
441:
442:
443:
444:
445:
446:
447:
448:
449:
450:
451:
452:
453:
454:
455:
456:
457:
458:
459:
460:
461:
462:
463:
464:
465:
466:
467:
468:
469:
470:
471:
472:
473:
// (Can be used / modified without permission by the author. Code comes with no warranties whatsoever. Credits not required)
{$B-}  
unit umath;

 
interface  
uses  
  Math, SysUtils;

 
// Evaluates a mathematical expression in a String. The Expression must not contain spaces.  
// Usage:  
//  evalMath('1+2*3');  
//  evalMath('1+x^2+sin(x)', ['x',10]);
//  Variables in [] have to be single character  

 
(*  

Characters:
-----------
STRICH_OP :- "+" | "-"
PUNKT_OP :- "*" | "/"
HOCH_OP :- "^"
ALPHA :- "a" | ... | "z" | "A" | ... | "Z"
ZIFFER :- "0" | ... | "9"

Operationen:
------------                                               // Beispiel:
Addition :- Multiplikation { STRICH_OP Multiplikation }    // "a+a"
Multiplikation :- Faktor { PUNKT_OP Faktor }               // "a*a"
Faktor :- { STRICH_OP } Exponent { HOCH_OP Exponent }      // "-a^a"
Exponent :- { STRICH_OP } ( Realzahl | VarFunc | Klammer ) // "-x", "-1", "+(...)"
Klammer :- "(" Addition ")"                                // "(...)"

Zahlen, Funktionen, Konstanten, Variablen:
------------------------------------------
RealZahl :- GanzZahl [ ( "." | "," ) GanzZahl ] [ ( "e" | "E" ) [ STRICH_OP ] GanzZahl]
                                                           // "123.456", "1.2e-10", "1,2"

GanzZahl :- Ziffer { Ziffer }                              // "123"
VarFunc :- Bezeichner | Funktion                           // "pi", "cos(...)"
Funktion :- Bezeichner Klammer                             // "sin(...)"
Bezeichner :- Alpha { Alpha | Ziffer }                     // "hello01"

Vordefinierte Konstanten und Funktionen:
----------------------------------------
sin, cos, tan, cot, arcsin, arccos, arctan, ln, log, exp, sqrt
pi, e, inf

[] bedeutet 0 oder 1 mal (Optional)  
{} bedeutet 0 oder beliebig viele Male  
| bedeutet "oder"  
*)


 
Function evalMath(const S : String) : Extended; overload;  
Function evalMath(const S : Stringconst Variables : array of const) : Extended; overload;  

 
implementation  

 
Function evalMath(const S : String) : Extended;  
begin  
 Result := evalMath(S, []);  
end;  

 
Function evalMath(const S : Stringconst Variables : array of const) : Extended;  
type  
OutputType = record  
 Length : Integer;  
 Wert : Extended;  
end;  
Type  
 cTyp = (cSTRICHOP,cPUNKTOP,cHOCHOP,cKLAMMERAUF,cKLAMMERZU,cUNDEFINIERT,cPUNKT,cEXP);  
 parseType = function(start : Integer; var Output : OutputType) : Boolean;  
function parseVARFUNC(start : Integer; var Output : OutputType) : Boolean; forward;  
function parseGANZZAHL(start : Integer; var Output : OutputType) : Boolean; forward;  
function parseZIFFER(start : Integer; var Output : OutputType) : Boolean; forward;
function parseFAKTOR(start : Integer; var Output : OutputType) : Boolean; forward;  
function parseKLAMMER(start : Integer; var Output : OutputType) : Boolean; forward;  
function parseMULTIPLIKATION(start : Integer; var Output : OutputType) : Boolean; forward;  
function parseADDITION(start : Integer; var Output : OutputType) : Boolean; forward;
function parseEXPONENT(start : Integer; var Output : OutputType) : Boolean; forward;  
function parseREALZAHL(start : Integer; var Output : OutputType) : Boolean; forward;  

 
function returnVariable(const aVarName : Char): Extended;  
var
  I: Integer;  
  Count : Integer;  
  VarName : Char;  
begin  
  Count := High(Variables)+1;  
  if Odd(Count) then  
   raise Exception.Create('Argument count must be even.');  
  for I := 0 to Count div 2-1 do
  begin  
   with Variables[I*2do
    case VType of  
     vtChar:       VarName := VChar  
     else  
      raise Exception.Create('Invalid variable type: Must be single character. ');  
    end;  
   if VarName = aVarName then  
   begin  
    with Variables[I*2+1do  
     case VType of  
      vtExtended:   Result := VExtended^;  
      vtInteger:    Result := VInteger;  
      vtInt64:      Result := VInt64^ else  
       raise Exception.Create('Invalid variable format.');  
     end;  
    exit;  
   end;  
  end;
 Result := NaN;
end;


function parseCHARTYP(start : Integer; Typ : cTyp; var Operation : Char) : Boolean;
Var
 sTyp : cTyp;
begin
 sTyp := cUNDEFINIERT;
 if (start <= length(S)) and (start >= 1then
 case S[start] of
  '^': sTyp := cHOCHOP;
  '.',',': sTyp := cPUNKT;
  '+','-': sTyp := cSTRICHOP;  
  '*','/': sTyp := cPUNKTOP;  
  '(': sTyp := cKLAMMERAUF;  
  ')': sTyp := cKLAMMERZU;
  'e','E': sTyp := cEXP;
 end;  
 if sTyp = Typ then
 begin    
  Operation := S[start];    
  Result := True;    
  exit;  
 end;    
 Result := False;    
end;  
function parseZIFFER(start : Integer; var Output : OutputType) : Boolean;  
begin  
 if not ((start <= length(S)) and (start >= 1)) then  
 begin  
  result := False;  
  exit;  
 end;  
 Case S[start] of  
  '0'..'9':  
  begin  
   Output.Length := 1;  
   Output.Wert := ord(S[start])-ord('0');  
   Result := True;  
   exit;
  end;  
 end;  
 Result := False;  
end;  
function parseALPHANUMERIC(start : Integer; var Output : Char) : Boolean;  
begin
 if not ((start <= length(S)) and (start >= 1)) then  
 begin  
  result := False;
  exit;  
 end;  
 Case S[start] of  
  '0'..'9','A'..'Z','a'..'z':  
  begin  
   Output := S[start];  
   Result := True;
   exit;  
  end;  
 end;  
 Result := False;  
end;  
function parseGANZZAHL(start : Integer; var Output : OutputType) : Boolean;  
Var  
 Ziffer : OutputType;  
 Count : Integer;  
begin  
 Output.Length := 0;  
 Count := 0;  
 while parseZiffer(start, Ziffer) do  
 begin  
  inc(count);  
  if count = 1 then  
   Output.Wert := Ziffer.Wert  
  else  
   Output.Wert := Output.Wert * 10 + Ziffer.Wert;  
  inc(Output.Length, Ziffer.Length);  
  inc(start,Ziffer.Length);
 end;  
 Result := Count > 0;  
end;  
function evalFunction(const S : Stringconst Argument : Extended) : Extended;
begin
 if S = 'sin' then
  Result := sin(Argument) else
 if S = 'cos' then
  Result := cos(Argument) else
 if S = 'tan' then
  Result := tan(Argument) else
 if S = 'cot' then
  Result := 1/tan(Argument) else
 if S = 'arcsin' then
  Result := arcsin(Argument) else
 if S = 'arccos' then
  Result := arccos(Argument) else
 if S = 'arctan' then
  Result := arctan(Argument) else
 if S = 'ln' then
  Result := ln(Argument) else
 if S = 'log' then
  Result := ln(Argument)/ln(10else
 if S = 'exp' then
  Result := exp(Argument) else
 if S = 'sqrt' then
  Result := sqrt(Argument) else
  Result := NaN;
end;


function parseVARFUNC(start : Integer; var Output : OutputType) : Boolean;
Var
 C : Char;
 S : String;
 Klammer : OutputType;
begin
 S := '';  
 while parseALPHANUMERIC(start, C) do  
 begin  
  S := S + C;  
  inc(start);  
 end;  
 Output.Wert := NaN;  
 if S <> '' then  
 begin
  Output.Length := length(S);  
  if parseKLAMMER(start, Klammer) then  
  begin  
   inc(Output.Length, Klammer.Length);  
   Output.Wert := evalFunction(S, Klammer.Wert);  
  end else  
  begin
   if length(S) = 1 then  
    case S[1of  
     'e': Output.Wert := exp(1else  
      Output.Wert := returnVariable(S[1]);  
    end  
   else  
   begin
    if S = 'pi' then // Mehrbuchstabige Konstanten
     Output.Wert := pi else
    if S = 'inf' then // Mehrbuchstabige Konstanten
     Output.Wert := infinity;
   end;
  end;
 end;  
 Result := not isNaN(Output.Wert);  
end;  
function parseREALZAHL(start : Integer; var Output : OutputType) : Boolean;
Var
 GanzZahl, NachKomma, Exp : OutputType;
 ignore, Vorzeichen : Char;
begin
 if parseGANZZAHL(start, GanzZahl) then
 begin
  inc(start, GanzZahl.Length);
  if parseCHARTYP(start, cPUNKT,ignore) then
  begin
   inc(start);
   if parseGANZZAHL(start, NachKomma) then
   begin
    inc(start);
    Output.Length := GanzZahl.Length + 1 + NachKomma.Length;
    Output.Wert := GanzZahl.Wert + NachKomma.Wert / Power(10, NachKomma.Length);
   end else
   begin
    Result := False;
    exit;
   end;
  end else
   Output := GanzZahl;

  if parseCHARTYP(start, cEXP,ignore) then
  begin
   inc(start);
   inc(Output.length);
   if parseCHARTYP(start, cSTRICHOP,Vorzeichen) then
   begin
    inc(start);
    inc(Output.Length);
   end else
    Vorzeichen := '+'// Standardvorzeichen
   if parseGANZZAHL(start, Exp) then
   begin
    inc(Output.Length, Exp.Length);
    if Vorzeichen = '-' then
     Exp.Wert := -Exp.Wert;
    Output.Wert := Output.Wert*Power(10,Exp.Wert);
    Result := True;
    exit;
   end;
  end else
  begin
   Result := True;
   exit;
  end;
 end;  
 Result := False;  
end;  
function parseKLAMMER(start : Integer; var Output : OutputType) : Boolean;  
var  
 ignore : Char;  
 Ausdruck : OutputType;  
begin  
 if parseCHARTYP(start, cKLAMMERAUF, ignore) then  
 begin  
  inc(start);  
  if parseADDITION(start, Ausdruck) then
  begin  
   inc(start, Ausdruck.Length);  
   if parseCHARTYP(start, cKLAMMERZU, ignore) then  
   begin  
    Output.Length := 1 + Ausdruck.Length + 1;  
    Output.Wert := Ausdruck.Wert;  
    Result := True;  
    exit;  
   end;  
  end;  
 end;  
 Result := False;
end;
function parseFAKTOR(start : Integer; var Output : OutputType) : Boolean;
var  
 Operation : Char;  
 Faktor, Exponent : OutputType;  
 Pending : Boolean;  
 Count : Integer;  
begin  
 if parseCHARTYP(start, cSTRICHOP, Operation) then // Vorzeichen vorhanden?  
  if parseFAKTOR(start+1, Faktor) then  
  begin  
   Output.Length := 1 + Faktor.Length;  
   Case Operation of  
    '-': Output.Wert := -Faktor.Wert;  
    '+': Output.Wert := +Faktor.Wert;  
   end;  
   Result := True;  
   exit;  
  end;  
 Output.Length := 0;  
 Count := 0;  
 Pending := False;  
 while parseEXPONENT(start, Exponent) do  
 begin  
  Pending := False;
  inc(Output.Length, Exponent.Length);  
  inc(start, Exponent.Length);  
  if Count = 0 then  
   Output.Wert := Exponent.Wert  
  else  
   Case Operation of  
    '^': Output.Wert := Power(Output.Wert, Exponent.Wert);
   end;  
  inc(Count);  
  if parseCHARTYP(start, cHOCHOP, Operation) then  
  begin  
   Pending := True;  
   inc(start);  
   inc(Output.Length);  
  end else break;  
 end;  
 Result := (Count>=1and not Pending;  
end;  
function parseEXPONENT(start : Integer; var Output : OutputType) : Boolean;  
var  
 Exponent : OutputType;  
 Operation : Char;  
begin  
 if parseCHARTYP(start, cSTRICHOP, Operation) then // Vorzeichen vorhanden?  
  if parseEXPONENT(start+1, Exponent) then  
  begin  
   Output.Length := 1 + Exponent.Length;  
   Case Operation of  
    '-': Output.Wert := -Exponent.Wert;  
    '+': Output.Wert := +Exponent.Wert;  
   end;  
   Result := True;  
   exit;  
  end;  

 
 Result := parseREALZAHL(start, Output) or parseVARFUNC(start, Output) or parseKLAMMER(start, Output);  
end;  
function parseMULTIPLIKATION(start : Integer; var Output : OutputType) : Boolean;  
var  
 Faktor : OutputType;  
 Operation : Char;  
 Pending : Boolean;  
 Count : Integer;  
begin  
 Output.Length := 0;  
 Count := 0;  
 Pending := False;  
 while parseFAKTOR(start, Faktor) do  
 begin  
  Pending := False;  
  inc(Output.Length, Faktor.Length);  
  inc(start, Faktor.Length);  
  if Count = 0 then  
   Output.Wert := Faktor.Wert  
  else  
   Case Operation of  
    '*': Output.Wert := Output.Wert * Faktor.Wert;  
    '/': Output.Wert := Output.Wert / Faktor.Wert;  
   end;  
  inc(Count);  
  if parseCHARTYP(start, cPUNKTOP, Operation) then  
  begin  
   Pending := True;  
   inc(start);  
   inc(Output.Length);  
  end else break;  
 end;  
 Result := (Count>=1and not Pending;  
end;  
function parseADDITION(start : Integer; var Output : OutputType) : Boolean;  
var  
 Term : OutputType;  
 Operation : Char;  
 Pending : Boolean;  
 Count : Integer;  
begin  
 Output.Length := 0;  
 Count := 0;  
 Pending := False;  
 while parseMULTIPLIKATION(start, Term) do  
 begin  
  Pending := False;  
  inc(Output.Length, Term.Length);  
  inc(start, Term.Length);  
  if Count = 0 then  
   Output.Wert := Term.Wert  
  else  
   Case Operation of  
    '+': Output.Wert := Output.Wert + Term.Wert;  
    '-': Output.Wert := Output.Wert - Term.Wert;  
   end;  
  inc(Count);  
  if parseCHARTYP(start, cSTRICHOP, Operation) then  
  begin  
   Pending := True;  
   inc(start);  
   inc(Output.Length);  
  end else break;  
 end;  
 Result := (Count>=1and not Pending;  
end;  
Var  
 Output : OutputType;  
begin  
 if parseADDITION(1, Output) and (Output.Length = length(S)) then  
  Result := Output.Wert  
 else  
  Result := NaN;  
end;  
end.


Beispielanwendung: (es befindet sich ein Image1 und ein Button1 mit onClick Event auf Form1)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
uses umath, math;

const
 Zoom = 50// Einheitsabstand

Var
 X0, Y0 : Integer; // Koordinatenmittelpunkt

Procedure Plot(const S : Stringconst Canvas : TCanvas);
Var
 X, L : Integer;
begin
 with Canvas do
 begin
  L := Canvas.ClipRect.Left;
  MoveTo(L, -trunc(evalMath(S,['x',((L-X0)/Zoom)])*Zoom)+Y0);
  For X := L+1 to Canvas.ClipRect.Right do
   LineTo(X, -trunc(evalMath(S,['x',((X-X0)/Zoom)])*Zoom)+Y0);
 end;
end;

procedure TForm1.Button1Click(Sender: TObject);
Var
 S : String;
begin
 S := 'sin(x)';
 if isNaN(evalMath(S,['x',0])) then
 begin
  ShowMessage('Syntaxfehler');
  exit;
 end;
 with Image1 do
 begin
  X0 := Width div 2;
  Y0 := Height div 2;
  with Canvas do
  begin
   Pen.Width := 1;
   MoveTo(0,Y0);LineTo(Width,y0);
   MoveTo(X0,0);LineTo(X0,Height);
   Pen.Width := 2;
   Plot(s,Canvas);
  end;
 end;
end;

// Edit: Hab noch einige Funktionen dazugetan... (arctan, log, ln)
// Edit: Vollständige Unterstützung von float-Zahlen (z.B. 1e+10), Konstante "inf"


retnyg - Di 01.03.05 13:45

schade dass dein parser z.b. (3+4) * 100 mit NaN quittiert ^^

edit: WTF ist eigentlich Nan ? :twisted: - aha laut math.pas 0.0
edit2: aha es liegt an den leerzeichen
edit3: ein einfaches trim() wirkt manchmal wunder....

ich mach das jetzt so:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function xtrim(s:string):string;
var i : integer;
begin
   result := '';
   for i := 1 to length(s) do if s[i] <> ' ' then result := result + s[i];
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 edit1.text := floattostr(evalMath(xtrim(edit1.text)));
end;

ansonsten super unit, bravo...


delfiphan - Di 01.03.05 14:46

Genau, Leerschläge verträgt er nicht :) NaN = "Not A Number". sin, cos, pi und alle Konstanten sind übrigens auch Case-sensitive.
Hab die Unit erst vorhin erweitert und raufgeladen (jetzt sind auch Variablen und Funktionen möglich). Evtl. hat's drum noch Bugs drin, ist noch nicht eingehend getestet.


retnyg - Di 01.03.05 15:17

Nan ist übrigens auch das indische fladenbrot, welches im unterschied zu chapati mit joghurt angerührt wird...


retnyg - Mi 02.03.05 15:56

nochn vorschlag: deine unit akzeptiert nur . aber keinen ,
dieser wird allerdings von der funktion floattostr zurückgegeben, wenn ich also mit dem resultat gleich weiterrechnen will führt das mal wieder zu fladenbrot....
also, baue das bitte noch ein dass auch beistriche akzeptiert werden.


delfiphan - Mi 02.03.05 16:14

Ein Komma? Floattostr? Hmm sowas ist mir nicht bekannt. Das muss eine Windows-weite Systemeinstellung sein. Das müsste ich dann noch einbauen, dass die jeweilig lokale Einstellung benützt wird. Übrigens: Ein "1e10" wird auch nicht unterstützt, genauso die Konstanten "Infinity" und "NaN". Wäre aber eigentlich nicht so schwer zu implementieren. Mach ich eventuell morgen, heute reichts von der Zeit her nicht mehr.
Danke für den Verbesserungsvorschlag! :)


delfiphan - Do 03.03.05 14:33

Update: "1e-10", "inf", und "1,2" werden jetzt auch erkannt (siehe Post von retnyg). Es sind weiterhin keine Whitespaces erlaubt.
Test:evalMath('-a^2+3*((-1)+x)*(-1+x^2-2^-3^-1)--2e+2', ['a',1,'x',2]); // = 184

Vergleich evalMath vs. andere Open-Source Parser im Delphi-Forum


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
+-------------+--------+-----------+-------+-------------------+---------+-------+--------------+
| Name        |  Speed | Syntx.chk | Fkt?  | selbstdef. Konst? | Klasse? | Lines | Autor        |
+-------------+--------+-----------+-------+-------------------+---------+-------+--------------+
| evalMath    | 100.0% | ja        |  ja   | ja                | nein    |   485 | delfiphan    |
| modparser   |  32.3% | ja        |  ja   | ja (max. 3)       | nein    |   207 | Sven         |
| TMathParser |  14.7% | nein      |  ja   | ja                | ja      |   158 | .Chef        |
| TParser     |  10.6% | nein      |  ja   | nein              | ja      |   489 | Christian S. |
| TMath       |   7.4% | nein      |  nein | nein              | ja      |   486 | deepgreen    |
| TMathParser²|   6.8% | nein      |  nein | ja                | ja      |   452 | Tyr          |
+-------------+--------+-----------+-------+-------------------+---------+-------+--------------+


Welche Ausdrücke wurden verglichen?

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
+-------------+------------------------------+
| modparser   | sqrt(0.5*exp(x)-0.5*sin(y))  |
| TMathParser | sin(4*x)*exp(-x/4)           |
| TParser     | sqrt(0.5*exp(1)-0.5*sin(1))  |
| TMath       | 13/(21*12)+3*7.3+2/7         |
| TMathParser²| (4*x)*(-x/4)                 |
+-------------+------------------------------+
y=1, x=1

Alle diese Ausdrücke wurden vom angegebenen Parser und von evalMath ausgewertet. Die relative Geschwindigkeit ist dann jeweils in der oberen Tabelle eingetragen (2. Spalte).

Kommentar:
- An dieser Stelle möchte ich betonen, dass ein direkter Vergleich von Parsern nicht gut möglich ist. Einige Parser haben Features, welche evalMath nicht unterstützt (beispielsweise Unterstützung von verschiedenen Typen wie Boolean und Integer).
- Ein String wurde jeweils nur ein Mal ausgewertet. Es gibt Parser, die dafür optimiert sind, Ausdrücke mehrere Male hintereinander zu parsen.

Fazit:
- evalMath ist in dieser Testreihe mit Abstand am schnellsten (3 bis 14 Mal schneller).
- Nur evalMath und modparser führen eine Syntaxüberprüfung durch
- Die meisten Parser sind als Klassen implementiert (evalMath ist eine Stand-Alone Function)
- 2 Parser unterstützen keine Funktionen wie sin und cos
- Die meisten Parser unterstützen externe Konstanten wie x und y
- evalMath besteht aus relativ viel Sourcecode (485 Zeilen), andere sind kleiner.
- evalMath behandelt als einziger verschachtelte Hoch-Operatoren korrekt: -2^-3^-4 = -4096
-> modparser: Unterstützt keine negativen Exponenten (Exception)
-> TMathParser: Gibt "-6" zurück
-> TParser: Endlosschleife bzw. Exception
-> TMath: Kein Hoch-Operator möglich
-> TMathParser²: Exception

Nachteile von evalMath:
- Unterstützt keine Typen (nur Extended)
- Mehrfachauswertungen können nicht optimiert werden (es wird weder ausführbarer Code erzeugt noch eine Baumstruktur)

Vorteile von evalMath:
- Scheint recht schnell zu sein
- Einfach und unkompliziert in der Anwendung. Es muss keine Klasse instanziert werden; Die Angabe von Konstanten via Parameter ist sehr einfach und intuitiv.
- Unterstützt komplizierte Ausdrücke wie doppelte (dreifache) Negationen, u.a.

Mögliche Verbesserungen:
- evalMath verwendet viele Rekursionen, welche sich fast vollständig vermeiden liesse. Dies würde die Auswertung weiter beschleunigen und den Sourcecode verkleinern.

(Für eventuelle Fehler in der Auswertungstabelle oder an anderen Stellen entschuldige ich mich... :))


retnyg - Mo 07.03.05 02:51

delfiphan := mathegott :flehan: :mrgreen:
wundert mich nicht dass der parser der schnellste ist.
Zitat:

evalMath verwendet viele Rekursionen, welche sich fast vollständig vermeiden liesse. Dies würde die Auswertung weiter beschleunigen und den Sourcecode verkleinern.

das wäre der hammer wenn du die rekursionen noch beseitigen würdest, käme auch der dateigrösse des kompilates zugute.
hatte ich nicht bei dir mal eine prozedur gesehen welche eine dynamische mathfunc per pointer zuwies ? :gruebel:


delfiphan - Mo 07.03.05 03:18

Naja, es gibt ganz bestimmt noch schnellere, aber unter allen Freewareparsern auf dieser Seite ist meiner offenbar der schnellste :D
Wenn es aber darum geht, den gleichen Ausdruck mehrere Male hintereinander auszuwerten, dann schneidet mein Parser leider nicht als bester ab. Das wird sich aber hoffentlich ändern, denn ich erweitere grad den Parser. Der neue generiert einen Bytecode, welcher dann ausgeführt werden kann. Ich hab mir überlegt, einen Compiler zu schreiben, also einen Parser der direkt Maschinencode erzeugt, jedoch ist der Stack der Floatingpointunit nur 8 floats tief. Und es ist ziemlich mühsam das zu programmieren. Es gibt auch keinen Maschinencode für "Hoch", usw..
Deswegen gibt's jetzt halt "nur" den Bytecode.


Dominique - Mo 07.03.05 13:42

user profile iconretnyg hat folgendes geschrieben:
Nan ist übrigens auch das indische fladenbrot, welches im unterschied zu chapati mit joghurt angerührt wird...

...schmeckt aber auch nur, wenn's stilecht im kleinen tandoori-ofen gebacken wurde...


jasocul - Mo 07.03.05 13:56

@retnyg:
Ließe sich für dein xtrim nicht besser ReplaceStr verwenden?

@delphifan:
Auf den ersten Blick ein schönes Ding. Schau ich mir bei Gelegenheit mal genauer an.


delfiphan - Mo 07.03.05 14:04

Die neue Version kommt aber erst noch, vemutlich heute noch. Dort werden die Whitespaces auch korrekt behandelt (d.h. ignoriert ;)). Das xtrim ist nicht in allen Situationen geeignet, z.B. "x y" wird dann zu "xy".
So wie's momentan aussieht ist der neue Parser etwa gleich schnell wie der alte, vielleicht sogar etwas langsamer, jedoch wird ein Bytecode generiert, welcher dann sehr schnell beliebig oft ausgeführt werden kann. Der Code ist aber jetzt schon 700 Zeilen lang...


retnyg - Mo 07.03.05 14:24

user profile iconjasocul hat folgendes geschrieben:
@retnyg:
Ließe sich für dein xtrim nicht besser ReplaceStr verwenden?
liesse sich schon, aber so ne mini-funktion ist schneller selbst geschrieben als lang in der hilfe rumzulesen wie da nochmal die richtigen parameter hiessen... ausserdem ists so sicher schneller ;) und ich muss nicht in meiner uses drauf achte dass die unit welche den befehl beinhaltet dabei ist... die grösse der exe datei wirds danken.
@delfiphan: hast du es nun wieder mit dem pointer auf eine parse-funktion gelöst ? und die redundazen beseitigt ? :mrgreen:


delfiphan - Mo 07.03.05 14:37

Wollte ich, geht aber nicht. Scheint wie n verzwicktes, unlösbares Delphiproblem zu sein:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
Procedure Funktion(const S : Stringconst A : array of const);
Type
 TProcedure = Procedure;
Procedure InternalFunction1;
Var I : Integer;
begin
 ShowMessage(Integer(A[0].VInteger)+' '+S[0]); // greift auf Array of const und S zu
end;
Procedure InternalFunction2(AnyProcedure : TProcedure);
begin
 AnyProcedure;
end;
begin
 InternalFunction2(InternalFunction1); // ist verboten
 InternalFunction2(@InternalFunction1); // gibt Probleme
end;

Lösung der Delphihilfe: Interne Funktion einfach rausnehmen, sodass die Funktionen nicht verschachtelt sind. Kann ich aber nicht, da eine der internen Funktionen auf ein "array of const" zugreift, was sich nicht auslagern lässt. Und das array of const die ganze Zeit von Prozedur zu Prozedur weiterzugeben scheint mir unsinnig.
Nein, ich müsste wennschon den ganzen Parser neu schreiben, ohne Rekursionen, bzw. nur dann, wenn es Klammern gibt.
Tja, so läufts, bin jetzt auf Zeile 720 ;)


delfiphan - Mo 07.03.05 21:46
Titel: Parser reloaded
So, hier ist er nun, der neue Parser!

Die neue Unit tyParser hat 4 Funktionen:

- function ParseExpr(S : String; Vars : Array of Const) : Expression;: Parst einen Stringausdruck und gibt einen Bytecode zurück (Typ "Expression")
- function EvalExpr(ByteCode : Expression; Args : Array of Const) : Extended;: Wertet den Bytecode mit den gegebenen Argumenten aus

- function CompileExpr(Bytecode : Expression) : ExprFunc;: Compiliert den Bytecode in Maschinencode und gibt eine Funktion zurück (Typ "ExprFunc")
- procedure FreeFunc(E : ExprFunc);: Gibt die Funktion (Maschinencode) wieder frei

Anwendungsbeispiele:
Beispiel 1 (Bytecode erzeugen und auswerten)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
Var
 Expr : Expression;
 Resultat : Extended;
begin
 Expr := ParseExpr('1 + x + y',['x','y']);
 Resultat := EvalExpr(Expr,[2,3]); // Wertet den Ausdruck aus, dabei ist x=2 und y=3
end;


Beispiel 2 (Bytecode erzeugen und compilieren (Maschinencode generieren))

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
Var
 Vars : array[0..1of Extended; // muss Extended sein!
 Expr : Expression;
 MyFunc : ExprFunc;
 Resultat : Extended;
begin
 Expr := ParseExpr('1 + x + y',['x','y']);
 MyFunc := CompileExpr(Expr);
 Vars[0] := 2// Entspricht der Variable x
 Vars[1] := 3// Entspricht der Variable y
 Resultat := MyFunc(Vars); // MyFunc kann direkt ausgeführt werden
end;


Es können natürlich beliebig komplizierte Ausdrücke wie "-a*x^2+y^2-sin(-pi*(x-1)*(y+1))+x^-y" ausgewertet werden.

Bugs/Probleme/Bemerkungen
Wenn der Ausdruck in Maschinencode umgewandelt werden soll, muss man darauf achten, dass der floatingpoint-Stack des Prozessors nur 8 Zahlen tief ist! Es darf in dem Fall nicht beliebig tief geklammert werden. Der compilierte Code darf maximal 4kb gross sein.

Falls nur ein Ausdruck ausgewertet werden soll, dann reicht schon die Vorgängerversion. Dort wird der String evtl. sogar ein bisschen schneller geparst. Wenn aber der gleiche Ausdruck mehrere Male ausgewertet werden soll (aber beispielsweise einfach mit anderen Werten für x), ist diese Unit um einiges schneller!

In dieser Version sind Whitespaces erlaubt.

Die Unit ist noch nicht fertig optimiert, es handelt sich um die erste Version.

Downloads:
Downloadlink: tyParser.pas [http://www.tyberis.com/download/tyParser.pas] (27kb)
FarbMix Parser-Demo: FarbMix.exe [http://www.tyberis.com/download/FarbMix.exe] (197kb)

user defined image
//Edit:
Performance
Natürlich ist ein in Delphi hardgecodeter Ausdruck im allgemeinen schneller, da kein Funktionsaufruf nötig ist. Vergleicht man aber Delphi-compilierte Funktionen mit tyParser-compilierten Funktionen, so stellt man fest, dass tyParser nicht wesentlich langsamer ist. Es gibt sogar Fälle, wo tyParser-Funktionen schneller sind als Delphifunktionen! Unten ist ein Beispiel dafür:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
Function MyFunc1(const x,y : extended) : Extended;
begin
 Result := arccos(cos(x))+sin(y);
end;
begin
 // Jeweils 10 Millionen Auswertungen
 For I := 1 to 10000000 do // Delphi: 4.3 Sekunden
 begin
  x := 2;
  y := 3;
  Resultat := MyFunc1(x,y);
 end;
 Expr := ParseExpr('arccos(cos(x))+sin(y)',['x','y']);
 MyFunc2 := CompileExpr(Expr);
 For I := 1 to 10000000 do // tyParser: 3.8 Sekunden
 begin
  Vars[1] := 2;
  Vars[2] := 3;
  Resultat := MyFunc2(Vars);
 end;

//Edits: 2 Bugfixes: Vorzeichenwechsel war noch nicht implementiert "(-1)". Bugfix bei der Konstante "e". Demoprogramm und Bild.


retnyg - Mo 07.03.05 21:56

Beeindruckend ^^
werde das morgen mal in Ruhe testen...


delfiphan - Do 10.03.05 22:30

tyParser.pas update:
* Ich hab das ganze noch weiter optimiert, sodass der tyParser Compiler in fast jedem Fall den schnelleren Code erzeugt als der Delphicompiler!! :D
* Funktionen arccot,abs,round,trunc,frac,heaviside,sign hinzugefügt
* Detaillierte Fehlermeldung bei Syntaxfehlern
* Optional: Interface wrapper (siehe Source für Info)
* Bugfix im Cotangens
* Der tyParser Compiler kann neu 3 verschiedene Funktionstypen erzeugen:

Delphi-Quelltext
1:
2:
3:
 ExprFuncR = function(var Args : array of Extended) : Extended;
 ExprFunc1V = function(const x : Extended) : Extended;       
 ExprFunc2V = function(const x,y : Extended) : Extended;


Downloads:
Downloadlink: tyParser.pas [http://www.tyberis.com/download/tyParser.pas] (35kb)
FarbMix Parser-Demo: FarbMix.exe [http://www.tyberis.com/download/FarbMix.exe] (199kb)

Beispielanwendung:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Var
 SinC : IExpr1V;
begin
  SinC := compileStr1V('sin(x)/x',['x']);

  if SinC.compiled then
   ShowMessage(FloatToStr( SinC.Eval(1.2) ));

  // (SinC muss nicht explizit zerstört werden)
end

// Weitere Beispiele mit mehreren Variablen in der Source. 
// Für absolute Top-Performance kann man auch eine direkte Funktion generieren lassen, siehe dazu Beispiele in der Source.


PS: Hab das Freeware Programm TyMathPlotter [http://www.delphi-forum.de/viewtopic.php?p=227018#227018] gestern auf die Beine gestellt: Als weiteres Demo des Parsers und als Tool um mathematische Plots zu zeichnen. Man findet zwar viele solche Programme im Netz; hier aber eine "fast and easy" Variante. Man kann damit sehr schnell, sehr einfach, schöne Plots generieren und diese dann als Vektorgraphik direkt ins Word kopieren.


AXMD - Mo 14.03.05 19:43

Ich hab nur ein Wort für deinen Parser: endgenial. Was noch super wäre, wäre die Implementation einer Variablen i oder j (wahlweise), die die imaginäre Einheit darstellt - damit der Parser alle Funktionen auch auf imaginäre Zahlen anwenden kann. Falls du die entsprechenden Implementationen für die komplexen Pendants der Funktionen brauchst kann ich sie dir gerne schicken oder hier posten ;)

Spitze :)
AXMD


retnyg - Mo 14.03.05 23:59

deine funktion liesse sich vielleicht noch schneller machen wenn du bei der hoch-berechnung statt virtualalloc stackalloc benutzt...siehe hier http://delphi.blue-aura.co.uk/showthread.php?p=65#post65


delfiphan - Di 15.03.05 00:28

user profile iconretnyg hat folgendes geschrieben:
deine funktion liesse sich vielleicht noch schneller machen wenn du bei der hoch-berechnung statt virtualalloc stackalloc benutzt...

Ich versteh nicht genau, was du meinst. Ich benütz nirgends ein VirtualAlloc für das "Hoch".. (??)
Der Compiler benützt VirtualAlloc um Speicher für die compilierte Funktion zu allozieren. Ich kann da meines Wissens nichts anderes nehmen als VirtualAlloc, da ich für den Speicherblock die Rechte PAGE_EXECUTE_READWRITE vergeben können muss.

Information: Aktueller Download 3 Posts weiter oben [http://www.delphi-forum.de/topic_Parser+fuer+mathematische+Ausdruecke+inkl+Var+und+Fkt_36946.html#227715].


prote - Do 07.04.05 21:52

super, sieht sehr interessant aus, werde ich mir mal bei Gelegenheit zu Gemüte führen :)

Allerdings hast Du'nen Fehler drin:
user profile icondelfiphan hat folgendes geschrieben:
- evalMath behandelt als einziger verschachtelte Hoch-Operatoren korrekt: -2^-3^-4 = -4096

das wäre nach den allseits bekannten Regeln 4096 und zwar ohne "-" und selbst das wäre nicht korrekt, weil man bei der Schreibweise erst -3^(-4) rechnen müßte und danach erst das Ergebnis als Potenz von -2 ... siehe hierzu auch ein interessanter Wachstumsalgo mit "Hyper-Potenzen": http://de.wikipedia.org/wiki/Ackermannfunktion


uall@ogc - Do 07.04.05 22:53

@delfiphan

um dein problem zu lösen mussu alle funktionen mit parameter einfach als stdcall; markieren, dadurch werden die parameter auf den stack gepushed und die funktion bekommt die richtigen


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Procedure Funktion(const S : String);
type AnyProcedureX = procedure;
  Procedure InternalFunction1;
  begin
    ShowMessage(S);
  end;
  Procedure InternalFunction2(AnyProcedure: AnyProcedureX); stdcall;
  begin
    AnyProcedure;
  end;
begin
  InternalFunction2(@InternalFunction1); //funzt einwandfrei
end;


funktioniert einwandfrei wärend hingegen


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure Funktion(const S : String);
type AnyProcedureX = procedure;
  Procedure InternalFunction1;
  begin
    ShowMessage(S);
  end;
  Procedure InternalFunction2(AnyProcedure: AnyProcedureX);
  begin
    AnyProcedure;
  end;
begin
  InternalFunction2(@InternalFunction1); // gibt Probleme
end;

crashed, wo du meintest es gibt probleme :)


delfiphan - Do 07.04.05 22:57

@prote: Der Text "-2^-3^-4 = -4096" stammt auch noch von der uralten Vor-Version ;) So falsch ist es aber nicht: Es wurde dort einfach standardmässig links geklammert (das Minus-Zeichen stimmt übrigens: -x^2 = -(x^2)!). In der jetzigen Version wird die Hochoperation aber mathematisch korrekt rechts geklammert. (PS: Ob man jetzt "^" links oder rechts klammert, da scheinen sich auch kommerzielle Softwarefirmen nicht einig zu sein (vgl. Texas Instruments, Maple, Mathematica))

@uall: Danke :) ich werd den Parser aber mal so lassen. Wegen den paar Zeilen, die ich so sparen würde, programmier ich das jetzt nicht mehr um. :\ Aber danke für die Info :)

Information: Aktueller Download siehe weiter oben [http://www.delphi-forum.de/topic_Parser+fuer+mathematische+Ausdruecke+inkl+Var+und+Fkt_36946.html#227715].


Tilo - Sa 28.05.05 19:55

Mal als Vergleich: Mein Taschenrechner rechnet -2^-3^-4=-2^(-3^-4)=-0,991...


.Chef - Sa 28.05.05 22:41

user profile icondelfiphan hat folgendes geschrieben:
- evalMath behandelt als einziger verschachtelte Hoch-Operatoren korrekt: -2^-3^-4 = -4096
-> TMathParser: Gibt "-6" zurück
Weil ich das gerade sehe: Mein Parser behandelt das sehr wohl, setzt allerdings die mathematisch korrekte Schreibweise -2^(-3)^(-4) voraus, also die Vorzeichen in Klammern. Diese "Ausnahmen" hab ich nicht mit berücksichigt.

Außerdem, wenn ich mich recht erinnere :gruebel:, beinhaltet mein Parser einen Syntaxcheck, d.h. fehlerhafte Stellen werden erkannt und die Ausführung abgebrochen. Es fehlt lediglich die Ausgabe. Es wäre aber ein leichtes - ich hatte nur keine Lust - den Quelltext an den entsprechenden Stellen um eine Exception und Fehlerstelle zu erweitern.


retnyg - Sa 04.06.05 21:43

da ist neue konkurrenz aufgetaucht: http://www.delphipraxis.net/topic54895,0,asc,0.html
ist deiner schneller ? ;)


delfiphan - Sa 04.06.05 22:32

Ganz interessant. Werd ich gleich mal ausprobieren!

Also so wie's aussieht ist die Anwendung bei mir recht viel einfacher. Die Geschwindigkeit kann ich nicht direkt vergleichen; Variablen werden bei ihm glaub ich nicht direkt reinkompiliert; oder doch? Auf jeden Fall scheint meiner so (siehe unten) ca. 10x schneller zu sein. Und das gegen meinen Interface-Wrapper, welcher ja langsamer ist, als meine nackte Variante. Aber vermutlich geht's bei ihm auch noch schneller, ich weiss nur nicht wie.

tyParser:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var
 sinc: IExpr1V;
begin
  sinc := compileStr1V('x+2*x',['x']);
  D := 0;
  repeat
    sinc.Eval(D);
    D := D + 1 / 1000000;
  until D > 1;
end;

Konkurrenz:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
var
 Parser: TExCQParser;
begin
  Parser := TExCQParser.Create;
  Parser.RegisterVariable('x',false);
  Parser.Parse('x+2*x');
  D := 0;
  repeat
    Parser.SetVariable('x',[D]);
    Parser.Solve;
    D := D + 1 / 1000000;
  until D > 1;
  Parser.Free;


Er hat vor allem ca. 7 Units und ich hab eine. Bei ihm ist es aber auch möglich, externe Funktionen zu definieren. Bei mir kann man nur externe Variablen definieren.

Mein Parser reiht schlussendlich einfach ein FPU-Befehl nach dem nächsten hin, von dem her gesehen kann es keinen Parser/Evaluator geben, der um Faktoren schneller ist. Was mein Parser nicht tut ist, einen Ausdruck zuerst zu vereinfachen. Er wird genau so übersetzt wie er eingegeben wird. Da ist also noch Verbesserungspotential.

Ich finde aber vor allem, dass die einfache Anwendung bei mir vor allem von grossem Nutzen ist. Allfällige Syntaxfehler werden bei mir nicht per Exceptions zurückgegeben, was ich irgendwie auch sympathischer finde (aber das ist Geschmackssache).
:)

Und: Bei ihm ergibt "-2^2" halt 4 (statt -4). Und "-(1/2)" oder auch "2*(-1)" ergibt Access Violation. Und solche Details. Bei mir kann es fast gar keine Access Violations geben. Ist nicht OOP und es wird auch fast nirgends irgendwelchen Speicher alloziert..


F34r0fTh3D4rk - Do 15.09.05 15:53

schaffst du es, deinen parser delphi3 konform zu machen ? dynamische arrays, overloads etc bereiten momentan probleme 8)

vielleicht auch n einfaches beispiel, wenn ich zb nur sowas hier ausrechnen möchte:
Zitat:

(2+(3-2))*4+10-8


ok habs so gemacht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TForm1.Button1Click(Sender: TObject);

Var
 sinc: IExpr1V;
begin
 sinc := compileStr1V(edit1.text);
 edit2.text := floattostr(sinc.Eval(1.0));
end;

schöner parser, gefällt mir ;)


delfiphan - Do 15.09.05 19:47

Hmm der Parser auf Delphi3? Ich hab nicht mal Delphi3 ...

[info]Ach ja, irgendwo hatte es noch einen Bug oder zwei. Ich glaub wenn ein Variablenname mit klein e beginnt oder sowas gibt's manchmal Probleme. Der Bug ist im SimpleCalculator [http://www.delphi-forum.de/viewtopic.php?t=46415] korrigiert; jedoch ist dort der Parser verändert worden, damit er mit komplexen Zahlen arbeitet. Der Compiler ist dort auch nicht mehr dabei. [/info]


F34r0fTh3D4rk - Do 15.09.05 20:15

ja die dynamischen arrays lassen sich ja abändern und die overloads umbenennen, wenn ich lust habe, bringe ich den mal bei delphi3 zum laufen.


Midori - Do 20.10.05 17:27

Wau das klingt ja mal sehr interessant und genau das was ich grad suche.
Wäre es dir möglich das CBuilder5 kompatibel zu machen?

Er meldet leider:

[Pascal Fehler] tyParser.pas(1175): Undefinierter Bezeichner: 'NaN'
[Pascal Fehler] tyParser.pas(1271): Undefinierter Bezeichner: 'sign'
[Pascal Fehler] tyParser.pas(1272): Undefinierter Bezeichner: 'cot'
[Pascal Fehler] tyParser.pas(1276): Undefinierter Bezeichner: 'arccot'

*traurig*

Sind scheinbar nur Konstanten und einfache Funktionen.
Wäre super wenn es so umschreiben könntest, das diese in deiner Klasse
integriert sind ;)


BenBE - Do 20.10.05 18:09

Sind ETWA so definiert:

const NaN = 0 / 0; ;-)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
function Cot (X): Datentyp
begin
    result := cos(X) / Sin(X);
end;

function ArcCot (X): Datentyp
begin
    Result := Pi / 2 - ArcTan(X);
end;


HTH.


Delphi-Quelltext
1:
Sign --> X / Abs(X); // 0 für X = 0                    


Midori - Do 20.10.05 20:50

Ok habs hinbekommen, aber wie das ganze unter CBuilder zum laufen zu bekommen ist habe ich noch net verstanden :)

SinC := CompileExpr(ParseExpr('sin(x)/x',['x']),tyPass1V);

ExprFunc1V SinC = CompileExpr(ParseExpr("sin(x)/x",OPENARRAY(TVarRec,("x"))),tyPass1V);
ExprFunc1V SinC = CompileExpr(ParseExpr("sin(x)/x",&TVarRec("x")),tyPass1V);

Mit dem ['x'] habe ich meine Probleme. Was ist das und wie muss man es in CBuilder angeben? Falls jemand ein kleines komplettes Beispiel hat wäre echt nett :)


FinalFantasy - Fr 04.11.05 14:22

Wie kriege ich die das ganze in eine DLL um sie aus C# aufrufen zu können?
Klassen sind ja in DLLs nicht erlaubt.

(Delphi 5)


delfiphan - Fr 04.11.05 22:52

Unter den Beispielen hat's einige, die ohne Klassen funktionieren. Die kannst du exportieren, jedoch weiss ich nicht, ob das mit dem array of const unter C# funktioniert...


Jakob Schöttl - Do 22.06.06 17:17

Wahnsinn! Die genaue Fehlerbeschreibung...

Bloß mit zu großen Zahln hat er noch ein problem: "Invalid Float point operation."
eine englische fehlermeldung. Vllt hast du das vergessen.