Looks like you're doing something similar to what I wanted: given that the comparison operators are all in sequence, it ought to be possible to e.g. come up with a scheme that generates the correct bits to match the operator (or the operator offsets from the first) and eor to give zero if the match is correct. Still needs converting to carry set = comparison true.
It's certainly a _clumsy_ routine at the moment, I think, ripe for improvement (and if I can save a few bytes, I can include peek and poke (probably as @ and ! or some similar one-character keyword to save space in the lookup table).
Here's the current offering. It's intended to run on the Symon emulator, in 65c02 mode, and Multicomp mode, or with a change of ACIA addresses, anything with a 6850 UART and prom at the top of memory. It's built for $e000 simply because that's where Symon expects it to be.
<outdated assembly listing removed; look later in this thread!>
Rough Guide to Neolithic Tiny Basic
Entering a program
The basic program is entered with one statement to a line - so 'for xxx goto' would be two lines. There are no multi-statement lines allowed. Each line is preceded by a line number; these may be entered in any order and will be automatically sorted. Consider incrementing the number count by ten or so to leave space for later additions.
Any line which begins with a number (leading whitespace is ignored) is treated as a program line and entered into memory. However, there are a number of instructions which can be entered the console and which are immediately executed. Some instructions are valid both immediately and in a program.
Variables
There are twenty-six 16-bit signed integer variables - a to z - which may be accessed in either upper or lower case. There is also a single string variable $ to which a string variable may be applied; individual members of the string can also be accessed as well as the complete string.
Instructions
Instructions may be entered in any case, but are internally converted to lower case for display. Text within quotation marks is not altered. With the exception of any leading spaces (between a line number and the instruction) no change is made to the text beyond tokenising the keywords (which is invisible to the user but saves memory space).
- let - immediate, program - assign a value of an expression to a variable or to the string. An expression may be any sequence of numbers and variables, with arithmetic operators applied in the normal priorities unless overridden by brackets (). Note that the 'let' keyword is optional; e.g.
let q = 7
a = -2
p = (2*2*2)*4*355/(3*113)
let $ = "hello"
$ = "world"
- ' - program - a comment line for programmer convenience
- print - immediate, program - print either a quoted string, the string variable, a character within the string variable, a variable, or the value of an expression. An integer variable may be preceded by '%' to give a right-formatted output and all integers are followed by a space; strings are not. Any print command produces a line feed after it prints unless it is followed by a semicolon. Zero or more parameters may be printed in a single statement. e.g.
print
print "hello ";"world" (printed on the same line; note the space after "hello")
print (2*2*2)*4*355/(3*113)
print hello (prints the values of the variables h, e, l (twice), and o, on separate lines)
print $
print $[4] (the fifth character of the string)
- list - immediate - list either all the program, a single line, or a range of lines. An attempt is made to indent the code according to the loop structures used; in general, if the indentation looks 'right' you probably didn't forget anything. e.g.
list
list 100
list 50-150
- new - immediate - clear the program memory
- run - immediate - execute the program memory from the lowest-numbered line
- input - program - assign a value from the keyboard to a variable or a string. Note that any required cue must be supplied by a previous 'print' instruction. e.g.
input a (accept a numeric value)
input $ (accept a string)
- if - program - test the value of the following expression and if it is true, execute code starting with the next line. If false, go directly to the line following the matching endif; that endif is required. If clauses can be nested (each on their own line). e.g.
... if a < 7
... print a
... endif
- else - program - an optional marker: if present, and the 'if' clause evaluates as false, then code executes from the line following the else statement.
- endif - program - required terminator for an if clause; each 'if' requires a matching 'endif'. See above for example.
- for - program - a loop mechanism which is always iterated at least once. A variable is assigned to the 'for' value and the following lines are executed until that variable is equal to the 'to' variable plus one. The variable is tested at the beginning of the loop. The variable is incremented by one automatically but may be altered (carefully) within the loop. If this variable is altered in such a way that it does not end with a value that matches 'to' plus one, the loop will never end.
Execution continues until the necessary 'next' statement (below) at which point the loop repeats. When the loop ends, program flow continues at the line following the 'next'. e.g.
... for q = 1 to 10
... print q
... next
- to - program - necessary adjunct to 'from'; see above for example
- next - program - terminating statement of the for/to/next clause. Necessary; note that loops may be nested and each level must have its own 'next'
- while - program - A loop construct which can execute either no, some, or infinitely many times. The expression following 'while' is evaluated each time around the loop; if it is false then execution continues after the matching 'wend' (below). The loop may be nested; each level requires a matching wend. Note that there is no modification within the while loop of any of the variables unless the programmer adjusts them directly. e.g.
... q = 1
... while q <= 10
... print q
... q = q + 1
... wend
- wend - program - necessary terminating statement for the while/wend clause.
- goto - program - move program execution to a specified line number (which can be calculated as an expression). Considered harmful! Using 'goto' to enter or leave a structured loop will almost certainly lead to tears before bedtime. e.g.
goto 150
goto 150+q*10
- gosub - program - transfer the program execution to the specified line number (again, like goto, the target address may be calculated. Execution continues until a matching return is encountered. It may be nested.
gosub 1000
...
1000 ' do gosub stuff
...
1100 return
- return - program - necessary terminator to gosub. See above.
- end - program - terminate execution and return to the console. Not required if the program flows to the last line, but useful if, say, higher lines contain subroutines and the program should not run into them.
- ! - program - set a memory location with an eight-bit value. Both the location and the value can be any valid expression e.g.
... ! 512, 65 (writes the value 65 ('A') to the first byte of the string variable)
Beware that you can crash your program quite easily with this instruction...
- @ - immediate, program - return an eight bit value from a memory location - the opposite to ! above. Note that this is not strictly a statement but instead a modifier to the expression parser, so using it on a line of its own is a syntax error. However, it can be used anywhere an expression is expected, though it may be necessary to wrap it in parentheses to force the evaluation. e.g.
... q = @ 513 (sets q to the value of the second byte of the string variable)
... print (@ 513) (prints the value of the second byte of the string variable. Requires the parentheses since print doesn't expect '@'; no variable is set.
Comparison operators
These do what you'd expect; the left operand is compared with the right operand and the result is either true or false to if/then or while/wend. The comparisons are all 16-bit signed arithmetic. The operators are:
- = - are the operands equal?
- != - are the operands different?
- < - is the left operand greater than the right operand?
- <= - is the left operand greater than or equal to the right operand?
- > - is the left operand smaller than the right operand?
- >= - is the left operand smaller than or equal to the right operand?
Errors
There are only three errors possible in Neolithic Tiny Basic:
- Memory! - you exceeded the available space for your basic program while entering text. Remember that white space within a program is ignored and can be safely removed (except within quoted strings); that leading spaces before a statement are removed automatically; and that the indentation you see in a listing is not stored in memory.
- Div/0 - you tried to divide a variable by zero. Don't.
- What? - a syntax error has occurred - usually because you spelled an instruction incorrectly or missed a parameter.
If you try an execute an instruction in immediate mode which is only available in program mode (e.g. an if/endif instruction) then this error will be printed; if the error is in the running program, it will stop, print the line number and then the error, and return to immediate mode.
Memory
The basic program is stored with keywords compressed from $300 (768) upwards.
The string variable is stored from $200 (512) upwards. Strings are terminated with a zero byte; the maximum length which can be entered from an instruction line is about 75 characters. However, the string space can be directly manipulated using ! and @ - though remember that if you want to create a printable string, it must start a $200 and it must terminate with a zero byte.
Zero page is used for local storage by the various instructions, plus the twenty-six integer variables.
Stack space is heavily used, both for return addresses but also parameters passed to subroutines.
API
Most subroutines take one parameter, which is passed in the Y:A registers or (for bytes) in the A register; the result is passed the same way. When a second parameter is required - for example, in the arithmetic routines and a couple of other places - then the first parameter is pushed to the stack and the second is passed in Y:A. There are (at present) no routines requiring more than two parameters.
Local variables are generally stored in zero page, if they are not required to allow nested (recursive) operation. If they are, they are stored on the stack. The X register is used as an offset into the stack, so must be preserved by all calls. An example of the stack after a call with two parameters and with local variables might be:
Code: Select all
two bytes -->[calling parameter]
two bytes -->[return address]
one byte -->[previous stack frame pointer - original X]
two bytes -->[local param 1]
two bytes -->[local param 2]
one byte -->[temporary storage]
one byte -->[temporary storage]
...
The calling parameter and the local parameters are all addressed using ABS,X instructions, where X is set after pushing its previous value. Temporary storage is accessed using the usual stack operators.
Neil
Edit 10/1/25 to update new instructions, document memory use, and explain the stack convention/ABI.
Edit 26/3/25 to include if/else/endif description.