BASIC code parsen...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich überlege gerade ein wenig, wie man BASIC Code parsen kann. Einen schrecklichen parser hatte ich gebastelt, dann gemerkt das es evtl. komplett mit einer Regular-Expression geht. Denn so kompliziert ist BASIC ja nicht.

So ganz klappt es aber doch noch nicht:

Code: Alles auswählen

ascii_listing = """\
10 PRINT "A STRING"
20 ' ONLY A COMMENT
30 A$="ANOTHER STRING" ' INLINE COMMENT
40 REM COMMENT AND NEXT LINE IS CODE ONLY:
50 B=3
60 PRINT "STRING WIHOUT CLOSE!
70 GOTO 10 : REM COMMENT AS SECOND STATEMENT
"""

regex = re.compile(
    r"""
        ^
        (?P<line_number>\d+)\s?
        (
                (
                    (?P<pre_comment>[^"\n]*)
                    (?P<comment> ('|REM).* )
                )?
            |
                (
                    (?P<pre_string>[^"\n]*)
                    (?P<string>"[^"\n]*"?)
                    (?P<post_string>[^"\n]*)
                )?
            |
            (?P<code>.*)
        )$
    """,
    re.VERBOSE | re.MULTILINE
)


def debug_match(match):
    print "Line: >>>%s<<<\n" % match.group(0)
    match_groupdict = match.groupdict()
    for name, text in match_groupdict.items():
        if text is not None:
            print "%15s: %r" % (name, text)
    print "-" * 79

print "-" * 79
regex.sub(debug_match, ascii_listing)
Ausgabe:

Code: Alles auswählen

-------------------------------------------------------------------------------
Line: >>>10 PRINT "A STRING"<<<

    line_number: '10'
     pre_string: 'PRINT '
         string: '"A STRING"'
    post_string: ''
-------------------------------------------------------------------------------
Line: >>>20 ' ONLY A COMMENT<<<

        comment: "' ONLY A COMMENT"
    line_number: '20'
    pre_comment: ''
-------------------------------------------------------------------------------
Line: >>>30 A$="ANOTHER STRING" ' INLINE COMMENT<<<

    line_number: '30'
     pre_string: 'A$='
         string: '"ANOTHER STRING"'
    post_string: " ' INLINE COMMENT"
-------------------------------------------------------------------------------
Line: >>>40 REM COMMENT AND NEXT LINE IS CODE ONLY:<<<

        comment: 'REM COMMENT AND NEXT LINE IS CODE ONLY:'
    line_number: '40'
    pre_comment: ''
-------------------------------------------------------------------------------
Line: >>>50 B=3<<<

    line_number: '50'
           code: 'B=3'
-------------------------------------------------------------------------------
Line: >>>60 PRINT "STRING WIHOUT CLOSE!<<<

    line_number: '60'
     pre_string: 'PRINT '
         string: '"STRING WIHOUT CLOSE!'
    post_string: ''
-------------------------------------------------------------------------------
Line: >>>70 GOTO 10 : REM COMMENT AS SECOND STATEMENT<<<

        comment: 'REM COMMENT AS SECOND STATEMENT'
    line_number: '70'
    pre_comment: 'GOTO 10 : '
-------------------------------------------------------------------------------

Damit kann man eigentlich schon gut leben, aber so ganz richtig ist es nicht, denn z.B. ist Zeile 30 falsch: post_string: " ' INLINE COMMENT" sollte eigentlich als "comment" erkannt werden.

Geht das überhaupt, alles mit einer RE zu erschlagen?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Das sieht alles etwas zu kurz gegriffen aus. Du hast da entweder eine Zeile mit irgendwas gefolgt von einem Kommentar, oder irgendwas, Zeichenkette, irgendwas. Was ist denn mit Zeilen mit Zeichenkette *und* Kommentar, oder mit Zeilen mit mehr als einer Zeichenkette? ``10 INPUT"NAME";N$;PRINT"HALLO ";N$;"!":REM YEAH``.

