Recursive Parsing: Basics! Wie geht das?

Für allgemeine Fragen zur Programmierung mit PureBasic.
SMaag
Beiträge: 184
Registriert: 08.05.2022 12:58

Recursive Parsing: Basics! Wie geht das?

Beitrag von SMaag »

Ich erinnere mich dunkel an einen Grundkurs Compilertechnik während des Studiums.
Dort hatten wir recursive Parser programmiert. Das erste Einstiegsbeispiel war ein Number-Parser, der einfach einen String aus Ziffern in einen Integer wandelt. Analog zum Val() Befehl. Leider find ich keine Unterlagen und keinen einzigen Beispielcode mehr dazu. Ich probier das nun schon über längere Zeit immer wieder. Es klappt aber einfach nicht. Entweder es kommt nix raus, oder es stürtzt gleich ab.
Die Umwandlung blanker Ziffern in einen Int so zu machen ist natürlich quatsch, aber es geht mir rein um die recursive Technik, sonst nichts.
Wenn ich das wieder verstanden hab, kann man mit der selben Technik komplette Formelparser bauen.

Ich habe im Endziel vor, mit dieser Technik, 3D Step und IGES Files zu parsen. STEP ist recht kompliziert mit seinen verschachtelten vorwärts und rückwärts Verweisen. Mit recursive parsing, muss man sich nur strikt an die Regeln halten und zum Schluß kommt ein korrektes Ergebnis raus, obwohl man nicht unbedingt versteht warum. Auch wenn man es selbst programmiert hat.

hier mal mein Code, der aber leider nicht geht!
Was ich einfach nicht blicke ist, wie ich dieses aufaddieren machen muss.
Ich versteh noch, dass erst eine Art Baum bis zum letzten Zeichen aufgebaut wird. Dann kollabiert die ganze Struktur rückwärts
und der Rückgabewert der ParseDec() Funktion muss aufaddiert werden und in jedem Zyklus muss die 10er-Stelle um 1 gerückt werden.
Dazu das N als Static was in 1,10,100,1000... läuft.
Ich hoffe es weis von euch jemand wie das geht!

Code: Alles auswählen

Procedure.i ParseDec(*Str.Character)
  ; recursive parsing a String to convert it into an Integer 
  ; das geht leider noch nicht!
  Static N
  Protected ret
  
  Select *Str\c 
              
    Case '0' To '9'   ; digigtal numbers
      
      ret =  ParseDec(*Str + SizeOf(Character)) * N
      Debug ret
      N * 10
      ProcedureReturn ret
              
    Default  ; End of decimals reached
       N = 1	; start with N=1
      ProcedureReturn 0
  EndSelect
EndProcedure

Procedure ParseDec_Classic(*Str.Character)
  ; classic Version -> das funktioniert! 
  Protected val, neg
  
  While *Str\c = 32 Or *Str\c = 9
    ; remove Spaces and Tabs
    *Str + SizeOf(Character)  
  Wend
  
  ; check Sign
  If *Str\c = '-'
    neg = #True 
    *Str + SizeOf(Character)
  ElseIf *Str\c = '+'
    *Str + SizeOf(Character)
  EndIf 
  
  While *Str\c   
    Select *Str\c
        
      Case '0' To '9' 
         val = val * 10 + (*Str\c - 48)
         
      Default ; EndOfDecimals reached
        Break
        
    EndSelect
    *Str + SizeOf(Character) ; Pointer to NextChar
  Wend
  
  If neg 
    ProcedureReturn -val
  Else
    ProcedureReturn val
  EndIf
    
EndProcedure

Define res
Define StrVal.s

StrVal = "12345678"

Debug "recursive Version"
res = ParseDec(@StrVal)
Debug "Ergebnis = " + Str(res)

Debug #Null$
Debug "Classic Version"
res = ParseDec_Classic(@StrVal)
Debug "Ergebnis = " + Str(res)

Benutzeravatar
Macros
Beiträge: 1361
Registriert: 23.12.2005 15:00
Wohnort: Olching(bei FFB)
Kontaktdaten:

Re: Recursive Parsing: Basics! Wie geht das?

Beitrag von Macros »

Hi Smaag,

da waren gleich ein zwei Fehler drinnen. Vermutlich weil da mal einer war und dann an falscher Stelle versuch wurde das Problem zu korrgieren ;)
Der Ansatz war schon richtig.

Hier mal eine funktionierende Version:

Code: Alles auswählen

Procedure.i ParseDec(*Str.Character)
  ; recursive parsing a String to convert it into an Integer 
  Static N
  Protected RET
  
  Select *Str\c 
              
    Case '0' To '9'   ; digigtal numbers
      RET =  ParseDec(*Str + SizeOf(Character))   +(*Str\c-'0')*N
      N * 10 ; Multiply N by 10 for the next digit
      ProcedureReturn RET
              
    Default  ; End of decimals reached
      N = 1  ; start with N=1
      ProcedureReturn 0 ; Return 0 to not change the result of the parsing
  EndSelect
EndProcedure


Define res
Define StrVal.s

StrVal = "12345678"

res = ParseDec(@StrVal)
Debug "Ergebnis = " + Str(res)
Korrekturen die ich vorgenommen habe:
  • Die Wertigkeit des 0 Characters abziehen, sonst entspricht eine 0 eben keiner Null in der Rechnung.
  • Die Multiplikation mit der Zehnerpotenzvariable N nur für die aktuelle Stelle, der Rest der schon geparsten Zahl wird hinzuaddiert.
Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Recursive Parsing: Basics! Wie geht das?

