{-----------------------------------------------------------------------+ | | | Recreation of Tiny BASIC for Gigatron TTL microcomputer | | | | - Integer BASIC, with PRINT supporting string constants | | - Supports roughly 300 line programs in the 32K system | | - Multiple statements per line, up to 25 characters combined | | - Based on Dennis Allison's original 1976 Tiny BASIC | | - Extended with POKE/PEEK/RND/FOR/NEXT/CLS/AT/PUT/MODE/Arrays/ | | LINE/SAVE | | | +-----------------------------------------------------------------------} 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() 2018-07-18 (marcelk) Fix GOSUB/RETURN parsing bug causing syntax error 2018-07-22 (marcelk) Add PUT, FOR/NEXT, ':', MODE (ROMv2), AT, CLS, arrays 2018-08-14 (marcelk) SAVE, LINE (both draft) 2018-08-22 (marcelk) LINE now wraps around edges to stay on screen 2018-08-31 (marcelk) SAVE into BabelFish EEPROM 2018-09-04 (marcelk) Faster start; Error when SAVE not there; Tiny BASIC v2 2019-07-06 (marcelk) Update GCL notation (no changes to GT1 file) 2019-07-07 (marcelk) PrintChar accepts arrow chars; Fix indent issue #40 2019-07-08 (marcelk) Reenable video on input after MODE 1975; Tiny BASIC v3 2019-12-14 (marcelk) Accept hexadecimal numbers $....; Tiny BASIC DEV Formal grammar: Line ::= Number Statements | Statements Statements ::= Statement (':' Statement)* Statement ::= ('PRINT'|'?') ((String|Expression) [,;])* (String|Expression)? | 'AT' Expression (',' Expression)? | 'PUT' Expression | 'CLS' | 'LINE' Expression ',' Expression | 'IF' Expression RelOp Expression 'THEN'? Statements | 'GOTO' Expression | 'INPUT' Variable (',' Variable)* | 'LET'? Variable ('(' Expression ')')? '=' Expression | 'FOR' Variable '=' Expression 'TO' Expression | 'NEXT' Variable | 'POKE' Expression ',' Expression | 'GOSUB' Expression | 'RETURN' | ('REM'|"'") Character* | 'MODE' Expression | 'NEW' | 'LIST' | 'RUN' | 'END' | 'SAVE' Expression ::= [+-]? Term ([+-] Term)* Term ::= Factor ([*/%] Factor)* Factor ::= Variable | Number | Hexnum | (Variable|'PEEK'|'RND'|'USR')? '(' Expression ')' RelOp ::= '=' | '<' | '>' | '<=' | '>=' | '<>' Number ::= [0-9]+ Hexnum ::= $[0-9A-F]+ 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) interrupted / Out of space for SAVE (510 bytes for ATTiny85!) Syntax error Parse error / Program too large for memory (~300 lines) Value error Value or index out of range / Calculation error (division by 0) / NEXT without FOR / Wrong ROM version 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 - $19ff BASIC interpreter, partly next to screen memory $1ba0 - $1bbf Input buffer ('line 0') $1bc0 - Programs stored in the invisible part of screen memory - $7ebf Arrays at the end, growing down $7ec0 - $7eff FOR-NEXT loop pointers $7fa0 - $7fbf SAVE buffer $7fc0 - $7fff BASIC variables A..Z $8000 - $ffff Optional extra memory for BASIC programs (64K extension) (Variables etcetera move to the last two pages) 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 RETURN 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! A(256) Many (too) large idexes give an error, but not all! Lowercase @ The backquote ('`') is recognized as alias for '@' FOR-NEXT Doesn't work over 16-bit ranges (FOR i=-30000 TO 30000) 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 'Maybe in vN+1' list: A(0)=x,y,z Array assignment with list expression A$ .. Z$ String variables (only in LET, PRINT, INPUT?) Boot Some kind of autorun of a saved program Speed Cache result of the statement detection (checkpointing) Speed Alternative: LET detection is now last, but should be first IF Reintroduce the missing syntax error check A="... As poor-men's substitute for DATA/READ? @ Give it an implicit meaning such as "max index in arrays"? Speed Cache result of NextBlock (at offset 30 of block?) 'Maybe in vN+2' list: Unary minus Original Tiny BASIC does it wrong in some edge cases: 8/-2 Speed Cache GOTO/GOSUB target (only for constant targets...) Speed SYS functions for NextBlock, EndOfLine, Number TUNE/PLAY Play a note/score ABS(/SGN( Built-in function A$() Indexing in string variable, gives integer (handle in Factor) Speed Move stack check from Expression to Factor (and GOSUB) LEN()/A$()= More string stuff 'Probably not soon' list: NEXT Without variable (complicates 'Variable' subroutine) A0 .. Z9 Much more variable space (as in Apple-1 basic) ROM peek rpeek()? Some predefined USR functions? A$(i,j) Substrings (only in PRINT) LOAD From where? EPROM? "Cassette interface". DIM A() And bound checks... TI/TI$ Built-in variable for time? (only in MS BASICs). Setting also? TAB() As in Apple-1 BASIC (Weird: TAB(N) advances to N-1...) SPC() Spaces Speed Binary search for GotoValue -> Only speeds up large programs "" or """ For printing the quote character itself (use: PUT 34) Overflow Errors on overflow for + - and * WAIT/PAUSE -> Make a loop subroutine LIST List one line a-z A-Z As different variables >< For unequal CLEAR For NEW ('CLEAR' is original TinyBASIC syntax, but who cares..) READ, DATA Possibly put in unused lines? (RESTORE) AND OR NOT At least in IF statements ELSE Will slow down IF, because it has to scan for this DEF FNx Memory layout: a0 b0 c0 d0 e0 f0 ff +-----------------------+-----------------------+-----------------------+ $1ba0 | Input buffer Begin->| Program line 10 | Program line 20 | +-----------------------+-----------------------+-----------------------+ $1ca0 | Program line 30 | Program line 40 | Program line 45 | +-----------------------+-----------------------+-----------------------+ $1da0 | Program line 50 | Program line 60 End->|K(96) Z(96)| +-----------------------+-----------------------+-----------------------+ | | @(95) J(95)|K(95) Z(95)| : : : : : : : : : : : : | | @(1) J(1)|K(1) Z(1)| +-----------------------+-----------------------+-----------------------+ $7da0 | | @(0) J(0)|K(0) Z(0)| +-----------------------+-----------------------+-----------------------+ $7ea0 | | For loops @ A B ... J | K ... S T U V W X Y Z | +-----------------------+-----------------------+-----------------------+ $7fa0 | SAVE buffer | Variables @ A B ... J | K ... S T U V W X Y Z | +-----------------------+-----------------------+-----------------------+ Totally random ideas: 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? Some concepts to fake strings Variable = String PRINT Variable '$' Variable '$' '=' (Variable '$' | String) PRINT Variable '$' But how about INPUT A$? Ideas for demo programs: Rainbow Show palette in a nice way Clock Maze The famous one-liner with / and \ -> Easter egg in ROMv2 Circles Lots of math Music Play tones, vary with waveforms Sprites Fake sprites with printing chars at arbitrary positions } {-----------------------------------------------------------------------+ | RAM page 2 | +-----------------------------------------------------------------------} $01df deek Pos= {Bottom character row in screen memory} {Slightly cheating with endianness} { Process an expression factor, result in Value } [def push Number! {Test for decimal constant} [if>=0 Spaces! pop ret] {Result already in Value} Active, $24^ [if=0 {Test for hexadecimal constant $....} &_Hexnum call Spaces! pop ret] Keyword! `( #0 [if<>0 Expression! else Keyword! `peek( #0 [if<>0 Expression! peek Value= else Keyword! `rnd( #0 [if<>0 Expression! \SYS_Random_34 _sysFn= {Prepare SYS call} [do 34!! if<0loop] {Get positive random number} Divide! {Modulo expression} i Value= else Keyword! `usr( #0 [if<>0 Expression! Value! Value= {vCPU call and result} else Variable! k= {Otherwise it MUST be a variable} Spaces! Keyword! `( #0 [if<>0 {Optional array index} push k %0= {Save address on stack} Expression! [if<0 ValueError!] {Index must be non-negative} %0 k= pop {Pop address from stack} >k, Value- 2- >k. End k^ {64K safe bound check} [if<0 End {Opposite signs} else k End-] {Equal signs} [if<0 ValueError!] Keyword! `) #0 if=0 SyntaxError!] k; Value= {Fetch value} pop ret ]]]] Keyword! `) #0 [if=0 SyntaxError!] pop ret ] Factor= { Print inline string. Returns 0 } [def \vLR; tmp= {vLR points to inline argument} [do tmp, 0 PrintChar! {Print as long as non-zero} loop] tmp! {Returns to caller} ] PrintS= [def \serialRaw, 3^ {Test for Ctrl-C (ASCII ETX) as break request} [if=0 PrintCharScreen PrintChar= {In case we break a SAVE command} Prompt! `Break #0] ret ] TestBreak= {-----------------------------------------------------------------------+ |} >\vLR++ ret { RAM page 3 | +-----------------------------------------------------------------------} *=$0300 { Print ASCII character (32..131) on screen in 5x8 pixels } { For valid characters, return the actual position } [def k= {Temporarily save character} [131- if<=0 {Suppress characters >131} >Pos, 120- if<=0 {Suppress when vertically protruding screen memory (note this can be in the middle of the screen after scrolling and using AT with non-multiples of 8, leading to unexpected missing of text} 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] {ASCII 82..127} k= i if>=0 {Suppress characters <32} 2<< i+ {Multiply by 5} k+ k= {Add to page address to reach bitmap data} \SYS_VDrawBits_134 {Prepare SYS calls} _sysFn= _sysArgs6 _sysArgs0= {Apply caller-defined colors} Pos \sysArgs4: {Position for character} %-2= {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} 0loop] {Looping} \sysArgs2. 134!! {Render 6th slice} %-2] {Return effective position} ret ] PrintChar= PrintCharScreen= { Accept ASCII characters from console input with line editting } [def push tmp= {Line number} { On ROM v4 and higher `MODE 1975' disables all video/audio/IO/etc } { completely. Better reactivate it before waiting for new input. } \SYS_SetMode_v2_80 _sysFn= \romType, \romTypeValue_ROMv4- [if>=0 255- 80!!] {Any value <0 will restore signal, except -177.} {But -177+255+$38 isn't valid romType content,} {so that's just fine.} &_Buffer Active= {Input buffer} tmp Active: {For error messages} $a2 0 {Enter/return breaks NEXTCHAR loop} 117^ [if=0 {Delete pressed (10^127 == 117)} $20 PrintChar! Pos= {Remove cursor} =0 {If not on first position} =0loop {Ignore apparent unprintable garbage} =0 {Fewer than 2 chars or 11 pixels will fit} $a2 \vLR++ ret { RAM page 4 | +-----------------------------------------------------------------------} *=$0400 GetLine= { Statement executor. Doesn't return through vLR } { Active must point to text or terminating zero, NOT to the line number } [def Spaces! Keyword! `goto #0 [if<>0 Expression! {Computed goto} GotoValue!] {Find that line and continue there} Keyword! `gosub #0 [if<>0 Expression! {Also does the stack check} push Active %0= {Push current line to stack} GotoValue!] {Find that line and continue there} Keyword! `return #0 [if<>0 \vSP, [if=0 StackError!] {Top of stack} deek Active= pop {Pop line from stack} EndOfLine!] Keyword! `if #0 [if<>0 Expression! 4-- %2= {Push first value on stack} RelOp! [if=0 SyntaxError!] %0= {Park RelOp bits on stack} Expression! Keyword! `then #0 {Optional} %2 Value^ {Do the comparison, beware of overflows} [if<0 %2 1| {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 Statements!] {Conditional execution} [do Active, if<>0 {Find end of line} 0 Expression! Address= Keyword! `, #0 [if=0 SyntaxError!] Expression! Address. {Write byte to memory} EndOfLine!] Keyword! `' #0 [if=0 Keyword! `rem #0] [if<>0 [do Active, if<>0 {Find end of line} 0 &_Line call] Keyword! `end #0 [if<>0 Prompt! #0] &_Statements2 call {Continue in another page} ] Statements= >\vLR++ >\vLR++ ret {Hop over next page} {-----------------------------------------------------------------------+ | RAM page 5 | +-----------------------------------------------------------------------} *=$0500 _Statements2=* { Continuation of Statement } Keyword! `next #0 [if<>0 Variable! Address= {Pointer to loop variable} push Active %0= {Park program index on stack} -$100 Address+ deek {Retrieve loop pointer} [if=0 ValueError!] {NEXT without FOR error} Active= {Switch back to tail of FOR statement} Expression! {Evaluate end value} Address; 1+ Address: {Increment loop variable} Value- {Compare with end value} { Value^ {XXX 16-bit safe comparison} [if<0 Value^ {Opposite signs} else Value^ Value-] } {Same signs} [if>0 {Loop variable > End value} %0 Active= {Break the loop} Spaces!] pop {Shorter than 2++} TestBreak! {Test for break request} EndOfLine!] Keyword! `for #0 [if<>0 Variable! Address= {Pointer to loop variable} Spaces! Keyword! `= #0 [if=0 SyntaxError!] Expression! {Evaluate expression} Address: {Store value in variable} Keyword! `to #0 [if=0 SyntaxError!] -$100 Address+ j= {Retrieve loop pointer} Active j: {Remember return point for NEXT} Expression! {Evaluate but ignore result} EndOfLine!] Keyword! `at #0 [if<>0 Expression! {X position} [if<0 ValueError!] 0 Expression! {Optional Y position} [if<0 ValueError!] 120- [if>=0 ValueError!] {Point into videoTable} 248+ 1<< {\videoTable+2*y} peek >Pos.] EndOfLine!] Keyword! `put #0 [if<>0 Expression! PrintChar! EndOfLine!] Keyword! `mode #0 [if<>0 Expression! {Degrade gracefully in ROM v1} \romType, \romTypeValue_ROMv2- [if>=0 \SYS_SetMode_v2_80 {Available from ROM v2} _sysFn= Value 80!!] EndOfLine!] &_Statements3 call _Hexnum=* 0 [do Value= {Update partial result} 4<< i= {Multiply by 16 before adding hex digit} =0 {Bail out if out of range} 10- {'9'} [if<0 {Decimal digit} 10+ {Map in 0..9 range} i+ loop] {Add it to value} $df& {Ignore case} 7- {'A'} if>=0 {Skip :;<=>?@ and Z[\]^_`} 6- {'F'} [if<0 {A..F or a..f} 16+ {Map in 10..15 range} i+ loop] {Add it to value} ] {Silently accepts bare $ as 0} 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} 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; $80- $c0- {Correct for variables} 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} 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} 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= $353f \sysArgs6: {Pen color blue/purple, background color white} { Note: It is safe here to call functions before hopping over to page 8 } &_Newline Newline= call {Scroll and clear last line} { Welcome message } PrintS! `***`Tiny`BASIC`DEV #0 Newline! { Hop to next page } $08a0 _vLR= ret _GetLine2=* { Continuation of GetLine } 0 Active. {Terminate input with zero} $20 PrintChar! {Remove cursor} Newline! $a2 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} i 2+ Active= {Skip past line number} TestBreak! {Test for break request} Statements!] {And continue from there} i NextBlock! loop] {Otherwise try next line} Prompt! `Line #0 {Line error} ] {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! `Stack #0 {Stack error} ] StackError= [def Prompt! `Syntax #0 {Syntax error} ] 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, 0 {Compare until non-zero} i= Active, {Grab next character from line} $20| {Ignore case} i^ [if=0 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} \vLR++ ret { RAM page 11 | +-----------------------------------------------------------------------} *=$0ba0 Number= { Verify that there is nothing else on the line, then continue with next } [def Active, [if<>0 $3a^ [if=0 {Accept statements separated by colon (':')} 0 {Stop at end of program} &_BasicProgram Active^ {Also stop if we came from the input buffer} if<>0 Active 2+ Active= {Skip past line number} Statements!] {And continue from there} Prompt! #0 {Program finished ok} ] EndOfLine= { Clear variables but not arrays. Returns begin address for programs } [def 0; i= {Top of memory} 2 {Clear variables and FOR state} [do j= i 54- i= {i -= 2*27} [do 0 i. {Clear byte} 0loop] {Until end of page} j 1- if<>0loop] &_BasicProgram Begin= ret ] {Clear=} {-----------------------------------------------------------------------+ |} >\vLR++ ret { RAM page 12 | +-----------------------------------------------------------------------} *=$0ca0 Clear= { Process a full expression, result in Value and vAC } [def push \vSP, 141- {Check stack for at least 12 bytes above $81} {Expression: 4, Term: 4, Factor: 4} [if<0 StackError!] {Too complex expression AND too deep} {First term} Keyword! `- #0 [if<>0 {Accept unary minus} Term! 0 Value- {Negate} else Keyword! `+ #0 {Ignore unary plus} Term!] push %0= {Put partial result on stack} [do {Optional additional terms} Keyword! `+ #0 [if<>0 Term! %0 Value+ %0= {Perform addition} loop] Keyword! `- #0 [if<>0 Term! %0 Value- %0= {Perform subtraction} loop] ] %0 Value= pop {Make stack value the result} pop ret ] Expression= [def Prompt! `Value #0 {Value error} ] ValueError= {-----------------------------------------------------------------------+ |} >\vLR++ ret { RAM page 13 | +-----------------------------------------------------------------------} *=$0da0 { Process an expression term, result in Value and vAC } [def push Factor! {First factor} push Value %0= {Put partial result on stack} [do {Optional additional factors} Keyword! `* #0 [if<>0 Factor! %0 Multiply! %0= loop] Keyword! `/ #0 [if<>0 Factor! %0 Divide! %0= loop] Keyword! `% #0 [if<>0 Factor! %0 Divide! i %0= loop] ] %0 Value= pop {Make stack value the result} pop ret ] Term= >\vLR++ ret {Hop to next page} _Statements5=* {Borrow a few bytes here for RUN} Keyword! `run #0 [if<>0 Clear! {Clear variables} deek Value= {First program line, if any} GotoValue!] {Also tests for break} &_Statements6 call {-----------------------------------------------------------------------+ | RAM page 14 | +-----------------------------------------------------------------------} *=$0ea0 { Calculate vAC / Value, result in vAC, remainder in i } [def j= Value^ k= {Park sign information} 0 j- [if<0 j] _sysArgs0= 0 Value- [if<0 Value] _sysArgs2= [if=0 ValueError!] {Avoid division by zero} 0 _sysArgs4= _sysArgs6 tmp= 1 _sysArgs6= \SYS_Divide_s16_v6_80 _sysFn= 80!! _sysArgs4 i= {Remainder} _sysArgs0 j= {Quotient } tmp _sysArgs6= k [if<0 0 j- ret] {Correct sign} j ret ] Divide= {-----------------------------------------------------------------------+ |} >\vLR++ ret { RAM page 15 | +-----------------------------------------------------------------------} *=$0fa0 { Calculate vAC * Value, result in vAC } [def _sysArgs0= \SYS_Multiply_s16_v6_66 _sysFn= Value _sysArgs2= 0 _sysArgs4= _sysArgs6 tmp= 1 _sysArgs6= 88!! tmp _sysArgs6= _sysArgs4 ret ] Multiply= { Get variable and calculate its address } [def 0; i= {Top of memory as base for variables} Active, {Next character from line} $5f& {Ignore case} $40- {'@'} [if>=0 {Bail out if out of range} 27- {'Z'} if<0 {Letter A..Z, @ or a..z} \vLR++ ret { RAM page 16 | +-----------------------------------------------------------------------} *=$10a0 { List program, count and print free bytes } [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 break request} j; Value= PrintValue! {Print line number} 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]} $80- $c0- {Correct for space used by variables} j^ if<>0 {If not equal to j} j NextBlock! j= {Advance to next block} Value 32+ {Count another 32-byte block} loop] PrintChar PrintCharScreen^ [if=0 {When not in SAVE mode} PrintValue! {vAC>=0 triggers conversion without sign} PrintS! ``bytes`free #0 ] pop ret ] {List=} {-----------------------------------------------------------------------+ |} >\vLR++ ret { RAM page 17 | +-----------------------------------------------------------------------} *=$11a0 List= { 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= 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= { Parse relational operator } { Use same encoding as original Tiny BASIC } { Doesn't accept >< (just use <>) } [def push 0 k= Keyword! `< #0 {'<' in bit 0} [if<>0 #0 {'>' in bit 2} [if<>0 k 4+ k=] Keyword! `= #0 {'=' in bit 1} [if<>0 k 2+ k=] k pop ret ] RelOp= &_Buffer deek End= {At startup 'End' comes from loaded segment} 2+ {Active=} {BASIC command(s) to run at startup} {-----------------------------------------------------------------------+ |} >\vLR++ ret { RAM page 18 | +-----------------------------------------------------------------------} *=$12a0 Active= { 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} [0 Newline!] {Conditional newline} 0 \vSP. {Reset stack} tmp, [if<>0 {Test for error in first byte of message} $3f {Begin error messages with '?'} [do {Print inline string as prelude} PrintChar! tmp, 0loop] PrintS! ``error #0 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` #0 PrintValue!] {Print line number} Newline!] PrintS! `Ok #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= Statements! {Execute statements} {-----------------------------------------------------------------------+ | RAM page 19 | +-----------------------------------------------------------------------} *=$13a0 _Print=* {PRINT} Active, [if<>0 {Special case for newlines} $3a^ if<>0 {Same for colon} [do {PRINTLIST} Active, if<>0 {Until end of line} $3a^ if<>0 {or ':'} $18^ {'"' = $22 = $3a^$18} [if=0 [do {PRINTSTRING} {String constant} 0 {Silently accept unterminated string} PrintChar! loop] {To PRINTSTRING} else Expression! PrintValue!] Keyword! #$2c #0 {','} [if<>0 [do $20 PrintChar! 0loop] {Tab stops every 4 characters} loop] {To PRINTLIST} Keyword! #$3b #0 {';'} if<>0loop {To PRINTLIST} Newline!] {When no trailing ',' or ';'} else Newline!] {Special case for newlines and colon} EndOfLine! {-----------------------------------------------------------------------+ | RAM page 20 | +-----------------------------------------------------------------------} *=$14a0 _Statements3=* Keyword! `? #0 [if=0 Keyword! `print #0] [if<>0 &_Print call] {PRINT} Keyword! `input #0 [if<>0 [do Active, 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! `, #0 {','} if<>0loop] {Next variable} EndOfLine!] &_Statements4 call {-----------------------------------------------------------------------+ | RAM page 21 | +-----------------------------------------------------------------------} *=$15a0 _Statements4=* Keyword! `cls #0 [if<>0 $100 peek >Pos. {Start at top of page} 14 [do {14 newlines} j= Newline! j 1- if>0loop] [do {Continue until video table is normal} Newline! $100 peek 8^ if<>0loop] 8 >Pos. {Return cursor to top of page} EndOfLine!] Keyword! `list #0 [if<>0 List! EndOfLine!] Keyword! `save #0 [if<>0 \romType, \romTypeValue_ROMv3- [if<0 ValueError!] {Error on ROM before v3} &_Save call] &_Statements5 call {-----------------------------------------------------------------------+ | RAM page 22 | +-----------------------------------------------------------------------} *=$16a0 _Statements6=* Keyword! `new #0 [if<>0 Clear! {Clear variables} End= {Clear program} List! {To print number of free bytes} Prompt! #0] {Return to prompt} Keyword! `let #0 {LET is optional} Variable! Address= {Park here} Spaces! Keyword! #$28 #0 {'('} [if<>0 {Optional array index} Expression! [if<0 ValueError!] {Index must be non-negative} >Address, Value- 2- >Address. End Address^ {64K safe bound check} [if<0 End {Opposite signs} else Address End-] {Equal signs} [if<0 ValueError!] Keyword! #$29 #0 {')'} if=0 SyntaxError!] Keyword! `= #0 {'='} [if=0 SyntaxError!] Expression! {Evaluate expression} Address: {Store value in variable} EndOfLine! {-----------------------------------------------------------------------+ | RAM page 23 | +-----------------------------------------------------------------------} *=$17a0 _Save=* {SAVE} { Saving works by redirecting PrintChar and then calling List. The first line out will be an implicit bare newline. This signals to BabelFish that a new program starts and the old can be deleted. } 0; \sysArgs3. {Number of bits to send (zero)} 96- Address= {SAVE line buffer} [def push k= \sysArgs3, 8+ \sysArgs3. {Always count 8 more bits} 0 {'BreakError' would be better} 3 \serialRaw. {Not a great hack...} TestBreak!] {...to initiate a break error} 8 \sysArgs3.] k Address. 0loop] List! {List whole program, redirected} 32 PrintChar! {Flushes the last line} PrintCharScreen PrintChar= {Restore PrintChar} EndOfLine! {-----------------------------------------------------------------------+ | RAM page 24 | +-----------------------------------------------------------------------} *=$18a0 _Newline=* {Newline} { Prepare for printing on new line } \SYS_VDrawBits_134 _sysFn= {Prepare SYS call} 0 Pos, 15+ {Go down upto 8 upto 15 lines} 120& [if=0 8] {Wrap around and re-align to 8-fold if needed} >Pos. \sysArgs6; \sysArgs0: {Apply caller-defined colors} Pos \sysArgs4: {sysArgs[4:5] is position on screen} [do 134!! <\sysArgs4++ {SYS call and advance to next slice} 134!! <\sysArgs4++ {A bit unrolling for speed} \sysArgs4, 160^ {Test for end of screen} if<>0loop] \videoTable i= {videoTable} 255| deek {Gets [$100] aka Y[0] in vAC high byte} Pos- \vACH, {Compare with Y position} [if=0 do i 16+ _sysArgs0= {sysArgs0 looks 8 entries ahead of i} peek \sysArgs2. {Swap scanlines} i, _sysArgs0. \sysArgs2, i. 0loop] {Until all done} = 0} 0 #\SUBW #\sysArgs2 {Inline assembly} \sysArgs2: {V = -V} 0 j- j=] %0 i= {dX argument as i} [if<0 {Make dX >= 0} 0 #\SUBW #\sysArgs0 {Inline assembly} \sysArgs0: {U = -U} 0 i- i=] j- [if<0 {Make dX >= dY} i tmp= j i= tmp j= {Swap i, j} \sysArgs0; tmp= \sysArgs2; {Swap U, V} \sysArgs0: tmp \sysArgs2:] pop {Shorter than 2++} &_Line2 call {-----------------------------------------------------------------------+ | RAM page 26 | +-----------------------------------------------------------------------} *=$1aa0 _Line2=* {Continuation of LINE} Value {dY argument} [if<0 $7800 else $8800] {Vertical gap} \sysArgs4: i tmp= {Setup line drawing inner loop} k= {Bresenham's error} [do {Inner loop} \sysArgs7, Pos. {Plot pixel in pen color} tmp 1- tmp= if>=0 {Loop counter} k j- j- k= {Update Bresenham's error} [if<0 {If threshold exceeded} i+ i+ k= \sysArgs2; Pos+ Pos=] {Pos += V {Step along short side}} \sysArgs0; Pos+ Pos= {Pos += U {Step along long side}} =0 {Wrapping around left/right border} [if>0 >Pos++ 64+] Pos, $78& [if=0 {This occurs midscreen if text has scrolled} \sysArgs4; Pos+ Pos=] {Map Y back into 8..127 range} loop] EndOfLine! { :-) } {-----------------------------------------------------------------------+ | 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 } *=$1ba0 _Buffer=* {Must be the "line" before user program} ##_Buffer {Initial end pointer, points to startup command} `NEW #0 {'NEW' to start interactive session} *=$1bc0 _BasicProgram=* {BASIC program starts here} {-----------------------------------------------------------------------+ | | +-----------------------------------------------------------------------}