![]() |
AW: Einheiten parsen
Zitat:
Zitat:
|
AW: Einheiten parsen
Zitat:
Ob dann etwas Sinnvolles herauskommt, wird sich noch zeigen müssen. |
AW: Einheiten parsen
So würde ich anfangen:
Code:
Compatible ist ein 2-dimensionales Feld ARRAY[TUnit,TUnit] OF Boolean, der Eintrag [cm,m] wird auf True gesetzt, [g,cm] auf False. Normalize ist ein ARRAY[TUnit,TUnit] OF Real und enthält die Umrechnungsfaktoren in die Darstellungeinheit: [cm,m] = 0.01,[kg,g] = 1000. Die Eingabe "30 knirsch" wird von der kontextfreien Fehlerbehandlung abgewiesen, die Eingabe "30g + 40 cm" von der kontext-sensitiven. Die Hinzunahme weiterer Einheiten bedeutet eine zusätzliche Zeile in der Grammatik und zwei Änderungen in Compatible und Normalize.
Expr -> Left_Expr '+' Right_Expr
=> "IF Compatible($Left_Expr.Unit,$Right_Expr.Unit)= True THEN $Expr := Normalize($Left_Expr.Value) + Normalize($Right_Expr.Value) ELSE Error(UnitsNotCompatible)" | Factor Unit Factor -> <Number> | '(' Expression ')' Unit -> | m | cm | g | kg Für das Rechnen und Umformen mit abgeleiteten Einheiten bietet sich ein endlicher Automat an, der über dem kompletten Syntaxbaum arbeitet. |
AW: Einheiten parsen
Hab ich ja schon gesagt daß ich keinen Extra Durchgang mache sondern daß die Einheitn im Parserprozess mitlaufen. Ich brauch auch wirklich nur die angegeben Einheiten. Und daß bei syntaktisch falschen Operationen (z.B. m + kN) nicht irgendwas rauskommt sondern keine Einheit angezeigt wird fängt der Parser nun ab.
|
AW: Einheiten parsen
Das Parsen gestaltet sich jetzt doch schwieriger als angenommen. :oops:
Der Term sieht intern so aus. In eckigen Klammern die Einheiten. Die Einheiten werden durch einfache Token ersetzt. 5,0[D]*0,70[K]/cos(30) Das das soll jetzt hier durchgejagt werden. Hab keinen Plan wie ich das hier anstellen soll?:gruebel: (Getrenntes Parsen von Einheitn und Term ist m.E. aus syntaktischen Gründen nicht möglich.) Im Beispiel landet der Parser irgendwann in GetPlus. Er muß aber auch mal irengdwann in FUnit.GetPlus landen (Der Parser hat ein Feld der Klasse TUntis (Siehe #18)).
Delphi-Quellcode:
// Die eigentliche Auflösung des Terms;
// *** Die Rekursion arbeitet rückwärts; // "~" (unäres Minus) immer zuerst prüfen ; // Sqrt vor Sqr, ArcXXX vor XXX; // Entweder LastPos und true oder FirstPos und false; // LastPos und true: "+", "-", "*", "/", ":", "\", (":" = div, "\" = mod); // FirstPos und false: "^", Functions; function TJMUnitParser.Solve(S: string; var Style: TUnitStyle): double; begin try if LastPos('+', S) > 0 then Result := GetPlus(Solve(Left(S, '+', true), Style), Solve(Right(S, '+', true), Style)) else ... Weitere .. else if Parenthesis(S, true) then // "~( .. )" Result := -Solve(S, Style) else if Parenthesis(S, false) then // "( .. )" Result := Solve(S, Style) else Result := StrToFloat(MyStringReplace(S, '~', '-', false, false)); except Result := 0; end; end; // Gibt den linken Teil einer Operation zurück; function TJMUnitParser.Left(const S, Substr: string; const Last: boolean): string; begin if Last then // Den Term von rechts nach links durchsuchen; Result := StrLeft(S, LastPos(Substr, S) - 1) else // Den Term von links nach rechts durchsuchen; Result := StrLeft(S, FirstPos(Substr, S) - 1); end; // Gibt den rechten Teil einer Operation zurück; function TJMUnitParser.Right(const S, Substr: string; const Last: boolean): string; begin if Last then // Den Term von rechts nach links durchsuchen; Result := StrRight(S, LastPos(Substr, S) + Length(Substr)) else // Den Term von links nach rechts durchsuchen; Result := StrRight(S, FirstPos(Substr, S) + Length(Substr)); end; function TJMUnitParser.GetPlus(const X, Y: double): double; begin Result := X + Y; end; |
AW: Einheiten parsen
Ich würde es für sinnvoller und wesentlich einfacher halten, wenn Du Dir einen fertigen Parser nimmst, und diesen entsprechend nach Deinen Bedürfnissen anpasst.
- Leerzeichen werden ignoriert - Du hast Zahlen, musst das Dezimalkomma ensprechend behandeln. - wenn auf eine Ziffer ein Buchstabe folgt, handelt es sich um eine Einheit, implizit wird eine Multiplikation eingefügt; die Einheit wird gegen den Satz von Einheiten geprüft - wenn auf einen Buchstaben eine Klammer auf folgt, ist es ein Funktionsaufruf, das Token wird dann gegen den Satz der verfügbaren Funktionen geprüft - Klammerung sollte der Standardparser bereits beherrschen. Es ist generell keine gute Idee, in den Parser gleich die Interpretation des Ausdrucks einzubauen. Stattdessen kannst Du den gepars-ten Ausdruck auswerten und weißt dabei vorher, dass Du keinen Syntaxfehler hast. |
AW: Einheiten parsen
Hallo Mikkey, das ist ein fertiger Parser. Und implizit kann man da m.E. nicht einfach mal eine Multiplikation einführen? Mir gehts hier nur um das Handling der Rekursion (Wie kommt man die Einheit ran?)
|
AW: Einheiten parsen
Eine Konstante ist nicht nur einfach eine Zahl, sondern entweder ein Faktor oder ein physikalischer Wert.
Code:
PS: Wenn du die Frage nach Rekursivität stellst, dann hast Du nicht wirklich einen Parser. Oder Du hast einen, weißt aber nicht, wie man ihn verwendet. Oder Du hast einen, weißt, wie man ihn verwendet, hast aber einfach die falsche Frage gestellt. ;-)
NumericConstant ::= Factor | PhysicalValue
Factor ::= AnyNumber PhysicalValue ::= AnyNumber [Unit] |
AW: Einheiten parsen
Liste der Anhänge anzeigen (Anzahl: 1)
Hi Bud,
ja, die Rekursion hatte ich gestern nicht wirklich (mehr) kapiert gehabt. Aber heute ist ein neuer Tag. So wie ich's vorhatte ging's aus mehreren Gründen nicht. Man kann entweder mit Äpfeln (double) mit oder Birnen (TEinheiten) rechnen. Also dann doch einen Extradurchlauf. Man braucht eine neue Solve.
Delphi-Quellcode:
Vom Ablauf her isses hier so: Im obigen Beispiel 5,0 kN/m2 * 0,70 m / cos(30) wird beim Aufbereiten für's Einheitenparsen zunächst [Einheit_1][Einheit_ kN/m2] * [Einheit_m] / [Einheit_1]. Um den Term Term mathematisch so zu parsen wie den Term ohne Einheiten braucht muß man nur [Einheit_1][Einheit_ X] zu [Einheit_ X] ersetzen (bzw. [Einheit_ X][Einheit_1] zu [Einheit_ X]).
function TJMUnitParser.Solve(S: string): TParserUnitStyle;
begin try if LastPos('+', S) > 0 then Result := GetPlus(Solve(Left(S, '+', true)), Solve(Right(S, '+', true))) else if LastPos('-', S) > 0 then Result := GetMinus(Solve(Left(S, '-', true)), Solve(Right(S, '-', true))) else if LastPos('*', S) > 0 then Result := GetMult(Solve(Left(S, '*', true)), Solve(Right(S, '*', true))) else if LastPos('/', S) > 0 then Result := GetDiv(Solve(Left(S, '/', true)), Solve(Right(S, '/', true))) else if LastPos(':', S) > 0 then // div; Result := GetIntDiv(Solve(Left(S, ':', true)), Solve(Right(S, ':', true))) else if LastPos('\', S) > 0 then // mod; Result := GetIntMod(Solve(Left(S, '\', true)), Solve(Right(S, '\', true))) else if FirstPos('^', S) > 0 then Result := GetPower(Solve(Left(S, '^', false)), Solve(Right(S, '^', false))) else if Parenthesis(S) then // ( .. ) Result := Solve(S) else Result := FUnits.SignToStyle(S); except Result := pusNone; end; end; Hab den Code mal angehängt. Falls du Böcke hat, eine Variante mit einem Stack wär' nicht schlecht. :-D LG Thomas |
AW: Einheiten parsen
Hmm. Du solltest deinen Parser so schreiben, wie man eine Grammatik schreibt.
D.h. 1. Du baust dir einen Tokenizer/Lexer, der dir aus deinem String die entsprechenden Token (Operator, Klammer, Zahl, Einheit) extrahiert. 2. Dann schreibst Du den Parser. Leider hab ich gerade keinen da, weil ich unterwegs bin und mein Privatlaptop mit einem fertigen Parser zuhause ist. Im Prinzip geht es so:
Delphi-Quellcode:
'IsZahl' liefert true, wenn das nächste Token eine Zahl ist. außerdem wird in 'leftNumber' dann die Zahl geliefert.
// Term ::= Zahl Operator Zahl | '(' Term ')'
function IsTerm (var term : TParserNode) : Boolean; Var leftNumber, operator, rightNumber : TToken; begin result := false if IsOpenBracket then begin if IsTerm (term) then if IsClosingBracket then exit(true); SyntaxError('Term expected'); end else if IsZahl(leftNumber) then if IsOperator(operator) then If IsZahl(rightNumber) then begin term := TTermNode.Create (leftNumber, operator, rightNumber) result := True; end; // falls result=false ist, müsstest du hier noch aufräumen, oder mit interfaces arbeiten end; Das gleiche gilt für 'IsOperator'. 'IsTerm' macht im Prinzip genau das Gleiche: Wenn es die Tokensequenz einen Term ergibt, liefert die Funktion 'True' und den Term. Wichtig ist, das man beim Erkennen eines Tokens zum nächsten geht. Manchmal muss man sich das aktuelle Token, sondern das darauf folgende Token anschauen, um zu entscheiden, was zu tun ist (look ahead). |
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:16 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz