Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Einheiten parsen (https://www.delphipraxis.net/184211-einheiten-parsen.html)

Bjoerk 9. Mär 2015 11:16

Einheiten parsen
 
Meine User würden gerne sowas eingeben können.
5,0 kN/m2 * 0,70 m / cos(30)

Die Idee war nun zwei Berechnungen durchzuführen:

1.) Die Einheiten rauslöschen und den Term herkömmlich parsen.
-> Parse(5,0 * 0,70 / cos(30))

2.) Die Term zu modifizieren und dann die Einheiten parsen.
-> UnitParse([kN/m2] / [m])

Mögliche Einheiten:
'kN/m3', 'kN/m2', 'kN/m', 'kNm/m', 'kNm', 'kN', 'm3', 'm2', 'm',
(Wann 'kNm/m' ebd ist und nicht kN kann ich vorher prüfen ('kNm/m' kann nur vorkommen wenn 'kNm' im Term vorhanden ist.))

Einheiten können nur in + - * / Operationen vorkommen.

Doch wie berechnet man das? Was nehm ich da für einen Typ? Kann man da irgendwie bitweise addieren? Hat da jemand eine Idee? Vom Ergebnis muß ja auch wieder auf die Einheit geschlossen werden können (MyTypToStr)?

Sir Rufo 9. Mär 2015 11:28

AW: Einheiten parsen
 
Du musst dir das einfach nur anders vorstellen:
Code:
5,0 kN/m2 * 0,7 m
entspricht eben
Code:
5,0 * (kN) / ( (m)*(m) ) * 0,7 * (m)
Die Einheiten sind somit eigenständige Werte und müssen auch also solche behandelt werden.

Am Besten du erstellst dir so einen Einheiten-Typ (record) und einen EinheitWert-Typ (record) - ein Zahlwert und eine Einheit - und definierst die Rechenregeln.

Bjoerk 9. Mär 2015 11:47

AW: Einheiten parsen
 
Zitat:

Zitat von Sir Rufo (Beitrag 1292778)
Am Besten du erstellst dir so einen Einheiten-Typ (record) und einen EinheitWert-Typ (record) - ein Zahlwert und eine Einheit - und definierst die Rechenregeln.

Ähm, hann ich nedd kapiert. Sorry..

p80286 9. Mär 2015 11:53

AW: Einheiten parsen
 
Zitat:

Zitat von Sir Rufo (Beitrag 1292778)
Du musst dir das einfach nur anders vorstellen:
Code:
5,0 kN/m2 * 0,7 m
entspricht eben
Code:
5,0 * (kN) / ( (m)*(m) ) * 0,7 * (m)
Die Einheiten sind somit eigenständige Werte und müssen auch also solche behandelt werden.

Am Besten du erstellst dir so einen Einheiten-Typ (record) und einen EinheitWert-Typ (record) - ein Zahlwert und eine Einheit - und definierst die Rechenregeln.

du kannst aber noch einen Schritt weiter gehen:
Code:
5,0 * 0,7 (kN) / ( (m)*(m) ) * (m)
Code:
5,0 * 0,7 (kN) * (m) / ( (m)*(m) )
Code:
5,0 * 0,7 (kN) /  (m)
Gruß
K-H

Jens01 9. Mär 2015 11:59

AW: Einheiten parsen
 
Cool, sowas willst Du programmieren...
Ich verstehe das so, dass Du als Ergebnis eine Zahl hast und die Einheit dieser Zahl.
Ich würde auch die Einheiten von den Zahlenwerten trennen und dann in einem separaten Parser durchrechnen. Sowas wie "k" wie Kilo oder "M" wie Mega würde ich aber in der Zahlenberechnung lassen, da das ja eigentlich nur eine andere Exponentialschreibweise ist.
Und pass auf, dass das noch hinten heraus nicht unheimlich kompliziert wird.

Bjoerk 9. Mär 2015 12:05

AW: Einheiten parsen
 
Ich glaub das Trennen ist nicht ganz einfach, besser in einem Rutsch?
Sir Rufo, meinst du so?

