Subtilis: A new BASIC-like compiler for RiscOS
Subtilis: A new BASIC-like compiler for RiscOS
"Now how do I do structures in BASIC again. Oh wow" and then ten minutes later "H'mm, this is kind of slow. What I really need is some sort of compiler". Three years later my A3000 port of Rebelstar is still no more than a few peeks and pokes in a tokenized BASIC file; but I have written a compiler...
Now before anybody gets too excited, note the following.
1. This is not a BBC BASIC compiler. The language, Subtilis, ressembles and is inpsired by BBC BASIC, but it is not and will never be compatible with it. For one thing, it parses ASCII files rather than tokenized BASIC files. In addition, the grammar is slightly different in places, there's a ton of keywords that aren't implemented and will never be implemented, e.g., GOSUB, READ, DATA, EVAL (well I might do a cut down version of EVAL at some point), RESTORE and many others. As time goes on, and I add more features to Subtilis, it's likely to diverge further and further from BBC BASIC.
2. It's massively untested and massively unfinished. A whole pile of the keywords are missing, including INPUT! There's no optimizer, no linker (so you're currently restricted to a single source file), no assembler, no error-recovery (so you get one terse parse error and that's it), and very little documentation, etc. There are also a pile of known bugs that I haven't got around to fixing yet (and presumably lots of unknown ones). To make matters worse, I still haven't implemented structures. I have just finished implementing strings though, and since you can now write Hello World in Subtilis, I thought the time had come to post about it.
Given the current state of the project, I'm not going to release any binaries for the compiler just yet, but for those of you among us who enjoy messing around with other people's, buggy, unfinished, under-documented code, then head on over to https://github.com/markdryan/subtilis. I'll leave the rest of you with the following attachment, compiled this morning with Subtilis. It's a statically compiled, absolute binary. To run, unzip it, copy the file to your Archimedes or RiscPC of choice, set the file type to Absolute and double click on it (at your own risk).
- Attachments
-
- RunImage.zip
- (2.39 KiB) Downloaded 111 times
Re: Subtilis: A new BASIC compiler for RiscOS
That works. Do you have the source for that, to see how it compares to BBC Basic?markdryan wrote: ↑Fri Aug 07, 2020 1:28 pm I'll leave the rest of you with the following attachment, compiled this morning with Subtilis. It's a statically compiled, absolute binary. To run, unzip it, copy the file to your Archimedes or RiscPC of choice, set the file type to Absolute and double click on it (at your own risk).
- daveejhitchins
- Posts: 7888
- Joined: Wed Jun 13, 2012 6:23 pm
- Location: Newton Aycliffe, County Durham
- Contact:
Re: Subtilis: A new BASIC compiler for RiscOS
Well done . . .
Dave H.
Re: Subtilis: A new BASIC compiler for RiscOS
Well done on your achievements so far!
Re: Subtilis: A new BASIC compiler for RiscOS
Anyway, here are the benchmark results for BBC Basic V (RiscOS 4.39), Subtilis (68b5f4b), and ABC 4.18. All these results were obtained on my 130MB Strong ARM RiscPC running RiscOS 4.39. All the benchmarks were run directly from the desktop. For these first set of benchmarks, lower scores are better.
Mandel
- BBC BASIC : 29.2
- Subtilis : 33.68
- ABC : 42.92
Fannkuch
- BBC BASIC : 625
- Subtilis : 25
- ABC : 39
Life
- BBC BASIC : 1535
- Subtilis : 211
- ABC : n/a
Sieve
- BBC BASIC : 26
- Subtilis : 5
- ABC : 9
Finally, I ran some of the micro benchmarks in ClockSp through BBC BASIC on the RiscPC and the compilers. The results here are more mixed, although we see the same patterns; integer good, floating point awful, strings okay. Note I had to disable some of the tests for ABC as it was optimising the entire tests away, which is quite impressive and is something that Subtilis cannot yet do. Going forward though I guess we should be able to optimise away some of these benchmarks. In the meantime, here are the results. Note in these benchmarks, bigger is better.
Real REPEAT Loop
- BBC BASIC : 2204
- Subtilis : 1389
- ABC : 1322
- BBC BASIC : 1659
- Subtilis : 34142
- ABC : 7468
- BBC BASIC : 2064
- Subtilis : 208
- ABC : n/a
Integer FOR loop
- BBC BASIC : 1854
- Subtilis : 3869
- ABC : n/a
- BBC BASIC : 12509
- Subtilis : 1600
- ABC : 1563
- BBC BASIC : 3277
- Subtilis : 6165
- ABC : 6062
Procedure Call
- BBC BASIC : 3571
- Subtilis : 9252
- ABC : 2605
Re: Subtilis: A new BASIC compiler for RiscOS
Neither to be honest, did I.
Re: Subtilis: A new BASIC compiler for RiscOS
Code: Select all
MODE 2
LOCAL DIM coords(43)
coords() = 0,0,0,2,0,-2,0,4,0,-4,0,5,0,-5,
-2,2,2,-2,-2,-2,2,2,
3,3,4,4,
3,-3,4,-4,
-3,-3,-4,-4,
-3,3,-4,4,
10,-6,-255,-255
VDU [23;8202;0;0;0;]
B%=0
FOR C%=0 TO 15
IF C%=8 THEN B%=7 ENDIF
VDU [19,C%,B%;0;]
NEXT
VDU [19,2,2;0;19,10,2;0;]
VDU [19,4,3;0;19,12,3;0;]
VDU [19,6,1;0;19,14,1;0;]
VDU [29,640;564;]
p=0.001
G%=0
PROCcircle(PI/20,0,2,0)
PROCcircle(PI/20,PI/5,4,0)
PROCcircle(PI/20,-PI/5,6,0)
S%=40
GCOL 0,8
counter% := 0
LOCAL X%
REPEAT
X% = coords(counter%)
Y% := coords(counter%+1)
counter% += 2
IF X%<>-255 THEN PROCsquare(X%,Y%) ENDIF
UNTIL X%=-255
G%=1
PROCcircle(PI/20,0,2,PI)
PROCcircle(PI/20,PI/5,4,PI)
PROCcircle(PI/20,-PI/5,6,PI)
C%=0
D%=1
REPEAT
T% := TIME
VDU [19,2+C%,2;0;19,10+C%,2;0;]
VDU [19,2+D%,0;0;19,10+D%,7;0;]
VDU [19,4+C%,3;0;19,12+C%,3;0;]
VDU [19,4+D%,0;0;19,12+D%,7;0;]
VDU [19,6+C%,1;0;19,14+C%,1;0;]
VDU [19,6+D%,0;0;19,14+D%,7;0;]
D%=C%
C%=C%EOR1
REPEAT UNTIL TIME>8+T%
UNTIL FALSE
DEF PROCsquare(X%,Y%)
Y%=Y%-2
MOVE X%*S%+S%,Y%*S%-S%
MOVE X%*S%-S%,Y%*S%-S%
PLOT 85,X%*S%+S%,Y%*S%+S%
PLOT 85,X%*S%-S%,Y%*S%+S%
ENDPROC
DEF PROCcircle(xr,zr,c,b)
C%=0
FOR a := b TO PI+b STEP PI/48
GCOL G%,c+C%
Z% := SIN(a)*450
X% := COS(a)*450
Y% := 0
IF xr <> 0 THEN
T1% := COS(xr)*Y%+SIN(xr)*Z%
T2% := -SIN(xr)*Y%+COS(xr)*Z%
Y% = T1%
Z% = T2%
ENDIF
IF zr<>0 THEN
T1% := COS(zr)*X%+SIN(zr)*Y%
T2% :=- SIN(zr)*X%+COS(zr)*Y%
X% = T1%
Y% = T2%
ENDIF
X%=X%/((Z%+1000)*p)
Y%=Y%/((Z%+1000)*p)
PLOT 69,X%,Y%
PLOT 69,X%,Y%+4
C%=C% EOR 1
NEXT
ENDPROC
Note the lack of a DATA statement, the square brackets around the VDU statements, and variable declaration with := which creates a local variable.
- Dave Footitt
- Posts: 998
- Joined: Thu Jun 22, 2006 10:31 am
- Location: Abandoned Uranium Workings
- Contact:
Re: Subtilis: A new BASIC compiler for RiscOS
Re: Subtilis: A new BASIC compiler for RiscOS
VAL is one of those compiler unfriendly keywords where the return type of the expression it generates cannot always be known at compile time. The compiler has to assume that it will be a floating point expression and so VAL is implemented by converting a string to a floating point number using floating point arithmetic, which is wonderfully slow on most Archimedes. For this reason, I've added variant of VAL which takes an extra parameter. The extra parameter denotes the base of the number represented by the string in the first parameter, and when specified, forces Subtilis to perform a string to integer conversion using integer arithmetic. For example,
Code: Select all
print val("256", 10)
print val("100", 16)
Code: Select all
256
256
I was planning to implement INSTR next, but I've been working on strings for almost 6 months now and am suffering from a case of string fatigue. So instead, I'm going to start looking at SYS and the assembler.
Re: Subtilis: A new BASIC compiler for RiscOS
Code: Select all
sys &6a
Code: Select all
sys "OS_Reset"
Code: Select all
a% := &6a
sys a%
Code: Select all
sys "OS_Write0", "hello world"
The other problem is that to make this work, the compiler needs to know about all the RiscOS SYS calls, and for the time being it only knows about a subset of them. For example, it doesn't currently know anything about the WIMP. I'll add these missing calls over time. I'll probably also allow the programmer to make a sys call using a constant integer ID that the compiler doesn't recognize. Such calls will be slower however as the compiler will need to assume all the registers have been corrupted.
For more information about SYS calls in Subtilis check out https://github.com/markdryan/subtilis/b ... lis.md#sys.
Now we can do SYS calls, we can do sprites. So there's probably enough working in Subtilis to write a simple (silent) game now. I did consider giving this a go, but decided that Subtilis was still a bit too irritating to develop in right now, so I'll continue with the language improvements and missing features for the being. Next on the list is GCOL TINT and COLOUR TINT.
Re: Subtilis: A new BASIC compiler for RiscOS
There are a bunch of compiler directives that can be used to tell it that you know what you are doing, and that strict adherance to some of the features of BBC Basic can be skipped. These make a huge difference to the overall speed.
For example -- you can turn off Array Bounds Checking - so that it doesn't generate loads of tediuos compare instructions for all the array indecies.
-- You can turn off Zeroing of the contents of local variables - which speeds up entry to proceedures.
-- And if you are using ! operators you can promise that they are 4-byte aligned and so it can use just one instruction rather than the whole song-and-dance sequence.
To do these try REM {NOZEROLOCALS} and REM {ALIGNEDPLING} and REM {NOARRAYCHECK} in the start of your program - Might be interesting
Paul
Head of Languages, Acornsoft, 1983-85
Microsystems Software Manager, Acorn 85-88
Re: Subtilis: A new BASIC compiler for RiscOS
REM {NOSTACKCHECK} turn off stack checking - this speeds up PROC and FN calls a lot
REM {NOESCAPECHECK} turns off the placement of a call to SWi OS_EscapeCheck between each statement. This is necessary for the strict behaviour, but very slow. If you want to escape from the code use this with its partner REM {ESCAPECHECK} selectively to control where the checks get done
REM {NOGOTOSUSED} If you promise me not to use GOTOs then I can remember whats in registers from one statement to another, without fear of you jumping into the middle and stuffing up. It also means I don't need to add a table of where all the GOTO targets might be on the end of your code, so it will be smaller.
REM {NOZEROLOCALS} Don't labouriously write zero into every newly instanced local varaible only to have it overwritten shortly afterwards.
REM {SYSCONSTONLY} and REM {SYSKNOWNONLY} With these I can guarentee to generate the native SWI call rather than code to have to go and look up the name of the SWI at runtime.
REM {ALIGNEDPLING} promise that all your use of ! is 4-byte aligned and I'll generate LDR and STR instructions that assume it too. Otherwise, we get to pick up the bytes one at a time and mash them together. Again, you could use these selectively arount different bits of code if there were certain sections that could be relied on the be aligned and others that might not be.
REM{NOARRAYCHECK} dont bother checking array indecies for out of bounds. Saves a reasonable number of instructions for array accesses
A bunch of these are ganged-together by the -quick option which can be added to the command line and which there was once a UI check box for in version 3 of the compiler, I think it has vanished in later ones.
There are more of these directives to deal with more code-saving measures... such as not clearing out registers on return from a SWI call
Anyway, just interested to see how this sits with what your doing - for me there was always the conflict between "Compatibility" with the range of things that could be writen for the interpreter and "Performance" where dropping certain checks saved lots of code - my answer was to default to the former, but offer the latter through the simple use of these directives.
Head of Languages, Acornsoft, 1983-85
Microsystems Software Manager, Acorn 85-88
Re: Subtilis: A new BASIC compiler for RiscOS
The sources for all the benchmarks I used are here https://github.com/markdryan/basic-benchmarks. I built them using the version of ABC that came with my copy of the DDE purchased from ROOL about 3 years ago. The version number of the DDE is 28d. I didn't enable any specific optimisations when compiling with ABC. I just dragged the BASIC programs to the ABC icon and executed the resulting binary. Reading your posts I think I need to re-run the ABC benchmarks with some of these optmisation enabled to get a fairer result. I'll try this out at the weekend.
These ones I'm not going to enable
REM {NOARRAYCHECK}
REM {NOZEROLOCALS}
REM {ALIGNEDPLING}
Subtilis always bounds checks arrays and always zeros locals and arrays and there's no way to tell it not to do so. Also, Subtilis doesn't support the indirection operators so none of the benchmarks I ran make use of these.
These I am
REM {NOESCAPECHECK}
REM {ESCAPECHECK}
Subtilis does check for the escape button but only does so at the start of each iteration of a loop and on entries to procedures and functions. So I can modify the ABC BASIC benchmarks to do the same thing to make things fairer. This is going to make a big difference I imagine.
REM {NOGOTOSUSED}
Subtilis doesn't support GOTO and so it's only fair to enable this.
REM {NOSTACKCHECK}
I need to enable this as well. It's on my TODO list, but right now, Subtilis doesn't check for stack overflow on procedure call. It will happily overwrite the heap for fun and profit. So for the time being I will enable this one to make things fairer.
REM {SYSCONSTONLY} and REM {SYSKNOWNONLY}
Subtilis doesn't support non constant SWI names, either strings or integers, so it's only fair to enable this one as well.
This one might be worth enabling. What does it do exactly? Subtilis only preserves and restores the registers that may be corrupted by a syscall and also are still live, i.e., still needed by the function. The other registers remain unchanged by a call to SYS. It also doesn't zero any of the registers not explicitly specified in the input or output lists, which BBC BASIC appears to do. I actually wasted a couple of hours the other day trying to figure out why the rocks example in chapter 4 of the Archimedes Game Makers Manual (http://www.riscos.com/support/developer ... .htm#l0046) didn't work in Subtilis. The reason was that the rocks example doesn't provide all the trailing parameters expected by some of the SYS calls it makes. It seems to assume that the parameters it doesn't pass will default to zero, and in BBC BASIC they appear to do so.There are more of these directives to deal with more code-saving measures... such as not clearing out registers on return from a SWI call
I guess things are simpler for me as I broke compatibility with BBC BASIC on day one when I decided to use text rather than tokenized source files. Since then I've taken all sorts of liberties with the language.Anyway, just interested to see how this sits with what your doing - for me there was always the conflict between "Compatibility" with the range of things that could be writen for the interpreter and "Performance" where dropping certain checks saved lots of code
Regarding compiler options, currently there are none. I will probably have to add some, like the ability to turn off escape checking, but I intend to keep these few in number. I haven't written the optimizer yet, but when I write it, it will always be on. There won't be any way to disable it. My current plan is that the options that are provided would be specified in a text file in the same directory as the rest of the source code, rather than inline in the code or via compiler options. None of this is implemented yet.
I have some questions about ABC, if you have time
1. How do you generate sys calls when the SYS call id is not known at compile time? Are you using self modifying code?
2. I noticed that ABC was executing some of the ClockSP benchmarks in 0 seconds, causing a division by zero error in ClockSP itself. Does it optimise away code like
Code: Select all
FOR i% = 0 TO 10000 : NEXT i%
Code: Select all
i% = 10000
Re: Subtilis: A new BASIC compiler for RiscOS
Code: Select all
REM {NOESCAPECHECK}
REM {NOGOTOSUSED}
REM {NOSTACKCHECK}
REM {SYSCONSTONLY}
REM {SYSKNOWNONLY}
Code: Select all
REM {ESCAPECHECK}
Mandel:
- Subtilis : 33.68
- ABC : 42.92
- ABC Opt Escape 42.93
Fannkuch:
- Subtilis : 25
- ABC: 39
- ABC Opt Escape : 39
Sieve:
- Subtilis : 5
- ABC : 9
- ABC Opt Escape: 9
I then removed all the
Code: Select all
REM {ESCAPECHECK}
Fannkuch:
- Subtilis : 25
- Subtilis No Escape : 21
- ABC: 39
- ABC Opt No Escape : 17
Sieve:
- Subtilis : 5
- Subtilis No Escape : 5
- ABC : 9
- ABC Opt No Escape: 5
Mandel:
- Subtilis : 33.68
- Subtilis No escape : 33.52
- ABC : 42.92
- ABC Opt No Escape 42.07
As the results for Sieve seemed to be tied I created a new version of the benchmark which increased the maximum number of iterations to 500000 to see whether there were any differences between the compilers.
Sieve 500000:
- Subtilis No Escape : 27
- ABC Opt No Escape: 30
It turns out there was a small difference.
In summary, ABC seems to be quite far ahead now in the Fannkuch benchmark, Subtilis is still far ahead in the mandel benchmark and it also does a little bit better in Sieve.
I also re-ran ClockSp with escape handling turned off for both compilers.
Subtilis No Escape
- Real REPEAT Loop: 1408
- Integer REPEAT Loop: 70294
- REAL FOR Loop: 209
- Integer FOR Loop: 6768
- Trig/Log Test: 1598
- String Manipulation: 6223
- Procedure Call: 16735
ABC No Escape
- Real REPEAT Loop: 1448
- Integer REPEAT Loop: 59750
- REAL FOR Loop: n/a
- Integer FOR Loop: n/a
- Trig/Log Test: 1570
- String Manipulation: 6217
- Procedure Call: 21756
ABC is doing better than Subtilis in general on these microbenchmarks. It's ahead on the for loops and procedure call but behind on the integer REPEAT loop. There's not much difference between the compilers with the other benchmarks.
It's hard to draw too many conclusions from this limited set of benchmarks, but the trend I'm seeing here is that ABC's escape handling seems to have a much bigger impact on the benchmark results than Subtilis's, even when both compilers are checking for escapes at the same locations in the code. With escape handling turned off there's not too much difference between the compilers. Subtilis does better on some benchmarks, ABC on others.
Regarding escape handling, Subtilis checks for the escape button at the entry to every loop, procedure and function call. It does so however, not by calling OS_EscapeCheck, but by reading an internal variable, stored in memory that is set by an escape handler, initialised right at the start of the program. Perhaps, this explains the apparent difference in performance of escape handling between the two compilers.
The source code for all the benchmarks I ran can be found here: https://github.com/markdryan/basic-benchmarks. I used the same version of the compilers that were mentioned in my initial benchmark post above. I'll update this original post later on with the results for escape checking disabled.
Re: Subtilis: A new BASIC compiler for RiscOS
Code: Select all
MODE 1
PROCbanner
def PROCbanner
[
def screen_width = 40
def header = STRING$(screen_width,"*")
def gap = STRING$(13," ")
ADR R0, header_label
SWI "OS_Write0"
SWI "OS_NewLine"
ADR R0, message
SWI "OS_Write0"
SWI "OS_NewLine"
ADR R0, header_label
SWI "OS_Write0"
SWI "OS_NewLine"
MOV PC, R14
header_label:
EQUS header
message:
EQUS "*" + gap + "Hello World!" + gap + "*"
]
There's an example of the FOR loop here https://github.com/markdryan/subtilis/b ... s/demo#L54 where it's used to create a table of coordinates for a SIN wave at compile time and also here https://github.com/markdryan/subtilis/b ... es/regs#L3 where it's used to initialise a bunch of registers.
There are still a few things missing, such as not being able to call other functions and procedures from an assembly language procedure. However, there's enough of it working now for me to move onto the next phase of the project. There's a small amount of documentation available about the assembler here: https://github.com/markdryan/subtilis/b ... #assembler.
Some fun facts about Subtilis. It's now almost 60000 lines of C code. It takes 2.5 seconds to build on my Haswell laptop, 9.5 minutes to build with the DDE on a StrongARM RiscPC. The RiscOS binary, compiled with -Otime, is 177kb. It runs fairly well on the RiscPC with most examples, which in fairness are all pretty small at the moment, taking about a second to compile. It does run on an A3000 but it's a bit too slow. The Fannkuch benchmark https://github.com/markdryan/basic-benc ... /FannkuchS takes 50 seconds to compile. The bottleneck in the compiler is the register allocator so hopefully things will speed up when I re-write it next year. The assembler, which doesn't use the register allocator is much quicker and is probably usable on the A3000 right now, as long as you keep the BASIC to a minimum. This small example https://github.com/markdryan/subtilis/b ... s/arm_fill takes 5 seconds to build on the A3000.
Re: Subtilis: A new BASIC compiler for RiscOS
Unfortunately, this isn't as useful as it sounds, at least not yet. Most prorgams I've tried on the Native copro are IO bound instead of CPU bound, so they don't actually run any faster when compiled with Subtilis, even though the computations are much faster. Also, as VFP2 doesn't support things like COS or SIN, some of the math functions aren't currently implemented. The PiTubeDirect compiler will assert if you try to build a Subtilis program that uses these functions at runtime. In addition, there's still the small problem that Subtilis itself isn't very useful at the moment. You're still limited to a single source file, and some important language features, such as file handling aren't implemented yet.
Still, the assembler has been updated to support most of the VFP2 instructions. MSR and MRS have been added as well. The rest of the ARMv6 instructions are missing but will be added over time. At some point I'll also get around to implementing COS, SIN and the other missing functions. For now though, I'm going to work on finishing off the language, starting with improvements to error handling and then file handling.
When you build Subtilis you now get two compilers:
- subtro - the RiscOS 3 and 4 compiler, previously called basicc
- subtptd - the Native ARM copro compiler
There's an example of a binary created with the new compiler, along with instructions of how to run it, here: viewtopic.php?f=29&t=21358#p302284
Re: Subtilis: A new BASIC compiler for RiscOS
As a possibly useful reference, the Raspberry Pi Pico's boot room has math function implementations, and it's BSD licensed which should be compatible with other open source licenses. See here and nearby. (The basic math functions have special implementations, which won't be portable or necessarily applicable, but if the higher level functions build on the basics in a straightforward way, they could instead be built on top of VFP2. Perhaps.)markdryan wrote: ↑Fri Jan 08, 2021 9:51 pm Subtilis now supports the native ARM copro of PiTube direct, which is fun. It also emits VFP2 code for floating point operations, so for the first time, floating point is fast in Subtilis...
...
...as VFP2 doesn't support things like COS or SIN, some of the math functions aren't currently implemented.
[Thanks to Dominic for the pointer. See also 2.7.2 in the Pico SDK doc (pdf).]
Re: Subtilis: A new BASIC compiler for RiscOS
Thanks for the link Ed. This sounds very interesting.BigEd wrote: ↑Sat Feb 06, 2021 3:02 pmAs a possibly useful reference, the Raspberry Pi Pico's boot room has math function implementations, and it's BSD licensed which should be compatible with other open source licenses. See here and nearby. (The basic math functions have special implementations, which won't be portable or necessarily applicable, but if the higher level functions build on the basics in a straightforward way, they could instead be built on top of VFP2. Perhaps.)markdryan wrote: ↑Fri Jan 08, 2021 9:51 pm Subtilis now supports the native ARM copro of PiTube direct, which is fun. It also emits VFP2 code for floating point operations, so for the first time, floating point is fast in Subtilis...
...
...as VFP2 doesn't support things like COS or SIN, some of the math functions aren't currently implemented.
I haven't made any progress with the compiler itself recently, but there have been a few updates to the assembler. It now understands some of the newer instructions supported on the PiZero that aren't present on the Archimedes (well not on my A3000 at least). MSR/MRS are now supported as are the miscellaneous load and store instructions, e.g., LDRSB, LDRSH, and all the SIMD instructions. None of these instructions, apart from MSR/MRS, are currently used by the compiler, but I might make use of the SIMD instructions when I get around to implementing byte arrays.
Re: Subtilis: A new BASIC compiler for RiscOS
Byte Data Types
I've added two new types, byte and arrays of byte. I borrowed the & suffix used in other basics to denote the byte type, but just to confuse you all, I made the type signed rather than unsigned. This only really matters when converting from byte to other types. I've also added a new keyword, INTZ that allows you to explicitly zero extend rather than sign extend. For example,
Code: Select all
b& := true
print b&
Code: Select all
b& := true
print intz(b&)
To support sign extension on the PDT build, the assembler now understands three new mnemomics; SXTB, SXTB16, SXTH. INTZ will be faster than implicit conversion on the RiscOS build, which lacks access to the sign extend instructions.
Byte Based File Handling
Basic file handling is now supported. OPENUP, OPENIN, OPENOUT, EXT#, PTR#, BGET#, BPUT#, and CLOSE# are now all supported. The syntax for PTR# when setting the file pointer is a little different to BBC Basic's syntax. A ',' is used rather than an '=' to separate the file handle from the location.
Try and Tryone
When an error occurs in Subtilis, normal execution of the procedure in which the error occurs is terminated and control jumps to the nearest error handler. The error can be consumed by the error handler or propagated, but normal execution of the procedure cannot be resumed. The try and tryone keywords allow you to override this behaviour. Try is a compound statement that can be used as either a statement on an expression. If an error occurs within a try block the try block stops executing and returns an error code (if used in an expression). The procedure that executed the try statement can then proceed to execute normally. Tryone is similar to try except that it only catches errors produced by the following statement. Here's a quick example of how you might use tryone to retry a failing function a number of times.
Code: Select all
retry% := 0
while tryone PROCMayFail and retry% < 5
retry% += 1
endwhile
Code: Select all
onerror
PROCCleanup
enderror
Code: Select all
onerror
tryone PROCCleanup
enderror
Over the next few weeks, I'm going to continue working on file handling and arrays. I'll probably implement OSCLI and SWAP as well.
Re: Subtilis: A new BASIC compiler for RiscOS
OSCLI
OSCLI is now implemented. *command isn't however. The grammar just can't cope with it. I guess I could introduce a form of * command using a different character that isn't used, say '|' for example, but I guess I'll stick with just OSCLI for now.
Memset
BBC BASIC V array memsetting syntax has now been implemented, e.g., to set the contents of an entire array to a single value you can now type
Code: Select all
a%() = 1
COPY
A new keyword, copy, has been introduced that in most cases does a memcpy between its arguments. It allows you to copy the contents of an array or string to another array or string. This should be faster than manually copying the elements using a loop as it eliminates most of the bounds checking, although I haven't actually benchmarked this. The copy command allows you to copy between variables of different types in limited ways. For example to copy between a byte array and a string one might write.
Code: Select all
copy b&(), a$
Two new keywords, GET# and PUT# have been added that permit reading and writing of blocks of data from and to files. For example, to read an entire file into a string in Subtilis one might write.
Code: Select all
print FNReadFile$("README")
def FNReadFile$(fname$)
f% := openin(fname$)
onerror
tryone close# f%
enderror
a$ := string$(ext#(f%), " ")
read% := get#(f%, a$)
tryone close# f%
<- a$
Re: Subtilis: A new BASIC compiler for RiscOS
I couldn't, not nicely anyway. I did modify the compiler locally to allow zero length one dimensional arrays whose size could be adjusted after creation, but it just broke too many things. So instead I've opted for a brand new type, the vector. Vectors work like one dimensional arrays except thatmarkdryan wrote: ↑Mon Apr 05, 2021 11:24 am Note that it's not currently possible to write a similar function that returns an array of bytes as it's not currently possible to create a zero length array (which might happen if the file was empty). I plan to fix this next by allowing single dimensional arrays to be created with a length of 0, appended to and sliced (if I can get it to work).
1. They are created, indexed and referenced using {} instead of ()
2. It's possible to create a zero length vector
3. Elements can be added to a vector after it has been created using the new append keyword
Zero length vectors can be created by specifying a negative dimension, or no dimension at all, e.g.,
Code: Select all
dim a%{}
dim b${-1}
Code: Select all
append(a%, 1)
Code: Select all
f% := openout("three")
onerror
tryone close# f%
enderror
put# f%, append(FNReadFile&{}("one"), FNReadFile&{}("two"))
close# f%
def FNReadFile&{}(fname$)
f% := openin(fname$)
onerror
tryone close# f%
enderror
local dim data&{ext#(f%) - 1}
read% := get#(f%, data&{})
close# f%
<-data&{}
So, one problem solved but another one created. It's not really safe to iterate through vectors, which may have no elements, using BASIC's for loop, so I'm going to need a new type of loop for iterating through things. I was planning to add one at some point as a cheap way to avoid some of the bounds checking, but this can't now be put off any longer.
Re: Subtilis: A new BASIC compiler for RiscOS
range https://github.com/markdryan/subtilis/b ... s.md#range
The range keyword introduces a new type of loop. It was primarily created as a safe way to iterate through vectors, which can have zero elements, but it can also be used to iterate through arrays. Here's a small example.
Code: Select all
dim a%(4)
a%() = 0, 1, 2, 3, 4
range v% := a%()
print v%
endrange
Code: Select all
0
1
2
3
4
Code: Select all
range v%, i% := a%()
a%(i%) = v% * 2
endrange
Code: Select all
dim a${} rem create an empty vector of strings
range v$ := a${}
print v$
endrange
Another nice feature of the range loop is that, in most cases, the compiler can eliminate the bounds checks, resulting in smaller, more efficient code. The binary size of the executable produced by compiling our first example is 48 bytes smaller than this functionally equivalent example that uses a for loop.
Code: Select all
dim a%(4)
a%() = 0, 1, 2, 3, 4
for i% := 0 to dim(a%(),1)
print a%(i%)
next
Subtilis now supports function pointers, lambda functions and higher order functions. The syntax for these new features takes a bit of getting used to. The difficulty with things like function pointers is that the type of a function pointer can be infinitely complex. A function can return a function that takes two functions as arguments that also return functions and so on. How then do we name a variable that points to such a function in BASIC?
One of the things I really like about BASIC is the way that the type of a variable is included in the variable's name. I find this makes the code really readable. This system works well as BASIC has a simple type system; strings and numeric types and arrays of strings and numeric types. It break down a bit when we add complex types to the language. How can we support complex types like function pointers while continuing to require that a variable's type is included in its name? I'm not sure what the best approach should be here, but this is what I ended up doing.
Before you can use a function pointer or a lambda function you need to declare a new type. A type is declared using the type keyword. For example,
Code: Select all
type FNCombine%(a%,b%)
Code: Select all
ptr@FNCombine = def FN%(a%, b%) <- (a% * b%) + b%
We can then invoke the function through our new variable like this
Code: Select all
print ptr@FNCombine(1,2)
You can also assign the value of an existing named function to a function pointer using the ! operator, e.g,
Code: Select all
ptr@FNCombine = !FNSub%
print ptr@FNCombine(1,2)
def FNSub%(a%, b%) <- a% - b%
Functions and procedures can be passed to and returned from functions. Here's a simple example
Code: Select all
type FNMap$(a$)
PROCMapper("hello", def FN$(a$) <- a$ + a$)
def PROCMapper(s$, a@FNMap)
print a@FNMap(s$)
endproc
It's also possible to create a type that references another type so you can take a pointer to a higher order function, e.g.,
Code: Select all
type PROC_dummy(a%)
type PROChigher(a%, b@PROC_dummy, c@PROC_dummy)
local f@PROChigher
Next I'm going to work on slices, and once these are implemented, there should be enough in the language to write a WIMP application. We'll see.
Re: Subtilis: A new BASIC compiler for RiscOS
I am interested that you seem to be fairly relaxed about adding a new keyword, even though that (presumably) means that existing programs which happen to use that word as a variable name will stop working. I have always felt that retaining compatibility with existing programs is paramount, so I try very hard to reuse existing keywords rather than adding new ones.markdryan wrote: ↑Tue Oct 19, 2021 9:29 pm The range keyword introduces a new type of loop.Code: Select all
range v% := a%() print v% endrange
When this functionality was discussed as a possible extension to my BASICs (in a thread to which you contributed at the time) my preferred approach was to extend the FOR keyword in this kind of context:
Code: Select all
FOR v% OF a%()
PRINT v%
NEXT
Code: Select all
FOR v% OF a,b,c,d,e
PRINT v%
NEXT
@ is of course permitted within a BBC BASIC variable or function name, so ptr@FNCombine is a valid identifier in all versions of BBC BASIC. I tend to use @ (purely as a convention, it has no significance to the interpreter) to make library function names unique, as in FNfunction@library.The @ character separates the variable prefix from the type name.Code: Select all
ptr@FNCombine = def FN%(a%, b%) <- (a% * b%) + b%
Function pointers in BBC BASIC for Windows and BBC BASIC for SDL 2.0 borrow the same syntax as pointers to other objects, so to invoke a function via a pointer one would do:We can then invoke the function through our new variable like thisCode: Select all
print ptr@FNCombine(1,2)
Code: Select all
DEF FNCombine(a%, b%) = (a% * b%) + b%
ptr@FNCombine = ^FNCombine()
PRINT FN(ptr@FNCombine)(1,2)
In my BASIC's that would be:Functions and procedures can be passed to and returned from functions. Here's a simple example
which will print out "hellohello"Code: Select all
type FNMap$(a$) PROCMapper("hello", def FN$(a$) <- a$ + a$) def PROCMapper(s$, a@FNMap) print a@FNMap(s$) endproc
Code: Select all
DEF FN2$(a$) = a$ + a$
PROCMapper("hello", ^FN2$())
END
DEF PROCMapper(s$, FnPtr)
PRINT FN(FnPtr)(s$)
ENDPROC
I hope you don't think I'm muscling in on your territory, but I thought it might be of interest to compare your approach with mine.
Re: Subtilis: A new BASIC compiler for RiscOS
At this stage in the project I am. The reason is that there's almost no existing programs to break. Subtilis is a new language, that's not compatible with BBC BASIC (although it's heavily influenced by it) and as far as I'm aware, there's only one user, me. All I use it for at the moment is writing programs to test the compiler. At some point, once the language is complete, I'll do a 1.0.0 release and from then on, there'll be no more compatability breaking changes.Richard Russell wrote: ↑Tue Oct 19, 2021 11:22 pm I am interested that you seem to be fairly relaxed about adding a new keyword, even though that (presumably) means that existing programs which happen to use that word as a variable name will stop working.
It is true though, that adding new keywords like this will make it harder for people to port BBC BASIC programs to Subtilis, should they want to do so. However, in reality the main barrier to porting isn't the extra keywords I've implemented, but rather the keywords I'm not going to implement, such as DATA, READ, GOTO, GOSUB, EVAL and also operators like ! and ?.
The reason I didn't go with FOR in the end is that the RANGE loop has a different behaviour to the FOR loop. The body of a FOR loop will always execute at least once, regardless of the loop condition, but this is not the case with the RANGE loop. The body of a RANGE loop may not execute at all when operating on vectors, as vectors can have zero elements. For this reason, I felt it would be confusing to re-use the FOR keyword for this new loop construct as it doesn't behave like a FOR loop. Incidentally, the RANGE keyword comes from Go (which has been the inspiration for many current and upcoming features of Subtilis) where interestingly it is used in conjunction with the for statement.When this functionality was discussed as a possible extension to my BASICs (in a thread to which you contributed at the time) my preferred approach was to extend the FOR keyword in this kind of context:
orCode: Select all
FOR v% OF a%() PRINT v% NEXT
Code: Select all
FOR v% OF a,b,c,d,e PRINT v% NEXT
Not at all. All comments, comparisons, criticisims and suggestions are gratefully received. Also thanks for sharing the BBC BASIC examples. Looking at these examples, and assuming I'm reading them correctly, one of the biggest differences I see between our approaches, apart from the syntax, is that the function pointers in Subtilis are strongly typed whereas in BBC BASIC they appear not to be. For example, this code sample will not compile in SubtilisI hope you don't think I'm muscling in on your territory, but I thought it might be of interest to compare your approach with mine.
Code: Select all
type FNMap$(a$)
PROCMapper("hello", def FN%(a$) <- len(a$))
def PROCMapper(s$, a@FNMap)
print a@FNMap(s$)
endproc
I should probably add that none of the new features I've added to the language are set in stone right now. At this stage I don't really know how well they're going to work, either in isolation, or when used in combination with each other. The only way to really find this out will be to write some sizable programs in Subtilis and see what works and what doesn't. I anticipate there will be changes to existing features before the 1.0.0 release.
Re: Subtilis: A new BASIC compiler for RiscOS
I find it hard to reconcile omitting READ and DATA with the subject line "Subtilis: A new BASIC compiler". I would have thought those keywords were amongst the features that any language calling itself BASIC must have.
The behaviour of FOR in always executing the loop once is specifically a feature of BBC BASIC, not BASICs in general; indeed the majority of BASICs don't execute the body of the loop at all if the conditions in the FOR statement are not met. That's why my automatic translators from QBASIC and Liberty BASIC into BBC BASIC create code like this:The reason I didn't go with FOR in the end is that the RANGE loop has a different behaviour to the FOR loop. The body of a FOR loop will always execute at least once
Code: Select all
FOR a = b TO c : WHILE (b) > (c) EXIT FOR : ENDWHILE
BBC BASIC isn't a strongly-typed language, it has only two incompatible types: numbers and strings (arrays and structures muddy the waters somewhat, but they are not scalar objects). So pointers are just numbers (as they are in C); the programmer can use a naming convention to hint at what they point to, but that's purely for his convenience.function pointers in Subtilis are strongly typed whereas in BBC BASIC they appear not to be.
It's interesting that in some BASICs even strings aren't a distinct type. In Pure BASIC strings are basically just pointers (again like C) and in Liberty BASIC strings and structures alias each other!
Re: Subtilis: A new BASIC compiler for RiscOS
...we have ADD$, MUL$, DIV$ which take two strings and optionally a precision. A very nice way to provide for conventional integer and floating point, as well as extended precision arithmetic.
Re: Subtilis: A new BASIC compiler for RiscOS
READ and DATA are also absent from Microsoft's Small BASIC, although I note that this does at least support GOTO. So I've only been taking 1 more liberty with the BASIC name than Microsoft . Having said that though, I do agree with you. Earlier on in this thread I observed that one of the code samples I'd posted didn't look much like BASIC. I'll update the thread's title.Richard Russell wrote: ↑Wed Oct 20, 2021 11:09 pm I find it hard to reconcile omitting READ and DATA with the subject line "Subtilis: A new BASIC compiler". I would have thought those keywords were amongst the features that any language calling itself BASIC must have.
I have considered this. Doing so would simplify the compiler and would also be less confusing to people used to languages such as C, Go and Java. On the other hand, the FOR loop construct in Subtilis is taken from BBC BASIC and Subtilis only generates code for Acorn machines, so I feel the current implementation is less surprising for the intended target audience. I'm going to leave it the way it is for now. I may well revisit this decision though before the 1.0.0 release is declared.So, having said Subtilis isn't BBC BASIC, there's no reason why it shouldn't use FOR in the more common way.
Incidentally, I checked locally on the other BASIC implementations I have to hand. Commodore BASIC (on the C64) does seem to execute the body of the FOR loop at least once, while Sinclair BASIC (on the spectrum 128) does not.
Re: Subtilis: A new BASIC-like compiler for RiscOS
Code: Select all
b$ := left$(a$,3)
Subtilis No Escape (October 2020)
• Real REPEAT Loop: 1408
• Integer REPEAT Loop: 70294
• REAL FOR Loop: 209
• Integer FOR Loop: 6768
• Trig/Log Test: 1598
• String Manipulation: 6223
• Procedure Call: 16735
Subtilis No Escape (November 2021 with slicing)
• Real REPEAT Loop: 1407
• Integer REPEAT Loop: 68285
• REAL FOR Loop: 209
• Integer FOR Loop: 6793
• Trig/Log Test: 1594
• String Manipulation: 12064
• Procedure Call: 16701
The string manipulation benchmark has almost doubled in speed.
Vectors and arrays are sliced using the ‘:' operator inside the indexing brackets, i.e., ‘()’ for arrays and ‘{}’ for vectors. The ‘:’ is preceded by one integer value and followed by a second. The first value specifies the index of the first element of the collection being sliced that is to be present in the slice. The second value indicates the first element of the collection being sliced that will not be present in the slice. Both values are optional. If the first integer value is missing the compiler assumes a default of 0. If the second is missing it assumes a default of the number of elements in the collection being sliced.
This is better explained with some examples. Given we have
Code: Select all
dim a%{10}
a%{} = 1,2,3,4,5,6,7,8,9,10,11
Code: Select all
range v% := a%{1 : 3}
print v%
endrange
Code: Select all
2
3
Code: Select all
range v% := a%{8 :}
print v%
endrange
Code: Select all
9
10
11
Code: Select all
def PROCdisplayError(e$)
local dim eblock&(len(e$) + 4)
eblock&(0) = 255
copy(eblock&(4:), e$)
sys "Wimp_ReportError", eblock&(), 1, "MyApp"
endproc
When you slice a vector or an array the newly created variable aliases the old one. However, unlike strings, arrays and vectors are not copy on write objects in Subtilis. Changes made to the slice or the original array or vector will be reflected in both collections. For example,
Code: Select all
dim a$(2)
a$() = "hello", "hello", "world"
b$() := a$(1:2)
b$(0) = "hi"
range v$ := a$()
print v$
endrange
Code: Select all
hello
hi
world
Re: Subtilis: A new BASIC-like compiler for RiscOS
SWAP
The SWAP keyword is now implemented. This works much the same way as it does in BBC BASIC with the exception that the variables being swapped must have exactly the same type, so you can't for example, swap a one dimensional array with a two dimensional array, as you can in BBC BASIC.
RECords
This is where it starts getting weird. I've begun to add support for records, or structures, to Subtilis. Before a record can be used a new type needs to be created for it, using the type keyword, much in the same way as new types are created for function pointers, e.g.,
Code: Select all
type RECPoint ( x% y% )
type RECShape (
name$
p@RECPoint
dim vertices@RECPoint{3}
)
Code: Select all
local a@RECShape
Code: Select all
a@RECShape := ( "square", ( 100, 450), ( (100, 0 ), (100, 100), (100, 400), (400, 400)))
Code: Select all
a@RECPoint := (100, 100)
b@RECPoint := a@RECPoint
a@RECPoint.x% = 200
print a@RECPoint.x%
print b@RECPoint.x%
Code: Select all
200
100
For more information on records see
Re: Subtilis: A new BASIC-like compiler for RiscOS
I know of another program you can add to your benchmarks, if you want. There was a BASIC-like compiler called !sBASIC (Simple BASIC) published in the *INFO column of the May 1992 issue of BBC Acorn User.
As the name implies, it was a very simple subset of BASIC that was very limited - no floating-point, no PROCs/FNs, integer division, no GOTOs, your only loop is WHILE..ENDWHILE, etc - so there's no way it could compare feature-wise to any other BASIC compiler. Yours is already far more advanced. But I am curious about how well it would perform given simple programs.
I've attached it to this post, along with the article text about it from the magazine itself (as the program didn't come with its own documentation) and the two example programs that it came with, in case you're interested too! As far as I know, there was never any update to it and I can't find any other information about it.
- Attachments
-
- sBASIC.zip
- (12.38 KiB) Downloaded 17 times