1090 lines
40 KiB
Plaintext
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}
|
|
|