; ----------------------------------------------------------------------------- ; Copyright 2018 Dimitri Theulings ; ; This file is part of Tasty Basic. ; ; Tasty Basic is free software: you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, either version 3 of the License, or ; (at your option) any later version. ; ; Tasty Basic is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with Tasty Basic. If not, see . ; ----------------------------------------------------------------------------- ; Tasty Basic is derived from earlier works by Li-Chen Wang, Peter Rauskolb, ; and Doug Gabbard. Refer to the source code repository for details ; . ; ----------------------------------------------------------------------------- #define dwa(addr) .db (addr >> 8) + 080h\ .db addr & 0ffh ctrlc .equ 03h bs .equ 08h lf .equ 0ah cr .equ 0dh ctrlo .equ 0fh ctrlu .equ 15h #ifdef ZEMU ; Z80 Emulator tty_data .equ 7ch tty_status .equ 7dh rx_full .equ 1 tx_empty .equ 0 TBC_LOC .equ 0 #else ; RomWBW #include "std.asm" #endif .org TBC_LOC start: ld sp,stack ; ** Cold Start ** ld a,0ffh jp init testc: ex (sp),hl ; ** TestC ** call skipspace ; ignore spaces cp (hl) ; test character inc hl ; compare the byte that follows the jr z,tc1 ; call instruction with the text pointer push bc ld c,(hl) ; if not equal, ad the seond byte ld b, 0h ; that follows the call to the old pc add hl,bc pop bc dec de tc1: inc de ; if equal, skip those bytes inc hl ; and continue ex (sp),hl ret skipspace: ld a,(de) ; ** SkipSpace ** cp ' ' ; ignore spaces ret nz ; in text (where de points) inc de ; and return the first non-blank jp skipspace ; character in A expr: call expr2 ; ** Expr ** push hl ; evaluate expression jp expr1 comp: ld a,h ; ** Compare ** cp d ; compare hl with de ret nz ; return c and z flags ld a,l ; old a is lost cp e ret finish: pop af ; ** Finish ** call fin ; check end of command jp qwhat ;************************************************************* ; ; *** REM *** IF *** INPUT *** & LET (& DEFLT) *** ; ; 'REM' CAN BE FOLLOWED BY ANYTHING AND IS IGNORED BY TBI. ; TBI TREATS IT LIKE AN 'IF' WITH A FALSE CONDITION. ; ; 'IF' IS FOLLOWED BY AN EXPR. AS A CONDITION AND ONE OR MORE ; COMMANDS (INCLUDING OTHER 'IF'S) SEPERATED BY SEMI-COLONS. ; NOTE THAT THE WORD 'THEN' IS NOT USED. TBI EVALUATES THE ; EXPR. IF IT IS NON-ZERO, EXECUTION CONTINUES. IF THE ; EXPR. IS ZERO, THE COMMANDS THAT FOLLOWS ARE IGNORED AND ; EXECUTION CONTINUES AT THE NEXT LINE. ; ; 'INPUT' COMMAND IS LIKE THE 'PRINT' COMMAND, AND IS FOLLOWED ; BY A LIST OF ITEMS. IF THE ITEM IS A STRING IN SINGLE OR ; DOUBLE QUOTES, OR IS A BACK-ARROW, IT HAS THE SAME EFFECT AS ; IN 'PRINT'. IF AN ITEM IS A VARIABLE, THIS VARIABLE NAME IS ; PRINTED OUT FOLLOWED BY A COLON. THEN TBI WAITS FOR AN ; EXPR. TO BE TYPED IN. THE VARIABLE IS THEN SET TO THE ; VALUE OF THIS EXPR. IF THE VARIABLE IS PROCEDED BY A STRING ; (AGAIN IN SINGLE OR DOUBLE QUOTES), THE STRING WILL BE ; PRINTED FOLLOWED BY A COLON. TBI THEN WAITS FOR INPUT EXPR. ; AND SET THE VARIABLE TO THE VALUE OF THE EXPR. ; ; IF THE INPUT EXPR. IS INVALID, TBI WILL PRINT "WHAT?", ; "HOW?" OR "SORRY" AND REPRINT THE PROMPT AND REDO THE INPUT. ; THE EXECUTION WILL NOT TERMINATE UNLESS YOU TYPE CONTROL-C. ; THIS IS HANDLED IN 'INPERR'. ; ; 'LET' IS FOLLOWED BY A LIST OF ITEMS SEPERATED BY COMMAS. ; EACH ITEM CONSISTS OF A VARIABLE, AN EQUAL SIGN, AND AN EXPR. ; TBI EVALUATES THE EXPR. AND SET THE VARIABLE TO THAT VALUE. ; TBI WILL ALSO HANDLE 'LET' COMMAND WITHOUT THE WORD 'LET'. ; THIS IS DONE BY 'DEFLT'. ;************************************************************* rem: ld hl,0000h ; ** Rem ** jr if1 ; this is like 'IF 0' iff: call expr ; ** If ** if1: ld a,h ; is the expr = 0? or l jp nz,runsml ; no, continue call findskip ; yes, skip rest of line jp nc,runtsl ; and run the next line jp rstart ; if no, restart inputerror: ld hl,(stkinp) ; ** InputError ** ld sp,hl ; restore old sp and old current pop hl ld (current),hl pop de ; and old text pointer pop de ; redo current input: push de ; ** Input ** call qtstg ; is next item a string? jp ip2 ; no call testvar ; yes and followed by a variable? jp c,ip4 ; no jp ip3 ; yes, input variable ip2: push de ; save for printstr call testvar ; must be variable jp c,qwhat ; no, what? ld a,(de) ; prepare for printstr ld c,a sub a ld (de),a pop de call printstr ; print string as prompt ld a,c ; restore text dec de ld (de),a ip3: push de ; save text pointer ex de,hl ld hl,(current) ; also save current push hl ld hl,input ld (current),hl ld hl,0000h add hl,sp ld (stkinp),hl push de ld a,':' call getline ld de,buffer call expr nop nop nop pop de ex de,hl ld (hl),e inc hl ld (hl),d pop hl ld (current),hl pop de ip4: pop af ; purge stack call testc ; is next character ','? .db ',' .db ip5-$-1 jr input ; yes, more items ip5: call finish deflt: ld a,(de) ; ** DEFLT ** cp cr ; empty line is fine jr z,lt1 ; else it's 'LET' let: call setval ; ** Let ** call testc ; set value to var .db ',' .db lt1-$-1 jr let ; item by item lt1: call finish ;************************************************************* ; ; *** PEEK *** POKE *** IN *** & OUT *** ; ; 'PEEK()' RETURNS THE VALUE OF THE BYTE AT THE GIVEN ; ADDRESS. ; 'POKE ,' SETS BYTE AT ADDRESS TO ; VALUE ; ;************************************************************* peek: call parn ; ** Peek(expr) ** ld a,h ; expression must be positive or a jp m,qhow ld a,(hl) ld h,0 ld l,a ret poke: call expr ; ** Poke ** ld a,h ; address must be positive or a jp m,qhow push hl call testc ; is next char a comma? .db ',' .db pk1-$-1 ; what, no? call expr ; get value to store ld a,0 ; is it > 255? cp h jp z,pk2 ; no, all good pop hl jp m,qhow pk2: ld a,l ; save value pop hl ld (hl),a call finish pk1: pop hl jp qwhat usrexec: call parn ; ** Usr(expr) ** push de ex de,hl ld hl,ue1 push hl ld ix,(usrptr) jp (ix) ue1: ex de,hl pop de ret ;************************************************************* ; ; *** EXPR *** ; ; 'EXPR' EVALUATES ARITHMETICAL OR LOGICAL EXPRESSIONS. ; :: ; ; WHERE IS ONE OF THE OPERATORS IN TAB8 AND THE ; RESULT OF THESE OPERATIONS IS 1 IF TRUE AND 0 IF FALSE. ; ::=(+ OR -)(+ OR -)(....) ; WHERE () ARE OPTIONAL AND (....) ARE OPTIONAL REPEATS. ; ::=(* OR />)(....) ; ::= ; ; () ; IS RECURSIVE SO THAT VARIABLE '@' CAN HAVE AN ; AS INDEX, FUNCTIONS CAN HAVE AN AS ARGUMENTS, AND ; CAN BE AN IN PARANTHESE. ;************************************************************* expr1: ld hl,tab8-1 ; look up rel.op jp exec ; go do it xp11: call xp18 ; rel.op.'>=' ret c ; no, return hl=0 ld l,a ; yes, return hl=1 ret xp12: call xp18 ; rel.op.'#' ret z ; no, return hl=0 ld l,a ; yes, return hl=1 ret xp13: call xp18 ; rel.op.'>' ret z ; no ret c ; also, no ld l,a ; yes, return hl=1 ret xp14: call xp18 ; rel.op.'<=' ld l,a ; set hl=1 ret z ; yes, return hl=1 ret c ld l,h ; else set hl=0 ret xp15: call xp18 ; rel.op.'=' ret nz ; no, return hl=0 ld l,a ; else hl=1 ret xp16: call xp18 ; rel.op.'<' ret nc ; no, return hl=0 ld l,a ; else hl=1 ret xp17: pop hl ; not rel.op ret ; return hl= xp18: ld a,c ; routine for all rel.ops pop hl pop bc push hl push bc ; reverse top of stack ld c,a call expr2 ; get second ex de,hl ; value now in de ex (sp),hl ; first in hl call ckhlde ; compare them pop de ; restore text pointer ld hl,0000h ; set hl=0, a=1 ld a,1 ret expr2: call testc ; is it minus sign? .db '-' .db xp21-$-1 ld hl,0000h ; yes, fake 0 - jr xp26 ; treat like subtract xp21: call testc ; is it plus sign? .db '+' .db xp22-$-1 xp22: call expr3 ; first xp23: call testc ; addition? .db '+' .db xp25-$-1 push hl ; yes, save value call expr3 ; get second xp24: ex de,hl ; 2nd in de ex (sp),hl ; 1st in hl ld a,h ; compare sign xor d ld a,d add hl,de pop de ; restore text pointer jp m,xp23 ; first and second sign differ xor h ; first and second sign are equal jp p,xp23 ; so is the result jp qhow ; else we have overflow xp25: call testc ; subtract? .db '-' .db xp42-$-1 xp26: push hl ; yes, save first call expr3 ; get second call changesign ; negate jr xp24 ; and add them expr3: call expr4 ; get first expr4 xp31: call testc ; multiply? .db '*' .db xp34-$-1 push hl ; yes, save first and get second call expr4 ; ld b,0 ; clear b for sign call checksign ex (sp),hl ; first in hl call checksign ; check sign of first ex de,hl ex (sp),hl ld a,h ; is hl > 255? or a jr z,xp32 ; no ld a,d ; yes, what about de or d ex de,hl jp nz,ahow xp32: ld a,l ld hl,0000h or a jr z,xp35 xp33: add hl,de jp c,ahow dec a jr nz,xp33 jr xp35 xp34: call testc ; divide .db '/' .db xp42-$-1 push hl ; yes, save first call expr4 ; and get the second one ld b,0h ; clear b for sign call checksign ; check sign of the second ex (sp),hl ; get the first in hl call checksign ; check sign of first ex de,hl ex (sp),hl ex de,hl ld a,d ; divide by 0? or e jp z,ahow ; err...how? push bc ; else save sign call divide ld h,b ld l,c pop bc ; retrieve sign xp35: pop de ; and text pointer ld a,h ; hl must be positive or a jp m,qhow ; else it's overflow ld a,b or a call m,changesign ; change sign if needed jp xp31 ; look for more terms expr4: ld hl,tab4-1 ; find function in tab4 jp exec ; and execute it xp40: call testvar jr c,xp41 ; nor a variable ld a,(hl) inc hl ld h,(hl) ; value in hl ld l,a ret xp41: call testnum ; or is it a number ld a,b ; number of digits or a ret nz ; ok parn: call testc .db '(' .db xp43-$-1 call expr ; "(expr)" call testc .db ')' .db xp43-$-1 xp42: ret xp43: jp qwhat ; what? rnd: call parn ; ** Rnd(expr) ** ld a,h ; expression must be positive or a jp m,qhow or l ; and non-zero jp z,qhow push de ; save de and hl push hl ld hl,(rndptr) ; get memory as random number ld de,LST_ROM call comp jr c,ra1 ; wrap around if last ld hl,start ra1: ld e,(hl) inc hl ld d,(hl) ld (rndptr),hl pop hl ex de,hl push bc call divide ; rnd(n)=mod(m,n)+1 pop bc pop de inc hl ret abs: call parn ; ** Abs (expr) ** dec de call checksign inc de ret size: ld hl,(textunfilled) ; ** Size ** push de ; get the number of free bytes between ex de,hl ; and varbegin ld hl,varbegin call subde pop de ret clrvars: ld hl,(textunfilled) ; ** ClearVars** push de ; get the number of bytes available ex de,hl ; for variable storge ld hl,varend call subde ld b,h ; and save in bc ld c,l ld hl,(textunfilled) ; clear the first byte ld d,h ld e,l inc de ld (hl),0h ldir ; and repeat for all the others pop de ret ;************************************************************* ; ; *** DIVIDE *** SUBDE *** CHKSGN *** CHGSGN *** & CKHLDE *** ; ; 'DIVIDE' DIVIDES HL BY DE, RESULT IN BC, REMAINDER IN HL ; ; 'SUBDE' SUBSTRACTS DE FROM HL ; ; 'CHKSGN' CHECKS SIGN OF HL. IF +, NO CHANGE. IF -, CHANGE ; SIGN AND FLIP SIGN OF B. ; ; 'CHGSGN' CHECKS SIGN N OF HL AND B UNCONDITIONALLY. ; ; 'CKHLDE' CHECKS SIGN OF HL AND DE. IF DIFFERENT, HL AND DE ; ARE INTERCHANGED. IF SAME SIGN, NOT INTERCHANGED. EITHER ; CASE, HL DE ARE THEN COMPARED TO SET THE FLAGS. ;************************************************************* divide: push hl ; ** Divide ** ld l,h ; divide h by de ld h,0h call dv1 ld b,c ; save result in b ld a,l ; (remainder + l) / de pop hl ld h,a dv1: ld c,0ffh ; result in c dv2: inc c ; dumb routine call subde ; divide using subtract and count jr nc,dv2 add hl,de ret subde: ld a,l ; ** subde ** sub e ; subtract de from hl ld l,a ld a,h sbc a,d ld h,a ret checksign: ld a,h ; ** CheckSign ** or a ; check sign of hl ret p changesign: ld a,h ; ** ChangeSign ** or l ; check if hl is zero jp nz,cs1 ; no, try to change sign ret ; yes, return cs1: ld a,h ; change sign of hl push af cpl ld h,a ld a,l cpl ld l,a inc hl pop af xor h jp p,qhow ld a,b ; and also flip b xor 80h ld b,a ret ckhlde: ld a,h ; same sign? xor d ; yes, compare jp p,ck1 ; no, exchange and compare ex de,hl ck1: call comp ret ;************************************************************* ; ; *** SETVAL *** FIN *** ENDCHK *** & ERROR (& FRIENDS) *** ; ; "SETVAL" EXPECTS A VARIABLE, FOLLOWED BY AN EQUAL SIGN AND ; THEN AN EXPR. IT EVALUATES THE EXPR. AND SET THE VARIABLE ; TO THAT VALUE. ; ; "FIN" CHECKS THE END OF A COMMAND. IF IT ENDED WITH ":", ; EXECUTION CONTINUES. IF IT ENDED WITH A CR, IT FINDS THE ; NEXT LINE AND CONTINUE FROM THERE. ; ; "ENDCHK" CHECKS IF A COMMAND IS ENDED WITH CR. THIS IS ; REQUIRED IN CERTAIN COMMANDS. (GOTO, RETURN, AND STOP ETC.) ; ; "ERROR" PRINTS THE STRING POINTED BY DE (AND ENDS WITH CR). ; IT THEN PRINTS THE LINE POINTED BY 'CURRNT' WITH A "?" ; INSERTED AT WHERE THE OLD TEXT POINTER (SHOULD BE ON TOP ; OF THE STACK) POINTS TO. EXECUTION OF TB IS STOPPED ; AND TBI IS RESTARTED. HOWEVER, IF 'CURRNT' -> ZERO ; (INDICATING A DIRECT COMMAND), THE DIRECT COMMAND IS NOT ; PRINTED. AND IF 'CURRNT' -> NEGATIVE # (INDICATING 'INPUT' ; COMMAND), THE INPUT LINE IS NOT PRINTED AND EXECUTION IS ; NOT TERMINATED BUT CONTINUED AT 'INPERR'. ; ; RELATED TO 'ERROR' ARE THE FOLLOWING: ; 'QWHAT' SAVES TEXT POINTER IN STACK AND GET MESSAGE "WHAT?" ; 'AWHAT' JUST GET MESSAGE "WHAT?" AND JUMP TO 'ERROR'. ; 'QSORRY' AND 'ASORRY' DO SAME KIND OF THING. ; 'AHOW' AND 'AHOW' IN THE ZERO PAGE SECTION ALSO DO THIS. ;************************************************************* setval: call testvar ; ** SetVal ** jp c,qwhat ; no variable push hl ; save address of var call testc ; do we have =? .db '=' .db sv1-$-1 call expr ; evaluate expression ld b,h ; value is in bc now ld c,l pop hl ; get address ld (hl),c ; save value inc hl ld (hl),b ret sv1: jp qwhat fin: call testc ; test for ':' .db ':' .db fi1 - $ - 1 pop af ; yes, purge return address jp runsml ; continue on same line fi1: call testc ; not ':', is it cr .db cr .db fi2 - $ - 1 pop af ; yes, purge return address jp runnxl ; run next line fi2: ret ; else return to caller endchk: call skipspace ; ** EndChk ** cp cr ; ends with cr? ret z ; ok, otherwise say 'what?' qwhat: push de ; ** QWhat ** awhat: ld de,what ; ** AWhat ** handleerror: sub a ; ** Error ** call printstr ; print error message pop de ld a,(de) ; save the character push af ; at where old de points sub a ; and put a 0 (zero) there ld (de),a ld hl,(current) ; get the current line number push hl ld a,(hl) ; check the value inc hl or (hl) pop de jp z,rstart ; if zero, just rerstart ld a,(hl) ; if negative or a jp m,inputerror ; then redo input call printline ; else print the line dec de ; up to where the 0 is pop af ; restore the character ld (de),a ld a,'?' ; print a ? call outc sub a ; and the rest of the line call printstr jp rstart qsorry: push de ; ** Sorry ** asorry: ld de,sorry jr handleerror ;************************************************************* ; ; *** GETLN *** FNDLN (& FRIENDS) *** ; ; 'GETLN' READS A INPUT LINE INTO 'BUFFER'. IT FIRST PROMPT ; THE CHARACTER IN A (GIVEN BY THE CALLER), THEN IT FILLS ; THE BUFFER AND ECHOS. IT IGNORES LF'S AND NULLS, BUT STILL ; ECHOS THEM BACK. RUB-OUT IS USED TO CAUSE IT TO DELETE ; THE LAST CHARACTER (IF THERE IS ONE), AND ALT-MOD IS USED TO ; CAUSE IT TO DELETE THE WHOLE LINE AND START IT ALL OVER. ; CR SIGNALS THE END OF A LINE, AND CAUSE 'GETLN' TO RETURN. ; ; 'FNDLN' FINDS A LINE WITH A GIVEN LINE # (IN HL) IN THE ; TEXT SAVE AREA. DE IS USED AS THE TEXT POINTER. IF THE ; LINE IS FOUND, DE WILL POINT TO THE BEGINNING OF THAT LINE ; (I.E., THE LOW BYTE OF THE LINE #), AND FLAGS ARE NC & Z. ; IF THAT LINE IS NOT THERE AND A LINE WITH A HIGHER LINE # ; IS FOUND, DE POINTS TO THERE AND FLAGS ARE NC & NZ. IF ; WE REACHED THE END OF TEXT SAVE AREA AND CANNOT FIND THE ; LINE, FLAGS ARE C & NZ. ; 'FNDLN' WILL INITIALIZE DE TO THE BEGINNING OF THE TEXT SAVE ; AREA TO START THE SEARCH. SOME OTHER ENTRIES OF THIS ; ROUTINE WILL NOT INITIALIZE DE AND DO THE SEARCH. ; 'FNDLNP' WILL START WITH DE AND SEARCH FOR THE LINE #. ; 'FNDNXT' WILL BUMP DE BY 2, FIND A CR AND THEN START SEARCH. ; 'FNDSKP' USE DE TO FIND A CR, AND THEN START SEARCH. ;************************************************************* getline: call outc ; ** GetLine ** ld de,buffer ; prompt and initalise pointer gl1: call chkio ; check keyboard jr z,gl1 ; no input, so wait cp bs ; erase last character? jr z,gl3 ; yes call outc ; echo character cp lf ; ignore lf jr z,gl1 or a ; ignore null jr z,gl1 cp ctrlu ; erase the whole line? jr z,gl4 ; yes ld (de),a ; save the input inc de ; and increment pointer cp cr ; was it cr? ret z ; yes, end of line ld a,e ; any free space left? cp bufend & 0ffh jr nz,gl1 ; yes, get next char gl3: ld a,e ; delete last character cp buffer & 0ffh ; if there are any? jr z,gl4 ; no, redo whole line dec de ; yes, back pointer ld a,08h ; and echo a backspace call outc jr gl1 ; and get next character gl4: call crlf ; redo entire line ld a,'>' jr getline findline: ld a,h ; ** FindLine ** or a ; check the sign of hl jp m,qhow ; it cannot be negative ld de,textbegin ; initialise the text pointer findlineptr: fl1: push hl ; save line number ld hl,(textunfilled) ; check if we passed end dec hl call comp pop hl ; retrieve line number ret c ; c,nz passed end ld a,(de) ; we didn't; get first byte sub l ; is this the line? ld b,a ; compare low order inc de ld a,(de) ; get second byte sbc a,h ; compare high order jr c,fl2 ; no, not there yet dec de ; else we either found it or b ; or it's not there ret ; nc,z:found; nc,nz:no findnext: inc de ; find next line fl2: inc de ; just passed first and second byte findskip: ld a,(de) ; ** FindSkip ** cp cr ; try to find cr jr nz,fl2 ; keep looking inc de ; found cr, skip over jr fl1 ; check if end of text ;************************************************************* ; ; *** PRTSTG *** QTSTG *** PRTNUM *** & PRTLN *** ; ; 'PRTSTG' PRINTS A STRING POINTED BY DE. IT STOPS PRINTING ; AND RETURNS TO CALLER WHEN EITHER A CR IS PRINTED OR WHEN ; THE NEXT BYTE IS THE SAME AS WHAT WAS IN A (GIVEN BY THE ; CALLER). OLD A IS STORED IN B, OLD B IS LOST. ; ; 'QTSTG' LOOKS FOR A BACK-ARROW, SINGLE QUOTE, OR DOUBLE ; QUOTE. IF NONE OF THESE, RETURN TO CALLER. IF BACK-ARROW, ; OUTPUT A CR WITHOUT A LF. IF SINGLE OR DOUBLE QUOTE, PRINT ; THE STRING IN THE QUOTE AND DEMANDS A MATCHING UNQUOTE. ; AFTER THE PRINTING THE NEXT 3 BYTES OF THE CALLER IS SKIPPED ; OVER (USUALLY A JUMP INSTRUCTION. ; ; 'PRTNUM' PRINTS THE NUMBER IN HL. LEADING BLANKS ARE ADDED ; IF NEEDED TO PAD THE NUMBER OF SPACES TO THE NUMBER IN C. ; HOWEVER, IF THE NUMBER OF DIGITS IS LARGER THAN THE # IN ; C, ALL DIGITS ARE PRINTED ANYWAY. NEGATIVE SIGN IS ALSO ; PRINTED AND COUNTED IN, POSITIVE SIGN IS NOT. ; ; 'PRTLN' PRINTS A SAVED TEXT LINE WITH LINE # AND ALL. ;************************************************************* printstr: ld b,a ps1: ld a,(de) ; get a character inc de ; bump pointer cp b ; same as old A? ret z ; yes, return call outc ; no, show character cp cr ; was it a cr? jr nz,ps1 ; no, next character ret ; yes, returns qtstg: call testc ; ** Qtstg ** .db 22h ; is it a double quote .db qt3-$-1 ld a,22h qt1: call printstr ; print until another cp cr pop hl jp z,runnxl qt2: inc hl ; skip 3 bytes on return inc hl inc hl jp (hl) ; return qt3: call testc ; is it a single quote .db 27h .db qt4-$-1 ld a,27h jr qt1 qt4: call testc ; is it back-arrow .db '_' .db qt5-$-1 ld a,8dh ; yes, cr without lf call outc call outc pop hl ; return address jr qt2 qt5: ret ; none of the above printnum: ld b,0h ; ** PrintNum ** call checksign ; check sign jp p,pn1 ; no sign ld b,'-' dec c pn1: push de ; save ld de, 000ah ; decimal push de ; save as flag dec c ; c=spaces push bc ; save sign & space pn2: call divide ; divide hl by 10 ld a,b ; result 0? or c jr z,pn3 ; yes, we got all ex (sp),hl ; no, save remainder dec l ; and count space push hl ; hl is old bc ld h,b ; moved result to bc ld l,c jr pn2 ; and divide by 10 pn3: pop bc ; we got all digits pn4: dec c ld a,c ; look at space count or a jp m,pn5 ; no leading spaces ld a,' ' ; print a leading space call outc jr pn4 ; any more? pn5: ld a,b ; print sign or a call nz,outc ld e,l ; last remainder in e pn6: ld a,e ; check digit in e cp lf ; lf is flag for no more pop de ret z ; if yes, return add a,30h ; else convert to ascii call outc ; and print the digit jr pn6 ; next digit printhex: ld c,h ; ** PrintHex ** call ph1 ; first hex byte printhex8: ld c,l ; then second ph1: ld a,c ; get left nibble into position rra rra rra rra call ph2 ; and turn into hex digit ld a,c ; then convert right nibble ph2: and 0fh ; mask right nibble add a,90h ; and convert to ascii character daa adc a,40h daa call outc ; print character ret printline: ld a,(de) ; ** PrintLine ** ld l,a ; low order line number inc de ld a,(de) ; high order ld h,a inc de ld c,04h ; print 4 digit line number call printnum ld a,' ' ; followed by a space call outc sub a ; and the the rest call printstr ret ;************************************************************* ; ; *** MVUP *** MVDOWN *** POPA *** & PUSHA *** ; ; 'MVUP' MOVES A BLOCK UP FROM WHERE DE-> TO WHERE BC-> UNTIL ; DE = HL ; ; 'MVDOWN' MOVES A BLOCK DOWN FROM WHERE DE-> TO WHERE HL-> ; UNTIL DE = BC ; ; 'POPA' RESTORES THE 'FOR' LOOP VARIABLE SAVE AREA FROM THE ; STACK ; ; 'PUSHA' STACKS THE 'FOR' LOOP VARIABLE SAVE AREA INTO THE ; STACK ;************************************************************* mvup: call comp ; ** mvup ** ret z ; de = hl, return ld a,(de) ; get one byte ld (bc),a ; then copy it inc de ; increase both pointers inc bc jr mvup ; until done mvdown: ld a,b ; ** mvdown ** sub d ; check if de = bc jp nz,md1 ; no, go move ld a,c ; maybe, other byte sub e ret z ; yes, return md1: dec de ; else move a byte dec hl ; but first decrease both pointers ld a,(de) ; and then do it ld (hl),a jr mvdown ; loop back popa: pop bc ; bc = return address pop hl ; restore loopvar ld (loopvar),hl ld a,h or l jr z,pp1 ; all done, so return pop hl ld (loopinc),hl pop hl ld (looplmt),hl pop hl ld (loopln),hl pop hl ld (loopptr),hl pp1: push bc ; bc = return address ret pusha: ld hl,stacklimit ; ** PushA ** call changesign pop bc ; bc = return address add hl,sp ; is stack near the top? jp nc,qsorry ; yes, sorry ld hl,(loopvar) ; else save loop variables ld a,h or l jr z,pu1 ; only when loopvar not 0 ld hl,(loopptr) push hl ld hl,(loopln) push hl ld hl,(looplmt) push hl ld hl,(loopinc) push hl ld hl,(loopvar) pu1: push hl push bc ; bc = return address ret testvar: call skipspace ; ** testvar ** sub '@' ; test variables ret c ; not a variable jr nz,tv1 ; not @ array inc de ; is is the @ array call parn ; @ should be followed by (expr) add hl,hl ; as its index jr c,qhow ; is index too big? push de ; will it override text? ex de,hl call size ; find the size of free call comp jp c,asorry ; yes, sorry ld hl,varbegin ; no, get address of @(expr) and call subde ; put it in hl pop de ret tv1: cp 1bh ; not @, is it A to Z ccf ret c inc de ; if A trhough Z ld hl,varbegin ; calculate address of that variable rlca ; and return it in hl add a,l ; with the c flag cleared ld l,a ld a,0 adc a,h ld h,a ret testnum: ld hl,0000h ; ** TestNum ** ld b,h ; test if the text is a number call skipspace tn1: cp '0' ; if not,return 0 in b and hl ret c cp ':' ; if a digit, convert to binary in ret nc ; b and hl ld a,0f0h ; set b to number of digits and h ; if h>255, there is no room for jr nz,qhow ; next digit inc b ; b counts number of digits push bc ld b,h ; hl=10*hl+(new digit) ld c,l add hl,hl ; where 10* is done by shift and add add hl,hl add hl,bc add hl,hl ld a,(de) ; and (digit) is by stripping the inc de ; ascii code and 0fh add a,l ld l,a ld a,0 adc a,h ld h,a pop bc ld a,(de) jp p,tn1 qhow: push de ; ** Error How? ** ahow: ld de,how jp handleerror msg1 .db "TASTY BASIC",cr msg2 .db " BYTES FREE",cr how .db "HOW?",cr ok .db "OK",cr what .db "WHAT?",cr sorry .db "SORRY",cr ;************************************************************* ; ; *** MAIN *** ; ; THIS IS THE MAIN LOOP THAT COLLECTS THE TINY BASIC PROGRAM ; AND STORES IT IN THE MEMORY. ; ; AT START, IT PRINTS OUT "(CR)OK(CR)", AND INITIALIZES THE ; STACK AND SOME OTHER INTERNAL VARIABLES. THEN IT PROMPTS ; ">" AND READS A LINE. IF THE LINE STARTS WITH A NON-ZERO ; NUMBER, THIS NUMBER IS THE LINE NUMBER. THE LINE NUMBER ; (IN 16 BIT BINARY) AND THE REST OF THE LINE (INCLUDING CR) ; IS STORED IN THE MEMORY. IF A LINE WITH THE SAME LINE ; NUMBER IS ALREADY THERE, IT IS REPLACED BY THE NEW ONE. IF ; THE REST OF THE LINE CONSISTS OF A CR ONLY, IT IS NOT STORED ; AND ANY EXISTING LINE WITH THE SAME LINE NUMBER IS DELETED. ; ; AFTER A LINE IS INSERTED, REPLACED, OR DELETED, THE PROGRAM ; LOOPS BACK AND ASKS FOR ANOTHER LINE. THIS LOOP WILL BE ; TERMINATED WHEN IT READS A LINE WITH ZERO OR NO LINE ; NUMBER; AND CONTROL IS TRANSFERED TO "DIRECT". ; ; TINY BASIC PROGRAM SAVE AREA STARTS AT THE MEMORY LOCATION ; LABELED "TXTBGN" AND ENDS AT "TXTEND". WE ALWAYS FILL THIS ; AREA STARTING AT "TXTBGN", THE UNFILLED PORTION IS POINTED ; BY THE CONTENT OF A MEMORY LOCATION LABELED "TXTUNF". ; ; THE MEMORY LOCATION "CURRNT" POINTS TO THE LINE NUMBER ; THAT IS CURRENTLY BEING INTERPRETED. WHILE WE ARE IN ; THIS LOOP OR WHILE WE ARE INTERPRETING A DIRECT COMMAND ; (SEE NEXT SECTION). "CURRNT" SHOULD POINT TO A 0. ;************************************************************* rstart: ld sp,stack st1: call crlf sub a ; a=0 ld de,ok ; print ok call printstr ld hl,st2 + 1 ; literal zero ld (current),hl ; reset current line pointer st2: ld hl,0000h ld (loopvar),hl ld (stkgos),hl st3: ld a,'>' ; initialise prompt call getline push de ; de points to end of line ld de,buffer ; point de to beginning of line call testnum ; check if it is a number call skipspace ld a,h ; hl = value of the number, or or l ; 0 if no number was found pop bc ; bc points to end of line jp z,direct dec de ; back up de and save the value of ld a,h ; the value of the line number there ld (de),a dec de ld a,l ld (de),a push bc ; bc,de point to begin,end push de ld a,c sub e push af ; a = number of bytes in line call findline ; find this line in save area push de ; de points to save area jr nz,st4 ; nz: line not found push de ; z: found, delete it call findnext ; find next line ; de -> next line pop bc ; bc -> line to be deleted ld hl,(textunfilled) ; hl -> unfilled text area call mvup ; move up to delete ld h,b ; txtunf -> unfilled area ld l,c ld (textunfilled),hl st4: pop bc ; get ready to insert ld hl,(textunfilled) ; but first check if the length pop af ; of new line is 3 (line# and cr) push hl cp 3h ; if so, do not insert jr z,rstart ; must clear the stack add a,l ; calculate new txtunf ld l,a ld a,0 adc a,h ld h,a ; hl -> new unfilled area ld de,textend ; check to see if there is space call comp jp nc,qsorry ; no, sorry ld (textunfilled),hl ; ok, update textunfilled pop de ; de -> old unfilled area call mvdown pop de ; de,hl -> begin,end pop hl call mvup ; copy new line to save area jr st3 ;************************************************************* ; ; WHAT FOLLOWS IS THE CODE TO EXECUTE DIRECT AND STATEMENT ; COMMANDS. CONTROL IS TRANSFERED TO THESE POINTS VIA THE ; COMMAND TABLE LOOKUP CODE OF 'DIRECT' AND 'EXEC' IN LAST ; SECTION. AFTER THE COMMAND IS EXECUTED, CONTROL IS ; TRANSFERED TO OTHERS SECTIONS AS FOLLOWS: ; ; FOR 'LIST', 'NEW', AND 'STOP': GO BACK TO 'RSTART' ; FOR 'RUN': GO EXECUTE THE FIRST STORED LINE IF ANY, ELSE ; GO BACK TO 'RSTART'. ; FOR 'GOTO' AND 'GOSUB': GO EXECUTE THE TARGET LINE. ; FOR 'RETURN' AND 'NEXT': GO BACK TO SAVED RETURN LINE. ; FOR ALL OTHERS: IF 'CURRENT' -> 0, GO TO 'RSTART', ELSE ; GO EXECUTE NEXT COMMAND. (THIS IS DONE IN 'FINISH'.) ;************************************************************* ; ; *** NEW *** CLEAR *** STOP *** RUN (& FRIENDS) *** GOTO *** ; ; 'NEW(CR)' SETS 'TXTUNF' TO POINT TO 'TXTBGN' ; ; 'CLEAR(CR)' CLEARS ALL VARIABLES ; ; 'END(CR)' GOES BACK TO 'RSTART' ; ; 'RUN(CR)' FINDS THE FIRST STORED LINE, STORE ITS ADDRESS (IN ; 'CURRENT'), AND START EXECUTE IT. NOTE THAT ONLY THOSE ; COMMANDS IN TAB2 ARE LEGAL FOR STORED PROGRAM. ; ; THERE ARE 3 MORE ENTRIES IN 'RUN': ; 'RUNNXL' FINDS NEXT LINE, STORES ITS ADDR. AND EXECUTES IT. ; 'RUNTSL' STORES THE ADDRESS OF THIS LINE AND EXECUTES IT. ; 'RUNSML' CONTINUES THE EXECUTION ON SAME LINE. ; ; 'GOTO EXPR(CR)' EVALUATES THE EXPRESSION, FIND THE TARGET ; LINE, AND JUMP TO 'RUNTSL' TO DO IT. ;************************************************************* #ifndef ZEMU bye: call endchk ; ** Reboot ** LD A,BID_BOOT ; BOOT BANK LD HL,0 ; ADDRESS ZERO CALL HB_BNKCALL ; DOES NOT RETURN HALT #endif new: call endchk ; ** New ** ld hl,textbegin ld (textunfilled),hl clear: call clrvars ; ** Clear ** jp rstart endd: call endchk ; ** End ** jp rstart run: call endchk ; ** Run ** ld de,textbegin runnxl: ld hl,0h ; ** Run Next Line ** call findlineptr jp c,rstart runtsl: ex de,hl ; ** Run Tsl ld (current),hl ; set current -> line # ex de,hl inc de inc de runsml: call chkio ; ** Run Same Line ** ld hl, tab2-1 ; find the command in table 2 jp exec ; and execute it goto: call expr push de ; save for error routine call endchk ; must find a cr call findline ; find the target line jp nz, ahow ; no such line # pop af ; clear the pushed de jr runtsl ; go do it ;************************************************************* ; ; *** LIST *** & PRINT *** ; ; LIST HAS TWO FORMS: ; 'LIST(CR)' LISTS ALL SAVED LINES ; 'LIST #(CR)' START LIST AT THIS LINE # ; YOU CAN STOP THE LISTING BY CONTROL C KEY ; ; PRINT COMMAND IS 'PRINT ....;' OR 'PRINT ....(CR)' ; WHERE '....' IS A LIST OF EXPRESIONS, FORMATS, BACK- ; ARROWS, AND STRINGS. THESE ITEMS ARE SEPERATED BY COMMAS. ; ; A FORMAT IS A POUND SIGN FOLLOWED BY A NUMBER. IT CONTROLS ; THE NUMBER OF SPACES THE VALUE OF A EXPRESION IS GOING TO ; BE PRINTED. IT STAYS EFFECTIVE FOR THE REST OF THE PRINT ; COMMAND UNLESS CHANGED BY ANOTHER FORMAT. IF NO FORMAT IS ; SPECIFIED, 6 POSITIONS WILL BE USED. ; ; A STRING IS QUOTED IN A PAIR OF SINGLE QUOTES OR A PAIR OF ; DOUBLE QUOTES. ; ; A BACK-ARROW MEANS GENERATE A (CR) WITHOUT (LF) ; ; A (CRLF) IS GENERATED AFTER THE ENTIRE LIST HAS BEEN ; PRINTED OR IF THE LIST IS A NULL LIST. HOWEVER IF THE LIST ; ENDED WITH A COMMA, NO (CRLF) IS GENERATED. ;************************************************************* list: call testnum ; check if there is a number call endchk ; if no number we get a 0 call findline ; find this or next line ls1: jp c,rstart call printline ; print the line call chkio ; stop on ctrl-c call findlineptr ; find the next line jr ls1 ; and loop back print: ld c,6 ; c = number of spaces call testc ; is it a semicolon? .db ':' .db pr2-$-1 call crlf jr runsml pr2: call testc ; is it a cr? .db cr .db pr0-$-1 call crlf jr runnxl pr0: call testc ; is it format? .db '#' .db pr1-$-1 call expr ld c,l jr pr3 pr1: call testc ; is it a dollar? .db '$' .db pr4-$-1 call expr ld c,l call testc ; do we have a comma? .db ',' .db pr6-$-1 push bc call expr pop bc ld a,8 ; 8 bits? cp c jp nz,pr9 ; no, try 16 call printhex8 ; yes, print a single hex byte jp pr3 pr9: ld a,10h ; 16 bits? cp c jp nz,qhow ; no, show error message call printhex ; yes, print two hex bytes jp pr3 pr4: call qtstg ; is it a string? jr pr8 pr3: call testc ; is it a comma? .db ',' .db pr6-$-1 call fin jr pr0 pr6: call crlf ; list ends call finish pr8: call expr ; evaluate the expression push bc call printnum pop bc jr pr3 ;************************************************************* ; ; *** GOSUB *** & RETURN *** ; ; 'GOSUB EXPR;' OR 'GOSUB EXPR (CR)' IS LIKE THE 'GOTO' ; COMMAND, EXCEPT THAT THE CURRENT TEXT POINTER, STACK POINTER ; ETC. ARE SAVE SO THAT EXECUTION CAN BE CONTINUED AFTER THE ; SUBROUTINE 'RETURN'. IN ORDER THAT 'GOSUB' CAN BE NESTED ; (AND EVEN RECURSIVE), THE SAVE AREA MUST BE STACKED. ; THE STACK POINTER IS SAVED IN 'STKGOS', THE OLD 'STKGOS' IS ; SAVED IN THE STACK. IF WE ARE IN THE MAIN ROUTINE, 'STKGOS' ; IS ZERO (THIS WAS DONE BY THE "MAIN" SECTION OF THE CODE), ; BUT WE STILL SAVE IT AS A FLAG FOR NO FURTHER 'RETURN'S. ; ; 'RETURN(CR)' UNDOS EVERYTHING THAT 'GOSUB' DID, AND THUS ; RETURN THE EXECUTION TO THE COMMAND AFTER THE MOST RECENT ; 'GOSUB'. IF 'STKGOS' IS ZERO, IT INDICATES THAT WE ; NEVER HAD A 'GOSUB' AND IS THUS AN ERROR. ;************************************************************* gosub: call pusha ; ** Gosub ** call expr ; save the current "FOR" params push de ; and text pointer call findline ; find the target line jp nz,ahow ; how? because it doesn't exist ld hl,(current) ; found it, save old 'current' push hl ld hl,(stkgos) ; and 'stkgos' push hl ld hl,0000h ; and load new ones ld (loopvar),hl add hl,sp ld (stkgos),hl jp runtsl ; and run the line return: call endchk ; there must be a cr ld hl,(stkgos) ; check old stack pointer ld a,h ; or l jp z,what ; what? not found ld sp,hl ; otherwise restore it pop hl ld (stkgos),hl pop hl ld (current),hl ; and old 'current' pop de ; and old text pointer call popa ; and old 'FOR' params call finish ; and we're back ;************************************************************* ; ; *** FOR *** & NEXT *** ; ; 'FOR' HAS TWO FORMS: ; 'FOR VAR=EXP1 TO EXP2 STEP EXP3' AND 'FOR VAR=EXP1 TO EXP2' ; THE SECOND FORM MEANS THE SAME THING AS THE FIRST FORM WITH ; EXP3=1. (I.E., WITH A STEP OF +1.) ; TBI WILL FIND THE VARIABLE VAR, AND SET ITS VALUE TO THE ; CURRENT VALUE OF EXP1. IT ALSO EVALUATES EXP2 AND EXP3 ; AND SAVE ALL THESE TOGETHER WITH THE TEXT POINTER ETC. IN ; THE 'FOR' SAVE AREA, WHICH CONSISTS OF 'LOPVAR', 'LOPINC', ; 'LOPLMT', 'LOPLN', AND 'LOPPT'. IF THERE IS ALREADY SOME- ; THING IN THE SAVE AREA (THIS IS INDICATED BY A NON-ZERO ; 'LOPVAR'), THEN THE OLD SAVE AREA IS SAVED IN THE STACK ; BEFORE THE NEW ONE OVERWRITES IT. ; TBI WILL THEN DIG IN THE STACK AND FIND OUT IF THIS SAME ; VARIABLE WAS USED IN ANOTHER CURRENTLY ACTIVE 'FOR' LOOP. ; IF THAT IS THE CASE, THEN THE OLD 'FOR' LOOP IS DEACTIVATED. ; (PURGED FROM THE STACK..) ; ; 'NEXT VAR' SERVES AS THE LOGICAL (NOT NECESSARILLY PHYSICAL) ; END OF THE 'FOR' LOOP. THE CONTROL VARIABLE VAR. IS CHECKED ; WITH THE 'LOPVAR'. IF THEY ARE NOT THE SAME, TBI DIGS IN ; THE STACK TO FIND THE RIGHT ONE AND PURGES ALL THOSE THAT ; DID NOT MATCH. EITHER WAY, TBI THEN ADDS THE 'STEP' TO ; THAT VARIABLE AND CHECK THE RESULT WITH THE LIMIT. IF IT ; IS WITHIN THE LIMIT, CONTROL LOOPS BACK TO THE COMMAND ; FOLLOWING THE 'FOR'. IF OUTSIDE THE LIMIT, THE SAVE AREA ; IS PURGED AND EXECUTION CONTINUES. ;************************************************************* for: call pusha ; save old save area call setval ; set the control variable dec hl ; its address is hl ld (loopvar),hl ; save that ld hl,tab5-1 ; use 'exec' to find 'TO' jp exec fr1: call expr ; evaluate the limit ld (looplmt),hl ; and save it ld hl,tab6-1 ; use 'exec' to find 'STEP' jp exec fr2: call expr ; found 'STEP' jr fr4 fr3: ld hl,0001h ; no 'STEP' so set to 1 fr4: ld (loopinc),hl ; and save that too fr5: ld hl,(current) ; save current line number ld (loopln),hl ex de,hl ; and text pointer ld (loopptr),hl ld bc,0ah ; dig into stack to find loopvar ld hl,(loopvar) ex de,hl ld h,b ld l,b add hl,sp .db 3eh fr7: add hl,bc ld a,(hl) inc hl or (hl) jr z,fr8 ld a,(hl) dec hl cp d jr nz,fr7 ld a,(hl) cp e jr nz,fr7 ex de,hl ld hl,0000h add hl,sp ld b,h ld c,l ld hl,000ah add hl,de call mvdown ld sp,hl fr8: ld hl,(loopptr) ; all done ex de,hl call finish next: call testvar ; get address of variable jp c,qwhat ; what, no variable ld (varnext),hl ; yes, save it nx0: push de ; save the text pointer ex de,hl ld hl,(loopvar) ; get the variable in 'FOR' ld a,h or l ; if 0, there never was one jp z,awhat call comp ; else check them jr z,nx3 ; yes, they agree pop de ; no, complete current loop call popa ld hl,(varnext) ; and pop one level jr nx0 ; go check again nx3: ld e,(hl) inc hl ld d,(hl) ; de = value of variable ld hl,(loopinc) push hl ld a,h xor d ld a,d add hl,de jp m,nx4 xor h jp m,nx5 nx4: ex de,hl ld hl,(loopvar) ld (hl),e inc hl ld (hl),d ld hl,(looplmt) pop af or a jp p,nx1 ; step > 0 ex de,hl ; step < 0 nx1: call ckhlde ; compare with limit pop de ; restore the text pointer jr c,nx2 ; over the limit ld hl,(loopln) ; within the limit ld (current),hl ld hl,(loopptr) ex de,hl call finish nx5: pop hl pop de nx2: call popa ; purge this loop call finish ; init: ld hl,start ; initialise random pointer ld (rndptr),hl ld hl,usrfunc ; initialise user defined function ld (usrptr),hl ld a,0c3h ld (usrfunc),a ; initiase default USR() behaviour ld hl,qhow ; (i.e. HOW? error) ld (usrfunc+1),hl ld hl,textbegin ; initialise text area pointers ld (textunfilled),hl ld (ocsw),a ; enable output control switch call clrvars ; clear variables call crlf ld de,msg1 ; output welcome message call printstr call crlf call size ; output free size message call printnum ld de,msg2 call printstr jp rstart chkio: call haschar ; check if character available ret z ; no, return call getchar ; get the character push bc ; is it a lf? ld b,a sub lf jr z,io1 ; yes, ignore an return ld a,b ; no, restore a and bc pop bc cp ctrlo ; is it ctrl-o? jr nz,io2 ; no, done ld a,(ocsw) ; toggle output control switch cpl ld (ocsw),a jr chkio ; get next character io1: ld a,0h ; clear or a ; set the z-flag pop bc ; restore bc ret ; return with z set io2: cp 60h ; is it lower case? jp c,io3 ; no and 0dfh ; yes, make upper case io3: cp ctrlc ; is it ctrl-c? ret nz ; no jp rstart ; yes, restart tasty basic crlf: ld a,cr outc: push af ld a,(ocsw) ; check output control switch or a jr nz,oc1 ; output is enabled pop af ; output is disabled ret ; so return oc1: pop af call putchar cp cr ; was it a cr? ret nz ; no, return ld a,lf ; send a lf call outc ld a,cr ; restore register ret ; and return putchar: #ifdef ZEMU call uart_tx_ready ; see if transmit is available out (tty_data),a ; and send it ret uart_tx_ready: push af uart_tx_ready_loop: in a,(tty_status) bit tx_empty,a jp z,uart_tx_ready_loop pop af #else PUSH AF PUSH BC PUSH DE PUSH HL ; OUTPUT CHARACTER TO CONSOLE VIA HBIOS LD E,A ; OUTPUT CHAR TO E LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR RST 08 ; HBIOS OUTPUTS CHARACTER POP HL POP DE POP BC POP AF #endif ret haschar: #ifdef ZEMU in a,(tty_status) ; check if character available bit rx_full,a #else PUSH BC PUSH DE PUSH HL ; GET CONSOLE INPUT STATUS VIA HBIOS LD C,CIODEV_CONSOLE; CONSOLE UNIT TO C LD B,BF_CIOIST ; HBIOS FUNC: INPUT STATUS RST 08 ; HBIOS RETURNS STATUS IN A POP HL POP DE POP BC #endif ret getchar: #ifdef ZEMU in a,(tty_data) ; get the character #else PUSH BC PUSH DE PUSH HL ; INPUT CHARACTER FROM CONSOLE VIA HBIOS LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR RST 08 ; HBIOS READS CHARACTDR LD A,E ; MOVE CHARACTER TO A FOR RETURN ; RESTORE REGISTERS (AF IS OUTPUT) POP HL POP DE POP BC #endif ret ;************************************************************* ; ; *** TABLES *** DIRECT *** & EXEC *** ; ; THIS SECTION OF THE CODE TESTS A STRING AGAINST A TABLE. ; WHEN A MATCH IS FOUND, CONTROL IS TRANSFERED TO THE SECTION ; OF CODE ACCORDING TO THE TABLE. ; ; AT 'EXEC', DE SHOULD POINT TO THE STRING AND HL SHOULD POINT ; TO THE TABLE-1. AT 'DIRECT', DE SHOULD POINT TO THE STRING. ; HL WILL BE SET UP TO POINT TO TAB1-1, WHICH IS THE TABLE OF ; ALL DIRECT AND STATEMENT COMMANDS. ; ; A '.' IN THE STRING WILL TERMINATE THE TEST AND THE PARTIAL ; MATCH WILL BE CONSIDERED AS A MATCH. E.G., 'P.', 'PR.', ; 'PRI.', 'PRIN.', OR 'PRINT' WILL ALL MATCH 'PRINT'. ; ; THE TABLE CONSISTS OF ANY NUMBER OF ITEMS. EACH ITEM ; IS A STRING OF CHARACTERS WITH BIT 7 SET TO 0 AND ; A JUMP ADDRESS STORED HI-LOW WITH BIT 7 OF THE HIGH ; BYTE SET TO 1. ; ; END OF TABLE IS AN ITEM WITH A JUMP ADDRESS ONLY. IF THE ; STRING DOES NOT MATCH ANY OF THE OTHER ITEMS, IT WILL ; MATCH THIS NULL ITEM AS DEFAULT. ;************************************************************* tab1: ; direct commands .db "LIST" dwa(list) .db "RUN" dwa(run) .db "NEW" dwa(new) .db "CLEAR" dwa(clear) #ifndef ZEMU .db "BYE" dwa(bye) #endif tab2: ; direct/statement .db "NEXT" dwa(next) .db "LET" dwa(let) .db "IF" dwa(iff) .db "GOTO" dwa(goto) .db "GOSUB" dwa(gosub) .db "RETURN" dwa(return) .db "REM" dwa(rem) .db "FOR" dwa(for) .db "INPUT" dwa(input) .db "PRINT" dwa(print) .db "POKE" dwa(poke) .db "END" dwa(endd) dwa(deflt) tab4: ; functions .db "PEEK" dwa(peek) .db "RND" dwa(rnd) .db "ABS" dwa(abs) .db "USR" dwa(usrexec) .db "SIZE" dwa(size) dwa(xp40) tab5: ; 'TO' in 'FOR' .db "TO" dwa(fr1) tab6: ; 'STEP' in 'FOR' .db "STEP" dwa(fr2) dwa(fr3) tab8: ; relational operators .db ">=" dwa(xp11) .db "#" dwa(xp12) .db ">" dwa(xp13) .db "=" dwa(xp15) .db "<=" dwa(xp14) .db "<" dwa(xp16) dwa(xp17) direct: ld hl,tab1-1 ; ** Direct ** exec: call skipspace ; ** Exec ** push de ex1: ld a,(de) inc de cp '.' jr z,ex3 inc hl cp (hl) jr z,ex1 ld a,7fh dec de cp (hl) jr c,ex5 ex2: inc hl cp (hl) jr nc,ex2 inc hl pop de jr exec ex3: ld a,7fh ex4: inc hl cp (hl) jr nc,ex4 ex5: ld a,(hl) inc hl ld l,(hl) and 7fh ld h,a pop af jp (hl) ;------------------------------------------------------------------------------- LST_ROM: ; all the above _can_ be in rom ; all following *must* be in ram .org (TBC_LOC + 09feh) usrptr: .ds 2 ; -> user defined function area .org (TBC_LOC + 0a00h) usrfunc .ds 2 ; start of user defined function area .org (TBC_LOC + 0c00h) ; start of state ocsw .ds 1 ; output control switch current .ds 2 ; points to current line stkgos .ds 2 ; saves sp in 'GOSUB' varnext .ds 2 ; temp storage stkinp .ds 2 ; save sp in 'INPUT' loopvar .ds 2 ; 'FOR' loop save area loopinc .ds 2 ; loop increment looplmt .ds 2 ; loop limit loopln .ds 2 ; loop line number loopptr .ds 2 ; loop text pointer rndptr .ds 2 ; random number pointer textunfilled .ds 2 ; -> unfilled text area textbegin .ds 2 ; start of text save area .org (TBC_LOC + 07dffh) textend .ds 0 ; end of text area varbegin .ds 55 ; variable @(0) varend .ds 0 ; end of variable area buffer .ds 72 ; input buffer bufend .ds 1 stacklimit .ds 1 .org (TBC_LOC + 07fffh) stack .equ $ #ifndef ZEMU SLACK .EQU (TBC_END - LST_ROM) .FILL SLACK,'t' .ECHO "TASTYBASIC space remaining: " .ECHO SLACK .ECHO " bytes.\n" #endif .end