RESTARTING in 68K Assembler for QDOS

Anything QL Software or Programming Related.
User avatar
ql_freak
Gold Card
Posts: 354
Joined: Sun Jan 18, 2015 1:29 am

RESTARTING in 68K Assembler for QDOS

Post by ql_freak »

As no toolkit or SB-extension fullfills my requirements for a SuperBASIC extension to read a line fully, I have now started to (re)learn programming in 68k Assembler for QDOS. I have installed QMAC (the QUANTA version - which I have bought formerly - I was a QUANTA member- from Dilwyns page). Below there's a screenshot of what I did so far:



Image


Here is the source code (unfortunately not finished, indeed not even one line of the real code is implemented):

Code: Select all

* SuperBASIC/SBASIC function string$ = GETLINE$(channel_number):REMark # optional
*
* Copyright (c) 2016 Dec 25, Peter Sulzer, Fuerth (Germany) - ALL RIGHTS RESERVED

* Definitions:
BP.INIT    EQU    $110        Initialise SuperBASIC/SBASIC PROCedures and FuNctions

            SECTION CODE


* Initialise SuperBASIC/SBASIC Extensions:
            LEA PROC_DEF,A1
            MOVE.W BP.INIT,A2  Note: In SMS(/Q) BP.INIT has a different name
            JSR (A2)

PROC_DEF
            DC.W    0           No of PROCedures
            DC.W    0           END of definitition of PROCedures
            DC.W    1           One (1) FuNction
            DC.W    GETLINE-*   FuNction GETLINE$(channel_number)
            DC.B    8           Name of GETLINE$ is 8 characters long
            DC.B    'GETLINE$'  The characters (8) of GETLINE$
            DC.W    0           END of definitions of FuNctions
            DC.L    0           For safety (LEN(GETLINE$) is longer than 7 chars)

GETLINE
*           Start below with the code of the GETLINE$() function
*            START HERE WITH OUR CODE

            END                 This signals to QMAC that this is the end

