The BASIC compiler for 8bit CPUs
Version 1 - beta. Use on your own risk.
- Numbers can be decimal (123, -456) or hexadecimal ($BEEF)
- You can omit the line number, if it is not necessary
- The output is compatible with ASM80 syntax
- Case insensitive (you can write PRINT as well as print or Print)
- Label can be an integer ("line number") or string (ended with a colon, e.g.
hello:
) - String slices (like a ZX Spectrum BASIC)
- Data structures
- Named subroutines
- Pointers
- Local variables (very limited)
- Data push and pop (for recursion)
- Heap allocation / free
- Integer numbers only
- Two bytes integer, i.e. -32768 to +32767
- No computed GOTO, GOSUB
- Files
- Graphics
- Sound
- Basic objects
- Exceptions
The LET keyword can be omitted.
Multiple assignment is allowed, just use LET var[,var,var...]=expr
. Vars have to be scalar int for multiassignment
Can has one or more parameters, delimited with semicolon or comma.
PRINT a,b,c
PRINT a$,b$
PRINT a+b*c
PRINT "Hello,";
PRINT " world."
Allows combine more variables as well as PRINT expressions
INPUT a
INPUT a,b
INPUT "Your name:",a$
Needs a constant target: GOTO 100
. No "computed GOTOs" allowed.
Call subroutine at given label.
Return from subroutine. Expression value is, if used, returned as from function
Evaluate expression. If its value is zero, then skip to the next line. If nonzero, continues.
Shortcut for IF expr THEN GOTO label
Evaluate expression. If its value is zero, then skip to the ELSE part. If nonzero, continues until the ELSE part, then skip to the next line.
THEN label
and ELSE label
are the shortcuts for THEN GOTO label
or ELSE GOTO label
A multiline variant of IF-THEN[-ELSE]. E.g.
IF a=10 THEN
... do something for a=10
ENDIF
or
IF a=10 THEN
... do something for a=10
ELSE
... do something for a is not 10
ENDIF
Evaluate an expression and GOTO to n-th label. Indexed from 0, so if expr=0, then goto to l0, if expr=1 then goto to l1 etc. You can use up to 128 labels at once. If expr > num of labels, then no goto is performed.
ON takes only the lower part of expression value. So expr=256 is the same as expr=0.
The same as ON expr GOTO, but this time it is calling a subroutine instead the jump.
It allows overrule the error handling. Instead of message output, it will catch the error and performs GOTO to specified label. There are no warranty about anything (especially the stack), so the safest way is do what is necessary and re-initialization the whole program.
You can specify the error number:
- 0 is for overflow during multiplication
- 1 is for index out of bounds
- 2 is for division by zero
- 3 is for out of memory errror
- 4 is for out of data error
- 5 is for STOP command (it has no common sense at all)
- 6 and 7 are for user-specified exceptions (to be implemented)
Throw an error. Errnum should be in range (0..7), for detail description see above (ON ERROR)
E.g. THROW 2
simulate the "division by 0" error.
Runs the program again. It means the stack pointer is restored, heap is reallocated, all variables are undefined, etc.
Remark. Compiler ignores everything after this keyword until the end of line.
End of program, return to the monitor.
End of program with a "STOPPED" message.
The essential loop in BASIC.
Next iteration for the FOR loop.
Define some data and store them into memory
Reads some data from DATAs
Set pointer for READ to the first line with DATA after given label
Store actual DATA pointer into a variable and move pointer forward by N items.
READPTR object, 4
...
sprite:
DATA 1,2,4,4
sprite2:
DATA 10,11,15,27
A non-standard DATA equivalent. Given values are stored as a byte (not two bytes like standard data). So it's not suitable for READ etc. Its aim is to DPTR function to have a method for defining some data tables.
The begin of REPEAT - UNTIL loop
If cond is false, jump to the appropriate REPEAT command. Otherwise continues.
If cond is false, skip after the appropriate ENDWHILE command. Otherwise continues into the loop.
Goes immediately back to the appropriate WHILE command...
Usable in the FOR, REPEAT or WHILE loops. Invoke the next iteration.
Usable in the FOR, REPEAT or WHILE loops. Properly exits the loop.
Store one byte to given address
Store two bytes to given address
System call invoke a subroutine at the given address. You can specify the contents for register pairs HL, DE, BC and A (8080-based and Z80-based systems only)
Send one byte to the given I/O port
The WAIT statement stops execution until a specific port matches a specific bit pattern. The data read at the port is XORed with the value xorVAL, and then ANDed with the desired value. If result is zero, read is repeated until value is nonzero.
Swaps values for two scalar variables
Prepare an array of int. Arrays are indexed from 0, so DIM A(10) prepares an array with 10 items, denotes as A(0) .. A(9)
DIM should appears in the source code prior to the first using of array! Not in program flow, so this won't work:
10 GOTO 50
20 A(5) = 5
...
50 DIM A(10)
60 GOTO 20
You have to place DIM at the top of code. DIM does nothing in code, it just inform the compiler about the array limits.
Array length has to be a constant, so no computed DIMs allowed!
Sets the first unused addr to given constant expression. BASIC will not use any memory above the RAMTOP (stack is not affected)
Should be used only once (It takes the last value, ramtop is not dynamic)
This is an attempt to bring a FUNCTION concept into BASIC. Yopu can write function as a regular subroutine (like one for the GOSUB) and begin it with TAKE command.
TAKE takes one or two integers from calling environment and store them into given variables. FN() acts like a GOSUB - the first argument is a label (line number or string), the second argument (and the third, if given) acts like a parameters. They are passed to the subroutine. Subroutine can take them with the TAKE command.
print "10+20=",fn(adding,10,20)
end
adding: take p1,p2
return p1+p2
TAKE does not any variable checking, so if you use the same name for parameter and a regular variable, it takes the same place, i.e. TAKE overwrites the global variable...
PUSH and POP helps to simulate local variables for recursion. PUSH just store the values of given variables somewhere to the LIFO structure (on the stack in fact), POP takes the values from the same structure (i.e. from the stack) and assigned them back to the variables. Caution: it works only with scalar int variables (no arrays, no strings) and the orders of POP must be reversed (remember: LIFO!). So PUSH a,b,c
needs POP c,b,a
print fn(factorial,5)
factorial: take fact
if fact=1 then return 1
push fact
temp = fn(factorial,fact-1)
pop fact
return temp*fact
A syntactic sugar for the case you need compute some return value and THEN pop some variables. Good for tail recursion.
factorial: take fact
if fact=1 then return 1
push fact
return fact*fn(factorial,fact-1);fact
If you want to PUSH some variables PRIOR to taking a value, use this syntax.
factorial: take fact;fact
if fact=1 then return 1;fact
return fn(factorial,fact-1)*fact;fact
Or with more sugar:
DEF FN factorial
factorial: take fact;fact
if fact=1 then return 1;fact
return factorial(fact-1)*fact;fact
Like FN(label,par) or FN(label,par1,par2), but drops the return value and acts just like a command. Useful when you need just to invoke a function with parameters, but ignore the result.
It's a syntactic sugar again. If you annotate function with DEF FN on the beginning of a source code, you can use the function name without FN(funcLabel,...), just directly like funcLabel(...)
So you can use both of those variants:
PRINT fn(factorial,5)
or
DEF FN factorial
...
PRINT factorial(5)
Another sugar for you. If you annotate function with DEF PROC on the beginning of a source code, you can use the function name as a procedure without CALL funcLabel,..., just directly like funcLabel par[,par]
So you can use both of those variants:
CALL myproc,5,10*a
or
DEF PROC myproc
...
myproc 5,10*a
Of course you can use one label as FN and PROC.
Returns the absolute value
Returns the negative value (*-1)
Returns a pseudorandom number (-32768 .. 32767)
Number sign. 1 if positive, -1 if negative
Returns the length of string.
Returns the byte value at given address
Returns the word value (two bytes) at given address
Returns the byte value from given port number
Returns the decimal value of string.
Returns the ASCII code of the first character in a string.
Returns upper / lower byte of int
Gets a pointer to the given label (pointer lead to a code area!)
Gets a pointer to the first DATA after the given label (pointer lead to a data area!)
Allocates SIZE bytes on the heap memory. Returns a pointer to this area. You can free this area by FREE command (see below).
You can get a pointer (an unsigned int) to a variable, an array, a string variable or a string constant. Use angle braces around the element, e.g. LET a = [b]
to get an address to a memory place where the B variable resides.
Member is a record in the form name(type), e.g. value(int) or name(str). Structures can use a "byte" type. It takes only one byte. The order of struct members is preserved. E.g. structure defined by def struct mydata key(int),value(int),flag(byte)
has following footprint:
key: bytes 0, 1 value: bytes 2, 3 flag: byte 4
and it is 5 bytes long.
Structure has to be declared before its first use!
Allocates memory for given struct type in a static memory area. DIM mydata a,b,c
allocates three areas, 5 bytes each, for three structured variable, named a, b and c
Static structure has to be dimensed before its first use!
Use dot notation as in other languages, e.g. a.key, b.value, ... You can use this notation in expression and in LET command:
LET a.key = 15
LET n = a.value * 3
You can get a pointer to structure member by [] notation, see above. So:
LET ptr = [a.key]
assign pointer to member key
of structure a
.
LET ptr = [a.]
is a syntactic sugar - takes a pointer to the first structure member.
Lets assume that ptr
is a pointer to the first member of a structure. E.g.LET ptr = [c.]
. Now you can work with member values by a curly braces notation:
PRINT ptr{mydata.value}
takes a variable "ptr" and assumes that ptr contains a pointer to some structure of type mydata
. Then prints a value
member (in fact, it takes bytes 2 and 3).
PRINT ptr{value}
is a shorthand form, but it assumes that there is a only one structure with member named "value". If there are more than one structure with a "value" member, it throws an error.
LET ptr{key} = ptr{key} + 5
- structure pointer can be used on a both sides of an assign.
WARNING! Structure pointers has no checks! If you use a pointer to an invalid area, results will be unpredictable and it can crash or destroy data!
You can dynamic allocate the structure on a heap memory and free this memory back.
It allocates enough memory for a structure and store the address of memory (i.e. pointer) to the given variable. You can allocate more structs with single ALLOC.
ALLOC mydata p,q
allocates two areas in a memory. Both areas has size equal to the size of the mydata struct.
Free memory area, previously allocated by ALLOC (or MALLOC) command. FREE always performs a garbage collection, so you can use simple FREE (without any variables) to force garbage collecting.
String variable can be "sliced" (like with LEFT$, MID$, RIGHT$), but in a more flexible way. Just use the string slice syntax as described below.
Lets assume A$ = "Hello world". Then
A$(3 TO 5)
= "lo "
A$( TO 5)
= "Hello "
A$(5 TO )
= "world"
Syntax is var$(first TO last)
. If first
is omitted it assumes first=0. If last
is omitted it assumes last=LEN(var$)-1. Slice returns characters from FIRST to LAST (included).
Slice can be used as left side of assign command (LET):
A$ = "Hello world" A$(3 TO 4) = "p," A$ -> "Help, world"
=, <>, <, >, <=, >= as usual
& - bitwise AND | - bitwise OR ^ - bitwise XOR
AND, OR - logic AND, OR
- var++ increment
- var+++ var+=2
- var-- decrement
- var--- var-=2
- var** var*=2