Delphi-Quellcode:
type
  TParserUnitStyle =
    (pusKNdivM3, pusKNdivM2, pusKNdivM, pusKNMdivM, pusKNM, pusKN, pusM3, pusM2, pusM);

  TParserUnit = record
  private
    FValue: TParserUnitStyle;
  public
    function GetPlus(const A, B: TParserUnit):TParserUnitStyle;
    function GetMinus(const A, B: TParserUnit):TParserUnitStyle;
    function GetDiv(const A, B: TParserUnit):TParserUnitStyle;
    function GetMult(const A, B: TParserUnit):TParserUnitStyle;
    property Value: TParserUnitStyle read FValue write FValue;
  end;

  TParserFloat = record
    Value: double;
    UnitValue: TParserUnit;
  end;

BUG 9. Mär 2015 12:21

AW: Einheiten parsen
 
Ich würde mir so etwas wie "atomare" Einheiten definieren: m und kN.
Dann kann man sich zusammengesetzte Einheiten definieren:
Delphi-Quellcode:
type = record
  units: array[m..kN]of integer;
end;
Der Inhalt des Arrays gibt dann den Exponenten der Einheit an. Zum Beispiel: m entspricht {1, 0}; kN/m entspricht {-1, 1}
Dann kann man sich Operationen auf den Zusammengesetzten Einheiten definieren: Multiplikation/Division entspricht der Addition/Substraktion der Arrays. Addition/Subtraktion entspricht einem Test auf Gleichheit der Arrays; bei Ungleichheit könnte man eine Exception werfen.

Aber ehrlich gesagt würde ich die Werte gleich mit rein packen:
Delphi-Quellcode:
type = record
  value: double;
  units: array[m..kN]of integer;
end;

Bjoerk 9. Mär 2015 12:44

AW: Einheiten parsen
 
Ja. In etwa so war auch mein erster Ansaz. Das wollte ich aber umgehen.

Delphi-Quellcode:
constructor TUnitParser.Create;
begin
  inherited Create;
  FM := 1000.0;
  FKN := -1.0;
  SetUnit(0, 'R', 'kN/m3', FKN / FM / FM / FM);
  SetUnit(1, 'S', 'kN/m2', FKN / FM / FM);
  SetUnit(2, 'T', 'kN/m', FKN / FM);
  SetUnit(3, 'U', 'kNm/m', FKN);
  SetUnit(4, 'V', 'kNm', FKN * FM);
  SetUnit(5, 'W', 'kN', FKN);
  SetUnit(6, 'X', 'm3', FM * FM * FM);
  SetUnit(7, 'Y', 'm2', FM * FM);
  SetUnit(8, 'Z', 'm', FM);
end;

Kann man nicht irgendwie mit eigenen Typen rechnen? Wäre mir lieber.:gruebel:

Jens01 9. Mär 2015 12:58

AW: Einheiten parsen
 
@BUG
Du meinst Grundeinheiten?: m, N, kg, s, ...

@Bjoerk
Hast Du sowas?

@TUnitParser
Was machst Du, wenn jemand mit MN oder cm um die Ecke kommt.

Bjoerk 9. Mär 2015 13:06

AW: Einheiten parsen
 
Ja, genau das. Wußte gar nicht daß D2007 das schon hat. Probiers mal aus und melde mich nochmal. Thanx.

BUG 9. Mär 2015 13:11

AW: Einheiten parsen
 
Zitat:

Zitat von Jens01 (Beitrag 1292791)
Du meinst Grundeinheiten?: m, N, kg, s, ...

Genau das etwas meine ich (Basiseinheit) :stupid:
Im Prinzip könnte man so ein Einheitensystem auch anwendungspezifisch festlegen, aber es ist vermutlich clever SI-Einheiten zu benutzen. Die "zusammengesetzt Einheiten" sind nach der Terminologie dann abgeleite Einheiten.

Sir Rufo 9. Mär 2015 13:26

AW: Einheiten parsen
 
Ja, man kommt dann nicht drumherum diese Basiseinheiten mit allen ihren Ausprägungen (m, cm => 1/100m, km => 1000m, s, min => 60s, h => 3600s, ...) als Typ zu definieren.

