Hallo alle zusammen,
ich versuche gerade mit der Parser Bibliothek pypeg2 eine kleine Sprache zu definieren. Ich habe vorher mit ply3.1 gearbeitet. Es gibt mittlerweile die Version ply 4.0. Aber im Gihub wird erwähnt, daß die Verwendung zwar frei ist, aber es sich nicht mehr um eine offiziell gepflegte Bibliothek handelt.
Daher mein Umstieg auf pypeg2. Meine Versuche funktionieren eigentlich, aber im Fehlerfall (falsche Sprachsyntax) wird die Fehler Position völlig falsch angegeben. Das erste Problem konnte ich lösen. Beim Parsen kann man beim Aufruf von pypeg2.parse( ...., comment=mycomment) angeben. "mycomment" ist dabei ein Syntax Regel (z.B. re.compile(...)). Der Kommentar wird dann ordnungsgemäß aus den zu parsenden Text entfernt, aber die Positionsangaben der gefundenen Textelemente ist völlig falsch, da der Kommentar in den Positionsangaben nicht berücksichtigt wird. Umgehen kann man das Problem, wenn der Kommentar statt im Parse Aufruf, als pypeg2 Grammatik mit berücksichtigt wird. [b]Aber auch dann sind die von pypeg2 (re.compile()?) angegebenen Positionsdaten nicht richtig. [/b]
z.B. pyppeg2 Grammatik eine Oktalzahl zu identifizieren grammar = re.compile('0[oO][0-7]+') (gesamter Beispiel Code siehe unten). Hier wird die Positionsangabe um 2 Zeichen zu kurz angegeben. Meine Vermutung war, daß die Oktalzahl Kennung "0o" nicht mitgezählt wird. Ich kann in pypeg2 in die Syntax Erkennung eingreifen und dann für die Oktalzahl die Textposition um 2 erhöhen (gleiches gilt für Hexzahlen und Binärzahlen).
Die Korrektur funktioniert aber nur beim ersten mal (siehe auskommentierten Text in Beispiel unten --> Zeilen 145 - 152). Dann verrutsch die Positionsangabe wieder und gibt nicht den von der Grammatik gefundene Code Position wieder. Was läuft da falsch? Wie muß ich den Code ändern , um die richtige Postion im geparsten Text zu finden?
Kurze Erläuterung zum Code Beispiel:
Am Anfang wird mein Wunsch definiert, wir eine Kommentar auszusehen hat (C++, C und Python Kommentar)
Dann wird eine Token definiert, da mir die pypeg2 Definition eines Symbols nicht gefällt (meine Variablen/Funktionsnamen müssen mit einem Buchstaben anfangen, dann sind aber auch Zahlen erlaubt. z.B. A1234 = 1234
soll erlaubt sein).
Dann erfolgt die pypeg2 Grammatik in einem Test String ein "quoted" String (URL als Spezialfall), eine float, hex, octal oder Integerzahl zu finden.
Ich greife immer direkt in die pypeg2 Parser Erkennung ein, (def __ini__() in den Klassendefinitionen), um später mehr oder weniger einen kompletten AST des geparten Textes zu haben)
Dabei konvertiere ich die erkannten Textpassagen in das entsprechende Datenformat "self.value = ) und erzeuge eine Zusatzvariable mit dem Variablen Typ (self.arg_type) --> alles für ein finales AST Ergebnis.
In Zeile 123 werden dann alle Definitionen zusammen gefaßt (incl. des Kommentars) --> class ParameterList:". Diese so definierte Parser Grammatik wird dann im Hauptprogramm verwendet (Zeile 179), um beim eigentlichen Parser Aufruf als "Grammatik mitgegeben zu werden.
In Zeile 114 wird die eigentliche Grammatik zum Erkennen eines Kommentars definiert, die in der Klasse ParameterList verwendet wird. Dies ist nötig, um zu verhindern, daß das Komma von dem Kommentar aus dem Test String gelöscht wird.
Im Hauptprogamm gebe ich dann die im Text erkannten Elemente aus und zur Kontrolle den Teil aus dem Test String, der eigentlich dem erkannten Element entspricht. Genau hier liegt das Problem. Die von pypeg2 oder dem re.compile() angegebene Text Position rutscht durch. Um sicherzustellen, daß der Parser die korrekten Position verendet, habe ich noch in den Klassen der Variablen Typ Erkennung die Variable "self.arg_orig" gesetzt --> dieser Inhalt entspricht immer meiner Erwartung.
Meine Vermutung, daß die Erkennung der Hex, Oktal und Binär Zahlen (ox", "0o", "0b") bei Zählen weggelassen wird, bestätigt sich leider nicht (siehe auskommentierten Text in Beispiel unten --> Zeilen 145 - 152). Wenn man den Code in diesen Zeilen aktiviert, werden die ersten Positionen im erkannten Text richtig, aber am Schluß rutsch das Ganze in der anderen Richtung durch
Ich habe den Beispiel Code in die Tags [code] und [\code] gestellt. In der Vorsschau wird allerdings nur der plain Code angezeigt --> hoffentlich nur ein Problem der Vorschau. Sonst ist das Code Beispiel unbrauchbar. Im plain Code Text sind die Einrückungen korrekt.
[code]
# -------------------------- Code Beispiel Start -----------------------------
"""Definition of the object classes to used in the parser."""
# Parser part: definition of the possible variable types
import re
from pypeg2 import parse, csl, some, ignore, maybe_some, blank, contiguous
# different kind of comments
# comment_c = re.compile("""/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/""", re.MULTILINE | re.DOTALL)
# comment_cpp_singleLine = re.compile("""\/\/[^\n\r]+?(?:\*\)|[\n\r])""")
# comment_c_and_cpp = re.compile("""(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)""")
# mycomment allow "/* ... */" (c comment) and "// ... " (cpp one line comment) and "# .... " (Python comment line)
mycomment = re.compile("""(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)|(#.*)""")
# needed for function and command identification
# token = re.compile('[a-zA-Z_]\w*') # token: only small and capital letters are allowed
token = re.compile(r"[a-zA-Z_][0-9a-zA-Z_]*") # token: first literal has to be a small or capital letter. Following literals can be digits or small or capital letters
class StrArg:
"""Pattern matching for quoted "string" (both quote types " and ' are allowed."""
grammar = re.compile(r"""(["'])((?:\\1|(?:(?!\1)).)*)(\1)""", re.MULTILINE | re.DOTALL)
# An URL is special kind of quoted string. Thus, decision if string represents URL can be done only after string parsing
@staticmethod
def identify_url(arg):
"""Identify if strng is URL."""
url = re.findall(r'(http|ftp|https|ftps):\/\/([\w\-_]+(?:(?:\.[\w\-_]+)+))([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?',
arg, re.IGNORECASE)
if(url):
return("URL", arg)
else:
return(str, arg)
def __init__(self, arg):
self.arg_type, self.value = self.identify_url(arg)
self.orig_str = repr(arg)
# floating constants
class FloatArg:
"""Pattern matching for "float" number."""
grammar = re.compile("(([+-]?[0-9]+(e-?\d+))|([+-]?\.\d+(e-?\d+)?)|([+-]?\d+\.\d*(e-?\d+)?)|([+-]?\d(e-?\d+))|([+-]?\d+\.\d*))",
re.IGNORECASE) # ignore case necessary for exponent "e" and "E" is allowed
def __init__(self, arg=""):
self.arg_type = float
self.value = float(arg)
self.orig_str = repr(arg)
class IntArg:
"""Pattern matching for "int" number."""
grammar = re.compile('[+-]?[0-9][0-9]*')
def __init__(self, arg=""):
self.arg_type = int
self.value = int(arg)
self.orig_str = repr(arg)
class OctalArg:
"""Pattern matching for "octal" number."""
grammar = re.compile('0[oO][0-7]+')
def __init__(self, arg=""):
self.arg_type = oct
self.value = int(arg, 8)
self.orig_str = repr(arg)
class HexArg:
"""Pattern matching for "hex" number."""
grammar = re.compile('0[xX][0-9a-fA-F]+')
def __init__(self, arg=""):
self.arg_type = hex
self.value = int(arg, 16)
self.orig_str = repr(arg)
class BinArg:
"""Pattern matching for "hex" number."""
grammar = re.compile('0[bB][01]+')
def __init__(self, arg=""):
self.arg_type = bin
self.value = int(arg, 2)
self.orig_str = repr(arg)
class BoolArg:
"""Pattern matching for "bool" number."""
grammar = re.compile('True|False')
def __init__(self, arg=""):
self.arg_type = bool
if(arg == "True"):
self.value = True
else:
self.value = False
self.orig_str = repr(arg)
class comment:
"""Definiton of comment in text."""
grammar = ignore(","), mycomment
def __init__(self, arg=""):
self.arg_type = "mycomment"
class ParameterList:
"""Parse single parameter."""
# Attention: serial of Argument in CSL list is important. Pattern matching works as rhl (right hand rule)
# Starting from left side letter of elements will be analyzed. If one rule matches to this left part
# this first rule will be taken, even, following letters break the rules.
# Therefore, first float number (floatArg) has to be tested ("." or "e" in pattern),
# then hex number (hexArg), the bin number (binArg) and octal
# number (octArg) has to be checked "0x", "0o" or "0b" as start pattern.
# Then intArg (only digits). Then the quoted string (StrArg) has to be checked.
# attention: "StrArg" is not allowed to start with digit as first letter.
# At least the bool literal has to be checked (boolArg)(literal 'True' or 'False)
grammar = some([csl([FloatArg, BinArg, OctalArg, HexArg, IntArg, StrArg, BoolArg]), comment])
def __init__(self, args=""):
self._parameter_list = []
offset_corr = 0
for item in args:
if(item.arg_type != "mycomment"):
item.position_in_text = (item.position_in_text[0], item.position_in_text[1] + offset_corr)
self._parameter_list.append(item)
# if hex, oct, bin as parameter set the "position_in_text" has to be increased by 2 ("ox", "0o", "ob")
# if(item.arg_type is hex):
# item.arg_type = int
# offset_corr += 2
# if(item.arg_type is oct):
# item.arg_type = int
# offset_corr += 2
# if(item.arg_type is bin):
# item.arg_type = int
if __name__ == '__main__':
# TEST_STRING = """122345, 0o12, True, 3.12345, // zweiter Kommmentar
# 0x7654, 6789, 0b11010, 'ssss', /* adasdasdasd
# adadda
# adsasddd
# */
# 3456e-5, 0o012676, 0xaaaa, # und letzer Kommenttar
# 3.12345e-10, 0xABC1, 0o10, 0b11111111, 0x1a,
# 11111111, False
# """
TEST_STRING = """122345, 0o12, True, 3.12345, # mein gewünschter Kommentar
0x7654, -6789, +676767, 0b11010, 'ssss', /* adasdasdasd
adadda
adsasddd
*/
3456e-5, 0o012676, 0xaaaaab , 0o1234567, # und letzer Kommenttar
3.12345e-10, 0xABC1, 0o10, 0b1111111100 , 0x1a,
+11111111, False
"""
# TEST_STRING = """123, 456, 789, 0"""
# TEST_STRING = """3.12345, 3.12345e-10, 0xABC1, 0o10, 0b11111111, 0x1a"""
# TEST_STRING = """'abcd', 'efgh', 'ijkl'"""
f = parse(TEST_STRING, ParameterList) #, comment=mycomment)
# print(" Parameter func: ", dir(f))
print(" Position in text from parameter list ", f.position_in_text)
for item in f._parameter_list:
print("Position in text: ", item.position_in_text, item.value, item.orig_str,
"--", TEST_STRING[item.position_in_text[1]:item.position_in_text[1]+len(item.orig_str)], "--")
# print(" --- var type, value, orig: ", item.arg_type, item.value, "orig: ", item.orig_str)
print("#"*40)
[/code]
pypeg2 , re.compile() --> Position im geparsten Text nicht korrekt
Ich habe gerade mal versucht deinen Post vernuenftig zu setzen - aber es will mir nicht gelingen. Mein momentaner Verdacht ist, dass deine umfangreiche Nutzung von [ und anderen Sonderzeichen da den Parser durcheinander bringt. Was ja durchaus ironisch ist. Vielleicht waere die Ablage in einem Pastebin & Link darauf ein funktionierender Workaround.
Hallo __deets__,
Für die Sonderzeichen im Code tut es mir leid. Aber die regex Regeln benutzen diese nun mal sehr intensiv. Ich habe versucht den Code partiell in diese Antwort zu kopieren, um die Stelle zu finden, die den Parser durcheinander bring --> jetzt sieht der Code, obwohl ich nichts geändert habe, in der Vorschau so aus wie gewünscht. Ich habe jetzt allerdings einen anderen Editor verwendet , aus dem heraus ich copy & past gemacht habe. Bei der originären Anfrage hatte ich den Code aus dem Microsoft VSCode Editor heraus kopiert. Es scheit, daß dabei nicht sichtbare Steuerzeichen mitkommmen, die den Parser durcheinander bringen.
Leider ist der Code jetzt zweimal im Forum. Sorry dafür
Trotzdem vielen Dank schon einmal für die Hilfe, überhaupt eine vernünfitige Anfrage in dem Forum hinzubekommmen.
Horst
Für die Sonderzeichen im Code tut es mir leid. Aber die regex Regeln benutzen diese nun mal sehr intensiv. Ich habe versucht den Code partiell in diese Antwort zu kopieren, um die Stelle zu finden, die den Parser durcheinander bring --> jetzt sieht der Code, obwohl ich nichts geändert habe, in der Vorschau so aus wie gewünscht. Ich habe jetzt allerdings einen anderen Editor verwendet , aus dem heraus ich copy & past gemacht habe. Bei der originären Anfrage hatte ich den Code aus dem Microsoft VSCode Editor heraus kopiert. Es scheit, daß dabei nicht sichtbare Steuerzeichen mitkommmen, die den Parser durcheinander bringen.
Leider ist der Code jetzt zweimal im Forum. Sorry dafür
Trotzdem vielen Dank schon einmal für die Hilfe, überhaupt eine vernünfitige Anfrage in dem Forum hinzubekommmen.
Horst
Code: Alles auswählen
"""Definition of the object classes to used in the parser."""
# Parser part: definition of the possible variable types
import re
from pypeg2 import parse, csl, some, ignore, maybe_some, blank, contiguous
# different kind of comments
# comment_c = re.compile("""/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/""", re.MULTILINE | re.DOTALL)
# comment_cpp_singleLine = re.compile("""\/\/[^\n\r]+?(?:\*\)|[\n\r])""")
# comment_c_and_cpp = re.compile("""(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)""")
# mycomment allow "/* ... */" (c comment) and "// ... " (cpp one line comment) and "# .... " (Python comment line)
mycomment = re.compile("""(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)|(#.*)""")
# needed for function and command identification
# token = re.compile('[a-zA-Z_]\w*') # token: only small and capital letters are allowed
token = re.compile(r"[a-zA-Z_][0-9a-zA-Z_]*") # token: first literal has to be a small or capital letter. Following literals can be digits or small or capital letters
class StrArg:
"""Pattern matching for quoted "string" (both quote types " and ' are allowed."""
grammar = re.compile(r"""(["'])((?:\\1|(?:(?!\1)).)*)(\1)""", re.MULTILINE | re.DOTALL)
# An URL is special kind of quoted string. Thus, decision if string represents URL can be done only after string parsing
@staticmethod
def identify_url(arg):
"""Identify if strng is URL."""
url = re.findall(r'(http|ftp|https|ftps):\/\/([\w\-_]+(?:(?:\.[\w\-_]+)+))([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?',
arg, re.IGNORECASE)
if(url):
return("URL", arg)
else:
return(str, arg)
def __init__(self, arg):
self.arg_type, self.value = self.identify_url(arg)
self.orig_str = repr(arg)
# floating constants
class FloatArg:
"""Pattern matching for "float" number."""
grammar = re.compile("(([+-]?[0-9]+(e-?\d+))|([+-]?\.\d+(e-?\d+)?)|([+-]?\d+\.\d*(e-?\d+)?)|([+-]?\d(e-?\d+))|([+-]?\d+\.\d*))",
re.IGNORECASE) # ignore case necessary for exponent "e" and "E" is allowed
def __init__(self, arg=""):
self.arg_type = float
self.value = float(arg)
self.orig_str = repr(arg)
class IntArg:
"""Pattern matching for "int" number."""
grammar = re.compile('[+-]?[0-9][0-9]*')
def __init__(self, arg=""):
self.arg_type = int
self.value = int(arg)
self.orig_str = repr(arg)
class OctalArg:
"""Pattern matching for "octal" number."""
grammar = re.compile('0[oO][0-7]+')
def __init__(self, arg=""):
self.arg_type = oct
self.value = int(arg, 8)
self.orig_str = repr(arg)
class HexArg:
"""Pattern matching for "hex" number."""
grammar = re.compile('0[xX][0-9a-fA-F]+')
def __init__(self, arg=""):
self.arg_type = hex
self.value = int(arg, 16)
self.orig_str = repr(arg)
class BinArg:
"""Pattern matching for "hex" number."""
grammar = re.compile('0[bB][01]+')
def __init__(self, arg=""):
self.arg_type = bin
self.value = int(arg, 2)
self.orig_str = repr(arg)
class BoolArg:
"""Pattern matching for "bool" number."""
grammar = re.compile('True|False')
def __init__(self, arg=""):
self.arg_type = bool
if(arg == "True"):
self.value = True
else:
self.value = False
self.orig_str = repr(arg)
class comment:
"""Definiton of comment in text."""
grammar = ignore(","), mycomment
def __init__(self, arg=""):
self.arg_type = "mycomment"
class ParameterList:
"""Parse single parameter."""
# Attention: serial of Argument in CSL list is important. Pattern matching works as rhl (right hand rule)
# Starting from left side letter of elements will be analyzed. If one rule matches to this left part
# this first rule will be taken, even, following letters break the rules.
# Therefore, first float number (floatArg) has to be tested ("." or "e" in pattern),
# then hex number (hexArg), the bin number (binArg) and octal
# number (octArg) has to be checked "0x", "0o" or "0b" as start pattern.
# Then intArg (only digits). Then the quoted string (StrArg) has to be checked.
# attention: "StrArg" is not allowed to start with digit as first letter.
# At least the bool literal has to be checked (boolArg)(literal 'True' or 'False)
grammar = some([csl([FloatArg, BinArg, OctalArg, HexArg, IntArg, StrArg, BoolArg]), comment])
def __init__(self, args=""):
self._parameter_list = []
offset_corr = 0
for item in args:
if(item.arg_type != "mycomment"):
item.position_in_text = (item.position_in_text[0], item.position_in_text[1] + offset_corr)
self._parameter_list.append(item)
# if hex, oct, bin as parameter set the "position_in_text" has to be increased by 2 ("ox", "0o", "ob")
# if(item.arg_type is hex):
# item.arg_type = int
# offset_corr += 2
# if(item.arg_type is oct):
# item.arg_type = int
# offset_corr += 2
# if(item.arg_type is bin):
# item.arg_type = int
if __name__ == '__main__':
# TEST_STRING = """122345, 0o12, True, 3.12345, // zweiter Kommmentar
# 0x7654, 6789, 0b11010, 'ssss', /* adasdasdasd
# adadda
# adsasddd
# */
# 3456e-5, 0o012676, 0xaaaa, # und letzer Kommenttar
# 3.12345e-10, 0xABC1, 0o10, 0b11111111, 0x1a,
# 11111111, False
# """
TEST_STRING = """122345, 0o12, True, 3.12345, # mein gewünschter Kommentar
0x7654, -6789, +676767, 0b11010, 'ssss', /* adasdasdasd
adadda
adsasddd
*/
3456e-5, 0o012676, 0xaaaaab , 0o1234567, # und letzer Kommenttar
3.12345e-10, 0xABC1, 0o10, 0b1111111100 , 0x1a,
+11111111, False
"""
# TEST_STRING = """123, 456, 789, 0"""
# TEST_STRING = """3.12345, 3.12345e-10, 0xABC1, 0o10, 0b11111111, 0x1a"""
# TEST_STRING = """'abcd', 'efgh', 'ijkl'"""
f = parse(TEST_STRING, ParameterList) #, comment=mycomment)
# print(" Parameter func: ", dir(f))
print(" Position in text from parameter list ", f.position_in_text)
for item in f._parameter_list:
print("Position in text: ", item.position_in_text, item.value, item.orig_str,
"--", TEST_STRING[item.position_in_text[1]:item.position_in_text[1]+len(item.orig_str)], "--")
# print(" --- var type, value, orig: ", item.arg_type, item.value, "orig: ", item.orig_str)
print("#"*40)
Alles gut, das Forum kommt damit schon klar. Das mit den Sonderzeichen ist natuerlich eine Erklaerung, nur etwas komisch, weil VSCode sowas eigentlich nicht beinhalten sollte. Nunja, wichtig ist ja nur, das es jetzt geht.
Mittlerweile ist es mir gelungen das Problem weiter einzukreisen. Der Kommentar in pypeg2 verursacht das eigentlich Problem
--> siehe den Code unten:
- Kommentar ist nur noch "#"
- wenn man in Zeile 50 den Text "# text" löscht, stimmen jetzt die Zeilen angaben von pypeg2, aber man sieht an der Zahl darüber, daß diese jetzt die ganzen Leerzeilen enthält und damit im Originaltext von pypeg2 die falsche Position angeben wird. Mit dem Kommentar im Text stimmen nicht einmal mehr die Zeilenangaben
--> siehe den Code unten:
- Kommentar ist nur noch "#"
- wenn man in Zeile 50 den Text "# text" löscht, stimmen jetzt die Zeilen angaben von pypeg2, aber man sieht an der Zahl darüber, daß diese jetzt die ganzen Leerzeilen enthält und damit im Originaltext von pypeg2 die falsche Position angeben wird. Mit dem Kommentar im Text stimmen nicht einmal mehr die Zeilenangaben
Code: Alles auswählen
"""Definition of the object classes to used in the parser."""
# Parser part: definition of the possible variable types
import re
from pypeg2 import parse, csl, some, ignore, endl, attr
class IntArg:
"""Pattern matching for "int" number."""
grammar = re.compile('[+-]?[0-9][0-9]*')
def __init__(self, arg=""):
self.arg_type = int
self.value = int(arg)
self.orig_str = repr(arg)
mycomment = re.compile("#.*")
class comment:
"""Definiton of comment in text."""
grammar = ignore(","), attr("mycomment", mycomment)
def __init__(self, arg=""):
self.arg_type = "mycomment"
class ParameterList:
"""Parse single parameter."""
grammar = some([csl([IntArg]), comment])
def __init__(self, args=""):
self._parameter_list = []
for item in args:
if(item.arg_type != "mycomment"):
item.position_in_text = (item.position_in_text[0], item.position_in_text[1])
self._parameter_list.append(item)
else:
self.comment = item
if __name__ == '__main__':
TEST_STRING = """122345, 44444,
5555, 6666,
777, 8888,
# text
9999, 101010
"""
f = parse(TEST_STRING, ParameterList)
print(" Position in text from parameter list ", f.position_in_text)
for item in f._parameter_list:
print("Position in text: ", item.position_in_text, item.value, item.orig_str,
"--", TEST_STRING[item.position_in_text[1]:item.position_in_text[1]+len(item.orig_str)], "--")
if(hasattr(f, "comment")):
print(" ------ ", f.comment.mycomment, "---")