;;; CP/M truth table generator ;;; Supported operators: ;;; ~ (not), & (and), | (or), ^ (xor) and => (implies) ;;; Variables are A-Z, constants are 0 and 1. putch: equ 2 puts: equ 9 TVAR: equ 32 TCONST: equ 64 TOP: equ 96 TPAR: equ 128 TMASK: equ 31 TTYPE: equ 224 org 100h lxi h,80h ; Have we got a command line argument? mov a,m ana a lxi d,noarg ; If not, print error message and stop. mvi c,puts jz 5 add l ; Otherwise, 0-terminate the argument string inr a mov l,a mvi m,0 inx h mvi m,'$' ; And $-terminate it also for error messages lxi h,opstk ; Pointer to operator stack on the system stack push h lxi h,80h ; Start of command line lxi b,expr ; Start of expression (output queue for shunting yard) parse: inx h mvi a,' ' ; Ignore all whitespace cmp m jz parse mov a,m ; Load current character ana a ; Done? jz pdone mov d,a ; Store copy in D ori 32 ; Check for variable sui 'a' cpi 26 jnc pconst ; If not variable, go check constants ori TVAR ; It _is_ a variable stax b ; Store token inx b jmp parse ; Next token pconst: mov a,d ; Restore character sui '0' ; 0 or 1 are constants cpi 2 jnc pparen ; If not constant, go check parenthesis ori TCONST ; It _is_ a constant stax b ; Store token inx b jmp parse pparen: mov a,d ; Restore character sui '(' ; ( and ) are parentheses jz ppopen ; Open parenthesis dcr a jnz poper ; If not ( or ), check operators xthl ; Closing parenthesis - get operator stack closep: mov a,l ; If at beginning, missing ( - give error ana a jz emiss dcx h ; Back up pointer mov a,m ; Found it? cpi TPAR jnz closes ; If not, keep scanning xthl ; Get input string back jmp parse ; Keep parsing closes: stax b ; Not parenthesis - put token in output queue inx b jmp closep ; And keep going ppopen: xthl ; Get operator stack mvi m,TPAR ; Store open parenthesis inx h xthl ; Get input string jmp parse poper: push h ; Check tokens - keep input string mvi e,0 ; Counter lxi h,opers ; Operator pointer opscan: mov a,m ; Check against character cmp d ; Found it? jz opfind inr e ; Increment counter ana a ; Otherwise, is it zero? inx h jnz opscan ; If not, keep scanning eparse: lxi d,pserr ; It is zero - print a parse error mvi c,puts call 5 pop d mvi c,puts call 5 rst 0 opfind: cpi '=' ; Special case - is it '='? jnz opfin2 ; If so it should be followed by '>' xthl inx h mov a,m xthl cpi '>' jnz eparse ; '=' not part of '=>' is parse error opfin2: mvi d,0 ; Look up the precedence for this operator lxi h,prec dad d mov d,m ; Store it in D (D=prec E=operator number) pop h ; Restore input string xthl ; Get operator stack pointer oppop: mov a,l ; At beginning of operator stack? ana a jz oppush ; Then done - push current operator dcx h ; Check what's on top mov a,m inx h cpi TPAR ; Parenthesis? jz oppush ; Then done - push current operator push b ; Store output pointer for a while push h ; As well as operator stack pointer mvi b,0 ; Get index of operator from stack ani TMASK mov c,a lxi h,prec ; Find precedence dad b mov a,m ; Load precedence into A pop h ; Restore operator stack pointer pop b ; As well as output pointer cmp d ; Compare to operator from input jc oppush ; If input precedence higher, then push operator dcx h ; Otherwise, pop from operator stack, mov a,m stax b ; And store in output queue inx b jmp oppop ; Keep popping from operator stack oppush: mov a,e ; Get input operator ori TOP mov m,a ; Store on operator stack inx h xthl ; Switch to input string jmp parse emiss: lxi d,missp ; Error message for missing parentheses mvi c,puts call 5 rst 0 pdone: pop h ; Get operator stack pointer ppop: mov a,l ; Pop whatever is left off ana a jz cntvar dcx h mov a,m ; Get value cpi TPAR ; If we find a paranthesis then the parentheses jz emiss ; don't match stax b ; Store in output queue inx b jmp ppop cntvar: stax b ; Zero-terminate the expression lxi h,vused+25 ; See which variables are used in the expr xra a vzero: mov m,a dcr l jp vzero lxi d,expr vscan: ldax d ; Load expression element inx d ; Next one next time ana a ; Was it zero? jz vdone ; Then we're done mov b,a ; Store copy ani TTYPE ; Is it a variable? cpi TVAR jnz vscan ; If not, ignore it mov a,b ani TMASK mov l,a ; If so, mark it inr m jmp vscan vdone: call eval ; Run the evaluation once to catch errors lxi h,vused ; Print header mvi b,0 ; Character counter varhdr: mov a,m ; Current variable used? ana a jz varnxt ; If not, skip it inr b ; Two characters inr b push h ; Keep registers push b mvi c,putch ; Print letter mov a,l adi 'A' mov e,a call 5 mvi c,putch ; Print space mvi e,' ' call 5 pop b ; Restore registers pop h varnxt: inr l mov a,l cpi 26 jnz varhdr inr b ; Two characters for "| " inr b push b lxi d,dvdr mvi c,puts call 5 pop b lxi h,81h ; Print expression exhdr: inr b ; One character push b push h mov e,m mvi c,putch call 5 pop h pop b mov a,m ; Until zero reached ana a inx h jnz exhdr push b ; Keep count lxi d,nwln ; Print newline mvi c,puts call 5 pop b linhdr: push b ; Print dashes mvi c,putch mvi e,'-' call 5 pop b dcr b jnz linhdr lxi h,vars ; Set all variables to 0 xra a zero: mov m,a inr l jnz zero mloop: lxi d,nwln ; Print newline mvi c,puts call 5 lxi h,vars ; Print current state lxi d,vused lxi b,1A00h pstate: ldax d ; Is variable in use? ana a jz pnext ; If not, try next one mov c,e ; Keep highest used variable mov a,m ; Otherwise, get value ani 1 ; 0 or 1 ori '0' push b ; Keep state push d push h mvi c,putch ; Print variable mov e,a call 5 mvi c,putch ; And space mvi e,' ' call 5 pop h ; Restore state pop d pop b pnext: inx h ; Print next one inx d dcr b jnz pstate push b ; Keep last variable lxi d,dvdr ; Print "| " mvi c,puts call 5 call eval ; Evaluate expr using current state ani 1 ; Print result ori '0' mvi c,putch mov e,a call 5 pop b ; Restore last used variable inr c lxi h,vars ; Find next state lxi d,vused istate: ldax d ; Is variable in use? ana a jz inext ; If not, try next one mov a,m ; Otherwise, get value ana a ; Is it zero? jnz iinc ; If not, keep going, inr m ; But if so, set it to one jmp mloop ; And print next state iinc: dcr m ; If one, set it to zero inext: inx d ; And try next variable inx h dcr c ; Test if we have variables left jnz istate ; If not, try next one rst 0 ; But if so, we're done eval: lxi b,expr ; Evaluate the expression lxi h,opstk ; Evaluation stack eloop: ldax b ; Load expression element inx b ana a ; Done? jz edone mov d,a ; Keep copy ani TTYPE cpi TCONST ; Constant? jz econst cpi TVAR ; Variable? jz evar mov a,d ; Otherwise it's an operator ani TMASK mov d,a ana a ; Not? jnz e2 dcr l ; Error if stack empty jm errop mov a,m ; Not cma mov m,a inr l jmp eloop e2: dcr l ; 2 values needed - error if stack empty mov e,m ; Right hand value dcr l mov a,m ; Left hand value jm errop dcr d ; And? jz eand dcr d ; Or? jz eor dcr d ; Xor? jz exor eimpl: ana a ; Implies - if A=1 then E else 1 jnz e_lde mvi m,-1 inr l jmp eloop e_lde: mov m,e inr l jmp eloop exor: xra e jmp estore eor: ora e jmp estore eand: ana e estore: mov m,a inr l jmp eloop econst: mov a,d ; Constant ani TMASK mov m,a inr l jmp eloop evar: mov a,d ; Variable ani TMASK push h mvi h,vars/256 mov l,a mov a,m pop h mov m,a inr l jmp eloop edone: dcr l ; Should be at 0 mov a,m rz lxi d,mop ; Missing operator (not all values used) jmp errop+3 errop: lxi d,mval ; Missing operand (stack underflow) mvi c,puts call 5 rst 0 nwln: db 13,10,'$' dvdr: db '| $' noarg: db 'Please enter a boolean expression on the command line.$' missp: db 'Missing parenthesis.$' pserr: db 'Parse error at: $' mval: db 'Missing operand.$' mop: db 'Missing operator.$' opers: db '~&|^=',0 ; Operators - note that impl is actually => prec: db 4,3,2,2,1 ; Precedence opstk: equ ($/256)*256+256 ; Operator stack (for shunting yard) vars: equ opstk+256 ; Space for variables vused: equ vars+256 ; Marks which variables are used expr: equ vused+26 ; Parsed expression is stored here