Dann benötigt man einen weiteren Typen, der mit den zusammengesetzten Basiseinheiten umgehen kann. Da hat man dann Basis-Einheiten im Zähler und im Nenner, die man dann auch wegkürzen kann, damit aus
Code:
60 Meter/Sekunde * 60 Sekunden = 3600 Meter
werden kann.
Code:
Wert1 => Wert: 60, Einheit Zähler: [Meter], Einheit Nenner: [Sekunde]
Wert2 => Wert: 60, Einheit Zähler: [Sekunde], Einheit Nenner: []

Ergebnis => Wert 3600, Einheit Zähler: [Meter], Einheit Nenner: []
Spannend wird es dann eben noch, wenn man unterschiedliche Einheiten aus der gleichen Familie hat:
Code:
Wert1 => Wert: 60, Einheit Zähler: [KilometerMeter], Einheit Nenner: [Stunde]
Wert2 => Wert: 60, Einheit Zähler: [Sekunde], Einheit Nenner: []

Ergebnis => Wert 1, Einheit Zähler: [KiloMeter], Einheit Nenner: []

BUG 9. Mär 2015 14:23

AW: Einheiten parsen
 
Zitat:

Zitat von Sir Rufo (Beitrag 1292799)
mit allen ihren Ausprägungen als Typ zu definieren... unterschiedliche Einheiten aus der gleichen Familie hat

Eben nicht für alle möglichen Ausprägungen und keine unterschiedlichen Einheiten (im Typsystem) für die gleiche Größe.
Beim Einlesen werden alle Werte und Einheiten zu den Basiseinheiten normalisiert. Beim Ausgeben muss man gucken, wie die Anforderungen sind.

Bjoerk 9. Mär 2015 14:38

AW: Einheiten parsen
 
Kann man da nicht mit Sets oder irgendwie bitweise was rechen, sonst tippt man sich ja den Wolf? (und sind hier gerade mal 9 mögliche Kombinationen). Die usNone müssen natürlich noch ausgefüllt werden.

Delphi-Quellcode:

type
  TUnitStyle = (usNone, usKNdivM3, usKNdivM2, usKNdivM, usKNMdivM, usKNM, usKN, usM3, usM2, usM);

function GetUnitPlus(const A, B: TUnitStyle): TUnitStyle;
begin
  if A = B then
    Result := A
  else
    Result := usNone;
end;

function GetUnitMinus(const A, B: TUnitStyle): TUnitStyle;
begin
  if A = B then
    Result := A
  else
    Result := usNone;
end;

function GetUnitDiv(const A, B: TUnitStyle): TUnitStyle;
begin
  Result := usNone;
  case A of
    usKNdivM3:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKNdivM2:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKNdivM:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKNMdivM:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKNM:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKN:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usM3:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usM2:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usM:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
  end;
end;

function GetUnitMult(const A, B: TUnitStyle): TUnitStyle;
begin
  Result := usNone;
  case A of
    usKNdivM3:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKNdivM2:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKNdivM:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKNMdivM:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKNM:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usKN:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usM3:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usM2:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
    usM:
    begin
      case B of
        usKNdivM3:
          Result := usNone;
        usKNdivM2:
          Result := usNone;
        usKNdivM:
          Result := usNone;
        usKNMdivM:
          Result := usNone;
        usKNM:
          Result := usNone;
        usKN:
          Result := usNone;
        usM3:
          Result := usNone;
        usM2:
          Result := usNone;
        usM:
          Result := usNone;
      end;
    end;
  end;
end;

end.

BUG 9. Mär 2015 14:50

AW: Einheiten parsen
 
Zitat:

Zitat von Bjoerk (Beitrag 1292807)
Kann man da nicht mit Sets oder irgendwie bitweise was rechen, sonst tippt man sich ja den Wolf?

Deswegen reden wir ja über die Basiseinheiten und abgeleiteten Einheiten :mrgreen:
Zum Beispiel mit den Array aus Beitrag #9 lässt sich dass doch ganz komfortabel machen.

EDIT: Hast du das Ganze per Hand heruntergeschrieben? :pale:

Bjoerk 9. Mär 2015 14:56

AW: Einheiten parsen
 
Zitat:

Zitat von BUG (Beitrag 1292810)
EDIT: Hast du das Ganze per Hand heruntergeschrieben? :pale:

Nee. Aber selbst das kopieren war schon Arbeit :)

