Hello, Guest the thread was called486 times and contains 23 replays

last post from dg5kr at the

Assembler: VAR$=MEINE_FUNC$(INTEGER) in BASIC Erweiterung. Aber wie?

  • Hallo 8Bit Spezialisten,

    ich stehe nun wieder vor einem Rätsel. Ich möchte in meine Basicerweiterung eine "echte" Funktion programmieren.

    Es soll eine Funktion$() werden, dessen Ergebnis dann in eine Stringvariable landet.


    Konkret möchte ich D=1289:H$=HEX$(D) dazu bringen "0509" in H$ ab zu speichern.
    Oder auch diese Variante: D=1289:PRINT HEX$(D): Ergibt 0509.


    Es geht hier nicht wie man das in einem ML Programm umrechnet. Das funktioniert längst.

    Frage 1: Wie kriege ich es hin, das ich in der Klammer sowohl eine Zahl, als auch eine Variable vom Typ Integer auswerten kann?

    Frage 2: Bei dem Konstrukt H$=HEX$(1234) wird der Befehl HEX$ nicht angesprungen, weil kein Token dafür angelegt wird. Ist auch klar, "H$" ergibt in meinen Tokenizer kein Token. Daher wird danach der CBM Tokenizer angesprungen. Der kann mit H$ was anfangen, aber nicht mit HEX$().Darum wird die Function HEX$ gar nicht erst angesprungen. Wie ist das der gängige Weg?


    Einen besten Dank an alle die mir helfen wollen.

  • Der kann mit H$ was anfangen, aber nicht mit HEX$().Darum wird die Function HEX$ gar nicht erst angesprungen. Wie ist das der gängige Weg?

    Du klinkst deine Erweiterung vor dem BASIC-Interpreter ein und prüfst auf Tokens deiner Erweiterung. Falls nicht vorhanden, wird zur normalen Systemadresse zur weiteren Auswertung gesprugen.

  • Frage 1: Wie kriege ich es hin, das ich in der Klammer sowohl eine Zahl, als auch eine Variable vom Typ Integer auswerten kann?

    Im Prinzip ruft man die allgemeine Ausdrucksauswertung an, $AD9E


    Ob die Klammererkennung "(" bei Anspringen einer Funktion dabei ist, weiß ich jetzt aus dem Stegreif nicht, aber im Prinzip hängt es nur davon ab, wie man dann mit dem Typ des Ergebnisses der Auswertung umgeht. Das kann man flexibel gestalten, also abhängig vom Typ (Nummerisch, Zeichenkette) machen. Im Programm Verlauf kann sich das ja ohnehin nicht so ohneweiters ändern (wenn es denn nicht Funktionen gibt, die von sich aus einen unterschiedlichen Typ retournieren können). Das Ergebnis kann auch Zahl oder Zeichenkette sein. Die Ausdrucksauswertung, die die Funktion aufruft, prüft schon, ob das Ergebnis zum bisherig ausgewerteten dazu passt. Bekanntermaßen flexibel ist da ja z.B. PRINT, welches mit beiden Typen etwas anfangen kann.


    Aber warum den gleichen Funktionsnamen für unterschiedliche Typen verwenden? Für BASIC ist das ziemlich unüblich.

    Funktionen mit $ im Namen sollten immer einen String als Ergebnis liefern, sonst ist ein nummerischer Wert vorgesehen. Wie ist das im obigen Beispiel mit HEX$ dann gemeint - oder bezieht sich die Frage auf etwas anderes?

  • Hallo „Freak“. Da gibst ein Missverständnis. Das Argument soll immer vom Typ Integer sein. Aber es kann ja eine Zahl direkt in den Klammen des Arguments kommen, oder eben in eine Variable verpackt. Da meinte ich damit.
    Der Typ des ausgegeben Wertes ist und bleibt String.
    Wie ich eine Klammer prüfe ist kein Problem. Ist das Argument eine Zahl, ist es kein Problem. Steckt das Argument in einer Variable vom Typ Integer habe ich keine Ahnung wie ich das Argument auswerte.


    Gruß

    DG5KR

  • Hallo „Freak“. Da gibst ein Missverständnis. Das Argument soll immer vom Typ Integer sein. Aber es kann ja eine Zahl direkt in den Klammen des Arguments kommen, oder eben in eine Variable verpackt. Da meinte ich damit.
    Der Typ des ausgegeben Wertes ist und bleibt String.
    Wie ich eine Klammer prüfe ist kein Problem. Ist das Argument eine Zahl, ist es kein Problem. Steckt das Argument in einer Variable vom Typ Integer habe ich keine Ahnung wie ich das Argument auswerte.

    Das ist ja gar kein Problem. Die Formelauswertungsroutine, die ich genannt habe, macht das alles. Wenn die fertig ist, hat die alles verarbeitet, was syntaktisch einem Ausdruck entspricht. Egal, ob Zahlenkonstanten, andere Variablen oder sonst was vorkommt. Der Witz ist ja, dass deine neue Funktion ja selbst in diesem Aufruf verarbeitet wird. Angenommen es ist eine Zuweisung wie N$= ... dann ruft das eigentlich LET-Kommando für den Teil nach dem "=" die Formelauswertung auf. Das was da rauskommt, muss vom Typ String sein und das LET-Kommando macht dann die Übertragung des Wertes in die Variable.


    Du musst dich nur darum kümmern, dass im Hook des Interpreters du deine Funktion abfängst und dann zur Ausführung kommt. Die Formelauswertung wird dabei natürlich rekursiv aufgerufen (was allerdings nur zu einer gewissen Verschachtelungstiefe geht).

  • Perfekt! Danke "Freak". Problem Nr. ist schonmal gelöst:

    Code
    1. jsr CHRGET ;Hole Zeichen
    2. cmp #"$" ;Ist ausdruck $?
    3. beq _HEX_01
    4. jmp $AF08 ;Syntax Error
    5. _HEX_01 jsr CHRGET ;Hole Zeichen
    6. jsr $AEFA ;Prüfe auf (
    7. jsr FRMNUM ;Hole numerischen Ausdruck (Paramter) und legt ihn in FAC
    8. jsr GETWORD2 ;in der fac liegenden integer wert in HEX wandeln word in hi=A lo=Y
    9. ; ABER: Wie kriege ich das Ergebnis in A/Y als String auf den Description Stack???


    Nun muß ich nur noch rausfinden wie ich das Ergebnis (liegt in Y/A) in eine BASIC Stringvariable packe.

  • Zur Ergänzung möchte ich mein Problem nochmal erläutern. Ich formalisiere den Vorgang ein wenig und hoffe so verstanden zu werden. Meine BASIC Erweiterung arbeit wohl nach einem gängigen Prinzip:


    MEIN_BEFEHL[ENTER]

    Mein Tokenizer -> mein Token -> Mein Dispatcher -> Meine Routine -> READY

    Das ist der Fall, wenn ich einen "neuen" Befehl eingebe. Das klappt alles wunderbar. Befehle die eine Aktion ausführen und keinen Wert zurückgeben klappen prime.



    Ein BASIC V2 Befehl läuft dann so ab:

    CBM_BEFEHL[ENTER]

    Mein Tokenizer -> kein Token, also -> CBM Tokenizer ->

    CBM Token -> CBM Dispatcher -> CBM Routine -> READY

    oder

    kein CBM Token -> Syntax error -> READY


    Auch alles Prima.

    Möchte ich aber eine Funktion mit Rückgabewert an eine Variable in BASIC zuweisen gibts ein Problem:


    H$ = MEINE_FUNKTION ( WERT )

    Mein Tokenizer -> Kein Token gefunden (klar, ist ja auch H$=) -> CBM Tokenizer -> Variablen Zuweisung erkannt -> MEINE_FUNKTION ->

    CBM Tokenizer -> kein CBM Token gefunden -> Bad Subscription Error


    Da zunächt mein Tokenizer durchlaufen wird, bekommt H$ kein Token daher geht es weiter zum CBM Tokenizer. Im CBM Tokenizer wird ganz klar eine Zuweisung erkannt. Als nächstes soll dann die Funktion tokenisiert werden. Aber die gibt es in BASIC V2 nicht und es kommt zum Fehler.

    Muss ich in MEINEM Tokenizer Ein Konstrukt wie H$=FUNKTION(wert) als Wertzuweisung abfangen?

  • H$ = MEINE_FUNKTION ( WERT )

    Mein Tokenizer -> Kein Token gefunden (klar, ist ja auch H$=) -> CBM Tokenizer -> Variablen Zuweisung erkannt -> MEINE_FUNKTION ->

    CBM Tokenizer -> kein CBM Token gefunden -> Bad Subscription Error

    Dein Tokenizer (Vektor $0304) muss eigentlich komplett die Routine Tokenize Text ersetzen. Es reicht nicht nur auf die ersten Zeichen zu schauen, sondern du muss dich durch die ganze Eingabe Zeile durcharbeiten. Leider ist die Routine nicht so modular, dass man sich in die bestehende Tabelle einfach "anhängen" kann - glaub ich zumindest. Vielmehr muss man erst mal in den eigenen Keywords suchen und dann so wie im ROM in den Keywords aus dem ROM wie gehabt absuchen. M. E. an der Stelle ... also vor $A5BC mit den eigenen Keywords vergleichen und wenn es passt in das "neue" Token nach $01fb,y schreiben.

    Code
    1. A5B8: BD 00 02 LDA $0200,X ; BASIC Input Buffer (Input Line from Screen)
    2. A5BB: 38 SEC
    3. A5BC: F9 9E A0 SBC $A09E,Y ; BASIC Command Keyword Table
    4. A5BF: F0 F5 BEQ $A5B6
    5. A5C1: C9 80 CMP #$80
    6. A5C3: D0 30 BNE $A5F5
    7. A5C5: 05 0B ORA $0B ; Input Buffer Pointer/Number of Subscripts

    Nur auf auf die ersten Zeichen zu schauen funktioniert im Grunde nur bei interaktiven Befehlen, nicht für Befehle oder Funktionen, die mitten in der Zeile vorkommen können. Vielleicht gibt es ja noch eine elegante Methode - ich hab mir jetzt nicht einschlägige Implementierungen diverser BASIC-Erweiterungen zu Gemüte geführt, aber es ist halt aufwendiger.
    Für einfache Erweiterungen ist es für mich persönlich immer einfacher in der Interpreterschleife mit einem Escape-Zeichen + einem Einbuchstabenbefehl in einer kleinen Liste zu suchen. Das ist für nur einige wenige Befehle vertretbar im Vergleich zum Token in der Tabelle nachschlagen.

  • Inzwischen bin ich zum Ergebnis gekommen, das es so nicht machbar ist. Mein eigenes Kommando "HEX$" wird vom CBM Parser als indizierte Variable gehandhabt.
    Der Aufruf A$=HEX$(16348) kommt daher immer zum "Bad subscription Error". Was auch klar ist. Dazu müßte ich das BASIC kopieren und abändern, das HEX$ als Basic Befehl angesehen wird und nicht als indizierte Variable. Ähnlich wie mit der STR$() Funktion.
    Daher habe ich mich vorerst für einen anderen Weg entschieden, so zusagenein Workaround. Vor dem Befehl definiere ich eine beliebige STRING Variable mit 4 Bytes Länge. Rufe den Befehl auf und dort wird dann das Ergebnis in diese Variable gelegt. Das sieht dann so aus:

    Das klappt ganz gut:



    Hier der ganze Code meines HEX$() Befehls:


    Vielen Dank an alle, die sich für mich den Kopf zerbrochen haben :gruebel:thnks:

  • Nee, bei mir funktioniert das:





    In Adresse $49 steht der Zeiger zum Descriptor. Dieser verweist auf den Variabelspeicher für die aktuell definierte Variable. Ab $A000 in Richtung Basicspeicher.

    Und genau dieser Belege ich mit dem Ergebnis meiner Routine:


    Mein BASICspeicher bleibt unberührt.

  • Nee, bei mir funktioniert das:


    äh... ja, im Direktmodus schon. In einem Programm sieht das etwas anders aus. Da zeigt der Zeiger dann tatsächlich direkt auf den Basic-Code (solange die Variable nicht noch einmal verändert wurde)

  • Inzwischen bin ich zum Ergebnis gekommen, das es so nicht machbar ist. Mein eigenes Kommando "HEX$" wird vom CBM Parser als indizierte Variable gehandhabt.
    Der Aufruf A$=HEX$(16348) kommt daher immer zum "Bad subscription Error". Was auch klar ist. Dazu müßte ich das BASIC kopieren und abändern, das HEX$ als Basic Befehl angesehen wird und nicht als indizierte Variable. Ähnlich wie mit der STR$() Funktion.
    Daher habe ich mich vorerst für einen anderen Weg entschieden, so zusagenein Workaround. Vor dem Befehl definiere ich eine beliebige STRING Variable mit 4 Bytes Länge. Rufe den Befehl auf und dort wird dann das Ergebnis in diese Variable gelegt. Das sieht dann so aus:

    Das hab ich ja in meinen vorigen Post ja beschrieben. Der Bad Subscript Error kommt daher, weil du nicht die Zeile mit dem Tokenizer abarbeitest, sondern nur die ersten Zeichen einer Zeile abfragst. Da A$= nicht mit HEX$ übereinstimmt und du weiter an den ROM-Tokenizer gibst, ist für den dann HEX$() eine Array-Variable, die nicht entsprechend dimensioniert ist.


    Als Workaround, wenn man sich den Tokenizer-Varianteaufwand nicht antun möchte, ist die Übergabe an eine fixe Variable natürlich auch gut. Da aber nicht darauf spekulieren, dass die Variable schon befüllt ist, sondern wie es sich eigentlich sauber gehört, den String-Speicher anfordern und in den Descriptor der String-Variable den neu angelegten String eintragen. Der eventuell alte String ist dann "Garbage", um den sich die Garbage-Collection kümmert (kein Angst, diese Funktion wird das Kraut auch nicht fett machen, wenn man Angst hat, dass einem die GC widerfährt ... dann halt eine lineare Implementierung nehmen).
    Für deinen Fall ginge das etwa so

  • Du kannst auch die Variable einfach suchen, statt davon auszugehen, dass die zuletzt verwendete sein muss oder so.

    Oder sogar den Variablennamen als Argument übergeben). Z.B. so

    HEX$(16384,H)

    Das Parsing ist ja da unter deiner Kontrolle, mit CHRGET nach dem Komma die nächsten max. 2 Zeichen als Variablenamen interpretieren und dann (hier jetzt fix mit HE ausformuliert) lokalisieren.


    Code
    1. LDA #"H"
    2. STA $45
    3. LDA #("E" | $80) ; String-Typ, also HE$ wird gesucht
    4. STA $46
    5. JSR $B0E7 ; Variable suchen und gegebenenfalls auch anlegen.
    6. ; Adresse der Variable in A/Y

    Der Hinweis oben bezüglich B$="....." hätte eigentlich keine Relevanz, wenn der Hex-String wirklich in den Stringspace - der angefordert wird! - kopiert werden würde. Dann gehört nur dessen Descriptor in die Variable übertragen. Wenn der B$-Descriptor auf einen String im Programmcode zeigte, dann wird der String nicht mehr referenziert, andernfalls wird der alte String im Stringspace eben zu "Garbage".
    Dann könnte man für obiges Beispiel auch B$="": HEX$(3000): PRINT B$ schreiben. Dann wäre es so, dass nur auf die zuletzt "gesuchte" Variable zugegriffen wird und die Zuweisung ist nur eine Dummy-Zuweisung.

  • Der HEX$-Befehl gehört dann in etwa so gepatcht (ab Zeile 54):

    Im ursprünglichen Code ist der Aufruf $B4F4 unnötig ... der angeforderte Speicher wird gar nicht verwendet, sondern in die bestehende Variable geschrieben, wobei nicht einmal die notwendige Mindestlänge von 4 Zeichen überprüft wird (geschweige denn richtig auf 4 gesetzt wird). Ein B$=STR$(1):HEX$(32768) korrumpiert den Speicher am String-Heap (B$ ist nur 2 Zeichen lang!) nach dem B$-String Richtung höherer Adressen.

  • Das hab ich ja in meinen vorigen Post ja beschrieben. Der Bad Subscript Error kommt daher, weil du nicht die Zeile mit dem Tokenizer abarbeitest, sondern nur die ersten Zeichen einer Zeile abfragst. Da A$= nicht mit HEX$ übereinstimmt und du weiter an den ROM-Tokenizer gibst, ist für den dann HEX$() eine Array-Variable, die nicht entsprechend dimensioniert ist.

    Ich hab bislang noch nicht eine Funktion als neues Token für meine Erweiterungen gebraucht. Die Fragestellung hat mich aber schon immer interessiert. Da der Aufwand bislang von mir immer als recht beträchtlich eingeschätzt wurde, hab ich immer einen Bogen darum gemacht. Aber jetzt hat mich doch mal interessiert, was man dazu alles braucht und es real ausprobiert.
    Ich möchte keinesfalls sagen, dass ich hier die optimale Lösung gefunden hätte. Ich hab mal ohne einschlägige Literatur zu konsultieren mal meine Vorstellung umgesetzt. ;)

    Exemplarisch gezeigt am Funktionen-Keyword HEX$. Ich verwendet dafür das Token $CC (das nächste freie bei BASIC V2). Nebeneffekt: es gilt damit automatisch als String-Funktion und man muss nicht bei der Ausdrucksauswertung bei der Typfestlegung nicht eingreifen.


    Für die Funktionserweiterung braucht man 3 Teile, den Tokenizer (der irgendwo in einer Zeile befindlichen Keywords in ein Token wandelt), die Auswertung, die das Token erkennt und die Funktion abhandelt und zu guter letzt auch den "Lister", der beim LIST-Befehl das Token auch wieder mit Keyword darstellt.


    Der aufwändige Teil ist der Tokenizer, der hier im Grunde faktisch nochmal in Kopie der aus dem ROM ist, nur bei der Keyword-Suche ist ein neuer Teil eingeschoben. Abgesehen von den anderen Hooks, ersetzt in diesem Fall der "neue" Tokenizer den alten. Etwaige bestehende Hooks von Erweiterungs- oder Alternativ-ROMs (z.B. SpeedDOS+) fallen so raus und deren Befehle werden nicht mehr erkannt.


    Das alles ist eher eine Studie, die genau auf ein Keyword hingearbeitet ist. Wenn man mehrere Funktionen bedienen möchte, muss man ein bisschen mehr dazubauen, also die Keyword-Suche durch eine Liste führen, den Aufruf der Tokens etwa über eine Sprungtabelle machen usw.


    Um gegenüber anderen Erweiterungen und Alternativ-ROMs neutral zu bleiben, hätte ich vielleicht noch einen anderen Ansatz, nämlich zuerst den "originalen" Tokenizer aufrufen und dann erst den eigenen Tokenizer in einer wirklich aufs Notwendigste reduzierten Variante (Strings, DATA und REM muss man aber trotzdem berücksichtigen) über die Token-Zeile in einer Art 2. Pass drüberlaufen lassen, um dann die neuen Tokens einzupflanzen ... kommt vielleicht dann auch noch. :D


    Für Try-Outs bitte einfach dieses Image probieren ... :)

    function.zip

  • jsr $b7f7 ; FAC#1 nach 16 Bit in Y/A bzw. $14/$15

    Vorsicht, da können $schlimmeDinge passieren: In $14/15 bewahren POKE/WAIT die gewünschte Speicheradresse auf, während die restlichen Argumente ausgewertet werden. Jede Basic-Funktion, die ihrerseits diese Speicherstellen benutzt, muss daher ihre Inhalte vorher retten und zum Schluss wieder restaurieren (siehe den Code der Funktion PEEK).

    ...und da Basic-Funktionen beliebig verschachtelt werden können, muss dieses Retten/Restaurieren unbedingt über den Stack erfolgen, nicht über Selbstmodifikationen.

  • jsr $b7f7 ; FAC#1 nach 16 Bit in Y/A bzw. $14/$15

    Vorsicht, da können $schlimmeDinge passieren: In $14/15 bewahren POKE/WAIT die gewünschte Speicheradresse auf, während die restlichen Argumente ausgewertet werden. Jede Basic-Funktion, die ihrerseits diese Speicherstellen benutzt, muss daher ihre Inhalte vorher retten und zum Schluss wieder restaurieren (siehe den Code der Funktion PEEK).

    ...und da Basic-Funktionen beliebig verschachtelt werden können, muss dieses Retten/Restaurieren unbedingt über den Stack erfolgen, nicht über Selbstmodifikationen.

    In der Tat, an das hab ich freilich nicht gedacht. Eindeutig nicht sauber, aber andererseits in der Praxis eher selten vorkommend (dass bei der Auswertung des 2. POKE-Parameters ausgerechnet Stringfunktionen, und dann auch noch HEX$ auftritt). Aber konstruieren kann man so einen Fall alle mal:

    Code
    1. 10 fori=1024to2048
    2. 20 poke i,63andasc(right$(hex$(i),1))
    3. 30 next

    Dazu also folgende aktualisierte Variante:

    function-ext.asm

    function.zip