WideString-Stack

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Nils
Beiträge: 130
Registriert: Mo 28. Mai 2007, 12:36
Kontaktdaten:

WideString-Stack

Beitrag von Nils »

Hi,

bisher kam ich immer mit ShortStrings aus, nun benötige ich allerdings WideStrings. Daher muss ich meine Stackklasse anpassen. Früher genügte es, einfach via SizeOf(Record) den nötigen Platz im Speicher zu ermitteln. Nur wie macht man das bei Records mit WideStrings ?

Falls das Interesse besteht, hier die Klassen selbst:

Code: Alles auswählen

TStack = class
  private
    fDataSize             : Word;
    fSize, fStackBaseSize : Cardinal;
    fCount                : Cardinal;
    fStack                : PByte;
  protected
    function GetDataSize: Word; virtual; abstract;
  public
    constructor Create(aSize : Cardinal);
    destructor Destroy; override;
    procedure Push(var p);
    function Pop   : Pointer;
    function Peek  : Pointer;
    property Count : Cardinal read fCount;
  end;
 
  TTokenStack = class(TStack)
  protected
    function GetDataSize : Word; override;
  public
    function Pop : TToken;
    function Peek : TToken;
    procedure Push(Token : TToken);
  end;
Das ist die Basisklasse, welche virtuell arbeitet:

Code: Alles auswählen

// TStack arbeitet vollkommen virtuell
constructor TStack.Create(aSize : Cardinal);
begin
  inherited Create;
  fDataSize      := GetDataSize;    // Datengröße bestimmen
  fStackBaseSize := aSize;          // Größe festhalten
  fSize          := fStackBaseSize; // Größe setzen
  GetMem(fStack, fSize*fDataSize);  // Speicher reservieren
  dec(fStack, fDataSize);
end;
 
destructor TStack.Destroy;
begin
  // Speicher freigeben
  dec(fStack, Pred(fCount)*fDataSize);
  FreeMem(fStack);
  inherited;
end;
 
function TStack.Peek : Pointer;
begin
  // Hochpunkt bestimmen
  Result := fStack;
end;
 
function TStack.Pop : Pointer;
begin
  if fSize-fCount >= fStackBaseSize then
  begin
    // Position heruntersetzen
    dec(fSize, fStackBaseSize);
    dec(fStack, Pred(fCount)*fDataSize);
    // Speicher realloziieren
    ReallocMem(fStack, fSize*fDataSize);
    inc(fStack, Pred(fCount)*fDataSize);
  end;
  // Eintrag zurückgeben und vom Stack entfernen
  Result := fStack;
  dec(fStack, fDataSize);
  dec(fCount);
end;
 
procedure TStack.Push(var p);
begin
  if fCount >= fSize then
  begin
    // Position erhöhen
    inc(fSize, fStackBaseSize);
    dec(fStack, Pred(fCount)*fDataSize);
    // Speicher realloziieren
    ReallocMem(fStack, fSize*fDataSize);
    inc(fStack, Pred(fCount)*fDataSize);
  end;
  // Speicherbereich mit neuem Eintrag füllen
  inc(fStack, fDataSize);
  Move(p, fStack^, fDataSize);
  inc(fCount);
end;
Darauf basieren dann die Klassen der einzelnen Typen. Ich habe davon zwei Klassen, eine für Extended und eine für einen eigenen Datentyp der WideStrings benutzt. Da Extended funktioniert, reicht denke ich die andere Klasse:

Code: Alles auswählen

type
  TToken = record
    Inhalt     : WideString;
    Wertigkeit : ShortInt;
  end;
  TDynTokenArray = Array of TToken;
  PToken = ^TToken;
 
// TTokenStack liefert die nötigen Größen und castet alles auf Basis von TStack
function TTokenStack.GetDataSize : Word;
begin
  // Der benötigte Speicher jedes TokenStack-Eintrags ist so groß wie der eines TToken
  Result := SizeOf(TToken);
end;
 
function TTokenStack.Pop : TToken;
begin
  // Die virtuellen Daten von TStack besorgen, als TToken casten und zurückgeben
  Result := PToken(inherited Pop)^;
end;
 
procedure TTokenStack.Push(Token : TToken);
begin
  // Daten in Stack pushen
  inherited Push(Token);
end;
 