Deinen Ansatz #9 hab ich nich kapiert. Montags kapier ich noch weniger als sonst.. Kannst nochmal etwas erläutern?

BUG 9. Mär 2015 16:28

AW: Einheiten parsen
 
Deine Basiseinheiten definierst du weiter als Aufzählungstyp:
Delphi-Quellcode:
type
  TFundamentalUnit = (fuKN, fuM);
Die zusammengesetzten Einheiten lassen sich auch in Exponentenschreibweise aufschreiben. Die Exponenten kannst du dann einfach in einem Array speichern.
Delphi-Quellcode:
type
  TDerivedUnit = array[fuKN..fuM] of Integer.
TUnitStyle Exponentenschreibweise Arrayinhalt
usNone kN^0 * m^0 0, 0
usKNdivM3 kN^1 * m^(-3) 1, -3
usKNM kN^1 * m^1 1, 1
sKN kN^1 * m^0 1, 0
usM3 kN^0 * m^3 0, 3

Zum Beispiel die Multiplikation kann man nun ganz einfach auf eine Addition der Array-Einträge abbilden (quasi mit den Rechenregeln für Potenzen):
  1. usM3 * usKNdivM2
  2. (kN^0 * m^3) * (kN^1 * m^(-2))
  3. (kN^(0+1) * m^(3-2)
  4. kN^1 * m^1
  5. usKNM :thumb:

Ich hoffe das ist verständlicher :wink:

Bjoerk 9. Mär 2015 20:03

AW: Einheiten parsen
 
Geht sehr gut. Danke Robert. :thumb:

Delphi-Quellcode:
type
  TUnitBase = (fuKN, fuM);
  TUnitStyle = (usNone, usKNdivM3, usKNdivM2, usKNdivM, usKNM, usKN, usM3, usM2, usM);

  TUnits = class
  private
    FItems: array[usNone..usM, fuKN..fuM] of integer;
    function GetValues(Index: TUnitStyle): double;
    function GetAsString(Index: TUnitStyle): string;
  public
    property Values[Index: TUnitStyle]: double read GetValues;
    property AsString[Index: TUnitStyle]: string read GetAsString;
    function GetPlus(const A, B: TUnitStyle): TUnitStyle;
    function GetMinus(const A, B: TUnitStyle): TUnitStyle;
    function GetDiv(const A, B: TUnitStyle): TUnitStyle;
    function GetMult(const A, B: TUnitStyle): TUnitStyle;
    constructor Create;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TUnits }

constructor TUnits.Create;
begin
  FItems[usNone, fuKN] := 0;
  FItems[usNone, fuM] := 0;

  FItems[usKNdivM3, fuKN] := 1;
  FItems[usKNdivM3, fuM] := -3;

  FItems[usKNdivM2, fuKN] := 1;
  FItems[usKNdivM2, fuM] := -2;

  FItems[usKNdivM, fuKN] := 1;
  FItems[usKNdivM, fuM] := -1;

  FItems[usKNM, fuKN] := 1;
  FItems[usKNM, fuM] := 1;

  FItems[usKN, fuKN] := 1;
  FItems[usKN, fuM] := 0;

  FItems[usM3, fuKN] := 0;
  FItems[usM3, fuM] := 3;

  FItems[usM2, fuKN] := 0;
  FItems[usM2, fuM] := 2;

  FItems[usM, fuKN] := 0;
  FItems[usM, fuM] := 1;
end;

function TUnits.GetValues(Index: TUnitStyle): double;
begin
  Result := IntPower(FItems[Index, fuKN], FItems[Index, fuM]);
end;

function TUnits.GetAsString(Index: TUnitStyle): string;
begin
  case Index of
    usKNdivM3:
      Result := 'kN/m3';
    usKNdivM2:
      Result := 'kN/m2';
    usKNdivM:
      Result := 'kN/m';
    usKNM:
      Result := 'kNm';
    usKN:
      Result := 'kN';
    usM3:
      Result := 'm3';
    usM2:
      Result := 'm2';
    usM:
      Result := 'm';
    else
      Result := '';
  end;
end;

function TUnits.GetPlus(const A, B: TUnitStyle): TUnitStyle;
begin
  if A = B then
    Result := A
  else
    Result := usNone;
end;

