;EVAL 06/10/80 ;XYBASIC Interpreter Source Module ;Copyright (C) 1978, 1979, 1980 by Mark Williams Company, Chicago ;expression evaluator ;EVAL evaluates an expression. ;Space above the control stack is used as a working stack, called the ;E(xpression)STACK. Each expression component already scanned is stored on ;the ESTACK as a byte identifier specifying its type (e.g. delimiter, paren, ;unary op) and precedence. Fns and ops have an addtional byte containing ;the fn id. Values are stored as a type byte followed by value bytes. String ;values saved in the ESTACK are addressed by string temporaries, for access ;during garbage collection. ;Many types of errors may occur during EVAL. ;SN error occurs if an expression is illformed. ;OM error occurs if ESTACK overflows the available space. ;TM error occurs if arguments are of wrong type. ;FC, OV, BY and similar errors can occur during fn/op execution. ;EX errors should never occur, and indicate something is wrong. ;Retn: Carry Set iff no found, i.e. first char parsed is bad ; A type token of result, preserved if Carry ; BC, DE preserved ; HL pointer to type (followed by value bytes), preserved if Carry eval: push4 ;save all in case failure mvi c,isdel+1 ;delimiter type/prec to C ;EVAL0 saves the token in C on the ESTACK. If it is a fn or op, ;the id in B is also ESTACKed. eval0: mov a,c ;fetch type/prec cpi isop1 jc eva0a ;not a fn/op, just ESTACK type/prec mov c,b ;else id to C mov b,a ;save type/prec in B call espsh ;ESTACK the id mov c,b ;restore type/prec to C eva0a: call espsh ;ESTACK the type/prec ;EVAL1 is the 'expecting value' state ;The next item parsed must be a literal, variable reference or 0-ary fn ;(evaluated and ESTACKed, then expecting op), or else a fn name, ( or ;unary op (ESTACKed, then expecting value). Any other acts as delimiter. eval1: lhld textp eva1a: mov a,m ;fetch next text char inx h cpi ' ' jz eva1a ;try next if space cpi 40H jc evlip ;0-3FH, must be literal or ( ora a jp evvar ;40-7FH, must be var ref call gttyp ;type to A, id info to BC jc evalx ;not a fn or op shld textp ;read the fn or op cpi isop1 jc evfn0 ; 0-ary or user-defined function jz eval0 ;ESTACK unary op cpi isop2 jz evopu ;binary op must be unary + or - eva1b: mvi d,'(' ;otherwise must be a function id call gtdsn ;so skip ( after fn jmp eval0 ;ESTACK type/prec and id and expect value ;EVAL2 pushes a value (type token and value bytes) to ESTACK. eval2: call esval ;value to ESTACK ;EVAL3 is the 'expecting op' state. ;The next item parsed should be a comma [fn(expr,expr)] or ;binary op [expr op expr] or ) [fn(expr) or fn(expr,expr) or (expr)]. ;Any other is treated as a delimiter. eval3: lhld estkp mov a,m ani tmask mov d,a ;previous type to D mov a,m ani pmask inr a mov e,a ;previous precedence + 1 to E lxi b,vbyts dad b ;address new ESTACK top shld estkp ;and update the pointer call gtcho ;look at next item call gttyp ;find its type cc evarp ;not an op, must be ) or comma or delimiter cpi isop2 ;check if binary op jnz eval4 ;no, must be delimiter -- reduce ;Compare precedences of current and previous op and reduce when appropriate. mov a,c ;fetch current type/prec ani pmask cmp e ;compare current to previous+1 jc eval4 ;reduce call gtcha ;else read current jmp eval0 ;and stack the info bytes ;At EVAL4 previous .>=. current precedence, so the stacktop is reduced eval4: lhld estkp lxi b,-vbyts+1 dad b ;address value on ESTACK mov a,d ;fetch previous type cpi isdel jz eval5 ;delimiter, done cpi isop1 jz evop1 ;unary op cpi isop2 jz evop2 ;binary op push h ;otherwise current char must be ) call gtcha ;so read it cpi ')' jnz snerr pop h ;and restore value pointer mov a,d ;restore previous type cpi iscom jz evfn2 ;comma -- do a binary function cpi islp jz evpar ;( ) becomes cpi isfn1 jz evfn1 ;unary function cpi isufn jz evuf1 ;unary user-defined function cpi isfn2 jz snerr ;binary fn with one arg jmp exerr ;else fatal EX error -- something wrong ;EVAL5 is the exit from a successful EVAL. eval5: pop d ;discard saved PSW value pop d ;and also saved HL value push h ;save return value for HL mov a,m ;fetch result type dcx h ;NB carry is reset from CPI ISDEL dcx h ;HL points to location before delimiter eval6: shld estkp ;reset ESTACK past delimiter pop3: pop h ;value to HL pop d pop1: pop b ;restore registers ret ;and done ;EVALX is the abnormal exit from EVAL. If ESTACK contains only the ;initial delimiter, returns with Carry. Otherwise issues SN error. evalx: lhld estkp mov a,m ;fetch top ESTACK item cpi isdel+1 ;compare to delimiter type/prec jnz snerr ;ESTACK nonempty, SN error dcx h ;else point past delimiter pop psw ;restore A stc ;set Carry jmp eval6 ;restore and return ;ESVAL pushes a value to the ESTACK. ;Entry: A type token ; BC value if integer ; C,DE value if string ; (TEMP),BCD value if floating esval: lhld estkp inx h mov m,a ;store type token inx h ;point to first value byte if float cpi sngst jnz esvl1 ;not floating lda temp ;recover value for A if f9511 jmp sto95 ;store floating value on ESTACK and return else jmp fstor ;store floating value on ESTACK and return endif endif esvl1: if strng cpi strst jnz esvl2 ;not string call atemp ;assign ESTACK location to string temp mov m,c ;length to ESTACK inx h mov m,e inx h mov m,d ;location to ESTACK ret endif esvl2: cpi intst jnz exerr ;not integer mov m,c inx h mov m,b ;integer value to ESTACK ret ;EVLIP looks for ( and stacks if present, else falls through to... ;EVLIT evaluates a literal, jumps to EVALX if none. evlip: cpi '(' jnz evlit ;not (, must be literal shld textp ;read the ( mvi c,islp+2 ;type/prec to C jmp eval0 ;( to ESTACK and expect value next evlit: call gtlt1 ;get literal jc evalx ;exit if not found jmp eval2 ;and ESTACK it ;EVVAR evaluates a variable reference. evvar: call gtvar ;perform variable reference jc evalx ;no var if strng cpi strst ;check if type string jnz evva0 ;nonstring xchg lhld estkp inx h inx h ;point to value destination call atemp ;assign string temporary xchg ;restore value location mvi a,strst ;and restore type token endif evva0: mov b,h ;EVFN0 entry point mov c,l ;source to BC evva1: lhld estkp ;EVPAR entry point inx h mov m,a ;ESTACK the type inx h ;value destination to HL mvi e,vbyts-1 ;byte count to E call movd0 ;move value to ESTACK jmp eval3 ;and expect op ;EVOPU deals with unary + and -. evopu: mov a,b ;refetch id cpi plust-udfnt jz eval1 ;ignore unary plus lxi b,isop1+12 ;id=0 to B, type/prec to C for unary minus cpi mint-udfnt jz eval0 ;stack unary minus call bakup ;else unread the binary op jmp evalx ;and exit ;EVPAR reduces () to . evpar: mov a,m ;type to A push h dcx h ;address ( dcx h ;address previous shld estkp ;reset ESTACK pop b inx b ;value location to BC jmp evva1 ;and move down the value ;EVARP deals with ) or comma in expecting op state, loading C with type/prec. ;The previous item must be checked because of cases like DIM A (2,3). evarp: mov a,d ;fetch previous type cpi isdel jz evar1 ;previous was delim, treat current as delim mov a,b ;refetch current mvi c,isrp+2 ;right paren type/prec to C cpi ')' rz mvi c,iscom+3 ;comma type/prec to C cpi ',' jnz evar1 ;treat others as delimiters mvi a,iscom ;comma type to A cmp d ;compare to previous type mvi a,isop2 ;for compare on return rnz ;return unless currant and previous both comma dcr e ;to allow successive commas for ternary fns ret evar1: mvi c,isdel ;treat any other as delimiter ret ;EVFN0 evaluates a 0-ary function and evaluates (if 0-ary) ;or ESTACKs (if N-ary) a user-defined function. evfn0: cpi isfn0 mov a,b ;fn id to A jz excut ;execute 0-ary function ;The current item is a user-defined function call. call gtnam ;get function name dcx h push h ;save TEXTP mvi a,ufnst call stlk0 ;look up the fn name jc snerr ;not found lda varty mov c,a ;save desired result type in C ldax d ;fetch type ani 0E0H ;mask off type bytes jz exuf0 ;execute 0-ary user-defined function cpi 20H jnz fcerf ;not 0-ary or N-ary, must be active already ;N-ary user-definable function information is ESTACKed. push b ;save desired result type push h mov b,d mov c,e ;type byte addr to BC call epsh2 ;and ESTACKed pop h mov c,m inx h mov b,m ;bound var addr to BC call epsh2 ;and ESTACKed pop b ;recover desired result type call espsh ;and ESTACK it pop h ;pop TEXTP mvi c,isufn+2 ;type token to C jmp eva1b ;skip ( and ESTACK type exuf0: mov a,m inx h mov h,m mov l,a ;function body address to HL shld textp ;reset TEXTP to scan fn body lhld symta push h ;save SYMTA mov a,c ;desired result type to A jmp exufn ;and execute the fn ;EVUFN evaluates a user-defined function. evuf1: mvi b,1 ;number of args is in B evufn: xchg ;bound var val addr to DE lhld textp push h ;save TEXTP lhld symta push h ;and SYMTA lxi h,-7 dad d shld estkp ;reset ESTACK call modem ;fetch type byte addr push d ;and save it call modem ;bound var addr to DE xchg shld textp ;bound var addr to TEXTP xchg inx h mov a,m push psw ;save desired result type inx h inx h ;point to bound var value evuf2: push h ;save value location push b ;save # args left push h ;and save value location call gtnam ;scan the bound var jc exerr mov c,a ;name length to C call fdva1 ;and build a new symbol table entry for it inx h ;point to value destination xthl ;save destination, bound var value loc to HL lda varty ;bound var type call cnvrt ;convert to desired type if strng cpi strst cz scopv ;free temp and copy to string space if string endif inx h ;point to value xthl ;destination to HL pop b ;value loc to BC call bytsd ;byte count to DE call moved ;copy value to destination pop b ;arg count to B dcr b ;and decremented jz evuf3 ;no more args mvi d,',' call gtdsn ;skip comma pop h ;value location to HL lxi d,vbyts+1 dad d ;address next value jmp evuf2 ;and repeat for next bound var evuf3: pop h ;pop saved value location call gtreq ;skip ) = after last bound var pop psw ;restore desired result type pop d ;type byte location to DE ;and execute the fn ;EXUFN executes a user-defined function. ;Entry: A desired result type ; DE symbol table FN entry type byte addr ; Stacked SYMTA and TEXTP values exufn: push psw ldax d ;refetch type byte ori 80H ;turn on active bit stax d ;and store xchg lxi d,stack+stakm-stakl call cplde xchg dad sp ;check for stack overflow jnc fcerf ;fatal FC error, recursion too deep call eval ;evaluate the fn body push psw ;save Carry status ldax d ani 7FH stax d ;reset active bit pop psw xchg ;save result location in DE pop h mov a,h ;desired result type to A pop h shld symta ;reset SYMTA pop h shld textp ;and reset TEXTP jc fcerf ;unsuccessful eval xchg ;recover result location call cnvrt ;convert result to desired type inx h ;point to value jmp evva0 ;and ESTACK the result ;EVCOM deals with ternary MID$ and INSTR and N-ary user-defined fns. evcom: pop b ;pop the saved arg address mvi b,2 ;# args seen thus far to B evco1: inx h ;point to arg cpi isufn+2 jz evufn ;N-ary user-defined fn if strng cpi isfn2+2 jz evfn3 ;binary fn, must be ternary MID$ or INSTR endif cpi iscom+3 ;look for another comma jnz snerr ;not found, something is wrong inr b ;bump arg count dad d ;address next token of ESTACK mov a,m ;and fetch it jmp evco1 ;and look for more ;EVFN3 deals with the special cases ternary MID$ and ternary INSTR. ;Branches to routine IINST3 or MID3 with arg pointers to args 1,2,3 ;in BC,DE,HL and return address to EVAL2 stacked. if strng evfn3: mov a,b sui 3 jnz snerr ;number of args must be 3 push h dcx h dcx h mov a,m ;fetch fn id dcx h shld estkp ;reset ESTACK lxi d,vbyts+1 lxi h,eval2 xthl ;push return address, arg1 addr to HL push h dad d ;address arg2 push h dad d ;address arg3 pop d pop b cpi instt-udfnt jz inst3 ;ternary INSTR cpi midst-udfnt jz mid3 ;ternary MID$ jmp snerr ;else SN error endif ;EVOP1 evaluates a unary op. ;EVFN1 evaluates a unary function. ;EVFN2 evaluates a binary function. evfn2: push h ;save arg2 address lxi d,-vbyts-2 dad d mov a,m ;fetch type/prec byte cpi isfn2+2 jnz evcom ;must be ternary fn or N-ary user-defined fn inx h ;point to arg1 pop d ;arg2 address to DE evop1: evfn1: mov b,h mov c,l ;arg1 address to BC dcx h ;to type dcx h ;to id mov a,m ;fn id to A jmp evopa ;and execute it ;EVOP2 evaluates a binary operator. evop2: push h ;save arg2 address dcx h dcx h mov a,m ;op id to A lxi d,-vbyts dad d pop d ;arg2 addr to DE mov b,h mov c,l ;arg1 addr to BC evopa: dcx h shld estkp ;reset ESTACK ;and fall through to EXCUT to execute ;EXCUT executes a function or operator. ;Entry: A fn/op identifier ; BC pointer to arg1 (if any) in expr stack ; DE pointer to arg2 (if any) in expr stack ;Exit: A result token ; BC result if integer ; C,DE result if string ; (FACC) result if floating ;EXCUT fetches the desired arg types, fn/op address and result type from ;the fn/op type information table. If the desired arg type is nonzero ;(zero means no arg or arg of ambiguous type), the desired and actual ;arg types are compared and conversion to the desired type performed if ;necessary. If the desired arg type is integer, the ;pointer in BC or DE is replaced by the actual value of the arg. If ;the desired arg1 type is floating, the value is loaded to FACC. Otherwise ;EXCUT branches to the fn/op routine with the pointers in BC and DE intact. ;The result type is returned from the fn/op routine if type AMBST, otherwise ;the specified result type is taken as the actual result type. excut: mvi h,0 mov l,a ;id * 1 dad h ;id * 2 dad h ;id * 4 call adahl ;id * 5 push d lxi d,funta dad d ;+ base address = first info addr pop d mov a,m ;fetch arg2 desired type ora a jz excu1 ;skip conversion push b ;save arg1 address mov b,d mov c,e ;arg2 addr to BC call cnvtb ;convert arg2 to desire type mov d,b mov e,c ;and return to DE pop b ;restore arg1 excu1: inx h mov a,m ;fetch arg1 desired type ora a cnz cnvtb ;convert arg1 inx h mov a,m inx h push h ;save info pointer mov h,m mov l,a ;fn/op address to HL push h lxi h,exret xthl ;return addr to stack, fn/op addr to HL pchl ;execute it exret: pop h ;restore info pointer inx h sta temp ;save returned result type info mov a,m if f9511 cpi sngst ;check if 9511 floating point op jz exre1 ;yes endif ora a jnz eval2 ;return specified result type lxi h,temp mov a,m ;else pass returned result type mov m,e ;and save E in TEMP in case floating jmp eval2 ;and ESTACK the result if f9511 exre1: call fet95 ;fetch result from 9511 stack sta temp ;save A value mov a,m ;restore floating result type jmp eval2 ;and ESTACK the result endif ;CNVRT converts between differing variable types. ;Call: A desired type token ; HL pointer to value to convert ;Retn: A,BC,DE,HL preserved, HL now pointing to converted value. ;A nonfatal TM error occurs if conversion is impossible. ;An EX error should never occur. ;EVALT does an EVALS, then converts result to type LHSTY. evalt: call evals lda lhsty ;fetch desired type, fall through to... cnvrt: cmp m ;compare actual to desired type rz ;actual = desired, done push b push d push h ;save registers mov b,a ;save desired type in B if strng cpi strst jz tmerr ;desired=string, actual=numeric mov a,m ;fetch actual cpi strst jz tmerr ;desired=numeric, actual=string mov a,b ;restore desired endif if float cpi intst jz cnvfi ;convert floating to integer cpi sngst jnz exerr ;Float an integer value mov a,m ;fetch actual cpi intst jnz exerr ;must be integer mov m,b ;store new type = floating inx h if f9511 ;load integer value to 9511 stack push h ;save location mov a,m ;fetch low order byte out d9511 ;and stack inx h mov a,m ;fetch high order byte out d9511 ;and stack mvi a,flt95 call o9511 ;float the integer value call fet95 ;fetch value from 9511 stack pop h ;restore value destination call sto95 ;and store on ESTACK jmp cnvrx ;restore registers and return as below else push h mov c,m ;lsbyte of value to C inx h mov b,m ;msbyte to B if fpbcd call fflot ;float the value else lxi d,0 ;0 to DE mvi a,16 ;scale factor to A call flot1 ;float the value endif pop h call fstor ;and store the floated value jmp cnvrx ;restore registers and return as below endif ;CNVFI fixes a floating value. cnvfi: mov a,m ;fetch actual cpi sngst jnz exerr ;actual must be floating mov m,b ;store new actual type = integer inx h push h ;save location if f9511 call lod95 ;value to 9511 stack call int95 ;fix floating value jnc cnvf1 ;successful conversion error n, o, v ;else nonfatal OV error else call fload ;load value to FACC call iint ;convert to integer cc iover ;cannot fix, nonfatal OV error endif cnvf1: pop h ;restore value loc mov m,c ;store lsbyte inx h mov m,b ;store msbyte cnvrx: pop h ;restore location mov a,m ;type to A pop d ;restore DE pop b ;and BC ret endif tmerr: error c, T, M ;issue TM error and scan on ;CNVRB is called from EXCUT to perform type conversion on arguments. ;Call: A type token ; BC arg pointer ;Retn: A,DE,HL preserved ; BC arg pointer to converted type, or arg value if integer ; FACC arg value if floating cnvbi: mvi a,intst ;convert to integer cnvtb: push b xthl ;HL saved, pointer to HL call cnvrt ;do the conversion cpi intst jz cnvb1 ;integer, fetch it if float cpi sngst cz fetcf ;floating, load the FACC endif xthl ;pointer saved, HL restored pop b ;pointer to BC ret cnvb1: call fetci ;fetch integer value pop h ;restore HL ret ;GTTYP gets type and precedence info for a fn/op. ;Call: A token ;Retn: A token type (precedence masked off) ; B fn id (offset into function table), ISDEL if Carry ; C type/precedence byte ; DE, HL preserved ; Carry Set iff (A) not fn/op token gttyp: mov b,a ;char to B sui udfnt ;subtract first fn/op token rc cpi nfuns ;compare to # of fns cmc rc mov b,a ;id to B push h lxi h,typta call adahl ;addr type/prec byte mov c,m ;type/prec byte to C pop h mov a,c ani tmask ;type to A ret ;ESPSH pushes C to ESTACK and checks for OM error. ;Since a value will be ESTACKed next, ESPSH assures that ;at least VBYTS of space remain above the pushed token. epsh2: call espsh ;push one byte mov c,b ;and fall through to push another espsh: lhld symta lxi d,-vbyts-1 dad d xchg ;SYMTA - VBYTS - 1 to DE lhld estkp inx h ;ESTKP + 1 to HL call cmdhu jc eserr ;SYMTA <= ESTKP + VBYTS + 1 mov m,c ;store value shld estkp ;and update pointer ret eserr: lhld eofad call cspst ;reset control stack error c, O, M ;issue OM error and scan to next ;EVALS does an EVAL, issues SN error if no found. evals: call eval rnc jmp snerr ;IEVAL does an EVAL, converts result to integer and returns it in BC. ieval: push h call eval jc ieva1 ;no expr mvi a,intst call cnvrt ;convert to integer call fetci ;result integer to BC ieva1: pop h ;restore HL ret ;GTEXP does an IEVAL, issues SN error if no found. gtexp: call ieval rnc jmp snerr ;GTBEX gets a byte-valued expression. ;GTCBE gets a comma followed by a byte-valued expression, branches to SN error ;if comma is not present. gtcbe: call gtcom jc snerr gtbex: call gtexp jmp isbyt ;end of EVAL page