function TTokenStack.Peek : TToken;
begin
  // Die virtuellen Daten von TStack besorgen, als TToken casten und zurückgeben
  Result := PToken(inherited Peek)^;
end;
Meine Musik: spiker-music.net

Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString-Stack

Beitrag von theo »

Nicht dass ich den Code verstünde, aber hast du denn früher mit ShortStrings (max. 256 Zeichen) gearbeitet?

Weil die Grösse von dem:

TToken = record
Inhalt : WideString;
Wertigkeit : ShortInt;
end;

Ist immer gleich, d.h. Grösse von ShortInt (2) + Grösse von Pointer auf WideString (4 bei 32bit). Das wäre aber auch mit AnsiString so gewesen.

Wenn du weiterhin mit ShortString arbeiten willst, müsstest du den Text wieder UTF8 enkodieren.
Vielleicht geht auch irgendwie WideToAnsi oder sowas. Müsste man testen mit und ohne WideStringManager.
Lustig, wa?

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: WideString-Stack

Beitrag von mse »

Am einfachsten ist die Verwendung eines dynamischen Arrays of deinrecordtyp und die Adressierung mittels Arrayindex, da sich dann der Compiler ums Initialisieren und Abräumen kümmert. Da du verschieden Recordtypen auf dem Stack hast, kommt diese Lösung nicht in Frage.
Was für dynamische Typen (ansistring, widestrings dynamische arrays) gemacht werden muss:
- initialisiere die dynamischen Variablen mit NIL, am einfachsten mit fillchar(thebuffer,buffersize,0);
- rufe finalize(therecord) auf zum Abräumen. Finalize setzt auch die dynamischen Felder auf NIL, das Nullen ist nur beim Einrichten des Puffers einmalig notwendig.
Falls es dich interessiert, in MSEgui's tdatalist und Konsorten musste ich mich mit ähnlichen Problemen herumschlagen, wirf doch mal einen Blick in lib/common/kernel/msedatalist.pas oder lib/common/kernel/mselist.pas trecordlist und tmseindexednamelist für ein Beispiel mit widestring.

Martin

Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString-Stack

Beitrag von theo »

Eine Idee hätte ich noch.
Lass den Code einfach wie er war. Alle Strings wie früher, auch ShortStrings.

Dann schalte den Widestring Manager aus, ersetzte das Grad Zeichen durch #$B0

Dann Lade deinen String von Memo einfach in einen Ansistring.

Das hier geht nämlich ohne WS-Manager, auch für öäü:

procedure TForm1.Button5Click(Sender: TObject);
var s:String;
begin
s:=UTF8Decode(Edit1.text);
Caption:=UTF8Encode(s);
end;

Das könnte klappen, aber natürlich nicht für Kyrillisch (Russisch) oder so.
Bisschen ein Hack aber probier's mal.

Nils
Beiträge: 130
Registriert: Mo 28. Mai 2007, 12:36
Kontaktdaten:

Re: WideString-Stack

Beitrag von Nils »

Im Folgenden der aktuelle Code, welcher eine Eingabe auseinanderbaut. Wie man sehen kann, wird alles in ein dynamisches Array gepackt. Später wird mit dem Array noch rumhantiert. Es werden dort Teile des Arrays in den Stack gepusht. Beim Pop tritt dann eine Zugriffsverletzung auf. In der Verarbeitungsprozedur wird keine Variable benutzt, es wird ausschließlich der Stack - welcher auf TToken basiert - sowie eine simple Stringlist verwendet. Daher ist dort der Fehler denke ich ausgeschlossen. Es scheint, als liefe was beim hinzufügen in das Array schief. Denn eine Zugriffsverletzung im Stack weist auf falsche Größen hin. Seht Ihr einen Grund dafür ?

Code: Alles auswählen

type
  TToken = record
    Inhalt     : ShortString;
    Wertigkeit : ShortInt;
  end;
  TDynTokenArray = Array of TToken;
  PToken = ^TToken;

Code: Alles auswählen

Tokens := TokenizeInfix(UTF8Decode(e.Text));

Code: Alles auswählen

function TfrmMain.TokenizeInfix(s : WideString) : TDynTokenArray;
  procedure AddArray(aInhalt : WideString; aWertigkeit : Integer);
  begin
    SetLength(Result, Succ(Length(Result)));
    with Result[High(Result)] do
    begin
      Inhalt     := UTF8Encode(aInhalt);
      Wertigkeit := aWertigkeit;
    end;
  end;