* Anything after the END directive (which doesn't produce any code) is ignored
It was, albeit I have programmed a lot of SB-Extensions in my old QDOS days, quite difficult after so much years. So I would be pleased, if you could tell me, if the above code is (PRINCIPALLY) correct.

p.s.: After some corrections (especially replacing the string delimiters from " to ') this source was assembled without errors with QMAC. Unfortunately it produces a "_rel" (relocatable object file), but I want to get a "_bin" file, so that I must not link. I have specified "-bin" in the command line - what's my fault?


http://peter-sulzer.bplaced.net
GERMAN! QL-Download page also available in English: GETLINE$() function, UNIX-like "ls" command, improved DIY-Toolkit function EDLINE$ - All with source. AND a good Python 3 Tutorial (German) for Win/UNIX :-)
User avatar
janbredenbeek
Super Gold Card
Posts: 631
Joined: Wed Jan 21, 2015 4:54 pm
Location: Hilversum, The Netherlands

Re: RESTARTING in 68K Assembler for QDOS

Post by janbredenbeek »

ql_freak wrote: Here is the source code (unfortunately not finished, indeed not even one line of the real code is implemented):

Code: Select all

* SuperBASIC/SBASIC function string$ = GETLINE$(channel_number):REMark # optional
*
* Copyright (c) 2016 Dec 25, Peter Sulzer, Fuerth (Germany) - ALL RIGHTS RESERVED

* Definitions:
BP.INIT    EQU    $110        Initialise SuperBASIC/SBASIC PROCedures and FuNctions

            SECTION CODE


* Initialise SuperBASIC/SBASIC Extensions:
            LEA PROC_DEF,A1
            MOVE.W BP.INIT,A2  Note: In SMS(/Q) BP.INIT has a different name
            JSR (A2)

PROC_DEF
            DC.W    0           No of PROCedures
            DC.W    0           END of definitition of PROCedures
            DC.W    1           One (1) FuNction
            DC.W    GETLINE-*   FuNction GETLINE$(channel_number)
            DC.B    8           Name of GETLINE$ is 8 characters long
            DC.B    'GETLINE$'  The characters (8) of GETLINE$
            DC.W    0           END of definitions of FuNctions
            DC.L    0           For safety (LEN(GETLINE$) is longer than 7 chars)

GETLINE
*           Start below with the code of the GETLINE$() function
*            START HERE WITH OUR CODE

            END                 This signals to QMAC that this is the end

* Anything after the END directive (which doesn't produce any code) is ignored
It was, albeit I have programmed a lot of SB-Extensions in my old QDOS days, quite difficult after so much years. So I would be pleased, if you could tell me, if the above code is (PRINCIPALLY) correct.
It looks good. The DC.L at the end is probably not needed, you only need to adjust the number of procs/fns if their names are longer than 7 characters (since the vector allocates (PROCNUM+FUNCNUM)*8 bytes in the name table and name list).
p.s.: After some corrections (especially replacing the string delimiters from " to ') this source was assembled without errors with QMAC. Unfortunately it produces a "_rel" (relocatable object file), but I want to get a "_bin" file, so that I must not link. I have specified "-bin" in the command line - what's my fault?
Have you tried -NOLINK? This was the option on GST MAC to generate executable code directly (I don't know if QMAC is different in this respect).

Jan.


User avatar
ql_freak
Gold Card
Posts: 354
Joined: Sun Jan 18, 2015 1:29 am

Re: RESTARTING in 68K Assembler for QDOS

Post by ql_freak »

Thank you Jan, that's it :-)


http://peter-sulzer.bplaced.net
GERMAN! QL-Download page also available in English: GETLINE$() function, UNIX-like "ls" command, improved DIY-Toolkit function EDLINE$ - All with source. AND a good Python 3 Tutorial (German) for Win/UNIX :-)
User avatar
ql_freak
Gold Card
Posts: 354
Joined: Sun Jan 18, 2015 1:29 am

Re: RESTARTING in 68K Assembler for QDOS

Post by ql_freak »

Next problem ;-) I try to find the "Name Table" of SuperBASIC (for then to find the address of the INPUT command), for testing I write it just in SuperBASIC, here is my code, but it returns 0, what's my fault:

Code: Select all

100 REMark Find Address of SuperBASICs INPUT Routine
200 :
300 SVS=VER$(-2):REMark  SVS is address of the system variables - ABSOLUTE!
400 SV_BASIC=HEX('10'):BV_NBAS=HEX('18'):BV_NLBAS=HEX('20')
900 :
1000 nt=PEEK_L(PEEK_L(SVS+SV_BASIC)+BV_NBAS):REMark get address of BASIC name table
1100 PRINT nt:REMark PRINT address of SuperBASIC Name Table - this prints 0 :-(
p.s.: There is now a first version (command line version) of the expression calculator in my python tutorial - there will be a GUI-program similar to my Coca later - but you will see the power of the "eval(...)" function from Python in this tutorial. It is not yet linked in the navigation frame, but only at the end of the second chapter of the tutorial at the end.

It is not finished, e. g. for the expression:

(-2)**.5 # ("#" is the comment character in Python and ** the "power off" operator) it prints 8.659...e-17 + 1.414...j - Not totally wrong but not nice


http://peter-sulzer.bplaced.net
GERMAN! QL-Download page also available in English: GETLINE$() function, UNIX-like "ls" command, improved DIY-Toolkit function EDLINE$ - All with source. AND a good Python 3 Tutorial (German) for Win/UNIX :-)
User avatar
janbredenbeek
Super Gold Card
Posts: 631
Joined: Wed Jan 21, 2015 4:54 pm
Location: Hilversum, The Netherlands

Re: RESTARTING in 68K Assembler for QDOS

Post by janbredenbeek »

ql_freak wrote:Next problem ;-) I try to find the "Name Table" of SuperBASIC (for then to find the address of the INPUT command), for testing I write it just in SuperBASIC, here is my code, but it returns 0, what's my fault:

Code: Select all

100 REMark Find Address of SuperBASICs INPUT Routine
200 :
300 SVS=VER$(-2):REMark  SVS is address of the system variables - ABSOLUTE!
400 SV_BASIC=HEX('10'):BV_NBAS=HEX('18'):BV_NLBAS=HEX('20')
900 :
1000 nt=PEEK_L(PEEK_L(SVS+SV_BASIC)+BV_NBAS):REMark get address of BASIC name table
1100 PRINT nt:REMark PRINT address of SuperBASIC Name Table - this prints 0 :-(
SV_BASIC points to the start of SB's job header, not the start of its data area. You should add this to the value of SV_BASIC before PEEKing the system variable:

Code: Select all

nt=PEEK_L(PEEK_L(SYS+SV_BASIC)+HEX('68')+BV_NBAS)
However this is not good programming practice since the S*BASIC data area is dynamic and may be moved when another job is EXECed or terminated, and even when S*BASIC itself has to allocate memory. Minerva and SMSQ/E have extended versions of PEEK and POKE that can handle relative addresses, either to the SV of BV base address. It's best demonstrated by a small utility I wrote to dump the contents of SB's name table:

Code: Select all

100 REMark ntdump_bas NameTable Dump Utility
110 REMark v0.0 20160720 Jan Bredenbeek
120 REMark requires: TK2 extensions AND Minerva OR SMSQ
130 DEFine PROCedure ntdump(ch$)
140 LOCal l,nt,ty,no,nl,nl$,ch
150   IF PARSTR$(ch$,1)<>"" THEN
160     ch=FOP_NEW(PARSTR$(ch$,1))
165     IF ch<0:PRINT#0;'Error opening file':RETurn
170   ELSE
180     ch=1
190   END IF
200   REMark loop for all NT entries
210   PRINT#ch;'Type','Pointer','Name'
220   FOR nt=0 TO PEEK_L(\\28)-PEEK_L(\\24)-8 STEP 8
230     ty=PEEK_W(\24\nt):REMark type
240     no=PEEK_W(\24\nt+2):REMark namelist offset
250     IF no>=0 THEN
260       nl=PEEK(\32\no):nl$=""
270       FOR l=1 TO nl:nl$=nl$&CHR$(PEEK(\32\no+l))
280     ELSE
290       nl$="*NO NAME*"
300     END IF
310     PRINT#ch;HEX$(ty,16),HEX$(no,16),HEX$(PEEK_L(\24\nt+4),32),nl$
320   END FOR nt
330   IF ch<>1:CLOSE#ch
340 END DEFine ntdump
Jan.


User avatar
ql_freak
Gold Card
Posts: 354
Joined: Sun Jan 18, 2015 1:29 am

Re: RESTARTING in 68K Assembler for QDOS

Post by ql_freak »

SV_BASIC points to the start of SB's job header, not the start of its data area. You should add this to the value of SV_BASIC before PEEKing the system variable:

Code: Select all

nt=PEEK_L(PEEK_L(SYS+SV_BASIC)+HEX('68')+BV_NBAS)
However this is not good programming practice since the S*BASIC data area is dynamic and may be moved when another job is EXECed or terminated, and even when S*BASIC itself has to allocate memory. Minerva and SMSQ/E have extended versions of PEEK and POKE that can handle relative addresses, either to the SV of BV base address. It's best demonstrated by a small utility I wrote to dump the contents of SB's name table:

Jan.[/quote]

Thank you, nice program

Peter


http://peter-sulzer.bplaced.net
GERMAN! QL-Download page also available in English: GETLINE$() function, UNIX-like "ls" command, improved DIY-Toolkit function EDLINE$ - All with source. AND a good Python 3 Tutorial (German) for Win/UNIX :-)
User avatar
ql_freak
Gold Card
Posts: 354
Joined: Sun Jan 18, 2015 1:29 am

Re: RESTARTING in 68K Assembler for QDOS

Post by ql_freak »

Hi!

My problem for writing a GETLINE$-FuNction is, that a QL-string may have a length of 32767 chars (or just 32766 chars, see this interressting thread). This will mean, that a SB-FuNction string$=GETLINE$(#channel) requires (IMHO) at least 64 KBytes. The FuNction must (eventually) return a string on the S*BASIC-Stack with a length of 32KBytes, this must be assigned to a S*BASIC variable (which of course requires 32KBytes), i.e. 64KBytes in total. Even if if the GETLINE$-FuNction just fetches one (1) char, 32KBytes must be reserved for the IO.FLINE/IO.FSTRG trap - you don't know before, how long the string is, especially not on none directory device driver channels, like a PIPE).

This is of course no problem on QPC or on a normal QL with memory extension. But this would be a great problem on an unexpanded QL, which has just 128KBytes. 32KBytes are reserved for the screen, another amount for QDOS/S*BASIC. AFAIK a 128KByte-QL has about 80KBytes free after a reboot.

This means, if there is just an Editor (like e.g. "The Editor") running, a GETLINE$-function which reserves 32KByte for input would fail on an unexpanded QL, even if the string, which must be read is so long that the 32KByte area plus the real string length wouldn't fit into the FREEMEM. On the other side, Sinclair (Tebby) did it. The INPUT command handles such situations. Of course INPUT string$ would fail on an unexpanded QL in such cases, it handles situations, where the inputted string is much smaller than 32766 characters. I have a (printed) disassembly (partly commented) of the JS version of the QL, and I have found the INPUT command - but I don't understand it.

In a first try (I have now started with the GETLINE$()-function) I will (I don't know if this is correct/possible) reserve 32KBytes on the BASIC stack, call the IO.FLINE or IO.FSTRG (or so), which stores the fetched string in the SBASIC stack and then assign it to a S*BASIC string variable. But in the worst case, this requires 64KBytes :-( IMHO unacceptable on an unexpanded QL. Please note, that this will at least require 32KBytes for the IO.FLINE, even if the fetched string is just 1 character long (you don't know before, how long the string is, especially, if it is not a directory device channel, but e.g. a PIPE).

Better would be, to read only "junks" of memory (e.g. 2048 bytes), if a LF is found, then return, else reserve more memory and read another 2K chunk (with IO.FLINE/IO.FSTRG), ... so long until an LF is found (up to a maximum of 32766 characters - longer strings seems not be supported in SBASIC - and I'm afraid, on a normal QL it is less). When LF has been found, make a SBASIC string variable (is this possible?) of the required length and copy the "chunks" of the characters to the S*BASIC string variable. Unfortunately this is a horror to implement :-(


http://peter-sulzer.bplaced.net
GERMAN! QL-Download page also available in English: GETLINE$() function, UNIX-like "ls" command, improved DIY-Toolkit function EDLINE$ - All with source. AND a good Python 3 Tutorial (German) for Win/UNIX :-)
User avatar
tofro
Font of All Knowledge
Posts: 2699
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: RESTARTING in 68K Assembler for QDOS

Post by tofro »

Peter,

there are some possible ways how you could address your problem:
  1. Move away from strings completely. This would allow you to read data into raw memory, allocated using RESPR or ALCHP. This would remove all of the limits you currently see with strings. Turbo Toolkit has functions to PEEK$ strings from such memory, but then again you would duplicate your memory overhead. Makes the BASIC programming a bit more awkward, though. This is how DP's EDITOR works, if I recall it right.
  2. Require your calling program to allocate the strings using DIM and pass that string into the MC procedure by reference - This is maybe the best approach. Get the variable in the MC procedure, look it up in the name table and check the amount of dimensions and the size of the allocation (first and second word entry in the name table's array descriptor after the value pointer). You would then only read as much bytes as fit into the DIMed string and read directly into the target string, without a temporary buffer, the user of your MC routine could decide how much memory he wants to spend, your MC routine would not have to deal with allocation at all. Add some reserved return value when your code detects "there's more to read" to signal to the user "your string is too small, come back to read more". There is a relatively precise description of the structure of DIMed strings and arrays in the DIY toolkit documentation on the MAX and MIN keywords. Such an approach does, however, rely on a valid name table structure and could thus not be compiled with Turbo (QLiberator would cope, though, I think). It might be a bit awkward to use, as you need to DIM a string before you can GETLINE$ a value into it, but I don't currently know a portable method to grow an S*Basic string in memory from an MC procedure.
  3. Stay with your approach and allocate memory to the Ari stack in chunks of, e.g. 1024 bytes as you go. This can be relatively tedious and slow for long strings, as you'd have to move the whole string allocation maximum 32 times downwards when you re-allocate the target memory area for the growing string - And it would still duplicate memory: If you do a x$=GETLINE$, you need the memory for the string twice, once for the temporary return value on the arithmetic stack, the other for the target variable.
    target string in the assigned variable. This might look like a way to go at first but collects all the possible downsides of return value passing ;)
And don't forget to add a number of strategically placed TRAP #4, all your addresses are relative to a6!

Hope this helps,
Tobias
Last edited by tofro on Thu Dec 29, 2016 10:32 am, edited 3 times in total.


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
Martin_Head
Aurora
Posts: 851
Joined: Tue Dec 17, 2013 1:17 pm

Re: RESTARTING in 68K Assembler for QDOS

Post by Martin_Head »

A rough suggestion..

1.Make sure the maths stack is clear of any supplied parameters

2.Reserve say 512 bytes on the maths stack with BV.CHRIX, then subtract 512 from A1 and set BV.RIP. This gives you a 512 byte buffer to work in on the maths stack

3. Start reading characters one at a time with IO.FBYTE and store them on the maths stack incrementing A1 as you go

4. If you have read 512 bytes, then reserve another 512 bytes on the maths stack with BV.CHRIX (you may need to reread BV.RIP first) , and move the 512 bytes you have just read down 512 bytes to make room for another 512 bytes

5. Continue 3-4 until you get a LineFeed or reach 32K in length

6. Reset A1 to the start of the string with a read of BV.RIP, set the string length word at the start of the string

7. set D4 to 1 for a string argument, and RTS

There may/will be a gap at the end of the string and the bottom of the maths stack. I don't know if QDOS/SMSQ will mind about this. You may have to shuffle your string up in memory to remove the gap before returning.

Martin Head

Pipped at the post by ToFro...


User avatar
janbredenbeek
Super Gold Card
Posts: 631
Joined: Wed Jan 21, 2015 4:54 pm
Location: Hilversum, The Netherlands

Re: RESTARTING in 68K Assembler for QDOS

Post by janbredenbeek »

Peter,

AFAIK the INPUT command works by using the S*BASIC buffer (at 0(a6) through 8(a6)) to read a chunk of data using IO.FLINE (or maybe IO.EDLIN if INPUTing from a console channel), and if this call returns a 'buffer full' error it allocates more buffer memory using the internal S*BASIC memory management (in the same manner as BV.CHRIX does). It then repeats the procedure but now at the start of the newly allocated buffer space (after the string already read in), since IO.FLINE continues to read from the next position in the file. This goes on until a LF is read. If the string is very long you'll end up with a big S*BASIC buffer which will not be released, even not after a NEW (at least on JS, Minerva is better behaved in this respect). Note that on JM and earlier there is no provision for strings longer than the buffer size, you just get a 'buffer full' error when trying to read more characters than fit in the buffer (128 bytes after switch-on)!

This is similar to Martin's suggestion, except that it uses IO.FLINE (which is much more efficient than IO.FBYTE and can still be used if the length is not known beforehand, as the above example shows). Unfortunately, you still have to copy your string to the RI stack in order to return it as a function (or assign a variable using BP.LET) so you would still need twice the memory, even three times when assigning to a variable!

If only S*BASIC would support pointers like C does... :(


Post Reply