gigatron/rom/Apps/TinyBASIC/TinyBASIC_v1.gcl
2025-01-28 19:17:01 +03:00

1090 lines
40 KiB
Plaintext

{-----------------------------------------------------------------------+
| |
| Recreation of Tiny BASIC for Gigatron TTL microcomputer |
| |
| - Integer BASIC, with PRINT supporting string constants |
| - One statement per line, up to 25 characters per line |
| - Supports roughly 300 line programs in the 32K system |
| - Based on Dennis Allison's original 1976 Tiny BASIC |
| |
+-----------------------------------------------------------------------}
gcl0x
{
History:
2018-06-19 (marcelk) Scaffolding: can evaluate direct print statements
2018-06-21 (marcelk) Can edit programs, delete lines, but no RUN yet
2018-06-23 (marcelk) Can RUN, GOTO, GOSUB, RETURN and break program
2018-06-24 (marcelk) Can do IF/THEN, INPUT and END: feature complete
2018-06-25 (marcelk) LIST shows free bytes; Trailing (semi-)colon in PRINT;
LET is now optional; PEEK/POKE; Better INPUT;
NEW clears variables; Aliases for PRINT and REM
2018-07-03 (marcelk) Add RND(), with 0 <= RND(n) < |n|; USR()
Formal grammar:
Line ::= Number Statement | Statement
Statement ::= ('PRINT'|'?') ((String|Expression) [,;])*
(String|Expression)? |
'IF' Expression RelOp Expression 'THEN'? Statement |
'GOTO' Expression |
'INPUT' Variable (',' Variable)* |
'LET'? Variable '=' Expression |
'POKE' Expression ',' Expression |
'GOSUB' Expression |
'RETURN' |
('REM'|"'") Character* |
'NEW' |
'LIST' |
'RUN' |
'END'
Expression ::= [+-]? Term ([+-] Term)*
Term ::= Factor ([*/%] Factor)*
Factor ::= Variable | Number | ('PEEK'|'RND'|'USR')? '(' Expression ')'
RelOp ::= '=' | '<' | '>' | '<=' | '>=' | '<>'
Number ::= [0-9]+
Variable ::= [A-Z]
String ::= '"' ([#x20-#x21] | [#x23-#x7E])* '"'
Character ::= [#x20-#x7E]
Recommended filename extension:
.gtb GigaTron BASIC / Gigatron Tiny BASIC
Error messages:
Break error Program (or list command) stopped by [Del] key
Syntax error Parse error / Program too large
Value error Calculation error (division by 0)
Stack error GOSUB nesting too deep / RETURN without GOSUB
Line error Line number not found / RUN empty program
Memory allocation:
$30 - $7f BASIC interpreter variables
$81 - $ff Stack for BASIC and vCPU (>50 GOSUB levels)
$200 - $11ff BASIC interpreter, partly next to screen memory
$13a0 - $13bf Input buffer ('line 0')
$13c0 - $13f3 BASIC variables A..Z
$14a0 - $7fff Programs stored in the invisible part of screen memory
$8000 - $ffff Optional extra memory for BASIC programs (64K extension)
Undocumented features and/or bugs:
PRINT vs. print Keywords and variable names are case-insensitive
PRINT 6/-2 Syntax error (original Tiny BASIC quirk, to be solved)
PRINT a;b Concatenates output without separation
IF statement There is no syntax check for trailing garbage
>< For 'not equal' is not implemented. Use just <>
<>= Also works as (useless) operator in condition of IF
No 'GO TO' Must be typed as 'GOTO'. Also 'GOSUB', not 'GO SUB'
INPUT Accepts full expressions as input, including variables!
INPUT Can give syntax error in direct mode (buffer conflict)
Break error Gives the line it was about to start: resume with GOTO
PRINT "xxx An unterminated string is silently accepted
60000 vs. 90000 Many (too) large constants give an error, but not all!
References:
https://en.wikipedia.org/wiki/Tiny_BASIC
Wikipedia article
http://www.ittybittycomputers.com/IttyBitty/TinyBasic/DDJ1/Design.html
DESIGN NOTES FOR TINY BASIC (Dennis Allison et al.)
http://www.ittybittycomputers.com/IttyBitty/TinyBasic/
Tom Pittman's implementation
http://p112.sourceforge.net/tbp112.html
P112 Tiny Basic User's Guide (V1.0, 18 FEB 1999)
http://www.bitsavers.org/pdf/interfaceAge/197612/092-108.pdf
Dr. Wang's Palo Alto Tiny Basic (Roger Rauskolb)
https://www.applefritter.com/node/2859
Apple 1 BASIC
Wish list:
@() Array as in Palo Alto Tiny BASIC (store vertically)
@ A() @ as variable name, and generic arrays (1-dimensional)
IF Reintroduce the missing syntax error check
Speed Cache result of the statement detection (checkpointing)
Speed Alternative: LET detection is now last, but should be first
'Maybe' list:
ROM peek rpeek()? Some predefined USR functions?
FOR TO NEXT (And STEP) Can be done with 1 reserved word per control variable
Speed Move stack check from Expression to Factor (and GOSUB)
A$ .. Z$ String variables (only in LET, PRINT, INPUT?)
Speed Cache GOTO/GOSUB target (only for constant targets...)
Speed Cache result of NextBlock (at offset 30 of block?)
Unary minus Original Tiny BASIC does it wrong: 8/-2
COLOR Easily change colors
SETXY Map (X and) Y to memory (through videoTable)
CLS Clear screen, move cursor to top, reset video table...
ABS(/SGN( Built-in function
A$() Indexing in string variable, gives integer (handle in Factor)
A$(i,j) Substrings (only in PRINT)
LEN()/A$()=
TUNE/PLAY Play a note/score
A0 .. Z9 Much more variable space (as in Apple-1 basic)
'Probably not' list:
LOAD From where? EPROM? "Cassette interface".
SAVE To where? (Babelfish, using /vSync length modulation?)
DIM A() And bound checks...
TI/TI$ Built-in variable for time? (only in MS BASICs). Setting also?
CHR expr Print an ASCII char from its value (not needed with strings)
TAB() As in Apple-1 BASIC (Weird: TAB(N) advances to N-1...)
SPC() Spaces
Indent Indent BASIC output 2 pixels (as in WozMon) -> Complex
Speed Binary search for GotoValue -> Only speeds up large programs
"" or """ For printing the quote character itself
Overflow Errors on overflow for + - and *
WAIT -> Make a loop subroutine
$xx Hexadecimal numbers
LIST <n> List one line
a-z A-Z As different variables
>< For unequal
CLEAR For NEW
READ, DATA Possibly put in unused lines? (RESTORE)
AND OR NOT At list for in IF statements
: Multiple statements in one line
ELSE Will slow down IF, because it has to scan for this
DEF FNx
Braindumps:
a0 b0 c0 d0 e0 f0 ff
+-----------------------+-----------------------+-----------------------+
$13a0 | Input buffer | Variables Z Y X ... P | O ... G F E D C B A @ |
+-----------------------+-----------------------+-----------------------+
Begin->| Program line 10 | Program line 20 P(1)| Program line 30 @(1)|
+-----------------------+-----------------------+-----------------------+
| Program line 40 | Program line 45 P(2)| Program line 50 @(2)|
+-----------------------+-----------------------+-----------------------+
| Program line 60 | P(3)| @(3)|
+-----------------------+-----------------------+-----------------------+
: : : :
$7fa0 | | P(108)| @(108)|
+-----------------------+-----------------------+-----------------------+
: : : :
$ffa0 | | P(236)| @(236)|
+-----------------------+-----------------------+-----------------------+
^End
Better idea: Put variables in top of memory, and grow arrays DOWN
1. This opens the road to string variables without garbage collection
2. And FOR-TO-NEXT this might work:
FOR Var = Expr TO Expr [STEP Expr]
^
NEXT will go back here, remember only this address
NEXT Var will then continue parsing TO (and STEP)
Alternative byte allocation in block:
0 Checkpoint offset (set by Insert to 5)
1-2 Checkpoint address (set by Insert to Statement)
3-4 Line number
5-29 Text (without line number) and terminating zero
30-31 Next block address? Goto address?
Ideas for demo programs:
Blinky Everyone's favorite
Clock
Maze The famous one-liner with / and \
Text Color of characters and background at arbitrary positions
LEDs Control the blinkenlights
Lines
Circles Lots of math
Music Play tones, vary with waveforms
Sprites Fake sprites with printing chars at arbitrary positions
TicTac.gtb Test longer programs. Might need to shorten/preprocess lines.
}
{-----------------------------------------------------------------------+
| RAM page 2 |
+-----------------------------------------------------------------------}
$01df deek Pos= {Bottom character row in screen memory}
{Slightly cheating with endianness}
$0f20 \sysArgs6: {Pen color yellow, background color blue}
\sysArgs0. {First Newline call will look here}
{ Process an expression factor, result in Value }
[def
push
Keyword! $28# 0# {'('}
[if<>0
Expression!
Keyword! $29# 0# {')'}
[if=0 SyntaxError!]
pop ret]
Number! {Test for numeric constant}
[if>=0
Spaces!
pop ret] {Result already in Value}
Keyword! $70# $65# $65# $6b# {'peek('}
$28# 0#
[if<>0
Expression!
peek Value=
Keyword! $29# 0# {')'}
[if=0 SyntaxError!]
pop ret]
Keyword! $72# $6e# $64# $28# {'rnd('}
0#
[if<>0
Expression!
Keyword! $29# 0# {')'}
[if=0 SyntaxError!]
\entropy; {Two random bytes}
\SYS_Random_34 \sysFn: {Prepare SYS call}
[do 34!! if<0loop] {Get positive random number}
Divide! {Modulo expression}
i Value=
pop ret]
Keyword! $75# $73# $72# $28# {'usr('}
0#
[if<>0
Expression!
Keyword! $29# 0# {')'}
[if=0 SyntaxError!]
Value! Value= {vCPU call and result}
pop ret]
Variable! {Otherwise it MUST be a variable}
deek Value= {Fetch value}
Spaces!
pop ret
] Factor=
{ Print inline string. Returns 0 }
[def
\vLR; tmp= {vLR points to inline argument}
[do
tmp, tmp<++ {Grab next character}
if<>0 PrintChar! {Print as long as non-zero}
loop]
tmp! {Returns to caller}
] PrintS=
{ Continue printing on new line. Returns 0 }
[def
\SYS_VDrawBits_134 \sysFn: {Prepare SYS call}
$800 Pos<. {Go back to start}
Pos+ [if<0 $0800] Pos= {Go down 8 lines and wrap around if needed}
\sysArgs4: {sysArgs[4:5] is position on screen}
\sysArgs2. {All-zero output pattern}
[do
134!! {SYS call}
\sysArgs4<++ {Advance to next slice}
\sysArgs4, 160^ {Test for end of screen}
if<>0loop]
{Scroll up by modifying videoTable}
$01ee i= {Last entry in video table}
[do
i, 120- [if<0 128^
else 8^]
i. {Rotate by 8 in 8..127 range}
i 2- i= {Previous entry in video table}
$fe^ if<>0loop] {Until all done}
ret
] Newline=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 3 |
+-----------------------------------------------------------------------}
$0300:
{ Print ASCII character (32..127) on screen in 5x8 pixels }
{ Returns the actual position }
[def
k= {Temporarily save character}
Pos<, 155- [if>0 {Automatic newline BEFORE printing}
push Newline! pop]
k 82- {Map ASCII code to offset in font table}
[if<0 50+ i= \font32up {ASCII 32..81}
else i= \font82up] k= {ASCII 82..127}
i 2<< i+ {Multiply by 5}
k+ k= {Add to page address to reach bitmap data}
\SYS_VDrawBits_134 \sysFn: {Prepare SYS calls}
\sysArgs6; \sysArgs0: {Apply caller-defined colors}
Pos \sysArgs4: {Position for character}
$fe%= {Temporarily park return value on the stack}
6+ Pos= {Advance position by 6 pixels}
5 [do i= {Draw 5 vertical slices}
k 0?? \sysArgs2. {Get slice from ROM}
134!! {Invoke SYS function to draw pixels}
k<++ \sysArgs4<++ {Advance to next slice}
i 1- if>0loop] {Looping}
$fe% {Return effective position}
ret
] PrintChar=
{ Accept ASCII characters from console input with line editting }
[def
push
tmp= {Line number}
$13a0 Active= {Input buffer}
tmp Active: {For error messages}
$a2 Active<. {Text area}
[do {NEXTCHAR}
127 \sysArgs7. {Pen color white for user input}
PrintChar! Pos= {Draw cursor symbol}
\serialRaw, [do {Wait for key change}
tmp= {Remember previous value}
\serialRaw, Active. {Read new input value}
tmp^ if=0 {If no change}
Active, loop] {Keep waiting}
Active, 10^ if<>0 {Enter/return breaks NEXTCHAR loop}
117^ [if=0 {Delete pressed (10^127 == 117)}
$20 PrintChar! Pos= {Remove cursor}
Pos<, 6- [if>=0 {If not on first position}
Pos<. {Step back}
Active 1- Active=] {Remove character from buffer}
loop] {To NEXTCHAR}
96- if>=0loop {Ignore apparent unprintable garbage}
Pos<, 150- [if>=0 {Fewer than 2 chars or 11 pixels will fit}
$a2 Active<. {Discard of too long input}
$5c PrintChar! {Overflow indicator '\'}
loop] {To NEXTCHAR, cursor will print on new line}
Active, PrintChar! {Print accepted characters}
Active<++ {Advance pointer, keeping the character}
loop] {To NEXTCHAR}
Active. {Terminate input with zero}
$20 PrintChar! {Remove cursor}
Newline!
$0f \sysArgs7. {Pen color yellow for output}
$a2 Active<. {Back to start of buffer}
Spaces! {Process leading spaces}
Active, {Tell caller if there is input}
pop ret
] GetLine=
{ Eat whitespace. Returns non-zero }
[def
[do Active, $20^ if=0 Active<++ loop]
ret
] {Spaces=}
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 4 |
+-----------------------------------------------------------------------}
$0400:
Spaces=
{ Statement executor. Doesn't return through vLR }
{ Active must point to text or terminating zero, NOT to the line number }
[def
Spaces!
Keyword! $67# $6f# $74# $6f# {'goto'}
0#
[if<>0
Expression! {Computed goto}
GotoValue!] {Find that line and continue there}
Keyword! $67# $6f# $73# $75# {'gosub'}
$62# 0#
[if<>0
2-- Active 0%= {Push current line to stack}
Expression! {Also does the stack check}
GotoValue!] {Find that line and continue there}
Keyword! $72# $65# $74# $75# {'return'}
$72# $6e# 0#
[if<>0
\vSP, [if=0 StackError!] {Top of stack}
deek Active= pop {Pop line from stack}
EndOfLine!]
Keyword! $69# $66# 0# {'if'}
[if<>0
Expression!
4-- 2%= {Push first value on stack}
RelOp!
[if=0 SyntaxError!]
0%= {Park RelOp bits on stack}
Expression!
Keyword! $74# $68# $65# {'then' (optional)}
$6e# 0#
2% Value^ {Do the comparison, beware of overflows}
[if<0 2% {Opposite signs}
else 2% Value-] {Same signs}
[if>0 4] {First is greater}
[if<0 1] {Second is greater}
[if=0 2] {Values are equal}
i= 0% i& {Compare to RelOp bits}
4++ {Restore stack}
[if<>0 Statement!] {Conditional execution}
[do
Active, if<>0 {Find end of line}
Active<++ loop]
EndOfLine!] {Continue with next line}
Keyword! $70# $6f# $6b# $65# {'poke'}
0#
[if<>0
Expression!
Address=
Keyword! $2c# 0# {','}
[if=0 SyntaxError!]
Expression!
Address. {Write byte to memory}
EndOfLine!]
Keyword! $72# $65# $6d# 0# {'rem'}
[if=0 Keyword! $27# 0#] {'\''}
[if<>0
[do
Active, if<>0 {Find end of line}
Active<++ loop]
EndOfLine!]
Keyword! $72# $75# $6e# 0# {'run'}
[if<>0
Clear! {Clear variables}
deek Value= {First program line, if any}
GotoValue!] {Also tests for break}
Keyword! $65# $6e# $64# 0# {'end'}
[if<>0 Prompt! 0#]
StatementCont! {Continue in another page}
] Statement=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 5 |
+-----------------------------------------------------------------------}
$0500:
{ Continuation of Statement }
[def
Keyword! {'list'}
$6c# $69# $73# $74# 0#
[if<>0 List! EndOfLine!]
Keyword! $6e# $65# $77# 0# {'new'}
[if<>0
Clear! {Clear variables}
End= {Clear program}
List! {To print number of free bytes}
Prompt! 0#] {Return to prompt}
Keyword! $70# $72# $69# $6e# {'print'}
$74# 0#
[if=0 Keyword! $3f# 0#] {'?'}
[if<>0
Active,
[if=0 Newline!] {Special case for newlines}
[do {PRINTLIST}
Active, if<>0 {Until end of line}
$22^ {'"'}
[if=0
[do {PRINTSTRING} {String constant}
Active<++ {Skip past opening quote or string character}
Keyword! $22# 0# {'"'}
if=0
Active, {Next character}
if<>0 {Silently accept unterminated string}
PrintChar!
loop] {To PRINTSTRING}
else
Expression!
PrintValue!]
Keyword! $2c# 0# {','}
[if<>0
[do
$20 PrintChar!
Pos<, 7& if<>0loop] {Tab stops every 4 characters}
loop] {To PRINTLIST}
Keyword! $3b# 0# {';'}
if<>0loop {To PRINTLIST}
Newline!] {When no trailing ',' or ';'}
EndOfLine!
]
Keyword! $69# $6e# $70# $75# {'input'}
$74# 0#
[if<>0
[do
Active,
[if<>0 PrintChar!] {Be nice and show variable name in prompt}
Variable! {Parse variable name}
Address= {Park here}
Spaces!
4-- Active 2%= {Save program pointer on stack}
31| 31- deek 0%= {Current line number in program}
[do
$3f PrintChar! {'?'}
Newline! {Avoid dealing with partially printed lines}
0% {Line number for errors}
GetLine! {Let user type something}
if=0loop] {Repeat with prompt when nothing is typed}
Expression! {Accept arbitrary expression as input \o/}
Address: {Store received value in variable}
2% Active= 4++ {Restore program pointer from stack}
Keyword! $2c# 0# {','}
if<>0loop] {Next variable}
EndOfLine!]
Keyword! $6c# $65# $74# 0# {'let' (optional)}
Variable!
Address= {Park here}
Spaces!
Keyword! $3d# 0# {'='}
[if=0 SyntaxError!]
Expression! {Evaluate expression}
Address: {Store value in variable}
EndOfLine!
] StatementCont=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 6 |
+-----------------------------------------------------------------------}
$0600:
{
Six scenarios for InsertLine Check Search Shift Copy Shift
memory line down buffer up
1. Append a new line > last X - - X -
2. True insert of a new line < last X X X X -
3. Overwrite of existing line <= last - X - X -
4. Delete of existing line <= last - X - - X
5. Delete of non-existing line < last X(!) X X(!) - X(!)
6. Delete of non-existing line > last - - - - -
}
{ Handle commands that start with a line number }
[def
push
Number!
[if>0
{ Lookup line number in program }
End i= j= {Setup i and j for below}
Begin^ [if<>0 {Last line number is zero when no program}
End PrevBlock! deek] {Or read it from the last block}
Value- {Compare to given number}
[if>=0 {If insert/modify instead of append}
Begin [do i= {Loop over program from beginning}
i; Value- if<0 {Stop if found or past it}
i NextBlock! loop] {Advance to next line}
if>0 {Insert scenario}
0; End^ {Compare against top of memory}
[if<>0 {Do the copy only when safe (give error later)}
[do {Copy rest of program one one slot up}
j k= PrevBlock! {Go up one block}
j= tmp=
[do {Copy tmp[0:31] to k[0:31]}
tmp, k. {Copy as bytes, don't bother with word copy}
tmp<++ k<++ {Advance both pointers}
tmp 31&
if<>0loop] {Copy all 32 bytes}
j i^ if<>0loop]
]
1 {Non-zero to trigger advancing of End below}
]
{ Expand program space if needed }
[if<>0
0; End^ {Compare End against top of memory}
[if=0 SyntaxError!] {Out of memory error if equal}
End NextBlock! End=] {Otherwise grab space for new line}
Active, {Inspect if line has any content}
[if<>0
{ Copy line from buffer into program }
Value i: {Copy line number}
i<++ {Copy line text}
[do
i<++
Active, Active<++ {From input buffer}
i. {Into program memory}
if<>0loop] {Until terminating zero}
else
{ Bare line number means delete that line }
i j= {i still points at line to be deleted}
[do
End^ if<>0 {Until end of program}
j NextBlock! j= tmp= {Go down one block}
[do {Copy tmp[0:31] to i[0:31]}
tmp, i. {Copy as bytes, don't bother with word copy}
tmp<++ i<++ {Advance both pointers}
tmp 31& if<>0loop] {Copy all 32 bytes}
j i=
loop]
End PrevBlock! End= {Remove last line from program}
]
Value {Report line number to caller}
]
pop ret
] Insert=
{ Clear variables. Returns begin address for programs }
[def
$13c0 i= {Clear variables A-Z and a few bytes more}
[do
0 i. {Clear byte}
i<++ {Advance pointer}
i<, if<>0loop] {Until end of page}
$14a0 Begin=
ret
] Clear=
{ Note: It is safe to call functions here before hopping over to page 8 }
Newline! {Scroll and clear last line}
{ Welcome message }
PrintS! {'*** Tiny BASIC'}
$2a# $2a# $2a# $20# $54# $69#
$6e# $79# $20# $42# $41# $53#
$49# $43# 0#
Newline!
Clear! {Clear variables}
$13a0 deek End= {At startup 'End' comes from loaded segment}
2+ Active= {BASIC command(s) to run at startup}
{-----------------------------------------------------------------------+
|}$08a0 \vLR: ret{ RAM page 8 |
+-----------------------------------------------------------------------}
$08a0:
{ Calculate address of next 32-byte block for storing BASIC lines }
{ Realign if needed }
[def
31| 1+ tmp= {Advance to next 32-byte memory block}
[if>0 {In the first 32K...}
$e0& if=0 {...wrap around visible screen memory}
tmp 160+ ret]
tmp
ret
] NextBlock=
{ Go to previous block. Assume vAC already aligned to valid 32-byte block }
[def
32- tmp= {Move back to previous 32 byte memory block}
[if>=0 {In the first 32K...}
$60& if=0 {...wrap around visible screen memory}
tmp 160- ret]
tmp
ret
] PrevBlock=
{ Find line with line number equal to Value and go there }
[def
Begin [do i= {Loop over program from beginning}
End^ if<>0 {Until end of program}
i; Value^ [if=0 {Or if matching value found}
{XXX Insert checkpointing here?}
i 2+ Active= {Skip past line number}
TestBreak! {Test for [Del] as break request}
Statement!] {And continue from there}
i NextBlock! loop] {Otherwise try next line}
Prompt! $4c# $69# $6e# $65# {'Line' error}
0#
] {GotoValue=}
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 9 |
+-----------------------------------------------------------------------}
$09a0:
GotoValue=
{ Destructively print Value as signed decimal number }
[def
push
[if<0 {If vAC is negative (vAC, NOT value!)}
0 Value- Value= {Negate value}
$2d PrintChar!] {Print minus sign}
0 k= {Suppress leading zeroes}
Value [if<0 {Bring large unsigned values in range}
-30000 Value+ Value= {Now Value<=35535, we need Value<=42767: OK}
3 k=]
10000 PrintDigit! {Print ten thousands, largest for 16 bits}
1000 PrintDigit! {Print thousands}
100 PrintDigit! {Print hundreds}
10 PrintDigit! {Print tens}
$30 Value+ PrintChar! {Print ones}
pop ret
] PrintValue=
[def
Prompt! $53# $74# $61# $63# {'Stack'}
$6b# 0#
] StackError=
[def
Prompt! $53# $79# $6e# $74# {'Syntax'}
$61# $78# 0#
] SyntaxError=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 10 |
+-----------------------------------------------------------------------}
$0aa0:
{ Parse an inline keyword. Returns 0 for failure }
[def
\vLR; tmp= {vLR points to inline argument}
Active j= {Save Active in case word match fails}
[do
tmp, tmp<++ {Next expected character from argument}
if<>0 {Compare until non-zero}
i=
Active, {Grab next character from line}
$20| {Ignore case}
i^ [if=0 Active<++ loop] {Accept if character matches}
j Active= {Restore program pointer on mismatch}
[do tmp, tmp<++ if<>0loop] {Eat remaining characters}
tmp!]
Spaces! {Remove trailing whitespace on match}
tmp! {Effectively returns past the keyword}
] Keyword=
{ Parse an unsigned decimal number and put in Value }
[def
$8000 {Stays negative if we don't see any digits}
[do
Value= {Update partial result}
2<< Value+ 1<< i= {Multiply by 10 before adding decimal}
Active, {Grab next character}
$30- {'0'} if>=0 {Bail out if out of range}
10- {'9'} if<0 {Decimal digit}
10+ {Map in 0..9 range}
i+ {Add it to value}
Active<++
loop]
Value {Result, negative value indicates error}
ret
] {Number=}
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 11 |
+-----------------------------------------------------------------------}
$0ba0:
Number=
{ Verify that there is nothing else on the line, then continue with next }
[def
Active, [if<>0 SyntaxError!] {There must be nothing left on the line}
Active NextBlock! Active= {Advance to next line}
End^ [if<>0 {Stop at end of program}
$13c0 Active^ if<>0 {Also stop if we came from the input buffer}
{XXX Insert checkpointing here?}
Active 2+ Active= {Skip past line number}
Statement!] {And continue from there}
Prompt! 0# {Program finished ok}
] EndOfLine=
{ Parse relational operator }
{ Use same encoding as original Tiny BASIC }
{ Doesn't accept >< (just use <>) }
[def
push
0 k=
Keyword! $3c# 0# {'<' in bit 0}
[if<>0 k<++]
Keyword! $3e# 0# {'>' in bit 2}
[if<>0 k 4+ k=]
Keyword! $3d# 0# {'=' in bit 1}
[if<>0 k 2+ k=]
k
pop ret
] RelOp=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 12 |
+-----------------------------------------------------------------------}
$0ca0:
{ Process a full expression, result in Value and vAC }
[def
push
\vSP, $8b- {Check stack for at least 10 bytes above $81}
{Expression: 4, Term: 4, Factor: 2}
[if<0 StackError!] {Too complex expression AND too deep}
{First term}
Keyword! $2d# 0# {'-'}
[if<>0 {Accept unary minus}
Term!
0 Value- {Negate}
else
Keyword! $2b# 0# {'+' Ignore unary plus}
Term!]
2-- 0%= {Put partial result on stack}
[do {Optional additional terms}
Keyword! $2b# 0# {'+'}
[if<>0
Term!
0% Value+ 0%= {Perform addition}
loop]
Keyword! $2d# 0# {'-'}
[if<>0
Term!
0% Value- 0%= {Perform subtraction}
loop]
]
0% Value= pop {Make stack value the result}
pop ret
] Expression=
[def
Prompt! $56# $61# $6c# $75# {'Value'}
$65# 0#
] ValueError=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 13 |
+-----------------------------------------------------------------------}
$0da0:
{ Process an expression term, result in Value and vAC }
[def
push
Factor! {First factor}
2-- Value 0%= {Put partial result on stack}
[do {Optional additional factors}
Keyword! $2a# 0# {'*'}
[if<>0
Factor!
0% Multiply! 0%=
loop]
Keyword! $2f# 0# {'/'}
[if<>0
Factor!
0% Divide! 0%=
loop]
Keyword! $25# 0# {'%'}
[if<>0
Factor!
0% Divide! i 0%=
loop]
]
0% Value= pop {Make stack value the result}
pop ret
] Term=
[def
\serialRaw, $7f^ {Test for [Del] as break request}
[if=0
Prompt! {'Break'}
$42# $72# $65# $61# $6b#
0#]
ret
] TestBreak=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 14 |
+-----------------------------------------------------------------------}
$0ea0:
{ Calculate vAC / Value, result in vAC, remainder in i }
[def
j= {i:j is the Remainder:Quotient pair}
Value^ k= {Park sign information}
j [if<0 0 j- j= ] {Non-negative}
Value [if<0 0 Value- Value=] {Non-negative}
[if=0 ValueError!] {Avoid division by zero}
0 i=
[do
tmp= {Loop counter}
i i+ i= {Shift left}
j [if<0 i<++] {Carry bit over to i}
j j+ j= {Shift left}
i Value- [if>=0 i= j<++] {Result bit}
tmp 1+ 15& {Iterate 16 times}
if<>0loop]
k [if<0 0 j- ret] {Return with corrected sign}
j ret
] {Divide=}
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 15 |
+-----------------------------------------------------------------------}
$0fa0:
Divide=
{ Calculate vAC * Value, result in vAC }
[def
tmp=
0 j= {Result variable}
1 {First bit}
[do {Loop over all 16 bits}
i= tmp& {Test next bit left}
[if<>0 j Value+ j=] {Add partial term}
Value Value+ Value= {Shift left}
i i+ if<>0loop] {Until all done}
j
ret
] Multiply=
{ Get variable and calculate its address }
[def
$13c0 i= {Base for variables}
Active, {Next character from line}
$5f& {Ignore case}
$41- {'A'} [if>=0 {Bail out if out of range}
26- {'Z'} if<0 {Letter A..Z or a..z}
Active<++ {Accept character}
26+ {Map in 0..25 range}
1<< i+ {Address is $13c0+2*n}
ret {Return address on success}
]
SyntaxError!
] Variable=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 16 |
+-----------------------------------------------------------------------}
$10a0:
{ List program, count and print free bytes. Returns 0 }
[def
push
Begin {List program from start of program memory}
[do {Loop over all program lines}
j=
End^ if<>0 {Repeat until the last line}
TestBreak! {Test for [Del] as break request}
j; Value= PrintValue! {Print line number}
j<++ {Print line text}
[do {Loop over all characters in program line}
j<++ j,
if<>0 PrintChar!
loop]
Newline!
j NextBlock! {Advance to next block}
loop]
[do {Continue looping, now over free memory}
Value= {Subtotal}
0; {Retrieve top of memory from RAM[0:1]}
j^ if<>0 {If not equal to j}
j NextBlock! j= {Advance to next block}
Value 32+ {Count another 32-byte block}
loop]
PrintValue! {vAC>=0 triggers conversion without sign}
PrintS! {' bytes free'}
$20# $62# $79# $74# $65#
$73# $20# $66# $72# $65#
$65# 0#
Newline!
pop ret
] List=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 17 |
+-----------------------------------------------------------------------}
$11a0:
{ Conditionally print leading decimal digit }
[def
push
i= {Radix as argument, keep in i}
Value [do {Calculate Value/Radix}
i- {Subtract i}
if>=0 {As many times as fits}
Value=
k<++ {Increment 0..9 times}
loop]
k [if<>0 {If non-zero digit or non-leading zero}
$30| PrintChar! {Map to $30..$39 range and print digit}
$30 k=] {And mark all further zeroes as non-leading}
pop ret
] PrintDigit=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 18 |
+-----------------------------------------------------------------------}
$12a0:
{ Error reporting function and re-entry point for main loop }
{ Call with inline partial error message, or with 0# for no error }
[def
\vLR; tmp= {vLR points to inline string argument}
[Pos<, if<>0 Newline!] {Conditional newline}
tmp, [if<>0 {Test for error in first byte of message}
$3f {Begin error messages with '?'}
[do {Print inline string as prelude}
PrintChar!
tmp, tmp<++
if<>0loop]
PrintS! {' error'}
$20# $65# $72# $72# $6f#
$72# 0#
\vSP. {Reset stack}
32- {$ffe0}
Active& {Align to begin of block}
0+ {dummy offset} deek {Fetch active line number from offset 0}
[if<>0 {Input buffer has line number 0, don't print}
Value= {Prepare line number for printing}
PrintS! {' in '}
$20# $69# $6e# $20# 0#
PrintValue!] {Print line number}
Newline!]
PrintS! $4f# $6b# 0# {'Ok'}
Newline!
{ Main loop }
[do
0 {Interactive mode is 'line 0'}
GetLine! {Get line from console}
if=0loop {Do nothing for empty lines}
Insert! {Test for line number and insert or delete}
if>0loop] {If success, continue without prompt}
\LDWI# {1-byte hack to fall through and skip 'Prompt='}
] Prompt=
Statement! {Execute statement}
{-----------------------------------------------------------------------+
| BASIC data |
+-----------------------------------------------------------------------}
{ BASIC data begins here. In this setup the interpreter can be patched
to include a BASIC program and a first command for execution }
$13a0: $a0# $13# {Point to self}
$4e# $45# $57# 0# {'NEW' to start interactive session}