OpenCores
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 start
       alignp 8
*
       xthl           !*** tstc or rst 1 *** 
       rst  5         !ignore blanks and 
       cmp  m         !test character
       jmp  tc1       !rest of this is at tc1
* 
crlf:  mvi  a,0dh     !*** crlf ***
* 
       push psw       !*** outc or rst 2 *** 
       lda  ocsw      !print character only
       ora  a         !iff ocsw switch is on
       jmp  oc2       !rest of this is at oc2
* 
       call expr2     !*** expr or rst 3 *** 
       push h         !evaluate an expresion 
       jmp  expr1     !rest of it is at expr1
       defb 'w' 
* 
       mov  a,h       !*** comp or rst 4 *** 
       cmp  d         !compare hl with de
       rnz            !return correct c and
       mov  a,l       !z flags 
       cmp  e         !but old a is lost 
       ret
       defb 'an'
* 
ss1:   ldax d         !*** ignblk/rst 5 ***
       cpi  40q       !ignore blanks 
       rnz            !in text (where de->)
       inx  d         !and return the first
       jmp  ss1       !non-blank char. in a
* 
       pop  psw       !*** finish/rst 6 ***
       call fin       !check end of command
       jmp  qwhat     !print "what?" iff wrong
       defb 'g' 
* 
       rst  5         !*** tstv or rst 7 *** 
       sui  100q      !test variables
       rc             !c:not a variable
*
tstv1: jnz  tv1       !not "@" array 
       inx  d         !it is the "@" array 
       call parn      !@ should be followed
       dad  h         !by (expr) as its index
       jc   qhow      !is index too big? 
       push d         !will it overwrite 
       xchg           !text? 
       call size      !find size of free 
       rst  4         !and check that
       jc   asorry    !iff so, say "sorry"
ss1a:  lxi  h,varbgn  !iff not, get address 
       call subde     !of @(expr) and put it 
       pop  d         !in hl 
       ret            !c flag is cleared 
tv1:   cpi  33q       !not @, is it a to z?
       cmc            !iff not return c flag
       rc 
       inx  d         !iff a through z
tv1a:  lxi  h,varbgn  !compute address of
       rlc            !that variable 
       add  l         !and return it in hl 
       mov  l,a       !with c flag cleared 
       mvi  a,0 
       adc  h 
       mov  h,a 
       ret
!* 
!*                 tstc   xch  hl,(sp)   !*** tstc or rst 1 *** 
!*                        ignblk         this is at loc. 8 
!*                        cmp  m         and then jmp here 
tc1:   inx  h         !compare the byte that 
       jz   tc2       !follows the rst inst. 
       push b         !with the text (de->)
       mov  c,m       !iff not =, add the 2nd 
       mvi  b,0       !byte that follows the 
       dad  b         !rst to the old pc 
       pop  b         !i.e., do a relative 
       dcx  d         !jump iff not = 
tc2:   inx  d         !iff =, skip those bytes
       inx  h         !and continue
       xthl 
       ret
!* 
tstnum:lxi  h,0       !*** tstnum ***
       mov  b,h       !test iff the text is 
       rst  5         !a number
tn1:   cpi  60q       !iff not, return 0 in 
       rc             !b and hl
       cpi  72q       !iff numbers, convert 
       rnc            !to binary in hl and 
       mvi  a,360q    !set a to # of digits
       ana  h         !iff h>255, there is no 
       jnz  qhow      !room for next digit 
       inr  b         !b counts # of digits
       push b 
       mov  b,h       !hl=10!*hl+(new digit)
       mov  c,l 
       dad  h         !where 10!* is done by
       dad  h         !shift and add 
       dad  b 
       dad  h 
       ldax d         !and (digit) is from 
       inx  d         !stripping the ascii 
       ani  17q       !code
       add  l 
       mov  l,a 
       mvi  a,0 
       adc  h 
       mov  h,a 
       pop  b 
       ldax d         !do this digit after 
       jp   tn1       !digit. s says overflow
qhow:  push d         !*** error: "how?" *** 
ahow:  lxi  d,how 
       jmp  error 
how:   defb 'how?',0dh 
ok:    defb 'ok',0dh 
what:  defb 'what?',0dh 
sorry: 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 pointer
st1:   call crlf      !and jump to here
       lxi  d,ok      !de->string
       sub  a         !a=0 
       call prtstg    !print string until 0dh
       lxi  h,st2+1   !literal 0 
       shld currnt    !currnt->line # = 0
st2:   lxi  h,0 
       shld lopvar
       shld stkgos
st3:   mvi  a,76q     !prompt '>' and
       call getln     !read a line 
       push d         !de->end of line 
st3a:  lxi  d,buffer  !de->beginning of line 
       call tstnum    !test iff it is a number
       rst  5 
       mov  a,h       !hl=value of the # or
       ora  l         !0 iff no # was found 
       pop  b         !bc->end of line 
       jz   direct
       dcx  d         !backup de and save
       mov  a,h       !value of line # there 
       stax d 
       dcx  d 
       mov  a,l 
       stax d 
       push b         !bc,de->begin, end 
       push d 
       mov  a,c 
       sub  e 
       push psw       !a=# of bytes in line
       call fndln     !find this line in save
       push d         !area, de->save area 
       jnz  st4       !nz:not found, insert
       push d         !z:found, delete it
       call fndnxt    !find next line