var i : Integer;
begin
  i := 1;
  while i <= Length(s) do
  begin
    if s[i] in ['^', '!', #$B0] then
    begin
      AddArray(s[i], 9);
      inc(i, Length(s[i]));
    else
      raise Exception.Create('[TOKENIZER] Token "'+s[i]+'" unbekannt. Abbruch.');
  end;
end;
Meine Musik: spiker-music.net

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6770
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: WideString-Stack

Beitrag von af0815 »

Code: Alles auswählen

SetLength(Result, Succ(Length(Result)));
Es schaut es ziemlich gewagt aus :-)

Sollte es nicht nur um die Länge von TToken erweitert werden?
Zuletzt geändert von af0815 am So 8. Feb 2009, 22:28, insgesamt 1-mal geändert.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Nils
Beiträge: 130
Registriert: Mo 28. Mai 2007, 12:36
Kontaktdaten:

Re: WideString-Stack

Beitrag von Nils »

Da die Prozedur quasi in der Funktion drinnen ist, ist Result das der Funktion.
Meine Musik: spiker-music.net

Benutzeravatar
theo
Beiträge: 10869
Registriert: Mo 11. Sep 2006, 19:01

Re: WideString-Stack

Beitrag von theo »

Nils, hast du diesen Code gänzlich selber geschrieben?

Ich muss af0815 recht geben. Der Code sieht ziemlich abenteuerlich aus. Der ist "seeking for trouble" ;-)

Vor allem im Stack mit ReallocMem, Move und Freunden. Da musst du schon ganz genau wissen was du womit tust.
Mir wär das zu unübersichtlich.
Kannst du keine Lösung bauen, welche mit den FCL Standardklassen wie TStack und TList statt DynArray auskommt?
Das ist alles getestet und damit ersparst du dir haufenweise Ärger.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6770
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: WideString-Stack

Beitrag von af0815 »

Nils hat geschrieben:Da die Prozedur quasi in der Funktion drinnen ist, ist Result das der Funktion.
Das war mir klar.

Wie wäre es, wenn du dir mal den Inhalt von Dynarray auf die Konsole ausgibst und auch die Speicherbelegung und das mal nachrechnest. Denn du vermutest ja den Fehler im Push Bereich. Also müsste er bei einer händischen Kontrolle ja auffallen.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: WideString-Stack

Beitrag von mse »

Nils hat geschrieben:Im Folgenden der aktuelle Code, welcher eine Eingabe auseinanderbaut. Wie man sehen kann, wird alles in ein dynamisches Array gepackt. Später wird mit dem Array noch rumhantiert. Es werden dort Teile des Arrays in den Stack gepusht.
Da nun im stack keine referenz gezählten Typen mehr vorkommen, sollte es keine Probleme geben. Funktioniert denn die stack Klasse grundsätzlich? Hast du gezielte Tests der stack Grundfunktionen durchgeführt? Für mich sieht sie in Ordnung aus. Es wäre vielleicht einfacher, einen pointer auf den allozierten Speicherblock zu halten statt vom aktuellen stackpointer auszugehen.
In TfrmMain.TokenizeInfix fehlt die Initialisierung von result (result:= nil), ich glaube, der compiler erledigt dies von sich aus. Gab es keine Warnung? inc(i, Length(s)); entspricht wohl inc(i) da Length(s) immer 1 liefert.
SetLength(Result, Succ(Length(Result))); kann durch SetLength(Result, high(Result)+2); ersetzt werden, dies ist ein bisschen schneller, da in dynamischen Arrays count-1 und nicht count gespeichert ist. lenght(dynar) braucht eine zusätzliche Addition.

Code: Alles auswählen

function fpc_dynarray_length(p : pointer) : tdynarrayindex;[Public,Alias:'FPC_DYNARRAY_LENGTH']; compilerproc;
  begin
     if assigned(p) then
       fpc_dynarray_length:=pdynarray(p-sizeof(tdynarray))^.high+1
     else
       fpc_dynarray_length:=0;
  end;
 
 
function fpc_dynarray_high(p : pointer) : tdynarrayindex;[Public,Alias:'FPC_DYNARRAY_HIGH']; compilerproc;
  begin
     if assigned(p) then
       fpc_dynarray_high:=pdynarray(p-sizeof(tdynarray))^.high
     else
       fpc_dynarray_high:=-1;
  end;