Beitrag von NicTheQuick »

Blöde Frage, weil das auch schon ewig her ist für mich aus dem Studium: Ist das wirklich sinnvoll so mit der statischen Variable zu arbeiten? Im Code oben wird der Wert für N für die aktuelle Rekursion von den nächsten Rekursionen gesetzt.
Ich hätte das tatsächlich umgekehrt gemacht mit einem weiteren optionalen Parameter. Oder ist das nicht wie man das üblicherweise tut?

Code: Alles auswählen

Procedure.i ParseDec(*Str.Character, prefix.i = 0)
	; recursive parsing a String to convert it into an Integer 
	Protected RET
	
	Select *Str\c 
		Case '0' To '9'   ; digigtal numbers
			RET =  ParseDec(*Str + SizeOf(Character), (prefix * 10) + (*Str\c - '0'))
			ProcedureReturn RET
			
		Default  ; End of decimals reached
			ProcedureReturn prefix
	EndSelect
EndProcedure


Define res
Define StrVal.s

StrVal = "12345678"

res = ParseDec(@StrVal)
Debug "Ergebnis = " + Str(res)
Benutzeravatar
Macros
Beiträge: 1361
Registriert: 23.12.2005 15:00
Wohnort: Olching(bei FFB)
Kontaktdaten:

Re: Recursive Parsing: Basics! Wie geht das?

Beitrag von Macros »

Hätte ich auch intuitiv so begonnen, aber da kann man wohl streiten.

Hier ist die statische Variable recht elegant wegen einem leichten Geschwindigkeitsvorteil und kleinerem Stack.

Großer Nachtteil von statischen Variablen ist die fehlende Möglichkeit für parallele Threads.

Edithinweis: Im Beitrag stand mal mehr, das war aber Schmarrn :wink:

Edit2: Hier aber noch der Hinweis, dass die statische Variable sozusagen ein Cheat ist, es ist keine voll rekursive Funktion mehr. Deswegen hat man im Studium auch gelernt sie nicht zu verwenden. Entsprechend gibt es sowas bei streng funktionalen Sprachen nicht.
Funktionelle Programmierung war ein Albtraum in meinem Studium und verwendet gesehen habs ichs nachher nur bei Spielereien.
Bild
SMaag
Beiträge: 184
Registriert: 08.05.2022 12:58

gelöst! Re: Recursive Parsing: Basics! Wie geht das?

Beitrag von SMaag »

Wow! Danke!

Ich hatte schon so viel rumprobiert und auf Papier nachgezeichnet sowie x-mal komplett von vorne begonnen, dass
ich es einfach nicht mehr geblickt hab.

Die Version von Marcos hatte ich gefühlt auch schon!
Nochmal darüber nachgedacht, ist es völlig klar, dass ich irgendwo 1x den Character in einen Wert umrechnen muss!

Die Version mit von NickTheQuick mit dem Prefix bereitet mir gerade noch Kopfschmerzen!

Da werd ich nochmal drüber schlafen müssen.

Dann geht's an Lektion2, den Formel Interpreter.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: gelöst! Re: Recursive Parsing: Basics! Wie geht das?

Beitrag von NicTheQuick »

SMaag hat geschrieben: 25.08.2023 13:10Die Version mit von NickTheQuick mit dem Prefix bereitet mir gerade noch Kopfschmerzen!
Füge einfach mal ein paar Debugs ein um dir zum Beispiel RET und prefix ausgeben zu lassen. Vielleicht macht es dann Klick. :)
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: gelöst! Re: Recursive Parsing: Basics! Wie geht das?

Beitrag von Kurzer »

SMaag hat geschrieben: 25.08.2023 13:10Die Version mit von NickTheQuick mit dem Prefix bereitet mir gerade noch Kopfschmerzen!
In prefix wächst bei jeden Aufruf der Prozedur ParseDec() das Integer-Äquivalent zu StrVal heran.

Mit prefix wird der bisher nach Integer gewandelte Teil der StrVal an die Prozedur mit übergeben, damit das nicht in irgend einer globalen Variablen zwischengespeichert werden muss.
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
DarkDragon
Beiträge: 6291
Registriert: 29.08.2004 08:37
Computerausstattung: Hoffentlich bald keine mehr
Kontaktdaten:

Re: Recursive Parsing: Basics! Wie geht das?

Beitrag von DarkDragon »

Macros hat geschrieben: 25.08.2023 11:35 Hätte ich auch intuitiv so begonnen, aber da kann man wohl streiten.

Hier ist die statische Variable recht elegant wegen einem leichten Geschwindigkeitsvorteil und kleinerem Stack.
Stattdessen kann man auch

1. Einen eigenen Stack nehmen (LinkedList kann man dafür nutzen) und das ganze in eine Schleife umwandeln.
2. Statt mehrerer Parameter einen Pointer übergeben

Code: Alles auswählen

Structure Args
  x.i
  y.i
EndStructure

Procedure Recursion(*args.Args)
...
EndProcedure
Bei anderen Sprachen könnte man innere Funktionen und Closures verwenden. Das sähe syntaktisch schöner aus.
Angenommen es gäbe einen Algorithmus mit imaginärer Laufzeit O(i * n), dann gilt O((i * n)^2) = O(-1 * n^2) d.h. wenn man diesen Algorithmus verschachtelt ist er fertig, bevor er angefangen hat.
Antworten