!*                                       de->next line 
       pop  b         !bc->line to be deleted
       lhld txtunf    !hl->unfilled save area
       call mvup      !move up to delete 
       mov  h,b       !txtunf->unfilled area 
       mov  l,c 
       shld txtunf    !update
st4:   pop  b         !get ready to insert 
       lhld txtunf    !but firt check if
       pop  psw       !the length of new line
       push h         !is 3 (line # and cr)
       cpi  3         !then do not insert
       jz   rstart    !must clear the stack
       add  l         !compute new txtunf
       mov  l,a 
       mvi  a,0 
       adc  h 
       mov  h,a       !hl->new unfilled area 
st4a:  lxi  d,txtend  !check to see if there 
       rst  4         !is enough space 
       jnc  qsorry    !sorry, no room for it 
       shld txtunf    !ok, update txtunf 
       pop  d         !de->old unfilled area 
       call mvdown
       pop  d         !de->begin, hl->end
       pop  h 
       call mvup      !move new line to save 
       jmp  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 commands 
       defb 'list'
       defb list shr 8 + 128,list and 0ffh
       defb 'run'
       defb run shr 8 + 128,run and 255
       defb 'new'
       defb new shr 8 + 128,new and 255
       defb 'load'
       defb dload shr 8 + 128,dload and 255
       defb 'save'
       defb dsave shr 8 + 128,dsave and 255
       defb 'bye',80h,0h   !go back to cpm
tab2:  equ  $         !direct/tatement
       defb 'next'
       defb next shr 8 + 128,next and 255
       defb 'let'
       defb let shr 8 + 128,let and 255
       defb 'out'
       defb outcmd shr 8 + 128,outcmd and 255 
       defb 'poke'
       defb poke shr 8 + 128,poke and 255
       defb 'wait'
       defb waitcm shr 8 + 128,waitcm and 255
       defb 'if'
       defb iff shr 8 + 128,iff and 255
       defb 'goto'
       defb goto shr 8 + 128,goto and 255
       defb 'gosub'
       defb gosub shr 8 + 128,gosub and 255
       defb 'return'
       defb return shr 8 + 128,return and 255
       defb 'rem'
       defb rem shr 8 + 128,rem and 255
       defb 'for'
       defb for shr 8 + 128,for and 255
       defb 'input'
       defb input shr 8 + 128,input and 255
       defb 'print'
       defb print shr 8 + 128,print and 255
       defb 'stop'
       defb stop shr 8 + 128,stop and 255
       defb deflt shr 8 + 128,deflt and 255
       defb 'you can add more' !commands but
            !remember to move default down.
tab4:  equ  $         !functions 
       defb 'rnd'
       defb rnd shr 8 + 128,rnd and 255
       defb 'inp'
       defb inp shr 8 + 128,inp and 255
       defb 'peek'
       defb peek shr 8 + 128,peek and 255
       defb 'usr'
       defb usr shr 8 + 128,usr and 255
       defb 'abs'
       defb abs shr 8 + 128,abs and 255
       defb 'size'
       defb size shr 8 + 128,size and 255
       defb xp40 shr 8 + 128,xp40 and 255
       defb 'you can add more' !functions but remember
                      !to move xp40 down
tab5:  equ  $         !"to" in "for" 
       defb 'to'
       defb fr1 shr 8 + 128,fr1 and 255
       defb qwhat shr 8 + 128,qwhat and 255
tab6:  equ  $         !"step" in "for" 
       defb 'step'
       defb fr2 shr 8 + 128,fr2 and 255
       defb fr3 shr 8 + 128,fr3 and 255
tab8:  equ  $         !relation operators
       defb '>='
       defb xp11 shr 8 + 128,xp11 and 255
       defb '#'
       defb xp12 shr 8 + 128,xp12 and 255
       defb '>'
       defb xp13 shr 8 + 128,xp13 and 255
       defb '='
       defb xp15 shr 8 + 128,xp15 and 255
       defb '<='
       defb xp14 shr 8 + 128,xp14 and 255
       defb '<'
       defb xp16 shr 8 + 128,xp16 and 255
       defb xp17 shr 8 + 128,xp17 and 255
!* 
direct:lxi  h,tab1-1  !*** direct ***
!* 
exec:  equ  $         !*** exec ***
ex0:   rst  5         !ignore leading blanks 
       push d         !save pointer
ex1:   ldax d         !iff found '.' in string
       inx  d         !before any mismatch 
       cpi  56q       !we declare a match
       jz   ex3 
       inx  h         !hl->table 
       cmp  m         !iff match, test next 
       jz   ex1 
       mvi  a,177q    !else, see iff bit 7
       dcx  d         !of tableis set, which
       cmp  m         !is the jump addr. (hi)
       jc   ex5       !c:yes, matched
ex2:   inx  h         !nc:no, find jump addr.
       cmp  m 
       jnc  ex2 
       inx  h         !bump to next tab. item
       pop  d         !restore string pointer
       jmp  ex0       !test against next item
ex3:   mvi  a,177q    !partial match, find 
ex4:   inx  h         !jump addr., which is
       cmp  m         !flagged by bit 7
       jnc  ex4 
ex5:   mov  a,m       !load hl with the jump 
       inx  h         !address from the table
       mov  l,m 
       ani  177q      !mask off bit 7
       mov  h,a 
       pop  psw       !clean up the gabage 
       pchl           !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,txtbgn
       shld 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 #
       xchg 
       inx  d         !bump pass line #
       inx  d 
!* 
runsml:call chkio     !*** runsml ***
       lxi  h,tab2-1  !find command in tab2
       jmp  exec      !and execute it
!* 
goto:  rst  3         !*** goto expr *** 
       push d         !save for error routine
       call endchk    !must find a 0dh
       call fndln     !find the target line
       jnz  ahow      !no such line #
       pop  psw       !clear the "push de" 
       jmp  runtsl    !go do it
cpm:   equ  5         !disk parameters
fcb:   equ  5ch
setdma:equ  26
open:  equ  15
readd: equ  20
writed:equ  21
close: equ  16
make:  equ  22
delete:equ  19
!*
dload: rst  5         !ignore blanks
       push h         !save h
       call fcbset    !set up file control block
       push d         !save the rest
       push b         
       lxi  d,fcb     !get fcb address
       mvi  c,open    !prepare to open file
       call cpm       !open it
       cpi  0ffh      !is it there?
       jz   qhow      !no, send error
       xra  a         !clear a
       sta  fcb+32    !start at record 0
       lxi  d,txtunf  !get beginning
load:  push d         !save dma address
       mvi  c,setdma  !
       call cpm       !set dma address
       mvi  c,readd   !
       lxi  d,fcb
       call cpm       !read sector
       cpi  1         !done?
       jc   rdmore    !no, read more
       jnz  qhow      !bad read
       mvi  c,close
       lxi  d,fcb 
       call cpm       !close file
       pop  d         !throw away dma add.
       pop  b         !get old registers back
       pop  d
       pop  h
       rst  6         !finish
rdmore:pop  d         !get dma address
       lxi  h,80h     !get 128
       dad  d         !add 128 to dma add.
       xchg           !put it back in d
       jmp  load      !and read some more
!*
dsave: rst  5         !ignore blanks
       push h         !save h
       call fcbset    !setup fcb
       push d
       push b         !save others
       lxi  d,fcb
       mvi  c,delete
       call cpm       !erase file if it exists
       lxi  d,fcb  
       mvi  c,make
       call cpm       !make a new one
       cpi  0ffh      !is there space?
       jz   qhow      !no, error
       xra  a         !clear a
       sta  fcb+32    !start at record 0
       lxi  d,txtunf  !get beginning
save:  push d         !save dma address
       mvi  c,setdma  !
       call cpm       !set dma address
       mvi  c,writed
       lxi  d,fcb 
       call cpm       !write sector
       ora  a         !set flags
       jnz  qhow      !if not zero, error
       pop  d         !get dma add. back
       lda  txtunf+1  !and msb of last add.
       cmp  d         !is d smaller?
       jc   savdon    !yes, done
       jnz  writmor   !dont test e if not equal
       lda  txtunf    !is e smaller?
       cmp  e
       jc   savdon    !yes, done
writmor:lxi  h,80h 
       dad  d         !add 128 to dma add.
       xchg           !get it back in d
       jmp  save      !write some more
savdon:mvi  c,close
       lxi  d,fcb 
       call cpm       !close file
       pop  b         !get registers back
       pop  d
       pop  h
       rst  6         !finish
!*
fcbset:lxi  h,fcb     !get file control block address
       mvi  m,0       !clear entry type
fnclr: inx  h         !next location
       mvi  m,' '     !clear to space
       mvi  a,fcb+8 and 255
       cmp  l         !done?
       jnz  fnclr     !no, do it again
       inx  h         !next
       mvi  m,'t'     !set file type to 'tbi'
       inx  h
       mvi  m,'b'
       inx  h
       mvi  m,'i'
exrc:  inx  h         !clear rest of fcb
       mvi  m,0
       mvi  a,fcb+15 and 255
       cmp  l         !done?
       jnz  exrc      !no, continue
       lxi  h,fcb+1   !get filename start
fn:    ldax d         !get character
       cpi  0dh       !is it a 'cr'
       rz             !yes, done
       cpi  '!'       !legal character?
       jc   qwhat     !no, send error
       cpi  '['       !again
       jnc  qwhat     !ditto
       mov  m,a        !save it in fcb
       inx  h         !next
       inx  d
       mvi  a,fcb+9 and 255
       cmp  l         !last?
       jnz  fn        !no, continue
       ret            !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 0
       call fndln     !find this or next line
ls1:   jc   rstart    !c:passed txtunf 
       call prtln     !print the line
       call chkio     !stop iff hit control-c 
       call fndlnp    !find next line
       jmp  ls1       !and loop back 
!* 
print: mvi  c,6       !c = # of spaces 
       rst  1         !iff null list & "!"
       defb 73q 
       defb 6q 
       call crlf      !give cr-lf and
       jmp  runsml    !continue same line
pr2:   rst  1         !iff null list (cr) 
       defb 0dh
       defb 6q
       call crlf      !also give cr-lf and 
       jmp  runnxl    !go to next line 
pr0:   rst  1         !else is it format?
       defb '#' 
       defb 5q
       rst  3         !yes, evaluate expr. 
       mov  c,l       !and save it in c
       jmp  pr3       !look for more to print
pr1:   call qtstg     !or is it a string?
       jmp  pr8       !iff not, must be expr. 
pr3:   rst  1         !iff ",", go find next
       defb ',' 
       defb 6q
       call fin       !in the list.
       jmp  pr0       !list continues
pr6:  call crlf      !list ends 
       rst  6 
pr8:   rst  3         !evaluate the expr 
       push b 
       call prtnum    !print the value 
       pop  b 
       jmp  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         !parameters
       push d         !and text pointer
       call fndln     !find the target line
       jnz  ahow      !not there. say "how?" 
       lhld currnt    !found it, save old
       push h         !'currnt' old 'stkgos' 
       lhld stkgos
       push h 
       lxi  h,0       !and load new ones 
       shld lopvar
       dad  sp
       shld stkgos
       jmp  runtsl    !then run that line
return:call endchk    !there must be a 0dh
       lhld stkgos    !old stack pointer 
       mov  a,h       !0 means not exist 
       ora  l 
       jz   qwhat     !so, we say: "what?" 
       sphl           !else, restore it
       pop  h 
       shld stkgos    !and the old 'stkgos'
       pop  h 
       shld currnt    !and the old 'currnt'
       pop  d         !old text pointer
       call popa      !old "for" parameters
       rst  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 area
       call setval    !set the control var.
       dcx  h         !hl is its address 
       shld lopvar    !save that 
       lxi  h,tab5-1  !use 'exec' to look
       jmp  exec      !for the word 'to' 
fr1:   rst  3         !evaluate the limit
       shld loplmt    !save that 
       lxi  h,tab6-1  !use 'exec' to look
       jmp  exec      !for the word 'step'
fr2:   rst  3         !found it, get step
       jmp  fr4 
fr3:   lxi  h,1q      !not found, set to 1 
fr4:   shld lopinc    !save that too 
fr5:   lhld currnt    !save current line # 
       shld lopln 
       xchg           !and text pointer
       shld loppt 
       lxi  b,12q     !dig into stack to 
       lhld lopvar    !find 'lopvar' 
       xchg 
       mov  h,b 
       mov  l,b       !hl=0 now
       dad  sp        !here is the stack 
       defb 76q 
fr7:   dad  b         !each level is 10 deep 
       mov  a,m       !get that old 'lopvar' 
       inx  h 
       ora  m 
       jz   fr8       !0 says no more in it
       mov  a,m 
       dcx  h 
       cmp  d         !same as this one? 
       jnz  fr7 
       mov  a,m       !the other half? 
       cmp  e 
       jnz  fr7 
       xchg           !yes, found one
       lxi  h,0q
       dad  sp        !try to move sp
       mov  b,h 
       mov  c,l 
       lxi  h,12q 
       dad  d 
       call mvdown    !and purge 10 words
       sphl           !in the stack
fr8:   lhld loppt     !job done, restore de
       xchg 
       rst  6         !and continue
!* 
next:  rst  7         !get address of var. 
       jc   qwhat     !no variable, "what?"
       shld varnxt    !yes, save it
nx0:   push d         !save text pointer 
       xchg 
       lhld lopvar    !get var. in 'for' 
       mov  a,h 
       ora  l         !0 says never had one
       jz   awhat     !so we ask: "what?"
       rst  4         !else we check them
       jz   nx3       !ok, they agree
       pop  d         !no, let's see 
       call popa      !purge current loop
       lhld varnxt    !and pop one level 
       jmp  nx0       !go check again
nx3:   mov  e,m       !come here when agreed 
       inx  h 
       mov  d,m       !de=value of var.
       lhld lopinc
       push h 
       dad  d         !add one step
       xchg 
       lhld lopvar    !put it back 
       mov  m,e 
       inx  h 
       mov  m,d 
       lhld loplmt    !hl->limit 
       pop  psw       !old hl
       ora  a 
       jp   nx1       !step > 0
       xchg 
nx1:   call ckhlde    !compare with limit
       pop  d         !restore text pointer
       jc   nx2       !outside limit 
       lhld lopln     !within limit, go
       shld currnt    !back to the saved 
       lhld loppt     !'currnt' and text 
       xchg           !pointer 
       rst  6 
nx2:   call popa      !purge this loop 
       rst  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  l 
       jnz  runsml    !no, continue
       call fndskp    !yes, skip rest of line
       jnc  runtsl
       jmp  rstart
!* 
inperr:lhld stkinp    !*** inperr ***
       sphl           !restore old sp
       pop  h         !and old 'currnt'
       shld currnt
       pop  d         !and old text pointer
       pop  d         !redo input
!* 
input: equ  $         !*** input *** 
ip1:   push d         !save in case of error 
       call qtstg     !is next item a string?
       jmp  ip2       !no
       rst  7         !yes. but followed by a
       jc   ip4       !variable?   no. 
       jmp  ip3       !yes.  input variable
ip2:   push d         !save for 'prtstg' 
       rst  7         !must be variable now
       jc   qwhat     !"what?" it is not?
       ldax d         !get ready for 'rtstg'
       mov  c,a 
       sub  a 
       stax d 
       pop  d 
       call prtstg    !print string as prompt
       mov  a,c       !restore text
       dcx  d 
       stax d 
ip3:   push d         !save in case of error 
       xchg 
       lhld currnt    !also save 'currnt'
       push h 
       lxi  h,ip1     !a negative number 
       shld currnt    !as a flag 
       lxi  h,0q      !save sp too 
       dad  sp
       shld stkinp
       push d         !old hl
       mvi  a,72q     !print this too
       call getln     !and get a line
ip3a:  lxi  d,buffer  !points to buffer
       rst  3         !evaluate input
       nop            !can be 'call endchk'
       nop
       nop
       pop  d         !ok, get old hl
       xchg 
       mov  m,e       !save value in var.
       inx  h 
       mov  m,d 
       pop  h         !get old 'currnt'
       shld currnt
       pop  d         !and old text pointer
ip4:   pop  psw       !purge junk in stack 
       rst  1         !is next ch. ','?
       defb ',' 
       defb 3q
       jmp  ip1       !yes, more items.
ip5:   rst  6 
!* 
deflt: ldax d         !*** deflt *** 
       cpi  0dh       !empty line is ok
       jz   lt1       !else it is 'let'
!* 
let:   call setval    !*** let *** 
       rst  1         !set value to var. 
       defb ',' 
       defb 3q
       jmp  let       !item by item
lt1:   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> value
expr1: lxi  h,tab8-1  !lookup rel.op.
       jmp  exec      !go do it
xp11:  call xp18      !rel.op.">=" 
       rc             !no, return hl=0 
       mov  l,a       !yes, return hl=1
       ret
xp12:  call xp18      !rel.op."#"
       rz             !false, return hl=0
       mov  l,a       !true, return hl=1 
       ret
xp13:  call xp18      !rel.op.">"
       rz             !false 
       rc             !also false, hl=0
       mov  l,a       !true, hl=1
       ret
xp14:  call xp18      !rel.op."<=" 
       mov  l,a       !set hl=1
       rz             !rel. true, return 
       rc 
       mov  l,h       !else set hl=0 
       ret
xp15:  call xp18      !rel.op."="
       rnz            !false, retrun hl=0
       mov  l,a       !else set hl=1 
       ret
xp16:  call xp18      !rel.op."<"
       rnc            !false, return hl=0
       mov  l,a       !else set hl=1 
       ret
xp17:  pop  h         !not rel.op. 
       ret            !return hl=<expr2> 
xp18:  mov  a,c       !subroutine for all
       pop  h         !rel.op.'s 
       pop  b 
       push h         !reverse top of stack
       push b 
       mov  c,a 
       call expr2     !get 2nd <expr2> 
       xchg           !value in de now 
       xthl           !1st <expr2> in hl 
       call ckhlde    !compare 1st with 2nd
       pop  d         !restore text pointer
       lxi  h,0q      !set hl=0, a=1 
       mvi  a,1 
       ret
!* 
expr2: rst  1         !negative sign?
       defb '-' 
       defb 6q
       lxi  h,0q      !yes, fake '0-'
       jmp  xp26      !treat like subtract 
xp21:  rst  1         !positive sign?  ignore
       defb '+' 
       defb 0q
xp22:  call expr3     !1st <expr3> 
xp23:  rst  1         !add?
       defb '+' 
       defb 25q 
       push h         !yes, save value 
       call expr3     !get 2nd<expr3> 
xp24:  xchg           !2nd in de 
       xthl           !1st in hl 
       mov  a,h       !compare sign
       xra  d 
       mov  a,d 
       dad  d 
       pop  d         !restore text pointer
       jm   xp23      !1st 2nd sign differ 
       xra  h         !1st 2nd sign equal
       jp   xp23      !so isp result
       jmp  qhow      !else we have overflow 
xp25:  rst  1         !subtract? 
       defb '-' 
       defb 203q
xp26:  push h         !yes, save 1st <expr3> 
       call expr3     !get 2nd <expr3> 
       call chgsgn    !negate
       jmp  xp24      !and add them
!* 
expr3: call expr4     !get 1st <expr4> 
xp31:  rst  1         !multiply? 
       defb '*' 
       defb 54q 
       push h         !yes, save 1st 
       call expr4     !and get 2nd <expr4> 
       mvi  b,0q      !clear b for sign
       call chksgn    !check sign
       xchg           !2nd in de now 
       xthl           !1st in hl 
       call chksgn    !check sign of 1st 
       mov  a,h       !is hl > 255 ? 
       ora  a 
       jz   xp32      !no
       mov  a,d       !yes, how about de 
       ora  d 
       xchg           !put smaller in hl 
       jnz  ahow      !also >, will overflow 
xp32:  mov  a,l       !this is dumb
       lxi  h,0q      !clear result
       ora  a         !add and count 
       jz   xp35
xp33:  dad  d 
       jc   ahow      !overflow
       dcr  a 
       jnz  xp33
       jmp  xp35      !finished
xp34:  rst  1         !divide? 
       defb '/' 
       defb 104q
       push h         !yes, save 1st <expr4> 
       call expr4     !and get 2nd one 
       mvi  b,0q      !clear b for sign
       call chksgn    !check sign of 2nd 
       xchg           !put 2nd in de 
       xthl           !get 1st in hl 
       call chksgn    !check sign of 1st 
       mov  a,d       !divide by 0?
       ora  e 
       jz   ahow      !say "how?"
       push b         !else save sign
       call divide    !use subroutine
       mov  h,b       !result in hl now
       mov  l,c 
       pop  b         !get sign back 
xp35:  pop  d         !and text pointer
       mov  a,h       !hl must be +
       ora  a 
       jm   qhow      !else it is overflow 
       mov  a,b 
       ora  a 
       cm   chgsgn    !change sign iff needed 
       jmp  xp31      !look or more terms 
!* 
expr4: lxi  h,tab4-1  !find function in tab4 
       jmp  exec      !and go do it
xp40:  rst  7         !no, not a function
       jc   xp41      !nor a variable
       mov  a,m       !variable
       inx  h 
       mov  h,m       !value in hl 
       mov  l,a 
       ret
xp41:  call tstnum    !or is it a number 
       mov  a,b       !# of digit
       ora  a 
       rnz            !ok
parn:  rst  1         !no digit, must be 
       defb '(' 
       defb 5q
       rst  3         !"(expr)"
       rst  1 
       defb ')' 
       defb 1q
xp42:  ret
xp43:  jmp  qwhat     !else say: "what?" 
!* 
rnd:   call parn      !*** rnd(expr) *** 
       mov  a,h       !expr must be +
       ora  a 
       jm   qhow
       ora  l         !and non-zero
       jz   qhow
       push d         !save both 
       push h 
       lhld ranpnt    !get memory as random
       lxi  d,lstrom  !number
       rst  4 
       jc   ra1       !wrap around iff last 
       lxi  h,start 
ra1:   mov  e,m 
       inx  h 
       mov  d,m 
       shld ranpnt
       pop  h 
       xchg 
       push b 
       call divide    !rnd(n)=mod(m,n)+1 
       pop  b 
       pop  d 
       inx  h 
       ret
!* 
abs:   call parn      !*** abs(expr) *** 
       call chksgn    !check sign
       mov  a,h       !note that -32768
       ora  h         !cannot change sign
       jm   qhow      !so say: "how?"
       ret
size:  lhld txtunf    !*** size ***
       push d         !get the number of free
       xchg           !bytes between 'txtunf'
sizea: lxi  h,varbgn  !and 'varbgn'
       call subde 
       pop  d 
       ret
!*
!*********************************************************
!*
!*   *** 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  3 
       mov  a,l
       sta  outio + 1
       rst  1
       defb ','
       defb 2fh
       rst  3
       mov  a,l
       call outio
       rst  1
       defb ','
       defb 03h
       jmp  outcmd 
       rst  6
waitcm:rst  3
       mov  a,l
       sta  waitio + 1
       rst  1
       defb ','
       defb 1bh
       rst  3
       push h
       rst  1
       defb ','
       defb 7h
       rst  3
       mov  a,l
       pop  h
       mov  h,a
       jmp  $ + 2
       mvi  h,0
       jmp  waitio
inp:   call parn
       mov  a,l
       sta  inpio + 1
       mvi  h,0
       jmp  inpio
       jmp  qwhat
poke:  rst  3
       push h
       rst  1
       defb ','
       defb 12h
       rst  3
       mov  a,l
       pop  h
       mov  m,a
       rst  1
       defb ',',03h
       jmp  poke
       rst 6
peek:  call parn
       mov  l,m
       mvi  h,0
       ret
       jmp  qwhat
usr:   push b
       rst  1
       defb '(',28d    !qwhat
       rst  3          !expr
       rst  1
       defb ')',7      !pasparm
       push d
       lxi  d,usret
       push d
       push h
       ret             !call usr routine
pasprm:rst  1
       defb ',',14d
       push h
       rst  3
       rst  1
       defb ')',9
       pop  b
       push d
       lxi  d,usret
       push d
       push b
       ret             !call usr routine
usret: pop  d
       pop  b
       ret
       jmp  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 de
       mvi  h,0 
       call dv1 
       mov  b,c       !save result in b
       mov  a,l       !(remainder+l)/de
       pop  h 
       mov  h,a 
dv1:   mvi  c,377q    !result in c 
dv2:   inr  c         !dumb routine
       call subde     !divide by subtract
       jnc  dv2       !and count 
       dad  d 
       ret
!* 
subde: mov  a,l       !*** subde *** 
       sub  e         !subtract de from
       mov  l,a       !hl
       mov  a,h 
       sbb  d 
       mov  h,a 
       ret
!* 
chksgn:mov  a,h       !*** chksgn ***
       ora  a         !check sign of hl
       rp             !iff -, change sign 
!* 
chgsgn:mov  a,h       !*** chgsgn ***
       cma            !change sign of hl 
       mov  h,a 
       mov  a,l 
       cma
       mov  l,a 
       inx  h 
       mov  a,b       !and also flip b 
       xri  200q
       mov  b,a 
       ret
!* 
ckhlde:mov  a,h 
       xra  d         !same sign?
       jp   ck1       !yes, compare
       xchg           !no, xch and comp
ck1:   rst  4 
       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.  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 variable 
       push h         !save address of var.
       rst  1         !pass "=" sign 
       defb '=' 
       defb 10q 
       rst  3         !evaluate expr.
       mov  b,h       !value in bc now 
       mov  c,l 
       pop  h         !get address 
       mov  m,c       !save value
       inx  h 
       mov  m,b 
       ret
sv1:   jmp  qwhat     !no "=" sign 
!* 
fin:   rst  1         !*** fin *** 
       defb 73q 
       defb 4q 
       pop  psw       !"!", purge ret addr.
       jmp  runsml    !continue same line
fi1:   rst  1         !not "!", is it cr?
       defb 0dh
       defb 4q 
       pop  psw       !yes, purge ret addr.
       jmp  runnxl    !run next line 
fi2:   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 character
       push psw       !at where old de ->
       sub  a         !and put a 0 there 
       stax d 
       lhld currnt    !get current line #
       push h 
       mov  a,m       !check the value 
       inx  h 
       ora  m 
       pop  d 
       jz   rstart    !iff zero, just rerstart
       mov  a,m       !iff negative,
       ora  a 
       jm   inperr    !redo input
       call prtln     !else print the line 
       dcx  d         !upto where the 0 is 
       pop  psw       !restore the character 
       stax d 
       mvi  a,77q     !printt a "?" 
       rst  2 
       sub  a         !and the rest of the 
       call prtstg    !line
       jmp  rstart
qsorry: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 init
gl1:   call chkio     !check keyboard
       jz   gl1       !no input, wait
       cpi  177q      !delete lst character?
       jz   gl3       !yes 
       cpi  12q       !ignore lf 
       jz   gl1 
       ora  a         !ignore null 
       jz   gl1 
       cpi  134q      !delete the whole line?
       jz   gl4       !yes 
       stax d         !else, save input
       inx  d         !and bump pointer
       cpi  15q       !was it cr?
       jnz  gl2       !no
       mvi  a,12q     !yes, get line feed
       rst  2         !call outc and line feed
       ret            !we've got a line
gl2:   mov  a,e       !more free room?
       cpi  bufend and 0ffh
       jnz  gl1       !yes, get next input 
gl3:   mov  a,e       !delete last character 
       cpi  buffer and 0ffh    !but do we have any? 
       jz   gl4       !no, redo whole line 
       dcx  d         !yes, backup pointer 
       mvi  a,'_'     !and echo a back-space 
       rst  2 
       jmp  gl1       !go get next input 
gl4:   call crlf      !redo entire line
       mvi  a,136q    !cr, lf and up-arrow 
       jmp  getln 
!* 
fndln: mov  a,h       !*** fndln *** 
       ora  a         !check sign of hl
       jm   qhow      !it cannt be -
       lxi  d,txtbgn  !init. text pointer
!* 
fndlnp:equ  $         !*** fndlnp ***
fl1:   push h         !save line # 
       lhld txtunf    !check iff we passed end
       dcx  h 
       rst  4 
       pop  h         !get line # back 
       rc             !c,nz passed end 
       ldax d         !we did not, get byte 1
       sub  l         !is this the line? 
       mov  b,a       !compare low order 
       inx  d 
       ldax d         !get byte 2
       sbb  h         !compare high order
       jc   fl2       !no, not there yet 
       dcx  d         !else we either found
       ora  b         !it, or it is not there
       ret            !nc,z:found! nc,nz:no
!* 
fndnxt:equ  $         !*** fndnxt ***
       inx  d         !find next line
fl2:   inx  d         !just passed byte 1 & 2
!* 
fndskp:ldax d         !*** fndskp ***
       cpi  0dh       !try to find 0dh
       jnz  fl2       !keep looking
       inx  d         !found cr, skip over 
       jmp  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 characterr 
       inx  d         !bump pointer
       cmp  b         !same as old a?
       rz             !yes, return 
       rst  2         !else print it 
       cpi  0dh       !was it a cr?
       jnz  ps1       !no, next
       ret            !yes, return 
!* 
qtstg: rst  1         !*** qtstg *** 
       defb '"' 
       defb 17q 
       mvi  a,42q     !it is a " 
qt1:   call prtstg    !print until another 
       cpi  0dh       !was last one a cr?
       pop  h         !return address
       jz   runnxl    !was cr, run next line 
qt2:   inx  h         !skip 3 bytes on return
       inx  h 
       inx  h 
       pchl           !return
qt3:   rst  1         !is it a ' ? 
       defb 47q 
       defb 5q
       mvi  a,47q     !yes, do same
       jmp  qt1       !as in " 
qt4:   rst  1         !is it back-arrow? 
       defb 137q
       defb 10q 
       mvi  a,215q    !yes, 0dhwithout lf!!
       rst  2         !do it twice to give 
       rst  2         !tty enough time 
       pop  h         !return address
       jmp  qt2 
qt5:   ret            !none of above 
!* 
prtnum push d         !*** prtnum ***
       lxi  d,12q     !decimal 
       push d         !save as a flag
       mov  b,d       !b=sign
       dcr  c         !c=spaces
       call chksgn    !check sign
       jp   pn1       !no sign 
       mvi  b,55q     !b=sign
       dcr  c         !'-' takes space 
pn1:   push b         !save sign & space 
pn2:   call divide    !devide hl by 10 
       mov  a,b       !result 0? 
       ora  c 
       jz   pn3       !yes, we got all 
       xthl           !no, save remainder
       dcr  l         !and count space 
       push h         !hl is old bc
       mov  h,b       !move result to bc 
       mov  l,c 
       jmp  pn2       !and divide by 10
pn3:   pop  b         !we got all digits in
pn4:   dcr  c         !the stack 
       mov  a,c       !look at space count 
       ora  a 
       jm   pn5       !no leading blanks 
       mvi  a,40q     !leading blanks
       rst  2 
       jmp  pn4       !more? 
pn5:   mov  a,b       !print sign
       rst  2         !maybe - or null 
       mov  e,l       !last remainder in e 
pn6:   mov  a,e       !check digit in e
       cpi  12q       !10 is flag for no more
       pop  d 
       rz             !iff so, return 
       adi  60q         !else convert to ascii
       rst  2         !and print the digit 
       jmp  pn6       !go back for more
!* 
prtln: ldax d         !*** prtln *** 
       mov  l,a       !low order line #
       inx  d 
       ldax d         !high order
       mov  h,a 
       inx  d 
       mvi  c,4q      !print 4 digit line #
       call prtnum
       mvi  a,40q     !followed by a blank 
       rst  2 
       sub  a         !and then the text 
       call prtstg
       ret
!* 
!**************************************************************
!* 
!* *** 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, return 
       ldax d         !get one byte
       stax b         !move it 
       inx  d         !increase both pointers
       inx  b 
       jmp  mvup      !until done
!* 
mvdown:mov  a,b       !*** mvdown ***
       sub  d         !test iff de = bc 
       jnz  md1       !no, go move 
       mov  a,c       !maybe, other byte?
       sub  e 
       rz             !yes, return 
md1:   dcx  d         !else move a byte
       dcx  h         !but first decrease
       ldax d         !both pointers and 
       mov  m,a       !then do it
       jmp  mvdown    !loop back 
!* 
popa:  pop  b         !bc = return addr. 
       pop  h         !restore lopvar, but 
       shld lopvar    !=0 means no more
       mov  a,h 
       ora  l 
       jz   pp1       !yep, go return
       pop  h         !nop, restore others 
       shld lopinc
       pop  h 
       shld loplmt
       pop  h 
       shld lopln 
       pop  h 
       shld loppt 
pp1:   push b         !bc = return addr. 
       ret
!* 
pusha: lxi  h,stklmt  !*** pusha *** 
       call chgsgn
       pop  b         !bc=return address 
       dad  sp        !is stack near the top?
       jnc  qsorry    !yes, sorry for that.
       lhld lopvar    !else save loop var.s
       mov  a,h       !but iff lopvar is 0
       ora  l         !that will be all
       jz   pu1 
       lhld loppt     !else, more to save
       push h 
       lhld lopln 
       push h 
       lhld loplmt
       push h 
       lhld lopinc
       push h 
       lhld lopvar
pu1:   push h 
       push 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  a 
oc2:   jnz  oc3       !it is on
       pop  psw       !it is off 
       ret            !restore af and return 
oc3:   pop  a         !get old a back
       push b         !save b on stack
       push d         !and d
       push h         !and h too
       sta  outcar    !save character
       mov  e,a       !put char. in e for cpm
       mvi  c,2       !get conout command
       call cpm       !call cpm and do it
       lda  outcar    !get char. back
       cpi  0dh       !was it a 'cr'?
       jnz  done      !no, done
       mvi  e,0ah     !get linefeed
       mvi  c,2       !and conout again
       call cpm       !call cpm
done:  lda  outcar    !get character back
idone: pop  h         !get h back
       pop  d         !and d
       pop  b         !and b too
       ret            !done at last
chkio: push b         !save b on stack
       push d         !and d
       push h         !then h
       mvi  c,11      !get constat word
       call cpm       !call the bdos
       ora  a         !set flags
       jnz  ci1       !if ready get character
       jmp  idone     !restore and return
ci1:   mvi  c,1       !get conin word
       call cpm       !call the bdos
       cpi  0fh       !is it control-o?
       jnz  ci2       !no, more checking
       lda  ocsw      !control-o  flip ocsw
       cma            !on to off, off to on
       sta  ocsw      !and put it back
       jmp  chkio     !and get another character
ci2:   cpi  3         !is it control-c?
       jnz  idone     !return and restore if not
       jmp  rstart    !yes, restart tbi
lstrom:equ  $         !all above can be rom
outio: out  0ffh
       ret
waitio:in   0ffh
       xra  h
       ana  l
       jz   waitio
       rst  6
inpio: in   0ffh
       mov  l,a
       ret
outcar:defb 0         !output char. storage
ocsw:  defb 0ffh      !switch for output
currnt:defw 0         !points to current line
stkgos:defw 0         !saves sp in 'gosub'
varnxt:defw 0         !temporary storage
stkinp:defw 0         !saves sp in 'input'
lopvar:defw 0         !'for' loop save area
lopinc:defw 0         !increment
loplmt:defw 0         !limit
lopln: defw 0         !line number
loppt: defw 0         !text pointer
ranpnt:defw start     !random number pointer
txtunf:defw txtbgn    !->unfilled text area
txtbgn:defvs 1         !text save area begins 
msg1:  defb 7fh,7fh,7fh,'Tiny basic ver. 3.1',0dh 
init:  mvi  a,0ffh
       sta  ocsw      !turn on output switch 
       mvi  a,0ch     !get form feed 
       rst  2         !send to crt 
patlop:sub  a         !clear accumulator
       lxi  d,msg1    !get init message
       call prtstg    !send it
lstram:lda  7         !get fbase for top
       sta  rstart+2
       dcr  a         !decrement for other pointers
       sta  ss1a+2    !and fix them too
       sta  tv1a+2
       sta  st3a+2
       sta  st4a+2
       sta  ip3a+2
       sta  sizea+2
       sta  getln+3
       sta  pusha+2
       lxi  h,st1     !get new start jump
       shld start+1   !and fix it
       jmp  st1
       jmp  qwhat     !print "what?" iff wrong
txtend:equ  $         !text save area ends 
varbgn:defvs   2*27      !variable @(0)
       defvs   1         !extra byte for buffer
buffer:defvs   80        !input buffer
bufend:equ  $         !buffer ends
       defvs   40        !extra bytes for stack
stklmt:equ  $         !top limit for stack
       org  2000h
stack: equ  $         !stack starts here

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.