Hallo Besucher, der Thread wurde 6,4k mal aufgerufen und enthält 37 Antworten

letzter Beitrag von JeeK am

C64: Ausführung eigener BASIC-Befehl nach einem "then" = Syntax Error

  • Gibt es in dem Basic 3.5 auch Spritebefehle? Auf dem Plus/4 gibt es keine Hardware Sprites. Mir würde so etwas vorschweben, wie z.B. Spritedefinition, Spritekollision, etc. Das könnte interessant für die Spieleprogrammierung sein. Was in diesem Zusammenhang auch gut passen würde, wären Befehle für die Erstellung eigener Zeichensätze, ebenfalls für die Spieleprogrammierung.


    Trotzdem mal vielen Dank für die BASIC35.crt. Die probiere ich dann auch mal aus.

    ___________________________________________________________________________
    Ultimate64, TAPunio, SD2IEC, ZX Spectrum 48k, 1581 Replik, C64 Laptop, C64 MK II, C116, SX64,
    MiSTer FPGA, TI99/4A mit PEB, Atari 800 XL, Anycubic I3 Mega, Mega65, C64 Modular, Uniprom64

  • Somit werde ich ein eigenes "IF"/"THEN"-Token anlegen und die Routine nachbauen und für mich nutzen.

    Du kannst auch einfach die Original-Token weiterbenutzen, Deine Routinen auf den entsprechenden Vektoren können ja auch diese Werte überprüfen.

  • Ich glaube, BREAK verhält sich nicht so, wie es sollte. Oder ich verstehe es falsch. Das hier funktioniert:


    Code
    1. 10 i=2
    2. 20 repeat:i=i+1:o=i
    3. 30 print "i";i
    4. 35 repeat:o=o-1:print "o";o
    5. 38 until o=0
    6. 50 until i=5


    Das hier endet zu früh:


    Code
    1. 10 i=2
    2. 20 repeat:i=i+1:o=i
    3. 30 print "i";i
    4. 35 repeat:o=o-1:print "o";o
    5. 36 break o=1
    6. 38 until o=0
    7. 50 until i=5


    ...und danach funktioniert auch das obere Beispiel nicht mehr. Irgendwie ist danach der State verdreht.


    Ersetzt man in unterem Beispiel Zeile 50 durch:


    Code
    1. 50 until (i and 5)=5


    kommt, abgesehen davon, dass es eben ohnehin nicht richtig läuft, ein SYNTAX ERROR in 50 (obwohl da keiner ist).

    Sieht so aus, als ob das BREAK nicht den aktuellen Block beendet, sondern alle und dabei noch irgendwas verdreht zurücklässt.

  • Ich sehe es mir mal an. Ich denke aber das der REPEAT Block mit diesem Konstrukt

    20 repeat:i=i+1:o=i

    ein Problem bekommt. Das REPEAT legt die aktuelle Zeillen nummer auf dem Stack. Darin verbergen sich aber auch die Statements hinter dem : und die werden dann nicht ausgeführt.

    Wenn ich das so eintippe:

    10 i=2

    20 repeat

    30 :i=i+1:o=i

    40 :print "i";i

    50 :repeat

    60 ::o=o-1

    70 ::print "o";o

    80 :until o=0

    90 until i=5

    Funktioniert es:run

    i 3

    o 2

    o 1

    o 0

    i 4

    o 3

    o 2

    o 1

    o 0

    i 5

    o 4

    o 3

    o 2

    o 1

    o 0


    Ich muss also in der Syntaxauswertung ein : mit weiteren Statements als Syntax Error handeln.

    Gleiches gilt übrigens für den UNTIL Befehl, da der die Zeilennummern vom REPEAT jeweils vom Stack holt.


    :thumbup: Danke für den Hinweis. Da werde ich noch was einbauen.

  • Noch eine Gemeinheit von mir (Sorry, ich will das nicht kaputt machen oder so...ich habe nur selber zu viel BASIC geparst, als das ich solche Randbereiche nicht abprüfen könnte, wenn ich sie sehe). Ein Sprung mit RETURN raus aus einem REPEAT-UNTIL-Block funktioniert nicht. Bei FOR-NEXT ist es so, dass ein RETURN alle FOR-Schleifen nach dem letzten GOSUB beendet. Das hier funktioniert also:


    Code
    1. 10 for i=0 to 4
    2. 20 print"i=";i
    3. 30 gosub 1000
    4. 40 next:end
    5. 1000 for o=i to 0 step -1
    6. 1010 if o=0 then return
    7. 1020 next


    Das hier funktioniert aber nicht, sondern wirft einen RETURN without GOSUB-Fehler:


    Code
    1. 10 i=0:o=0
    2. 20 repeat
    3. 30 i=i+1:print"i=";i
    4. 40 gosub 1000
    5. 50 until i=4
    6. 60 end
    7. 1000 o=i:repeat
    8. 1010 o=o-1
    9. 1020 if o=0 then return
    10. 1030 until o=0
  • An solche verschachtelungen habe ich gar nicht gedacht. Deshalb ist es wichtig das andere Testen. Vielen Dank für den Hinweis. Wenn es deb Bug auch nicht beseitigt, so ist er dennoch erklärbar, weil strukturell bedingt.


    Das Repeat legt das Token und die aktuelle Zeilennummer auf den Stack (3Byte). Die Anweisungen werden ausgeführt. UNTIL prüft die Bedingung.

    Ist sie falsch, so wird auf die Zeilennummer wo das Repeat steht gesprungen, dazu werden die Zeilenummern vom Stack geladen. REPEAT wiederholt sich also und legt die aktuelle zeilennummer wieder auf den Stack zurück.

    Bei Bediengung=WAHR wird der Stack um 3 Bytes gepullt und zur nächsten Zeile gesprungen.


    Wenn nun in dem REPEAT Block ein Gosub kommt werden dessen Zeilennummern auf den Stack gelegt. Springt das GOSUB auf ein REPEAT werden diese Zeilnenummern auf den Stack gelegt. Nun kommt VOR dem UNTIL, also ohne den Block zu verlassen ein RETURN. Das holt sich die Zeilennummern zurück. Im Glauben es sind die vom GOSUB. Sind sie aber nicht, es sind die vom zweiten REPEAT. Und dann sagt das Return ich finde kein GOSUB.


    Wie ich diese, doch gemeine Konstellation umsetze ist mir noch unklar. Dazu wäre es gut den Code hier mal zur Diskussion zu stellen.

    Übrigens: Du verwechselst UNTIL mit BREAK. Ursprünglich sollte das BREAK einen REPEAT-UNTIL Block hinter einer IF-Condition=TRUE sofort verlassen, also sowas hier:

    REPEAT

    I=I+1

    IF I=5 THEN BREAK

    UNTIL I=25

    Aber wegen der ganz oben genannten IF-THEN Problematik habe ich dem BREAK noch eine eigene Condition spendiert. BREAK ist eigentlich mehr was fürs Auge. Den genauso könnte man mit einem GOTO auf das UNTIL springen.


    So, nun zum REPEAT UNTIL. Hier der Code dazu:

    Und hier UNTIL:

  • Nee, ich verwechsel das nicht...glaube ich. Aber vielleicht verstehe ich nicht, was das machen soll!? Ich bin davon ausgegangen, das macht das, was es in anderen Sprachen auch tut: Die Schleife (wobei man bei BASIC V2 durch die Stacknatur der ganzen Sache ja eigentlich gar nicht von Schleifen reden kann...die entstehe ja quasi nur aus Versehen) vorzeitig beenden! Macht es das nicht? Springt es stattdessen unter Umgehung des folgenden Codes zum UNTIL und das läuft dann ganz normal? Aber dann funktioniert es auch nicht, würde ich sagen. Bevor ich mir HELP genauer angesehen habe, habe ich BREAK übrigens so benutzt, wie du in dem Beispiel oben. Das hat den ganzen Rechner abstürzen lassen, aber leider habe ich den Quellcode nicht gesichert gehabt und hinterher konnte ich es nicht mehr reproduzieren.

  • Doch das soll BREAK machen. Ein REPEAT-UNTIL Block kann ohne BREAK mit nur einer Wahrheit beendet werden, nämlich die Condition im UNTIL.

    Gibt es aber mehr als eine Wahrheit kann ich den BREAK Befehl nutzen den Block sauber zu verlassen. Springe ich mit einem GOTO aus dem REPEAT Block, dann verletzte ich das LIFO Prinzip vom Stack und es kommt unweigerlich zu merkwürdigen Seiteneffekten. Ich meine das hier:

    (Das Programm ist natürlich unsinnig. Aber es zeigt ganz gut das Prinzip)


    (Nicht gut)

    REPEAT

    I=I+1

    IF I = 2 THEN GOTO LABEL1

    IF I = 4 THEN GOTO LABEL1

    ? I

    UNTIL I=10

    LABEL1: END


    (Besser)

    REPEAT

    I=I+1

    IF I = 2 THEN BREAK

    IF I = 4 THEN BREAK

    ? I

    UNTIL I=10

    LABEL1: END

    So wollte ich es zuerst haben. Aber da hat mir die IF-THEN Problematik einen Strich durch die Rechnung gemacht.


    Deshalb habe ich mir was einfallen lassen. Nämlich das hier:

    REPEAT

    I=I+1

    BREAK I = 2

    BREAK I = 4

    ? I

    UNTIL I=10

    LABEL1: END

    Wenn I=2 (BREAK) oder I=4 (BREAK) oder I=10 (UNTIL) ist, dann verläßt das BREAK die Schleife und stellt den STACK wieder her. Das funktioniert auch bei Blockverschachtelungen.

    Dein GUSUB Beispiel oben wäre m.e. von der Progrmmierlogik zuerst betracht richtig. Beim genaueren hinsehen aber etwas unsaber. Besser wenn du in Zeile 1020 statt eines RETURNS ein BREAK eingesetzt hättest und hinter dem UNTIL in Zeile 1030 die Zeile 1040 RETURN eingebaut hättest. In dem du mitten im Block einen Rücksprung einbaust verletzt du das LIFO Prinzip vom Stack.

    Also sowas hier:

    10 i=0:o=0

    20 repeat

    30 ::i=i+1:print"i=";i

    40 ::gosub 1000

    50 until i=4

    60 end

    1000 o=i

    1005 repeat

    1010 ::o=o-1

    1020 ::BREAK o=0

    1030 until o=0

    1040 RETURN


    Nebeinbei bemerkt zeigt sich hier warum BASIC einen so schlechten Ruf hat. Es verleitet nicht nur zu unübersichtlichen Spagettizeilen, sondern ermöglicht leider auch von Hause aus die STACK Regeln zu verletzen. Andere Sprachen verhindern das schon durch ihr Design.

  • Doch das soll BREAK machen. Ein REPEAT-UNTIL Block kann ohne BREAK mit nur einer Wahrheit beendet werden, nämlich die Condition im UNTIL.

    Gut, dann habe ich es richtig verstanden. Aber das macht mein Beispiel von oben doch so (!?) und das funktioniert eben nicht, weil er nicht aus der inneren Schleife "breakt", sondern scheinbar auch aus der äußeren. Ansonsten müsste I ja trotzdem bis 5 zählen und das passiert nicht mehr, wenn das BREAK in der inneren Schleife (die mit O) ist. Danach ist irgendwas komplett im Eimer und selbst ohne BREAK läuft es nicht mehr richtig durch.

    Zitat


    In dem du mitten im Block einen Rücksprung einbaust verletzt du das LIFO Prinzip vom Stack

    Ja und nein. Das habe ich schon absichtlich so gemacht. Ich sage ja nicht, dass das guter Stil ist. Mir ist schon klar, dass das Murks ist. Aber in Commodore BASIC geht das halt trotzdem. Ein RETURN nimmt nicht den letzten Eintrag vom Stack, sondern sucht sich den letzten passenden (das ist eben der letzte Sprung mit GOSUB) und schneidet den Rest dahinter quasi weg. Deswegen kann man aus FOR-NEXT returnen und die Schleife ist damit magisch beendet. Das müsste mit REPEAT-UNTIL eigentlich auch funktionieren, ansonsten hat man zwei ähnliche Schleifenvarianten mit unterschiedlichem Verhalten. Aber das geht eben hier nicht. Ich denke mal, das RETURN kann deinen REPEAT-Eintrag nicht als "zu überspringen/ignorieren" erkennen und bricht dann ab!?

  • Nebeinbei bemerkt zeigt sich hier warum BASIC einen so schlechten Ruf hat. Es verleitet nicht nur zu unübersichtlichen Spagettizeilen, sondern ermöglicht leider auch von Hause aus die STACK Regeln zu verletzen. Andere Sprachen verhindern das schon durch ihr Design.

    Ich sage ja nicht, dass das guter Stil ist. Mir ist schon klar, dass das Murks ist. Aber in Commodore BASIC geht das halt trotzdem.

    Das ist kein Murks. Und dass das geht, ist auch kein Alleinstellungsmerkmal von (Commodore) Basic. Aus einer (ggfs. geschachtelten) Schleife direkt mit RETURN herauszuspringen, geht auch in C und Konsorten, auch in Python und so ziemlich allem anderen. Unter anderem liegt das natürlich daran, dass, sobald das Programm kompiliert wird, Schleifen mit dem Stack überhaupt nichts mehr zu tun haben.


    Die hier gezeigte REPEAT/BREAK/UNTIL-Logik entspricht ziemlich genau dem DO/EXIT/LOOP von Basic3.5, evtl. lohnt sich da ein Blick ins ROM-Listing, wie da der RETURN-Aussprung gehandhabt wird...

  • Das ist kein Murks. Und dass das geht, ist auch kein Alleinstellungsmerkmal von (Commodore) Basic. Aus einer (ggfs. geschachtelten) Schleife direkt mit RETURN herauszuspringen, geht auch in C und Konsorten, auch in Python und so ziemlich allem anderen. Unter anderem liegt das natürlich daran, dass, sobald das Programm kompiliert wird, Schleifen mit dem Stack überhaupt nichts mehr zu tun haben.

    Das ist eher deswegen so, weil diese Sprachen alle die Schleifen als Blockkonstrukte verarbeiten. D.h. schon im Quelltext ist klar, wo die Schleife beginnt und wo sie endet und alles dazwischen ist der Schleifenkörper. Den gibt es in Commodore BASIC so aber gar nicht. Die meisten Quelltexte sehen zwar so aus, als würde immer ein NEXT ein öffnendes FOR schließen, aber ich kann auch problemlos FOR-Schleifen bauen, die aus einem FOR und 10* NEXT bestehen. Oder aus 10*FOR und einem NEXT. Ich sehe FOR und NEXT daher nicht als Schleifenkonstrukte, sondern als eigenständige Befehle, die mit dem Stack arbeiten und dabei "zufällig" eine Schleife bilden können.

  • Aus einer (ggfs. geschachtelten) Schleife direkt mit RETURN herauszuspringen, geht auch in C und Konsorten, auch in Python und so ziemlich allem anderen. Unter anderem liegt das natürlich daran, dass, sobald das Programm kompiliert wird, Schleifen mit dem Stack überhaupt nichts mehr zu tun haben.

    Das ist eher deswegen so, weil diese Sprachen alle die Schleifen als Blockkonstrukte verarbeiten.

    [...]

    Ich sehe FOR und NEXT daher nicht als Schleifenkonstrukte, sondern als eigenständige Befehle, die mit dem Stack arbeiten und dabei "zufällig" eine Schleife bilden können.

    Das ist ja alles richtig, aber ganz oben ging es nun mal um Schleifen, zudem noch um solche, die per REPEAT/UNTIL gebildet werden. Und auch wenn diese Anweisungen (gezwungenermaßen) genauso über den Stack implementiert sind wie FOR/NEXT, so ist doch allein durch das Vorhandensein des BREAK-Aussprungs schon vorgegeben, dass zu jedem REPEAT exakt ein UNTIL vorhanden sein muss, eben genau wie ab Basic 3.5 mit DO/LOOP. Solche Schleifen per RETURN verlassen zu wollen, ist nicht ungewöhnlich und sollte(tm) daher unterstützt werden.


    slightly offtopic:

    Dass RETURN den Stack nach GOSUB durchsucht und alle FORs wegschmeißt, ist bekannt. Weiterhin schmeißt ein "NEXT J" alle FORs vom Stack, die eine andere Zählvariable benutzen.

    Ich habe mich gerade gefragt: Schmeißt ein NEXT eigentlich auch GOSUBs vom Stack? Testresultat: Nein, tut es nicht, da kommt dann ein NEXT WITHOUT FOR ERROR.

    Die seltsame "Exception-Funktionalität" von NEXT ist also vermutlich mehr durch Zufall entstanden, sonst wäre wohl auch dieser Fall unterstützt worden.

  • Solche Schleifen per RETURN verlassen zu wollen, ist nicht ungewöhnlich und sollte(tm) daher unterstützt werden.

    Ja, da sind wir ja einer Meinung...:D Ich wollte nur darauf hinweisen, warum das bei BASIC (zumindest in dieser Implementierung) schwieriger ist als bei C oder auch Javascript.


    Zitat


    Die seltsame "Exception-Funktionalität" von NEXT ist also vermutlich mehr durch Zufall entstanden, sonst wäre wohl auch dieser Fall unterstützt worden.

    Davon gehe ich auch aus. Da dürfte einiges zufällig entstanden sein.

  • Gibt es in dem Basic 3.5 auch Spritebefehle? Auf dem Plus/4 gibt es keine Hardware Sprites. Mir würde so etwas vorschweben, wie z.B. Spritedefinition, Spritekollision, etc. Das könnte interessant für die Spieleprogrammierung sein. Was in diesem Zusammenhang auch gut passen würde, wären Befehle für die Erstellung eigener Zeichensätze, ebenfalls für die Spieleprogrammierung.


    Ich klinke mich hier mal ein bisschen offtopic ein, weil mich das doch ganz stark an ein sehr ähnliches Thema für den *nicht erschrecken* Amstrad CPC  :schreck!:erinnert.


    https://8bitsdepoder.blogspot.com/


    Hier wurden mittels einer ähnlich erstellten Bibliothek mit Hilfe von speziellen BASIC Erweiterungen schon eine ganze Reihe durchaus vorzeigbarer Spiele ausschließlich in BASIC programmiert. Finde ich persönlich spitze!


    Leider ist der Betreiber dem Spanischen sehr anhänglich, so dass dem einen oder anderen die Homepage bestimmt spanisch vorkommt. :D

    Ich habe deswegen angefangen seine Gebrauchsanleitung für die einzelnen Befehle in Deutsch zu übersetzen. Bin aber aus Mangel an Zeit bisher nicht fertig geworden. :/

  • liebe Diskussionsteilnehmer,

    Ich möchte mich für die vielen Hinweise auf meine „BASIC Erweiterung“ bedanken. Ich habe die intensiven Hinweise zu REPEAT/BREAK/UNTIL aufgenommen und schon zum Teil implementiert. Ich werde mich nun in mein stilles Kämmerlein zurück ziehen und weiter meine Assembler Kenntnisse und CBM BASIC „Unarten“, Zufälligkeiten und Programmiermöglichkeiten ausloten und verfeinern.

    Besten Dank geht an EgonOlsen71 mit seinen konstruktiven Tests und Vorschlägen.


    Ich werde mich mit der nächsten Version zu gegebener Zeit wieder melden und hier zur Verfügung stellen. Wer aktuelle „Nightbuilds“ haben will, bitte PN an mich.


    Hiermit schliesse ich den Thread.

  • Nebeinbei bemerkt zeigt sich hier warum BASIC einen so schlechten Ruf hat. Es verleitet nicht nur zu unübersichtlichen Spagettizeilen, sondern ermöglicht leider auch von Hause aus die STACK Regeln zu verletzen. Andere Sprachen verhindern das schon durch ihr Design.

    Ich sage ja nicht, dass das guter Stil ist. Mir ist schon klar, dass das Murks ist. Aber in Commodore BASIC geht das halt trotzdem.

    Das ist kein Murks. Und dass das geht, ist auch kein Alleinstellungsmerkmal von (Commodore) Basic. Aus einer (ggfs. geschachtelten) Schleife direkt mit RETURN herauszuspringen, geht auch in C und Konsorten, auch in Python und so ziemlich allem anderen. Unter anderem liegt das natürlich daran, dass, sobald das Programm kompiliert wird, Schleifen mit dem Stack überhaupt nichts mehr zu tun haben.


    Die hier gezeigte REPEAT/BREAK/UNTIL-Logik entspricht ziemlich genau dem DO/EXIT/LOOP von Basic3.5, evtl. lohnt sich da ein Blick ins ROM-Listing, wie da der RETURN-Aussprung gehandhabt wird...

    Das RETURN such am Stack gezielt FOR-NEXT-Stackframes (die haben die Signatur $81, während GOSUB-Stackframes $8D haben), bis der GOSUB-Stackframe gefunden ist. Wenn da weder das eine oder das andere am Stack zu finden ist, haut's den Interpreter auf.
    Lösungsmöglichkeiten:

    1. REPEAT legt einen FOR-NEXT-Stackframe an. Ginge relativ unkompliziert, wenn man als STEP-Wert 0 nehmen. Als Variable könnte man im Grunde die erste Variable nehmen (sofern eine existiert, müsste man sonst eine vorab anlegen analog zum FOR), die man findet, weil sie ja nicht geändert wird.
      Nebenbei würde statt UNTIL 0 (also nie abbrechen) auch ein NEXT funktionieren, also gäbe es auch eine REPEAT-NEXT-Struktur. ;)
    2. RETURN neu implementieren, das den REPEAT-Stackframe "erkennt".
  • Ich möchte mich für die vielen Hinweise auf meine „BASIC Erweiterung“ bedanken. Ich habe die intensiven Hinweise zu REPEAT/BREAK/UNTIL aufgenommen und schon zum Teil implementiert. Ich werde mich nun in mein stilles Kämmerlein zurück ziehen und weiter meine Assembler Kenntnisse und CBM BASIC „Unarten“, Zufälligkeiten und Programmiermöglichkeiten ausloten und verfeinern.

    Das ist übrigens der Vorteil von Veröffentlichungen, dass man nämlich auch die Blickwinkel der anderen bekommt, die einen ganz anderen Zugang haben und auf Sachen kommen, an die man einfach nicht draufkommt, solange man sozusagen im eigenen (Gedanken-)Saft schmort. ;)

    BTW: Ich würde das nicht unbedingt "Unarten" nennen (ja, ich weiß, steht in Anführungszeichen), aber es sind schlichtweg die Eigenschaften des Interpreters (die mitunter nicht immer so populär sind). Diese nicht zu kennen, macht noch lange keine Unarten daraus. :D