function TUnits.GetMinus(const A, B: TUnitStyle): TUnitStyle;
begin
  if A = B then
    Result := A
  else
    Result := usNone;
end;

function TUnits.GetMult(const A, B: TUnitStyle): TUnitStyle;
var
  I: TUnitStyle;
  KN, M: integer;
begin
  Result := usNone;
  KN := FItems[A, fuKN] + FItems[B, fuKN];
  M := FItems[A, fuM] + FItems[B, fuM];
  for I := Low(TUnitStyle) to High(TUnitStyle) do
    if (KN = FItems[I, fuKN]) and (M = FItems[I, fuM]) then
    begin
      Result := I;
      Break;
    end;
end;

function TUnits.GetDiv(const A, B: TUnitStyle): TUnitStyle;
var
  I: TUnitStyle;
  KN, M: integer;
begin
  Result := usNone;
  KN := FItems[A, fuKN] - FItems[B, fuKN];
  M := FItems[A, fuM] - FItems[B, fuM];
  for I := Low(TUnitStyle) to High(TUnitStyle) do
    if (KN = FItems[I, fuKN]) and (M = FItems[I, fuM]) then
    begin
      Result := I;
      Break;
    end;
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  Units: TUnits;
  A, B, C: TUnitStyle;
begin
  Units := TUnits.Create;
  try
    A := usM3;
    B := usKNdivM2;
    C := Units.GetMult(A, B);
    ShowMessage(Units.AsString[C]);
    A := usKN;
    B := usM;
    C := Units.GetDiv(A, B);
    ShowMessage(Units.AsString[C]);
  finally
    Units.Free;
  end;
end;

Sailor 9. Mär 2015 21:18

AW: Einheiten parsen
 
Und warum nicht einen stinknormalen Expression-Parser um Einheiten erweitern und die Umrechnung in die Semantikroutinen legen? Hätte auch noch den Vorteil einer komfortablen Fehlerbehandlung.

Bjoerk 9. Mär 2015 21:50

AW: Einheiten parsen
 
Wenn du uns noch kurz sagst wo hier eine Exception auftreten kann? :wink:

BUG 9. Mär 2015 23:42

AW: Einheiten parsen
 
Zitat:

Zitat von Bjoerk (Beitrag 1292838)
Geht sehr gut.

Ich hätte zwar versucht, die vordefinierten abgeleiteten Einheiten (TUnitStyle) ganz loszuwerden; aber schön das es dir geholfen hat :mrgreen:

Zitat:

Zitat von Bjoerk (Beitrag 1292853)
Wenn du uns noch kurz sagst wo hier eine Exception auftreten kann? :wink:

Ein Kandidat wäre das Addieren mit unterschiedlichen Einheiten: Dein Code verwirft die Einheiten dann einfach, was zu merkwürdigen Ergebnissen führen könnte.

Dejan Vu 10. Mär 2015 05:01

AW: Einheiten parsen
 
Zitat:

Zitat von Sailor (Beitrag 1292848)
Und warum nicht einen stinknormalen Expression-Parser um Einheiten erweitern und die Umrechnung in die Semantikroutinen legen? Hätte auch noch den Vorteil einer komfortablen Fehlerbehandlung.

Folgt diesem Weg. Der Von Bjoerk scheitert ja schon, wenn ich mal andere Einheiten nehmen will, z.B. PS, inch. Natürlich sollte man dem Evaluator (bzw. den 'Semantikroutinen') noch die Rechenregeln für Einheiten beibrigen (Basiseinheiten, Kürzen, Ersetzen: N*m/s => J etc.)

Ob dann etwas Sinnvolles herauskommt, wird sich noch zeigen müssen.

Sailor 10. Mär 2015 08:22

AW: Einheiten parsen
 
So würde ich anfangen:
Code:
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
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.
Für das Rechnen und Umformen mit abgeleiteten Einheiten bietet sich ein endlicher Automat an, der über dem kompletten Syntaxbaum arbeitet.

Bjoerk 10. Mär 2015 10:46

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.

Bjoerk 10. Mär 2015 17:01

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;

Mikkey 10. Mär 2015 17:15

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.

Bjoerk 10. Mär 2015 18:21

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?)

Dejan Vu 11. Mär 2015 04:39

AW: Einheiten parsen
 
