[gelöst] Formel Parser

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

[gelöst] Formel Parser

Beitrag von Michl »

Hi,

ich bin auf der Suche nach einem einfachen Formel Parser. Dazu habe ich ein wenig im Netz gestöbert. Da fpSpreadSheet ja eigentlich so einen anbietet, dachte ich mir, ich borge mir diesen aus.

Dazu habe ich einen kleinen Test geschrieben. Ich möchte gern immer ein Ergebnis (String vom Typ #,##) haben, egal, was der User eingibt:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  Parser: TsExpressionParser;
begin
  Parser := TsSpreadsheetParser.Create(nil);
  try
    Parser.LocalizedExpression[DefaultFormatSettings] := '32*5,2 + 24 *7 - 4* 2';
    Caption := Parser.Expression + ': ' + FormatFloat('#,##0.00', Parser.AsFloat);
    Parser.LocalizedExpression[DefaultFormatSettings] := '32*5 + 24 *7 - 4* 2';
    Caption := Parser.Expression + ': ' + FormatFloat('#,##0.00', Parser.AsInteger);
  finally
    Parser.Free;
  end;
end;
Das funktioniert soweit prima.


Nun weiß ich nicht, ob der Nutzer nur ganze Zahlen oder Zahlen mit Kommas eingibt (den String könnte man natürlich zuvor nach Kommas durchsuchen) und deshalb wollte ich folgenden Code verwenden:

Code: Alles auswählen

    Parser.LocalizedExpression[DefaultFormatSettings] := '32*5,2 + 24 *7 - 4* 2';
    case Parser.ResultType of
      rtFloat:   Caption := Parser.Expression + ': ' + FormatFloat('#,##0.00', Parser.AsFloat);
      rtInteger: Caption := Parser.Expression + ': ' + FormatFloat('#,##0.00', Parser.AsInteger);
      else       Caption := Parser.Expression + ': ' + FormatFloat('#,##0.00', 0);
    end;
Dabei wird aber als Parser.ResultType rtInteger zurückgegen und eine Exception ausgelöst "Invalid Result Type rtFloat".


Ich habe noch die Exceptions abgefangen (funktioniert aber nur, wenn der Parser nicht versucht eine Eingabe als eine "Zelle" vom WorkSheet zu interpretieren):

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  Parser: TsExpressionParser;
  i: Integer;
  s: String;
begin
  s := 'ab32*5,2 + 24 *7 - 4* 2';  //funktioniert nicht (versucht Zelle in WorkSheet zu finden)
  s := '32sd*5,2 + 24 *7 - 4* 2';  //funktioniert
  s := '32*5,2 + 24 *7 - 4* 2';    //funktioniert
  s := '32*5 + 24 *7 - 4* 2';      //funktioniert
  Parser := TsSpreadsheetParser.Create(nil);
  for i := 0 to 1 do
    try
      Parser.LocalizedExpression[DefaultFormatSettings] := s;
      case i of
        0: Caption := Parser.Expression + ': ' + FormatFloat('#,##0.00', Parser.AsInteger);
        1: Caption := Parser.Expression + ': ' + FormatFloat('#,##0.00', Parser.AsFloat);
      end;
      Parser.Free;
      Exit;
    except
      on e: Exception do
        if i = 1 then
          ShowMessage('Ungültige Eingabe: ' + s + LineEnding + e.Message);
    end;
  Parser.Free;
end;
Wie wäre das richtige Vorgehen?
Welchen Formel Parser würdet Ihr verwenden (es geht dabei wirklich nur um einfachste Rechenfunktionen á la a + b oder a * b + c * d oder a * b * c + d * e * f ...) ?
Zuletzt geändert von Michl am Di 5. Jan 2016, 15:23, insgesamt 1-mal geändert.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

wp_xyz
Beiträge: 5154
Registriert: Fr 8. Apr 2011, 09:01

Re: Formel Parser

Beitrag von wp_xyz »

Der Formelparser von fpspreadsheet basiert auf dem fpexpressionparser, der beim fpc mit in der Tüte ist. Mit dem bist du eigentlich besser bedient, weil du das ganze Spreadsheet-Zeug nicht mitschleppen musst (es sei denn, die verwendest fpspreadsheet ohnehin in deinem Programm). Ich habe dazu auch einmal einen wiki-Beitrag beschrieben: http://wiki.lazarus.freepascal.org/How_ ... sionParser. Allerdings ist der fpexpressionparser ziemlich eigen mit Datentypen, und soweit ich mich erinnere, musst du das Komma durch einen Punkt ersetzen, bevor es der Parser zu Gesicht bekommt.

Was ist in deinem Code "ab32" und "32sd"? Das ist keine Zahl und funktioniert nur, wenn du dies als Variable definierst, der du dann auch einen Wert zuweisen musst. (Der fpspreadsheet-Parser interpretiert alles, was keine Zahl, keine Variable und kein Funtionsname ist, als Zell-Adresse) - es wundert mich, dass "32sd" funktioniert.

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: Formel Parser

Beitrag von Michl »

wp_xyz hat geschrieben:Der Formelparser von fpspreadsheet basiert auf dem fpexpressionparser, der beim fpc mit in der Tüte ist.
Danke! Das habe ich nicht gewusst/gefunden.
wp_xyz hat geschrieben:Ich habe dazu auch einmal einen wiki-Beitrag beschrieben: http://wiki.lazarus.freepascal.org/How_ ... sionParser.
Tiptop, da habe ich etwas zu lesen :)
 
wp_xyz hat geschrieben:...und soweit ich mich erinnere, musst du das Komma durch einen Punkt ersetzen, bevor es der Parser zu Gesicht bekommt.
Der Test

Code: Alles auswählen

    Parser.LocalizedExpression[DefaultFormatSettings] := '2,5 *2';
    Caption := FormatFloat('#,##0.00', Parser.AsFloat);   
Gibt 5,00 aus. Alles gut so.
wp_xyz hat geschrieben:Was ist in deinem Code "ab32" und "32sd"?
Das war nur der Test für Fehleingaben vom User, hatte von meiner Seite her nichts mit Spreadsheet zu tun.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: Formel Parser

Beitrag von Michl »

Scheint das zu tun, was ich will. Vielen Dank wp!

Code: Alles auswählen

uses ..., fpexprpars;
...
procedure TForm1.Button1Click(Sender: TObject);
var
  Parser: TFPExpressionParser;
  aValue: Double;
  s: String;
begin
//  s := 'ab32*5,2 + 24 *7 - 4* 2';
//  s := '32sd*5,2 + 24 *7 - 4* 2';
//  s := '32*5.2 + 24 *7 - 4* 2';
  s := '32*5 + 24 *7 - 4* 2';
  Parser := TFPExpressionParser.Create(nil);
  try
    Parser.BuiltIns := [bcMath];
    Parser.Expression := s;
    case Parser.ResultType of
      rtFloat:   aValue := Parser.Evaluate.ResFloat;
      rtInteger: aValue := Parser.Evaluate.ResInteger;
      else       aValue := 0;
    end;
    Caption := Parser.Expression + ' = ' + FormatFloat('#,##0.00', aValue);
  except
    on e: Exception do
      ShowMessage('Ungültige Eingabe: ' + s + LineEnding + e.Message);
  end;
  Parser.Free;
end;

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

Antworten