URL
https://opencores.org/ocsvn/cpu8080/cpu8080/trunk
Subversion Repositories cpu8080
[/] [cpu8080/] [trunk/] [project/] [tinybasic.asm] - Rev 33
Compare with Previous | Blame | View Log
!**************************************************************!*!* tiny basic for intel 8080!* version 1.0!* by li-chen wang!* 10 june, 1976!* @copyleft!* all wrongs reserved!*!**************************************************************!*!* !*** zero page subroutines ***!*!* the 8080 instruction set lets you have 8 routines in low!* memory that may be called by rst n, n being 0 through 7.!* this is a one byte instruction and has the same power as!* the three byte instruction call llhh. tiny basic will!* use rst 0 as start and rst 1 through rst 7 for!* the seven most frequently used subroutines.!* two other subroutines (crlf and tstnum) are also in this!* section. they can be reached only by 3-byte calls.!! Note: this version was extensively damaged to adapt to CP/M,! I am attempting to find other copies to reference to in order! to correct it.!!*jmp ninit ! go main startalignp 8*xthl !*** tstc or rst 1 ***rst 5 !ignore blanks andcmp m !test characterjmp tc1 !rest of this is at tc1*crlf: mvi a,0dh !*** crlf ****push psw !*** outc or rst 2 ***lda ocsw !print character onlyora a !iff ocsw switch is onjmp oc2 !rest of this is at oc2*call expr2 !*** expr or rst 3 ***push h !evaluate an expresionjmp expr1 !rest of it is at expr1defb 'w'*mov a,h !*** comp or rst 4 ***cmp d !compare hl with dernz !return correct c andmov a,l !z flagscmp e !but old a is lostretdefb 'an'*ss1: ldax d !*** ignblk/rst 5 ***cpi 40q !ignore blanksrnz !in text (where de->)inx d !and return the firstjmp ss1 !non-blank char. in a*pop psw !*** finish/rst 6 ***call fin !check end of commandjmp qwhat !print "what?" iff wrongdefb 'g'*rst 5 !*** tstv or rst 7 ***sui 100q !test variablesrc !c:not a variable*tstv1: jnz tv1 !not "@" arrayinx d !it is the "@" arraycall parn !@ should be followeddad h !by (expr) as its indexjc qhow !is index too big?push d !will it overwritexchg !text?call size !find size of freerst 4 !and check thatjc asorry !iff so, say "sorry"ss1a: lxi h,varbgn !iff not, get addresscall subde !of @(expr) and put itpop d !in hlret !c flag is clearedtv1: cpi 33q !not @, is it a to z?cmc !iff not return c flagrcinx d !iff a through ztv1a: lxi h,varbgn !compute address ofrlc !that variableadd l !and return it in hlmov l,a !with c flag clearedmvi a,0adc hmov h,aret!*!* tstc xch hl,(sp) !*** tstc or rst 1 ***!* ignblk this is at loc. 8!* cmp m and then jmp heretc1: inx h !compare the byte thatjz tc2 !follows the rst inst.push b !with the text (de->)mov c,m !iff not =, add the 2ndmvi b,0 !byte that follows thedad b !rst to the old pcpop b !i.e., do a relativedcx d !jump iff not =tc2: inx d !iff =, skip those bytesinx h !and continuexthlret!*tstnum:lxi h,0 !*** tstnum ***mov b,h !test iff the text isrst 5 !a numbertn1: cpi 60q !iff not, return 0 inrc !b and hlcpi 72q !iff numbers, convertrnc !to binary in hl andmvi a,360q !set a to # of digitsana h !iff h>255, there is nojnz qhow !room for next digitinr b !b counts # of digitspush bmov b,h !hl=10!*hl+(new digit)mov c,ldad h !where 10!* is done bydad h !shift and adddad bdad hldax d !and (digit) is frominx d !stripping the asciiani 17q !codeadd lmov l,amvi a,0adc hmov h,apop bldax d !do this digit afterjp tn1 !digit. s says overflowqhow: push d !*** error: "how?" ***ahow: lxi d,howjmp errorhow: defb 'how?',0dhok: defb 'ok',0dhwhat: defb 'what?',0dhsorry: defb 'sorry',0dh!*!**************************************************************!*!* *** 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. iff 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. iff a line with the same line!* number is alredy there, it is replaced by the new one. if!* the rest of the line consists of a 0dhonly, it is not stored!* and any existing line with the same line number is deleted.!*!* after a line iss inserted, replaced, or deleted, the program!* loops back and ask for another line. this loop will be!* terminated when it reads a line with zero or no line!* number! and control is transfered to "dirct".!*!* tiny basic program save area starts at the memory location!* labeled "txtbgn" and ended 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:lxi sp,stack !set stack pointerst1: call crlf !and jump to herelxi d,ok !de->stringsub a !a=0call prtstg !print string until 0dhlxi h,st2+1 !literal 0shld currnt !currnt->line # = 0st2: lxi h,0shld lopvarshld stkgosst3: mvi a,76q !prompt '>' andcall getln !read a linepush d !de->end of linest3a: lxi d,buffer !de->beginning of linecall tstnum !test iff it is a numberrst 5mov a,h !hl=value of the # orora l !0 iff no # was foundpop b !bc->end of linejz directdcx d !backup de and savemov a,h !value of line # therestax ddcx dmov a,lstax dpush b !bc,de->begin, endpush dmov a,csub epush psw !a=# of bytes in linecall fndln !find this line in savepush d !area, de->save areajnz st4 !nz:not found, insertpush d !z:found, delete itcall fndnxt !find next line!* de->next linepop b !bc->line to be deletedlhld txtunf !hl->unfilled save areacall mvup !move up to deletemov h,b !txtunf->unfilled areamov l,cshld txtunf !updatest4: pop b !get ready to insertlhld txtunf !but firt check ifpop psw !the length of new linepush h !is 3 (line # and cr)cpi 3 !then do not insertjz rstart !must clear the stackadd l !compute new txtunfmov l,amvi a,0adc hmov h,a !hl->new unfilled areast4a: lxi d,txtend !check to see if thererst 4 !is enough spacejnc qsorry !sorry, no room for itshld txtunf !ok, update txtunfpop d !de->old unfilled areacall mvdownpop d !de->begin, hl->endpop hcall mvup !move new line to savejmp st3 !area!*!**************************************************************!*!* *** 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 ad 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. iff the!* string does not match any of the other items, it will!* match this null item as default.!*tab1: equ $ !direct commandsdefb 'list'defb list shr 8 + 128,list and 0ffhdefb 'run'defb run shr 8 + 128,run and 255defb 'new'defb new shr 8 + 128,new and 255defb 'load'defb dload shr 8 + 128,dload and 255defb 'save'defb dsave shr 8 + 128,dsave and 255defb 'bye',80h,0h !go back to cpmtab2: equ $ !direct/tatementdefb 'next'defb next shr 8 + 128,next and 255defb 'let'defb let shr 8 + 128,let and 255defb 'out'defb outcmd shr 8 + 128,outcmd and 255defb 'poke'defb poke shr 8 + 128,poke and 255defb 'wait'defb waitcm shr 8 + 128,waitcm and 255defb 'if'defb iff shr 8 + 128,iff and 255defb 'goto'defb goto shr 8 + 128,goto and 255defb 'gosub'defb gosub shr 8 + 128,gosub and 255defb 'return'defb return shr 8 + 128,return and 255defb 'rem'defb rem shr 8 + 128,rem and 255defb 'for'defb for shr 8 + 128,for and 255defb 'input'defb input shr 8 + 128,input and 255defb 'print'defb print shr 8 + 128,print and 255defb 'stop'defb stop shr 8 + 128,stop and 255defb deflt shr 8 + 128,deflt and 255defb 'you can add more' !commands but!remember to move default down.tab4: equ $ !functionsdefb 'rnd'defb rnd shr 8 + 128,rnd and 255defb 'inp'defb inp shr 8 + 128,inp and 255defb 'peek'defb peek shr 8 + 128,peek and 255defb 'usr'defb usr shr 8 + 128,usr and 255defb 'abs'defb abs shr 8 + 128,abs and 255defb 'size'defb size shr 8 + 128,size and 255defb xp40 shr 8 + 128,xp40 and 255defb 'you can add more' !functions but remember!to move xp40 downtab5: equ $ !"to" in "for"defb 'to'defb fr1 shr 8 + 128,fr1 and 255defb qwhat shr 8 + 128,qwhat and 255tab6: equ $ !"step" in "for"defb 'step'defb fr2 shr 8 + 128,fr2 and 255defb fr3 shr 8 + 128,fr3 and 255tab8: equ $ !relation operatorsdefb '>='defb xp11 shr 8 + 128,xp11 and 255defb '#'defb xp12 shr 8 + 128,xp12 and 255defb '>'defb xp13 shr 8 + 128,xp13 and 255defb '='defb xp15 shr 8 + 128,xp15 and 255defb '<='defb xp14 shr 8 + 128,xp14 and 255defb '<'defb xp16 shr 8 + 128,xp16 and 255defb xp17 shr 8 + 128,xp17 and 255!*direct:lxi h,tab1-1 !*** direct ***!*exec: equ $ !*** exec ***ex0: rst 5 !ignore leading blankspush d !save pointerex1: ldax d !iff found '.' in stringinx d !before any mismatchcpi 56q !we declare a matchjz ex3inx h !hl->tablecmp m !iff match, test nextjz ex1mvi a,177q !else, see iff bit 7dcx d !of tableis set, whichcmp m !is the jump addr. (hi)jc ex5 !c:yes, matchedex2: inx h !nc:no, find jump addr.cmp mjnc ex2inx h !bump to next tab. itempop d !restore string pointerjmp ex0 !test against next itemex3: mvi a,177q !partial match, findex4: inx h !jump addr., which iscmp m !flagged by bit 7jnc ex4ex5: mov a,m !load hl with the jumpinx h !address from the tablemov l,mani 177q !mask off bit 7mov h,apop psw !clean up the gabagepchl !and we go do it!*!**************************************************************!*!* 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!* tansfered to other sections as follows:!*!* for 'list', 'new', and 'stop': go back to 'rstart'!* for 'run': go execute the first stored line iff 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: iff 'currnt' -> 0, go to 'rstart', else!* go execute next command. (this is done in 'finish'.)!*!**************************************************************!*!* *** new *** stop *** run (& friends) *** & goto ***!*!* 'new(cr)' sets 'txtunf' to point to 'txtbgn'!*!* 'stop(cr)' goes back to 'rstart'!*!* 'run(cr)' finds the first stored line, store its address (in!* 'currnt'), 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.!* 'dload' loads a named program from disk.!* 'dsave' saves a named program on disk.!* 'fcbset' sets up the file control block for subsequent disk i/o.!*new: call endchk !*** new(cr) ***lxi h,txtbgnshld txtunf!*stop: call endchk !*** stop(cr) ***jmp rstart!*run: call endchk !*** run(cr) ***lxi d,txtbgn !first saved line!*runnxl:lxi h,0 !*** runnxl ***call fndlnp !find whatever line #jc rstart !c:passed txtunf, quit!*runtsl:xchg !*** runtsl ***shld currnt !set 'currnt'->line #xchginx d !bump pass line #inx d!*runsml:call chkio !*** runsml ***lxi h,tab2-1 !find command in tab2jmp exec !and execute it!*goto: rst 3 !*** goto expr ***push d !save for error routinecall endchk !must find a 0dhcall fndln !find the target linejnz ahow !no such line #pop psw !clear the "push de"jmp runtsl !go do itcpm: equ 5 !disk parametersfcb: equ 5chsetdma:equ 26open: equ 15readd: equ 20writed:equ 21close: equ 16make: equ 22delete:equ 19!*dload: rst 5 !ignore blankspush h !save hcall fcbset !set up file control blockpush d !save the restpush blxi d,fcb !get fcb addressmvi c,open !prepare to open filecall cpm !open itcpi 0ffh !is it there?jz qhow !no, send errorxra a !clear asta fcb+32 !start at record 0lxi d,txtunf !get beginningload: push d !save dma addressmvi c,setdma !call cpm !set dma addressmvi c,readd !lxi d,fcbcall cpm !read sectorcpi 1 !done?jc rdmore !no, read morejnz qhow !bad readmvi c,closelxi d,fcbcall cpm !close filepop d !throw away dma add.pop b !get old registers backpop dpop hrst 6 !finishrdmore:pop d !get dma addresslxi h,80h !get 128dad d !add 128 to dma add.xchg !put it back in djmp load !and read some more!*dsave: rst 5 !ignore blankspush h !save hcall fcbset !setup fcbpush dpush b !save otherslxi d,fcbmvi c,deletecall cpm !erase file if it existslxi d,fcbmvi c,makecall cpm !make a new onecpi 0ffh !is there space?jz qhow !no, errorxra a !clear asta fcb+32 !start at record 0lxi d,txtunf !get beginningsave: push d !save dma addressmvi c,setdma !call cpm !set dma addressmvi c,writedlxi d,fcbcall cpm !write sectorora a !set flagsjnz qhow !if not zero, errorpop d !get dma add. backlda txtunf+1 !and msb of last add.cmp d !is d smaller?jc savdon !yes, donejnz writmor !dont test e if not equallda txtunf !is e smaller?cmp ejc savdon !yes, donewritmor:lxi h,80hdad d !add 128 to dma add.xchg !get it back in djmp save !write some moresavdon:mvi c,closelxi d,fcbcall cpm !close filepop b !get registers backpop dpop hrst 6 !finish!*fcbset:lxi h,fcb !get file control block addressmvi m,0 !clear entry typefnclr: inx h !next locationmvi m,' ' !clear to spacemvi a,fcb+8 and 255cmp l !done?jnz fnclr !no, do it againinx h !nextmvi m,'t' !set file type to 'tbi'inx hmvi m,'b'inx hmvi m,'i'exrc: inx h !clear rest of fcbmvi m,0mvi a,fcb+15 and 255cmp l !done?jnz exrc !no, continuelxi h,fcb+1 !get filename startfn: ldax d !get charactercpi 0dh !is it a 'cr'rz !yes, donecpi '!' !legal character?jc qwhat !no, send errorcpi '[' !againjnc qwhat !dittomov m,a !save it in fcbinx h !nextinx dmvi a,fcb+9 and 255cmp l !last?jnz fn !no, continueret !truncate at 8 characters!*!*************************************************************!*!* *** 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 controlss!* 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. iff 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 iff the list is a null list. however iff the list!* ended with a comma, no (crl) is generated.!*list: call tstnum !test iff there is a #call endchk !iff no # we get a 0call fndln !find this or next linels1: jc rstart !c:passed txtunfcall prtln !print the linecall chkio !stop iff hit control-ccall fndlnp !find next linejmp ls1 !and loop back!*print: mvi c,6 !c = # of spacesrst 1 !iff null list & "!"defb 73qdefb 6qcall crlf !give cr-lf andjmp runsml !continue same linepr2: rst 1 !iff null list (cr)defb 0dhdefb 6qcall crlf !also give cr-lf andjmp runnxl !go to next linepr0: rst 1 !else is it format?defb '#'defb 5qrst 3 !yes, evaluate expr.mov c,l !and save it in cjmp pr3 !look for more to printpr1: call qtstg !or is it a string?jmp pr8 !iff not, must be expr.pr3: rst 1 !iff ",", go find nextdefb ','defb 6qcall fin !in the list.jmp pr0 !list continuespr6: call crlf !list endsrst 6pr8: rst 3 !evaluate the exprpush bcall prtnum !print the valuepop bjmp pr3 !more to print?!*!**************************************************************!*!* *** 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. iff 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 forr no further 'return's.!*!* 'return(cr)' undos everyhing that 'gosub' did, and thus!* return the excution to the command after the most recent!* 'gosub'. iff 'stkgos' is zero, it indicates that we!* never had a 'gosub' and is thus an error.!*gosub: call pusha !save the current "for"rst 3 !parameterspush d !and text pointercall fndln !find the target linejnz ahow !not there. say "how?"lhld currnt !found it, save oldpush h !'currnt' old 'stkgos'lhld stkgospush hlxi h,0 !and load new onesshld lopvardad spshld stkgosjmp runtsl !then run that linereturn:call endchk !there must be a 0dhlhld stkgos !old stack pointermov a,h !0 means not existora ljz qwhat !so, we say: "what?"sphl !else, restore itpop hshld stkgos !and the old 'stkgos'pop hshld currnt !and the old 'currnt'pop d !old text pointercall popa !old "for" parametersrst 6 !and we are back home!*!**************************************************************!*!* *** for *** & next ***!*!* 'for' has two forms:!* 'for var=exp1 to exp2 step exp1' and 'for var=exp1 to exp2'!* the second form means the same thing as the first form with!* exp1=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 expr2 and exp1!* and save all these together with the text pointerr etc. in!* the 'for' save area, which consists of 'lopvar', 'lopinc',!* 'loplmt', 'lopln', and 'loppt'. iff 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 iff this same!* variable was used in another currently active 'for' loop.!* iff 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'. iff they are not the same, tbi digs in!* the stack to find the rightt 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. iff it!* is within the limit, control loops back to the command!* following the 'for'. iff outside the limit, the save arer!* is purged and execution continues.!*for: call pusha !save the old save areacall setval !set the control var.dcx h !hl is its addressshld lopvar !save thatlxi h,tab5-1 !use 'exec' to lookjmp exec !for the word 'to'fr1: rst 3 !evaluate the limitshld loplmt !save thatlxi h,tab6-1 !use 'exec' to lookjmp exec !for the word 'step'fr2: rst 3 !found it, get stepjmp fr4fr3: lxi h,1q !not found, set to 1fr4: shld lopinc !save that toofr5: lhld currnt !save current line #shld loplnxchg !and text pointershld lopptlxi b,12q !dig into stack tolhld lopvar !find 'lopvar'xchgmov h,bmov l,b !hl=0 nowdad sp !here is the stackdefb 76qfr7: dad b !each level is 10 deepmov a,m !get that old 'lopvar'inx hora mjz fr8 !0 says no more in itmov a,mdcx hcmp d !same as this one?jnz fr7mov a,m !the other half?cmp ejnz fr7xchg !yes, found onelxi h,0qdad sp !try to move spmov b,hmov c,llxi h,12qdad dcall mvdown !and purge 10 wordssphl !in the stackfr8: lhld loppt !job done, restore dexchgrst 6 !and continue!*next: rst 7 !get address of var.jc qwhat !no variable, "what?"shld varnxt !yes, save itnx0: push d !save text pointerxchglhld lopvar !get var. in 'for'mov a,hora l !0 says never had onejz awhat !so we ask: "what?"rst 4 !else we check themjz nx3 !ok, they agreepop d !no, let's seecall popa !purge current looplhld varnxt !and pop one leveljmp nx0 !go check againnx3: mov e,m !come here when agreedinx hmov d,m !de=value of var.lhld lopincpush hdad d !add one stepxchglhld lopvar !put it backmov m,einx hmov m,dlhld loplmt !hl->limitpop psw !old hlora ajp nx1 !step > 0xchgnx1: call ckhlde !compare with limitpop d !restore text pointerjc nx2 !outside limitlhld lopln !within limit, goshld currnt !back to the savedlhld loppt !'currnt' and textxchg !pointerrst 6nx2: call popa !purge this looprst 6!*!**************************************************************!*!* *** rem *** iff *** 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 outher 'if's) seperated by semi-colons.!* note that the word 'then' is not used. tbi evaluates the!* expr. iff it is non-zero, execution continues. iff the!* expr. is zero, the commands that follows are ignored and!* execution continues at the next line.!*!* 'iput' command is like the 'print' command, and is followed!* by a list of items. iff the item is a string in single or!* double quotes, or is a back-arrow, it has the same effect as!* in 'print'. iff 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 iss then set to the!* value of this expr. iff 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.!*!* iff 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 varible to that value.!* tb will also handle 'let' command without the word 'let'.!* this is done by 'deflt'.!*rem: lxi h,0q !*** rem ***defb 76q!*iff: rst 3 !*** iff ***mov a,h !is the expr.=0?ora ljnz runsml !no, continuecall fndskp !yes, skip rest of linejnc runtsljmp rstart!*inperr:lhld stkinp !*** inperr ***sphl !restore old sppop h !and old 'currnt'shld currntpop d !and old text pointerpop d !redo input!*input: equ $ !*** input ***ip1: push d !save in case of errorcall qtstg !is next item a string?jmp ip2 !norst 7 !yes. but followed by ajc ip4 !variable? no.jmp ip3 !yes. input variableip2: push d !save for 'prtstg'rst 7 !must be variable nowjc qwhat !"what?" it is not?ldax d !get ready for 'rtstg'mov c,asub astax dpop dcall prtstg !print string as promptmov a,c !restore textdcx dstax dip3: push d !save in case of errorxchglhld currnt !also save 'currnt'push hlxi h,ip1 !a negative numbershld currnt !as a flaglxi h,0q !save sp toodad spshld stkinppush d !old hlmvi a,72q !print this toocall getln !and get a lineip3a: lxi d,buffer !points to bufferrst 3 !evaluate inputnop !can be 'call endchk'nopnoppop d !ok, get old hlxchgmov m,e !save value in var.inx hmov m,dpop h !get old 'currnt'shld currntpop d !and old text pointerip4: pop psw !purge junk in stackrst 1 !is next ch. ','?defb ','defb 3qjmp ip1 !yes, more items.ip5: rst 6!*deflt: ldax d !*** deflt ***cpi 0dh !empty line is okjz lt1 !else it is 'let'!*let: call setval !*** let ***rst 1 !set value to var.defb ','defb 3qjmp let !item by itemlt1: rst 6 !until finish!*!**************************************************************!*!* *** expr ***!*!* 'expr' evaluates arithmetical or logical expressions.!* <expr>::=<expr2>!* <expr2><rel.op.><expr2>!* where <rel.op.> is one of the operatorss in tab8 and the!* result of these operations is 1 iff true and 0 iff false.!* <expr2>::=(+ or -)<expr3>(+ or -<expr3>)(....)!* where () are optional and (....) are optional repeats.!* <expr3>::=<expr4>(<* or /><expr4>)(....)!* <expr4>::=<variable>!* <function>!* (<expr>)!* <expr> is recursive so that variable '@' can have an <expr>!* as index, fnctions can have an <expr> as arguments, and!* <expr4> can be an <expr> in paranthese.!*!* expr call expr2 this is at loc. 18!* push hl save <expr2> valueexpr1: lxi h,tab8-1 !lookup rel.op.jmp exec !go do itxp11: call xp18 !rel.op.">="rc !no, return hl=0mov l,a !yes, return hl=1retxp12: call xp18 !rel.op."#"rz !false, return hl=0mov l,a !true, return hl=1retxp13: call xp18 !rel.op.">"rz !falserc !also false, hl=0mov l,a !true, hl=1retxp14: call xp18 !rel.op."<="mov l,a !set hl=1rz !rel. true, returnrcmov l,h !else set hl=0retxp15: call xp18 !rel.op."="rnz !false, retrun hl=0mov l,a !else set hl=1retxp16: call xp18 !rel.op."<"rnc !false, return hl=0mov l,a !else set hl=1retxp17: pop h !not rel.op.ret !return hl=<expr2>xp18: mov a,c !subroutine for allpop h !rel.op.'spop bpush h !reverse top of stackpush bmov c,acall expr2 !get 2nd <expr2>xchg !value in de nowxthl !1st <expr2> in hlcall ckhlde !compare 1st with 2ndpop d !restore text pointerlxi h,0q !set hl=0, a=1mvi a,1ret!*expr2: rst 1 !negative sign?defb '-'defb 6qlxi h,0q !yes, fake '0-'jmp xp26 !treat like subtractxp21: rst 1 !positive sign? ignoredefb '+'defb 0qxp22: call expr3 !1st <expr3>xp23: rst 1 !add?defb '+'defb 25qpush h !yes, save valuecall expr3 !get 2nd<expr3>xp24: xchg !2nd in dexthl !1st in hlmov a,h !compare signxra dmov a,ddad dpop d !restore text pointerjm xp23 !1st 2nd sign differxra h !1st 2nd sign equaljp xp23 !so isp resultjmp qhow !else we have overflowxp25: rst 1 !subtract?defb '-'defb 203qxp26: push h !yes, save 1st <expr3>call expr3 !get 2nd <expr3>call chgsgn !negatejmp xp24 !and add them!*expr3: call expr4 !get 1st <expr4>xp31: rst 1 !multiply?defb '*'defb 54qpush h !yes, save 1stcall expr4 !and get 2nd <expr4>mvi b,0q !clear b for signcall chksgn !check signxchg !2nd in de nowxthl !1st in hlcall chksgn !check sign of 1stmov a,h !is hl > 255 ?ora ajz xp32 !nomov a,d !yes, how about deora dxchg !put smaller in hljnz ahow !also >, will overflowxp32: mov a,l !this is dumblxi h,0q !clear resultora a !add and countjz xp35xp33: dad djc ahow !overflowdcr ajnz xp33jmp xp35 !finishedxp34: rst 1 !divide?defb '/'defb 104qpush h !yes, save 1st <expr4>call expr4 !and get 2nd onemvi b,0q !clear b for signcall chksgn !check sign of 2ndxchg !put 2nd in dexthl !get 1st in hlcall chksgn !check sign of 1stmov a,d !divide by 0?ora ejz ahow !say "how?"push b !else save signcall divide !use subroutinemov h,b !result in hl nowmov l,cpop b !get sign backxp35: pop d !and text pointermov a,h !hl must be +ora ajm qhow !else it is overflowmov a,bora acm chgsgn !change sign iff neededjmp xp31 !look or more terms!*expr4: lxi h,tab4-1 !find function in tab4jmp exec !and go do itxp40: rst 7 !no, not a functionjc xp41 !nor a variablemov a,m !variableinx hmov h,m !value in hlmov l,aretxp41: call tstnum !or is it a numbermov a,b !# of digitora arnz !okparn: rst 1 !no digit, must bedefb '('defb 5qrst 3 !"(expr)"rst 1defb ')'defb 1qxp42: retxp43: jmp qwhat !else say: "what?"!*rnd: call parn !*** rnd(expr) ***mov a,h !expr must be +ora ajm qhowora l !and non-zerojz qhowpush d !save bothpush hlhld ranpnt !get memory as randomlxi d,lstrom !numberrst 4jc ra1 !wrap around iff lastlxi h,startra1: mov e,minx hmov d,mshld ranpntpop hxchgpush bcall divide !rnd(n)=mod(m,n)+1pop bpop dinx hret!*abs: call parn !*** abs(expr) ***call chksgn !check signmov a,h !note that -32768ora h !cannot change signjm qhow !so say: "how?"retsize: lhld txtunf !*** size ***push d !get the number of freexchg !bytes between 'txtunf'sizea: lxi h,varbgn !and 'varbgn'call subdepop dret!*!*********************************************************!*!* *** out *** inp *** wait *** poke *** peek *** & usr!*!* out i,j(,k,l)!*!* outputs expression 'j' to port 'i', and may be repeated!* as in data 'l' to port 'k' as many times as needed!* this command modifies !* this command modifies!* this command modify's a small section of code located!* just above address 2k!*!* inp (i)!*!* this function returns data read from input port 'i' as!* it's value.!* it also modifies code just above 2k.!*!* wait i,j,k!*!* this command reads the status of port 'i', exclusive or's!* the result with 'k' if there is one, or if not with 0,!* and's with 'j' and returns when the result is nonzero.!* its modified code is also above 2k.!*!* poke i,j(,k,l)!*!* this command works like out except that it puts data 'j'!* into memory location 'i'.!*!* peek (i)!*!* this function works like inp except it gets it's value!* from memory location 'i'.!*!* usr (i(,j))!*!* usr calls a machine language subroutine at location 'i'!* if the optional parameter 'j' is used its value is passed!* in h&l. the value of the function should be returned in h&l.!*!************************************************************!*outcmd:rst 3mov a,lsta outio + 1rst 1defb ','defb 2fhrst 3mov a,lcall outiorst 1defb ','defb 03hjmp outcmdrst 6waitcm:rst 3mov a,lsta waitio + 1rst 1defb ','defb 1bhrst 3push hrst 1defb ','defb 7hrst 3mov a,lpop hmov h,ajmp $ + 2mvi h,0jmp waitioinp: call parnmov a,lsta inpio + 1mvi h,0jmp inpiojmp qwhatpoke: rst 3push hrst 1defb ','defb 12hrst 3mov a,lpop hmov m,arst 1defb ',',03hjmp pokerst 6peek: call parnmov l,mmvi h,0retjmp qwhatusr: push brst 1defb '(',28d !qwhatrst 3 !exprrst 1defb ')',7 !pasparmpush dlxi d,usretpush dpush hret !call usr routinepasprm:rst 1defb ',',14dpush hrst 3rst 1defb ')',9pop bpush dlxi d,usretpush dpush bret !call usr routineusret: pop dpop bretjmp qwhat!*!**************************************************************!*!* *** divide *** subde *** chksgn *** chgsgn *** & ckhlde ***!*!* 'divide' divides hl by de, result in bc, remainder in hl!*!* 'subde' subtracts de from hl!*!* 'chksgn' checks sign of hl. iff +, no change. iff -, change!* sign and flip sign of b.!*!* 'chgsgn' chnges sign of hl and b unconditionally.!*!* 'ckhle' checks sign of hl and de. iff different, hl and de!* are interchanged. iff same sign, not interchanged. either!* case, hl de are then compared to set the flags.!*divide:push h !*** divide ***mov l,h !divide h by demvi h,0call dv1mov b,c !save result in bmov a,l !(remainder+l)/depop hmov h,adv1: mvi c,377q !result in cdv2: inr c !dumb routinecall subde !divide by subtractjnc dv2 !and countdad dret!*subde: mov a,l !*** subde ***sub e !subtract de frommov l,a !hlmov a,hsbb dmov h,aret!*chksgn:mov a,h !*** chksgn ***ora a !check sign of hlrp !iff -, change sign!*chgsgn:mov a,h !*** chgsgn ***cma !change sign of hlmov h,amov a,lcmamov l,ainx hmov a,b !and also flip bxri 200qmov b,aret!*ckhlde:mov a,hxra d !same sign?jp ck1 !yes, comparexchg !no, xch and compck1: rst 4ret!*!**************************************************************!*!* *** 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. iff it ended with "!",!* execution continues. iff it ended with a cr, it finds the!* next line and continue from there.!*!* "endchk" checks iff 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!* o the stack) points to. execution of tb is stopped!* and tbi is restarted. however, iff 'currnt' -> zero!* (indicating a direct command), the direct command is not!* printed. and iff '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.!* 'qhow' and 'ahow' in the zero page section also do this!*setval:rst 7 !*** setval ***jc qwhat !"what?" no variablepush h !save address of var.rst 1 !pass "=" signdefb '='defb 10qrst 3 !evaluate expr.mov b,h !value in bc nowmov c,lpop h !get addressmov m,c !save valueinx hmov m,bretsv1: jmp qwhat !no "=" sign!*fin: rst 1 !*** fin ***defb 73qdefb 4qpop psw !"!", purge ret addr.jmp runsml !continue same linefi1: rst 1 !not "!", is it cr?defb 0dhdefb 4qpop psw !yes, purge ret addr.jmp runnxl !run next linefi2: ret !else return to caller!*endchk:rst 5 !*** endchk ***cpi 0dh !end with cr?rz !ok, else say: "what?"!*qwhat: push d !*** qwhat ***awhat: lxi d,what !*** awhat ***error: sub a !*** error ***call prtstg !print 'what?', 'how?'pop d !or 'sorry'ldax d !save the characterpush psw !at where old de ->sub a !and put a 0 therestax dlhld currnt !get current line #push hmov a,m !check the valueinx hora mpop djz rstart !iff zero, just rerstartmov a,m !iff negative,ora ajm inperr !redo inputcall prtln !else print the linedcx d !upto where the 0 ispop psw !restore the characterstax dmvi a,77q !printt a "?"rst 2sub a !and the rest of thecall prtstg !linejmp rstartqsorry:push d !*** qsorry ***asorry:lxi d,sorry !*** asorry ***jmp error!*!**************************************************************!*!* *** 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!* 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 charater (iff there is one), and alt-mod is used to!* cause it to delete the whole line and start it all over.!* 0dhsignals the end of a line, and caue '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. iff 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.!* iff that line is not there and a line with a higher line #!* is found, de points to there and flags are nc & nz. iff!* we reached the end of text save are 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 0dhand then start search.!* 'fndskp' use de to find a cr, and then strart search.!*getln: rst 2 !*** getln ***lxi d,buffer !prompt and initgl1: call chkio !check keyboardjz gl1 !no input, waitcpi 177q !delete lst character?jz gl3 !yescpi 12q !ignore lfjz gl1ora a !ignore nulljz gl1cpi 134q !delete the whole line?jz gl4 !yesstax d !else, save inputinx d !and bump pointercpi 15q !was it cr?jnz gl2 !nomvi a,12q !yes, get line feedrst 2 !call outc and line feedret !we've got a linegl2: mov a,e !more free room?cpi bufend and 0ffhjnz gl1 !yes, get next inputgl3: mov a,e !delete last charactercpi buffer and 0ffh !but do we have any?jz gl4 !no, redo whole linedcx d !yes, backup pointermvi a,'_' !and echo a back-spacerst 2jmp gl1 !go get next inputgl4: call crlf !redo entire linemvi a,136q !cr, lf and up-arrowjmp getln!*fndln: mov a,h !*** fndln ***ora a !check sign of hljm qhow !it cannt be -lxi d,txtbgn !init. text pointer!*fndlnp:equ $ !*** fndlnp ***fl1: push h !save line #lhld txtunf !check iff we passed enddcx hrst 4pop h !get line # backrc !c,nz passed endldax d !we did not, get byte 1sub l !is this the line?mov b,a !compare low orderinx dldax d !get byte 2sbb h !compare high orderjc fl2 !no, not there yetdcx d !else we either foundora b !it, or it is not thereret !nc,z:found! nc,nz:no!*fndnxt:equ $ !*** fndnxt ***inx d !find next linefl2: inx d !just passed byte 1 & 2!*fndskp:ldax d !*** fndskp ***cpi 0dh !try to find 0dhjnz fl2 !keep lookinginx d !found cr, skip overjmp fl1 !check iff end of text!*!*************************************************************!*!* *** prtstg *** qtstg *** prtnum *** & prtln ***!*!* 'prtstg' prints a string pointed by de. it stops printing!* 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. iff none of these, return to caller. iff back-arrow,!* output a 0dhwithout a lf. iff 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!* iff needed to pad the number of spaces to the number in c.!* however, iff 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' prinsra saved text line with line # and all.!*prtstg:mov b,a !*** prtstg ***ps1: ldax d !get a characterrinx d !bump pointercmp b !same as old a?rz !yes, returnrst 2 !else print itcpi 0dh !was it a cr?jnz ps1 !no, nextret !yes, return!*qtstg: rst 1 !*** qtstg ***defb '"'defb 17qmvi a,42q !it is a "qt1: call prtstg !print until anothercpi 0dh !was last one a cr?pop h !return addressjz runnxl !was cr, run next lineqt2: inx h !skip 3 bytes on returninx hinx hpchl !returnqt3: rst 1 !is it a ' ?defb 47qdefb 5qmvi a,47q !yes, do samejmp qt1 !as in "qt4: rst 1 !is it back-arrow?defb 137qdefb 10qmvi a,215q !yes, 0dhwithout lf!!rst 2 !do it twice to giverst 2 !tty enough timepop h !return addressjmp qt2qt5: ret !none of above!*prtnum push d !*** prtnum ***lxi d,12q !decimalpush d !save as a flagmov b,d !b=signdcr c !c=spacescall chksgn !check signjp pn1 !no signmvi b,55q !b=signdcr c !'-' takes spacepn1: push b !save sign & spacepn2: call divide !devide hl by 10mov a,b !result 0?ora cjz pn3 !yes, we got allxthl !no, save remainderdcr l !and count spacepush h !hl is old bcmov h,b !move result to bcmov l,cjmp pn2 !and divide by 10pn3: pop b !we got all digits inpn4: dcr c !the stackmov a,c !look at space countora ajm pn5 !no leading blanksmvi a,40q !leading blanksrst 2jmp pn4 !more?pn5: mov a,b !print signrst 2 !maybe - or nullmov e,l !last remainder in epn6: mov a,e !check digit in ecpi 12q !10 is flag for no morepop drz !iff so, returnadi 60q !else convert to asciirst 2 !and print the digitjmp pn6 !go back for more!*prtln: ldax d !*** prtln ***mov l,a !low order line #inx dldax d !high ordermov h,ainx dmvi c,4q !print 4 digit line #call prtnummvi a,40q !followed by a blankrst 2sub a !and then the textcall prtstgret!*!**************************************************************!*!* *** mvup *** mvdown *** popa *** & pusha ***!*!* 'mvup' moves a block up from here 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: rst 4 !*** mvup ***rz !de = hl, returnldax d !get one bytestax b !move itinx d !increase both pointersinx bjmp mvup !until done!*mvdown:mov a,b !*** mvdown ***sub d !test iff de = bcjnz md1 !no, go movemov a,c !maybe, other byte?sub erz !yes, returnmd1: dcx d !else move a bytedcx h !but first decreaseldax d !both pointers andmov m,a !then do itjmp mvdown !loop back!*popa: pop b !bc = return addr.pop h !restore lopvar, butshld lopvar !=0 means no moremov a,hora ljz pp1 !yep, go returnpop h !nop, restore othersshld lopincpop hshld loplmtpop hshld loplnpop hshld lopptpp1: push b !bc = return addr.ret!*pusha: lxi h,stklmt !*** pusha ***call chgsgnpop b !bc=return addressdad sp !is stack near the top?jnc qsorry !yes, sorry for that.lhld lopvar !else save loop var.smov a,h !but iff lopvar is 0ora l !that will be alljz pu1lhld loppt !else, more to savepush hlhld loplnpush hlhld loplmtpush hlhld lopincpush hlhld lopvarpu1: push hpush b !bc = return addr.ret!*!**************************************************************!*!* *** outc *** & chkio ****!!* these are the only i/o routines in tbi.!* 'outc' is controlled by a software switch 'ocsw'. iff ocsw=0!* 'outc' will just return to the caller. iff ocsw is not 0,!* it will output the byte in a. iff that is a cr, a lf is also!* send out. only the flags may be changed at return, all reg.!* are restored.!*!* 'chkio' checks the input. iff no input, it will return to!* the caller with the z flag set. iff there is input, z flag!* is cleared and the input byte is in a. howerer, iff the!* input is a control-o, the 'ocsw' switch is complimented, and!* z flag is returned. iff a control-c is read, 'chkio' will!* restart tbi and do not return to the caller.!*!* outc push af this is at loc. 10!* ld a,ocsw check software switch!* ior aoc2: jnz oc3 !it is onpop psw !it is offret !restore af and returnoc3: pop a !get old a backpush b !save b on stackpush d !and dpush h !and h toosta outcar !save charactermov e,a !put char. in e for cpmmvi c,2 !get conout commandcall cpm !call cpm and do itlda outcar !get char. backcpi 0dh !was it a 'cr'?jnz done !no, donemvi e,0ah !get linefeedmvi c,2 !and conout againcall cpm !call cpmdone: lda outcar !get character backidone: pop h !get h backpop d !and dpop b !and b tooret !done at lastchkio: push b !save b on stackpush d !and dpush h !then hmvi c,11 !get constat wordcall cpm !call the bdosora a !set flagsjnz ci1 !if ready get characterjmp idone !restore and returnci1: mvi c,1 !get conin wordcall cpm !call the bdoscpi 0fh !is it control-o?jnz ci2 !no, more checkinglda ocsw !control-o flip ocswcma !on to off, off to onsta ocsw !and put it backjmp chkio !and get another characterci2: cpi 3 !is it control-c?jnz idone !return and restore if notjmp rstart !yes, restart tbilstrom:equ $ !all above can be romoutio: out 0ffhretwaitio:in 0ffhxra hana ljz waitiorst 6inpio: in 0ffhmov l,aretoutcar:defb 0 !output char. storageocsw: defb 0ffh !switch for outputcurrnt:defw 0 !points to current linestkgos:defw 0 !saves sp in 'gosub'varnxt:defw 0 !temporary storagestkinp:defw 0 !saves sp in 'input'lopvar:defw 0 !'for' loop save arealopinc:defw 0 !incrementloplmt:defw 0 !limitlopln: defw 0 !line numberloppt: defw 0 !text pointerranpnt:defw start !random number pointertxtunf:defw txtbgn !->unfilled text areatxtbgn:defvs 1 !text save area beginsmsg1: defb 7fh,7fh,7fh,'Tiny basic ver. 3.1',0dhinit: mvi a,0ffhsta ocsw !turn on output switchmvi a,0ch !get form feedrst 2 !send to crtpatlop:sub a !clear accumulatorlxi d,msg1 !get init messagecall prtstg !send itlstram:lda 7 !get fbase for topsta rstart+2dcr a !decrement for other pointerssta ss1a+2 !and fix them toosta tv1a+2sta st3a+2sta st4a+2sta ip3a+2sta sizea+2sta getln+3sta pusha+2lxi h,st1 !get new start jumpshld start+1 !and fix itjmp st1jmp qwhat !print "what?" iff wrongtxtend:equ $ !text save area endsvarbgn:defvs 2*27 !variable @(0)defvs 1 !extra byte for bufferbuffer:defvs 80 !input bufferbufend:equ $ !buffer endsdefvs 40 !extra bytes for stackstklmt:equ $ !top limit for stackorg 2000hstack: equ $ !stack starts here