Eine Konstante ist nicht nur einfach eine Zahl, sondern entweder ein Faktor oder ein physikalischer Wert.

Code:
NumericConstant ::= Factor | PhysicalValue
Factor ::= AnyNumber
PhysicalValue ::= AnyNumber [Unit]
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. ;-)

Bjoerk 11. Mär 2015 15:15

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:
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;
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]).

Hab den Code mal angehängt. Falls du Böcke hat, eine Variante mit einem Stack wär' nicht schlecht. :-D

LG
Thomas

Dejan Vu 12. Mär 2015 06:15

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:
// 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;
'IsZahl' liefert true, wenn das nächste Token eine Zahl ist. außerdem wird in 'leftNumber' dann die Zahl geliefert.
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).

Bjoerk 12. Mär 2015 06:52

AW: Einheiten parsen
 
Ja, das weiß ich doch selbst? Dieser Parser arbeitet aber halt anders. Ansonsten ja zum Beispiel so, so einen hab ich auch mal gemacht.
Delphi-Quellcode:
procedure TParser.ParseFunctions(var S: string);
var
  Index: integer;
begin
  Index := OperatorPos(S, 'A');
  while Index > 0 do
  begin
    MathWork(S, Index);
    Index := OperatorPos(S, 'A');
  end;
end;

procedure TParser.ParseOperator(var S: string; const AOperator: Char);
var
  Index: integer;
begin
  Index := OperatorPos(S, AOperator);
  while Index > 0 do
  begin
    MathWork(S, Index);
    Index := OperatorPos(S, AOperator);
  end;
end;

procedure TParser.Parse(var S: string);
begin
  // Diese Reihenfolge
  ParseFunctions(S); // Funktionen, Von Rechts nach Links;
  ParseOperator(S, '^'); // Power, Von Rechts nach Links;
  ParseOperator(S, '*'); // Punktrechnung, Von Links nach Rechts;
  ParseOperator(S, '+'); // Strichrechnung Von Links nach Rechts;
end;

function TParser.Solve(T: string; Sender: boolean): string; // Aufruf mit Sender = false;
var
  A, B: Integer;
  S: string;
  AFloat: double;
begin
  while HaveBrackets(T, A, B) do
  begin
    S := StrMid(T, A + 1, B - 1);
    Solve(S, true);
    Parse(S);
    T := StrLeft(T, A - 1) + S + StrRight(T, B + 1);
  end;
  if not Sender then
  begin
    Parse(T);
    if TryStrToFloat(GetNumber(T), AFloat) then
      Result := GetNumber(T)
    else
      Result := '0'; // Syntaxerror
  end;
end;

Bjoerk 12. Mär 2015 19:24

AW: Einheiten parsen
 
Was ich aber nicht hinkrieg ist daraus einen Stack zu machen? :oops:

Delphi-Quellcode:
function TJMUnitParser.Solve(S: string): TParserUnitStyle;
begin
  try
    if LastPos('+', S) > 0 then
      Result := FUnits.GetAdd(Solve(Left(S, '+', true)), Solve(Right(S, '+', true)))
    else
      if LastPos('/', S) > 0 then
        Result := FUnits.GetMult(Solve(Left(S, '/', true)), Solve(Right(S, '/', true)))
      else
        if LastPos('*', S) > 0 then
          Result := FUnits.GetDiv(Solve(Left(S, '*', true)), Solve(Right(S, '*', true)))
        else
          if FirstPos('^', S) > 0 then
            Result := FUnits.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;
In meinem jugendlichen Leichtsinn hab ich mal dahin überlegt? Geht das so in der Richtung? :gruebel:
Delphi-Quellcode:
function TJMUnitParser.Solve(const Term: string): TParserUnitStyle;
var
  Stack: TParserStack;
begin
  Result := pusNone;
  Stack := TParserStack.Create;
  try
    Stack.Push(Term);
    while not Stack.Empty do
    begin
      S := Stack.Pop.S;
      if LastPos('+', S) > 0 then
      begin
        Result :=
        Stack.Push(S);
      end
      else
        if LastPos('/', S) > 0 then
        begin
          Result :=
          Stack.Push(S);
        end
        else
          if LastPos('*', S) > 0 then
          begin
            Result :=
            Stack.Push(S);
          end
          else
            if Parenthesis(S) then
            begin
              Result :=
              Stack.Push(S);
            end
            else
              Result := FUnits.SignToStyle(S);
    end;
  finally
    Stack.Free;
  end;