Und aus eigener Erfahrung mit einem Tokenizer für CBM BASIC: Neben ``REM`` fällt auch ``DATA`` ein wenig aus dem Rahmen. Ist vermutlich bei Deinem BASIC auch so.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Stimmt. Nach DATA kann wohl "alles" kommen.

Bin gerade dabei mehrere REGEXs zu bauen und diese nacheinander ab zu arbeiten. So elegant wirkt das allerdings nicht.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Naja, ”alles”, aber nur bis zum nächsten ':' der nicht in einer Zeichenkette steckt.

Der Code ist mir zwar ein bisschen peinlich und, äh, unterdokumentiert, aber das hier benutze ich zum (de)tokenisieren von CBM BASIC V2: https://bitbucket.org/blackjack/pycbm-b ... at=default
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab mal ein bisschen mit DragonPy probiert...

Offensichtlich darf nach DATA alles kommen. Egal was, es wird mit einem Komma getrennt und an READ weiter gereicht.

bsp:

Code: Alles auswählen

10 PRINT "GO":DATA 00
20 READ HB$
30 IF HB$="END" THEN 130
40 PRINT HB$;
50 GOTO 20
60 ' FOO
70 DATA XX,DATA YY
80 DATA "JO"
90 DATA 4F,'REM?
100 PRINT "X":DATA 5B
110 ' BAR
120 DATA 5F,END
130 PRINT:PRINT "BYE"
Ausgabe ist dann:
RUN
GO
00XXDATA YYJO4F'REM?5B5F
BYE
OK

EIN ":" in DATA geht aber anscheinend nicht. bzw. wird ignoriert.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Also beim C64 kann man nach einem ``DATA`` wieder ganz normal mit der nächsten Anweisung weitermachen wenn man die mit einem Doppelpunkt trennt:

Code: Alles auswählen

10 DATA A,B,C:PRINT"HALLO"
RUN
HALLO

READY.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Stimmt, das geht auch:

Code: Alles auswählen

10 DATA A,B,C:PRINT"HALLO"
20 FOR I=1 TO 3
25 READ D$
30 PRINT I;D$
40 NEXT
RUN
HALLO
1 A
2 B
3 C
OK
Dann muß ich wohl vorher was falsch gemacht haben...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Der Code ist mir zwar ein bisschen peinlich und, äh, unterdokumentiert, aber das hier benutze ich zum (de)tokenisieren von CBM BASIC V2: https://bitbucket.org/blackjack/pycbm-b ... at=default
So viel wie du da gemacht hast, will ich erstmal nicht.
Ich möchte nur Unterscheiden zwischen: Code, String, Data und Kommentare
Alles weitere kommt im nächsten Schritt.

Dennoch finde ich deine def _tokenize() mit dem regex.finditer() interessant.
Ich experimentiere, ob ich nicht mit der regex ("|DATA|:|REM|') und dem selben Prinzip klar komme: Wäre allerdings dann so eine "state" Geschichte. Also:
1. Beim ersten " bin ich im String-Teil, bis wieder ein " oder \n kommt
2. Beim ersten DATA warten bis : oder \n
3. erstes REM oder ' bis \n -> Kommentar
4. alles andere ist Code

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Bei 2. darauf achten das der ':' auch innerhalb einer Zeichenkette stehen kann und dann nicht ”zählt”. Beispiel ``10 DATA"ANSWER:",42:PRINT"HALLO"``, da muss man bis zum ':' vor dem ``PRINT`` gehen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Danke für den Hinweis. Hätte ich sicherlich übersehen...

Mit https://github.com/jedie/DragonPy/commi ... 51af4ef708 ist nun die erste Funktionierende Lösung fertig.
Hoffe ich hab mit den unittests auch alle Eventualitäten abgedeckt.

So richtig schön finde ich die Lösung nicht. Es wiederholen sich recht ähnliche Code-Teile, die aber doch kleine Unterschiede haben :?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten