Autor Beitrag
delfiphan
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: So 20.02.05 14:29 
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:
ausblenden 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 (35kb)
FarbMix Parser-Demo: FarbMix.exe (199kb)

Beispielanwendung:
ausblenden 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 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
www.delphi-forum.de/...r+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:
ausblenden 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
ausblenden volle Höhe 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)
ausblenden volle Höhe 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"


Zuletzt bearbeitet von delfiphan am So 19.11.06 18:28, insgesamt 4-mal bearbeitet

Für diesen Beitrag haben gedankt: MagicRain
retnyg
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2754

SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
BeitragVerfasst: 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:
ausblenden 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2754

SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
BeitragVerfasst: Di 01.03.05 15:17 
Nan ist übrigens auch das indische fladenbrot, welches im unterschied zu chapati mit joghurt angerührt wird...
retnyg
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2754

SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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

ausblenden 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?
ausblenden 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... :))

Für diesen Beitrag haben gedankt: MagicRain
retnyg
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2754

SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
BeitragVerfasst: 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:

_________________
es gibt leute, die sind genetisch nicht zum programmieren geschaffen.
in der regel haben diese leute die regel...
delfiphan Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 92



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 6326
Erhaltene Danke: 128

Windows 7 + Windows 10
Tokyo Prof + CE
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2754

SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
BeitragVerfasst: 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:

_________________
es gibt leute, die sind genetisch nicht zum programmieren geschaffen.
in der regel haben diese leute die regel...
delfiphan Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: Mo 07.03.05 14:37 
Wollte ich, geht aber nicht. Scheint wie n verzwicktes, unlösbares Delphiproblem zu sein:
ausblenden 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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)

ausblenden 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))
ausblenden 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 (27kb)
FarbMix Parser-Demo: 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:
ausblenden 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.


Zuletzt bearbeitet von delfiphan am Di 08.03.05 13:37, insgesamt 4-mal bearbeitet
retnyg
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2754

SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
BeitragVerfasst: Mo 07.03.05 21:56 
Beeindruckend ^^
werde das morgen mal in Ruhe testen...

_________________
es gibt leute, die sind genetisch nicht zum programmieren geschaffen.
in der regel haben diese leute die regel...
delfiphan Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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:
ausblenden 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 (35kb)
FarbMix Parser-Demo: FarbMix.exe (199kb)

Beispielanwendung:
ausblenden 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 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.

Für diesen Beitrag haben gedankt: MagicRain
AXMD
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 4006
Erhaltene Danke: 7

Windows 10 64 bit
C# (Visual Studio 2019 Express)
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2754

SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
BeitragVerfasst: 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 delphi.blue-aura.co....read.php?p=65#post65

_________________
es gibt leute, die sind genetisch nicht zum programmieren geschaffen.
in der regel haben diese leute die regel...
delfiphan Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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.


Zuletzt bearbeitet von delfiphan am So 04.12.05 15:22, insgesamt 1-mal bearbeitet