end;

Dejan Vu 12. Mär 2015 19:40

AW: Einheiten parsen
 
Ein Parser wird einen Baum erzeugen (wenn er das Ergebnis nicht schon zur Parsezeit ausrechnet). Mir fehlt bei deinem Parser einfach nur der Lexer. Ich würde das einfach nicht alles zusammen wurschteln, das ist alles.

Bjoerk 12. Mär 2015 20:54

AW: Einheiten parsen
 
Der Witz an diesem Parser ist ja daß er keinen Tokenizer braucht. Durch z.B.
Delphi-Quellcode:
(Solve(Left(S, '+')), Solve(Right(S, '+')))
wird das Token + gezogen. Left ist der Tel links vom Operator, Right rechts davon.

Ich hab echt keinen Plan wie ich das hier machen kann? Hab nur einmal einen Stack ala Floodfill gemacht.

Dejan Vu 13. Mär 2015 14:51

AW: Einheiten parsen
 
Na ja. Man braucht wirklich keinen modularen Aufbau und kann alles in eine Klasse stecken, ist schon klar. Irgendwo hier ist auch Code für einen Parser der mit 50 Zeilen Code auskommt.

Bjoerk 13. Mär 2015 15:30

AW: Einheiten parsen
 
Ich lass es rekursiv. Die Wahrscheinlichkeit eines Stackoverflow ist m.E. ziemlich gering.

Der Stack sieht z.B. so aus. Das ist keine UPN.

1+3+5

Result1 = 1+3+5 = Result3 + Result4 + Result5
Result2 = 1+3 (Left) = Result3 + Result4
Result3 = 1 (Left)
Result4 = 3 (Right)
Result5 = 5 (Right)

Result = Result1

Bjoerk 13. Mär 2015 16:32

AW: Einheiten parsen
 
Zitat:

Zitat von Dejan Vu (Beitrag 1293385)
Irgendwo hier ist auch Code für einen Parser der mit 50 Zeilen Code auskommt.

50 Zeilen ist heftig. Bin bei 120 Zeilen (inkl. unärem Minus). Schönes WE. :)
Delphi-Quellcode:
unit uSimpleParser; // (C) 2011, Dr. Joachim Mohr, Dipl.-Ing. Thomas Abel;

interface

uses
  SysUtils, StrUtils, Math;

function TermToFloat(S: string): double;

implementation

function OperatorPos(const Substr, S: string): integer;
var
  I, N: integer;
begin
  Result := 0;
  N := 0;
  for I := Length(S) downto 1 do
  begin
    if S[I] = '(' then Inc(N);
    if S[I] = ')' then Dec(N);
    if N = 0 then
      if PosEx(Substr, S, I) = I then
      begin
        Result := I;
        Break;
      end;
  end;
end;

function StrMid(const S: string; const A, B: integer): string;
begin
  Result := Copy(S, A, B - A + 1)
end;

function StrLeft(const S: string; const B: integer): string;
begin
  Result := StrMid(S, 1, B);
end;

function StrRight(const S: string; const A: integer): string;
begin
  Result := StrMid(S, A, Length(S));
end;

function Left(const S, Substr: string): string;
begin
  Result := StrLeft(S, OperatorPos(Substr, S) - 1);
end;

function Right(const S, Substr: string): string;
begin
  Result := StrRight(S, OperatorPos(Substr, S) + Length(Substr));
end;

function Parenthesis(var S: string; const Minus: boolean): boolean;
begin
  Result := false;
  if Minus then
  begin
    if Length(S) > 3 then
      if Copy(S, 1, 2) = '~(' then
      begin
        S := StrMid(S, 3, Length(S) - 1);
        Result := true;
      end;
  end
  else
  begin
    if Length(S) > 2 then
      if S[1] = '(' then
      begin
        S := StrMid(S, 2, Length(S) - 1);
        Result := true;
      end;
  end;
end;