Code: Alles auswählen

if s[i] in ['^', '!', #$B0] then      <<<<<<<<<-1
    begin                                    <<<<<<<<<-2
      AddArray(s[i], 9);
      inc(i, Length(s[i]));
    else
Für das begin bei 2 fehlt ein end?
Bei 1 wird von widechar mittels des widestringmanager auf char umgewandelt, es wäre besser die widechar direkt zu testen, der compiler macht das ebenfalls ohne set:
setofchar.png
Die Prozedur liesse sich etw so schreiben:

Code: Alles auswählen

function TfrmMain.TokenizeInfix(s : WideString) : TDynTokenArray;
  procedure AddArray(aInhalt : WideChar; aWertigkeit : Integer);
  begin
    SetLength(Result, high(result)+2);
    with Result[High(Result)] do
    begin
      Inhalt     := UTF8Encode(aInhalt);
      Wertigkeit := aWertigkeit;
    end;
  end;
var 
  i: Integer;
  wch1: widechar;
begin
  result:= nil;
  for i:= 1 to length(s) do begin
    wch1:= s[i];
    if (wch1 = '^') or (wch1 = '!') or (wch1 = #$B0) then begin
      AddArray(wch1, 9);
    end
    else begin
      raise Exception.Create('[TOKENIZER] Token "'+s[i]+'" unbekannt. Abbruch.');
    end;
  end;
end;
Edit: AddArray(s, 9); durch AddArray(wch1, 9); ersetzt
setofchar2.png

Nils
Beiträge: 130
Registriert: Mo 28. Mai 2007, 12:36
Kontaktdaten:

Re: WideString-Stack

Beitrag von Nils »

Das fehlende end; kommt von der Kürzung des Codes. Die Prozedur AddArray auf ein WideChar zu vergewaltigen ist auch unmöglich, aufgrund der Kürzung konnte man nicht sehen, dass auch Funktionsnamen ala sin, cos in einem Stück im Array landen - ebenso Zahlen. Ich habe jetzt gemacht, was du vorgeschlagen hast, aber der Fehler besteht weiterhin. Ich habe nun übrigens einfach mal eine ArrArrayC eingeführt, die AddArray bloß als WideChar darstellt. Das Result := nil habe ich extra weggelassen, da genau diese Codezeilen relativ überflüssig sind, da der Compiler sie übernimmt. Den Code hier könnt ihr denke ich zusammen mit dem Stack von oben direkt abkopieren und selbst kompilieren. Es sind einige Kommentare entfernt, da diese teilweise 10 Zeilen lang sind (Erklärungen des Codes). Ich empfände sie nämlich an eurer Stelle als sehr störend. Wie man leicht sehen kann (erster Kommentar), zerlegt der Algorithmus eine mathematische Rechnung in gewohnter Schreibweise in alle Einzelteile. Dabei wird drauf geachtet, das Zahlen größer als 9 trotzdem in einem "Arrayeintrag" landen, genauso auch Funktionsnamen. Um die Prozedur sinnvoll auszuführen ist eine Eingabe nötig. Ein TokenizeInfix(UTF8Decode('sin(25°)')) dürfte reichen. Ich bin mir da halt nicht sicher, was für ein String das ist, im Zweifelsfall einfach ein Edit nehmen, dann hat ihr es genau wie ich. Das nur für den Fall, dass hier jemand richtig ran will. Es ist natürlich mies, dass der Algo nun so groß hier steht, aber an sich ist er nicht gerade komplex und so dürften Missverständnisse beseitigt werden.
Die Fehlerursache kann ich nicht finden, eigentlich macht der Code so ja Sinn.

Code: Alles auswählen

// Zerlegt einen String in Infix-Notation in Tokens
function TfrmMain.TokenizeInfix(s : WideString) : TDynTokenArray;
  function ScanNumber(s : WideString; i : Integer) : WideString;
  var j : integer;
  begin
    j := i;
    while (j < Length(s)) and (s[Succ(j)] in ['0'..'9', '.', ',']) do
    begin
      if s[Succ(j)] = '.' then
        s[Succ(j)] := ',';
      inc(j);
    end;
    Result := Copy(s, i, j-i+1);
  end;
 
  procedure AddArray(aInhalt : WideString; aWertigkeit : Integer);
  begin
    SetLength(Result, High(Result)+2);
    with Result[High(Result)] do
    begin
      Inhalt     := UTF8Encode(aInhalt);
      Wertigkeit := aWertigkeit;
    end;
  end;
 
  procedure AddArrayC(aInhalt : WideChar; aWertigkeit : Integer);
  begin
    SetLength(Result, High(Result)+2);
    with Result[High(Result)] do
    begin
      Inhalt     := UTF8Encode(aInhalt);
      Wertigkeit := aWertigkeit;
    end;
  end;
var i, j   : Integer;
    ka, ke : Integer;
    Tmp    : WideString;
    WChar  : WideChar;
begin
  if s[1] in ['-', '+'] then
    s := '0'+s;
  i := 1;
  while i <= Length(s) do
  begin
    WChar := s[i];
    if (s[i] in ['0'..'9']) or (((Length(Result) > 0) and (s[i] in ['-', '+'])) and ((Result[High(Result)].Wertigkeit in [6, 9, 8]) or (Result[High(Result)].Wertigkeit = -1)))
    then
    begin
      // Hier kann man leicht nicht erlaubte mathematische Operatorenfolgen auszuschließen: ++ und -+ und ^+
      if s[i] = '+' then
        raise Exception.Create('[TOKENIZER] -+ oder ++ oder ^+ gefunden. Abbruch.');
      // Damit auch Zahlen >9 als EIN Token vorhanden sind, muss der String bei dem ersten Kontakt mit einer Zahl näher untersucht werden
      Tmp := ScanNumber(s, i);
      AddArray(Tmp, 0);
      inc(i, Length(Tmp));
    end else
    if s[i] in ['a'..'z', 'A'..'Z'] then
    begin
      // Buchstaben weisen eindeutig auf Funktionen hin.
      ka := -1;
      ke := -1;
      j  := i;
      // Da eine Funktion einen Parameter besitzt, welcher in Klammern steht, müssen die Positionen der Klammern im String bestimmt werden.
      while (j < Length(s)) do
      begin
        if s[Succ(j)] = '(' then
          ka := Succ(j)
        else
        if s[Succ(j)] = ')' then
        begin
          ke := Succ(j);
          Break;
        end;
        inc(j);
      end;
      if (ka <> -1) and (ke <> -1) then
      begin
        // Sind beide Klammern gefunden worden, muss man...
        // ...die öffnende Klammer als Token hinzufügen
        AddArray('(', -1);
        // ...den Funktionsterm als Tokens hinzufügen
        Result := TokenizeInfix(Copy(s, Succ(ka), Pred(ke-ka)));
        // ...die schließende Klammer als Token hinzufügen
        AddArray(')', -2);
        AddArray(Copy(s, i, ka-i), 10);
      end else
        // Ist kein Parameter vorhanden, handelt es sich um keine Funktion. Abbruch.
        raise Exception.Create('[TOKENIZER] Funktion enthält keinen Parameter. Abbruch.');
      i := Succ(ke);
    end else
    // Operatoren
    // Fakultäten und DegToRad ähneln von der endgültigen Abarbeitung her der Potenz
    if (WChar = '^') or (WChar = '!') or (WChar = #$B0) then
    begin
      //AddArray(s[i], 9); // Potenz vor...
      AddArrayC(WChar, 9); // Potenz vor...
      inc(i);
    end else
    if s[i] in ['*', '/', '%'] then
    begin
      AddArray(s[i], 8); // Punkt vor...
      inc(i);
    end else
    if s[i] in ['+', '-'] then
    begin
      AddArray(s[i], 6); // Strich vor...
      inc(i);
    end else
    if s[i] = '(' then
    begin
      AddArray(s[i], -1); // Klammer auf.
      inc(i);
    end else
    if s[i] = ')' then
    begin
      AddArray(s[i], -2); // Die Wertigkeit ist nur -2, da die Wertigkeit an sich unbedeutend und somit regelrecht am niedrigsten ist
      inc(i);
    end else
    // Leerzeichen werden ignoriert
    if s[i] = ' ' then
      inc(i)
    else
      // Im Falle eines unbekannten Zeichens, ist die Weiterzerlegung unmöglich.
      raise Exception.Create('[TOKENIZER] Token "'+s[i]+'" unbekannt. Abbruch.');
  end;
end;
Meine Musik: spiker-music.net

Antworten