Hello,
I have created a BASIC tokeniser (and AMSDOS header) for a big BASIC project. I know WinApe can do it by 'typing' a copy/paste text, but it is slow (you have a x10 but you need to switch it ON and OFF) and it can't do several files... With this, I can easily add several BAS files into a DSK file.
It works for me and seem to create identical tokens than my tests with WinApe. Only missing point: no floating point tokeniser... If you know how to create the 5 bytes from a string, feel free to tell me (I have some documentation and a link to the ROM assembly, but I can't figure it out in python).
Link to project (https://jolletx.visualstudio.com/CPCBasicator/_git/CPCBasicator)
https://www.cpcwiki.eu/index.php?title=Technical_information_about_Locomotive_BASIC&mobileaction=toggle_view_desktop#Floating_Point_data_definition
Yes I know this one (I put a link to it in doc directory of project).
Just not so easy to reprogram (even with readable ASM method).
https://github.com/Bread80/CPC6128-Firmware-Source look for REAL_5byte_to_real
Also, my project was not using floating point numbers so no real need for them :D
@SagaDS if floatnumber=0 then
exponent=0
mantissa=0
else
exponent =-127
norm= abs(floatnumber)/(2^exponent)
while norm>=1 or norm<0.5
exponent=exponent+1
norm= abs(floatnumber)/(2^exponent)
wend
if sgn(floatnumber)=1 then norm=norm-0.5
mantissa=int(norm*(2^32))
exponent=exponent+128
endif
4 bytes Little endian mantissa and 1 byte exponent
I wrote a small program in Locomotive BASIC to do the conversion. It should not be a problem to convert any part of it to Python or something else.
100 MODE 2 : ZONE 16
110 float!=-123.456
120 PRINT "Number: ",float!
130 PRINT
140 IF float!<>0 THEN GOTO 170
150 PRINT "Special case: Set all bytes to zero."
160 GOTO 350
170 sign=SGN(float!)
180 intDigits=INT(LOG(sign*float!)/LOG(2))
190 exponent=128+intDigits+1
200 mantissa=(sign*float!)/(2^intDigits)
210 PRINT "Sign: ",sign
220 PRINT "Exponent: ",exponent,
230 PRINT "&";RIGHT$("0"+HEX$(exponent),2)
240 mantissa=mantissa-1
250 mantissa=mantissa/2
260 PRINT "Mantissa:"
270 FOR i=1 TO 4
280 mantissa=mantissa*256
290 intPart=INT(mantissa)
300 mantissa=mantissa-intPart
310 IF i=1 AND sign<0 THEN intPart=intPart+128
320 PRINT i;": ",intPart,
330 PRINT "&";RIGHT$("0"+HEX$(intPart),2)
340 NEXT
350 PRINT
360 PRINT "Memory:"
370 FOR i=0 TO 4
380 byte=PEEK(@float!+i)
390 PRINT i;": ",byte,
400 PRINT "&";RIGHT$("0"+HEX$(byte),2)
410 NEXT
420 PRINT
Some explanation:
- Line 110: In modern languages often there is some kind of parsing function to convert a string to a floating point number. Here I use the BASIC interpreter to do this work. Afterwards the floating point number is deconstructed to its parts.
- Line 140: Value 0.0 is a special case stored as five bytes with &00.
- Line 180: Instead of searching for the first bit, it can be calculated with a logarithm.
- Line 190: Because the result of the logarithm is rounded down, the exponent is increased by one.
- Line 200: The mantissa value will always start with 1,...
- Line 240: Remove the leading 1. The mantissa is now 0,...
- Line 250: Reserve one bit for the sign. Shift the mantissa right. It is now 0,0...
- Line 280: Shift the mantissa left by 8 bits. Extract these 8 bits.
- Line 310: Only for the first mantissa byte: Add the sign bit.
- Line 370: As can be seen, in memory the values are stored in reverse.
Finally I have to say that I only did a few tests. There could be some corner cases left where unexpected things happen. Also using a floating point number itself to do the calculations could introduce rounding errors. This effect will be minimized on modern systems that use 64 bits, much more than the needed 32 bits.
This works on every CPC and emulator:
- Load ASCII file (every line begins with an number)
- Save it with SAVE"xzy
Now you got a BASIC program on you disc / cassette
Quote from: GUNHED on 15:11, 20 July 25This works on every CPC and emulator:
- Load ASCII file (every line begins with an number)
- Save it with SAVE"xzy
Now you got a BASIC program on you disc / cassette
There are several ways to generate BASIC files.
My purpose here was to generate them directly on a DSK produce on PC.
Quote from: McArti0 on 21:35, 19 July 25@SagaDS
if floatnumber=0 then
exponent=0
mantissa=0
else
exponent =-127
norm= abs(floatnumber)/(2^exponent)
while norm>=1 or norm<0.5
exponent=exponent+1
norm= abs(floatnumber)/(2^exponent)
wend
if sgn(floatnumber)=1 then norm=norm-0.5
mantissa=int(norm*(2^32))
exponent=exponent+128
endif
4 bytes Little endian mantissa and 1 byte exponent
Thanks for proposed algo (@lightforce6128 too).
I will give it a go sometime in future.
Just hope that python will not modify float precision in a way that result won't be the same...
That is why I was looking for a text parser (thus the ROM information) instead of a conversion from float...
You have to calculate with double numbers.
I have implemented algorithm of lightforce6128.
I had to modify one thing when testing with more values in python (was working in BASIC):
intDigits=int(math.log(sign*floatnumber)/math.log(2))
if intDigits<0:
intDigits-=1
exponent=128+intDigits+1
New version v1.0 pushed.
It took me a while to figure it out in TypeScript. I hope it's correct...
https://github.com/benchmarko/CPCBasicTS/blob/8496dd96ecc1a2585626637fc14b6d23e3c0952f/src/CodeGeneratorToken.ts#L375C1-L394C3
private static floatToByteString(number: number) {
let mantissa = 0,
exponent = 0,
sign = 0;
if (number !== 0) {
if (number < 0) {
sign = 0x80000000;
number = -number;
}
exponent = Math.ceil(Math.log(number) / Math.log(2));
mantissa = Math.round(number / Math.pow(2, exponent - 32)) & ~0x80000000;
if (mantissa === 0) {
exponent += 1;
}
exponent += 0x80;
}
return CodeGeneratorToken.convInt32ToString(sign + mantissa) + CodeGeneratorToken.convUInt8ToString(exponent);
}
And the reverse (bytes to number):
https://github.com/benchmarko/CPCBasicTS/blob/8496dd96ecc1a2585626637fc14b6d23e3c0952f/src/BasicTokenizer.ts#L98C1-L114C3
...