function Solve(S: string): double;
begin
  try
    if OperatorPos('+', S) > 0 then
      Result := Solve(Left(S, '+')) + Solve(Right(S, '+'))
    else
      if OperatorPos('-', S) > 0 then
        Result := Solve(Left(S, '-')) - Solve(Right(S, '-'))
      else
        if OperatorPos('*', S) > 0 then
          Result := Solve(Left(S, '*')) * Solve(Right(S, '*'))
        else
          if OperatorPos('/', S) > 0 then
            Result := Solve(Left(S, '/')) / Solve(Right(S, '/'))
          else
            if Parenthesis(S, true) then
              Result := -Solve(S)
            else
              if Parenthesis(S, false) then
                Result := Solve(S)
              else
                Result := StrToFloat(StringReplace(S, '~', '-', []));
  except
    Result := 0;
  end;
end;

function TermToFloat(S: string): double; // ~ für unäres Minus;
begin
  Result := 0;
  S := StringReplace(S, #32, '', [rfReplaceAll]);
  if Length(S) > 0 then
  begin
    if S[1] = '-' then S[1] := '~';
    if S[1] = '+' then Delete(S, 1, 1);
    S := StringReplace(S, '*+', '*', [rfReplaceAll]);
    S := StringReplace(S, '/+', '/', [rfReplaceAll]);
    S := StringReplace(S, '(+', '(', [rfReplaceAll]);
    S := StringReplace(S, '*-', '*~', [rfReplaceAll]);
    S := StringReplace(S, '/-', '/~', [rfReplaceAll]);
    S := StringReplace(S, '(-', '(~', [rfReplaceAll]);
    Result := Solve(S);
  end;
end;

end.

Sailor 13. Mär 2015 18:36

AW: Einheiten parsen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Es ist kein simples Problem, jedenfalls, wenn man es allgemeiner halten will. Ich habe mich aus Interesse mal daran versucht, im Anhang gibt es dazu ein kleines, bei weitem nicht perfektes Programm. Für eine generelle Lösung müßte man natürlich einen Einheitenparser gemäß SI implementieren.

Bjoerk 14. Mär 2015 15:48

AW: Einheiten parsen
 
Hallo Sailor,
konnte deinen Code leider nicht testen weil ich keine Generics hab. Was ich bei Tests mit meinen Parser jedoch festgestellt habe, daß im Verlauf des Parsens Einheiten entstehen können die nicht existieren, z.B. bei 1 kN/m3 / 1 m3 * 1 m3 = 1 kN/m6 * 1 m3 = 1 kN/m3. Deshalb mache ich zwei Durchläufe, einmal / vor * und einmal * vor /.

Sailor 14. Mär 2015 17:20

AW: Einheiten parsen
 
Die Generics sind nur der Beqemlichkeit geschuldet, man kann die üblichen arithmetischen Operatoren beibehalten. Ändere die generischen Operatoren (+,-,*,/) einfach in Funktionsaufrufe. d.h. da, wo in der Unit Actions "Normalize(Pop)+Normalize(Pop)" steht, machst Du "Add(Normalize(Pop),Normalize(Pop))" draus und änderst "CLASS OPERATOR Add..." in der Unit ExpStack zu "FUNCTION Add...". In der Unit Actions müßten bei "/" und "-" eigentlich die beiden oberen Stackelemente vertauscht werden. Ich habe aber gelernt, daß zumindest in D2010 die Parameter von rechts nach links ausgewertet werden. Na ja, Funktionen mit Seiteneffekten sind eben des Teufels. Ansonsten ist deine Aufgabe nicht ohne. Das eigentliche Problem ist die Synchronität von numerischen Ausdrücken und Einheitenausdrücken, ansonsten kann da viel Unsinn passieren. In einer verallgemeinerten Variante würde ich eine Arithmetik über den Einheiten aufbauen. Richtig fies wird es aber erst, wenn Variable zugelassen sind. Dann gibt es so schöne Dinge wie "3 kN/m * 4 kN / 3 m", eine echte Herausforderung :twisted:. Und wie gesagt, in meiner Variante gibt es sicher auch noch Überraschungen, ich hatte keine Zeit, das Ding ausgiebig zu testen.


Alle Zeitangaben in WEZ +1. Es ist jetzt 07:07 Uhr.
Seite 1 von 2  1 2      

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