; ROMAN.Z80 ; ; Converts between decimal numbers and Roman numerals. ; Vers equ 10 SubVers equ ' ' ; ; Version 1.0 -- June 1, 1991 -- Gene Pizzetta ; Added ability to convert from Roman numerals to decimal. ; Now also runs under vanilla CP/M. ; ; Version 0.4 -- May 31, 1991 -- Gene Pizzetta ; Several code changes, but no new features. No longer sends ; control characters to the screen. Converted to Zilog mnemonics. ; Added type-4 version. Not released. ; ; Version 0.3a -- April 7, 1990 -- Gene Pizzetta ; Fixed minor bug, discovered by Howard Goldstein, that caused ; strange behavior if a character less than '0' was entered. ; ; Version 0.3 -- April 1, 1990 -- Gene Pizzetta ; Converted to ZCPR3 utility. Extensively modified screen displays. ; Added command line mode and usage message. ; ; Version 0.2a -- February 13, 1983 -- D. McLanahan, Marlow, NH ; Modified for CP/M. ; ; Version 0.2 -- October 10, 1978 -- M. Pedder ; Converts decimal numbers to Roman numerals. ; From SIG/M disk volume 75 ; ; Original code copyright (c) 1978 M. Pedder. ; ; System addresses . . . ; DmaBuf equ 80h ; default DMA buffer ; ; ASCII . . . ; CtrlC equ 03h ; ^C TAB equ 09h ; tab CR equ 0Dh ; carriage return LF equ 0Ah ; line feed ; .request z3lib,syslib ; ext z3init,getefcb ext cin,cout,ccout,epstr,phlfdc,phl4hc,isdigit,caps,comphd ; ; TYP3HDR.Z80, Version 1.1 -- This code has been modified as suggested by ; Charles Irvine so that it will function correctly with interrupts enabled. ; Entry: jr start0 ; Must use relative jump db 0 ; Filler db 'Z3ENV',3 ; Type-3 environment Z3EAdr: dw 0 ; Filled in by Z33 dw entry ; Intended load address ; start0: ld hl,0 ; Point to warmboot entry ld a,(hl) ; Save the byte there di ; Protect against interrupts ld (hl),0c9h ; Replace warmboot with a return opcode rst 0 ; Call address 0, pushing RETADDR onto stack retaddr: ld (hl),a ; Restore byte at 0 dec sp ; Get stack pointer to point dec sp ; ..to the value of RETADDR pop hl ; Get it into HL and restore stack ei ; We can allow interrupts again ld de,retaddr ; This is where we should be xor a ; Clear carry flag push hl ; Save address again sbc hl,de ; Subtract -- we should have 0 now pop hl ; Restore value of RETADDR jp z,Start ; If addresses matched, begin real code ; ld de,notz33msg-retaddr ; Offset to message add hl,de ex de,hl ; Switch pointer to message into DE ld c,9 jp 0005h ; Return via BDOS print string function ; notz33msg: db 'Not Z33+$' ; Abort message if not Z33-compatible ; ; Messages . . . ; MsgSOn: db 'ROMAN Version ' db Vers/10+'0','.',Vers mod 10+'0',SubVers db ' (loaded at ',0 MsgSO1: db 'h)',CR,LF,0 MsgUse: db 'Converts between decimal numbers and Roman numerals.',CR,LF db 'Usage:',CR,LF,' ',0 MsgNam: db 'ROMAN',0 MsgUs1: db ' {number}',CR,LF db 'Number can be decimal (1 to 3999) or Roman (I to MMMCMXCIX).',CR,LF db 'If no number is given, ROMAN enters interactive mode.',0 MsgInf: db CR,LF,'Enter number (1-3999 or I-MMMCMXCIX). ^C to exit.',CR,LF,0 MsgSpc: db ' ',0 MsgPpt: db CR,LF,'>> ',0 Msg2Lg: db ' Too Large!',0 MsgIll: db ' Illegal character!',0 MsgInv: db ' Invalid Roman numeral!',0 RomTbl: db 0,0,'IVXLCDM' RomNum: db 'MDCLXVI' DecTbl: dw 1,5,10,50,100,500,1000 ; ; Start of program . . . ; Start: ld hl,(Z3EAdr) ; set up environment call z3init ld (Stack),sp ; save old stack pointer ld sp,Stack ; ..and set new stack ld de,DmaBuf ; see if there's a command tail ld a,(de) or a jr z,NoTail inc de call EatSpc and 7Fh ; strip parity bit cp 0 jr z,NoTail cp '/' ; request for help? jp z,Help ; (yes) ld hl,MsgSpc ; space over call epstr ld hl,0 ; initialize decimal total ld (Total),hl ld bc,NumBuf ; point to decimal buffer ld hl,NumCnt ; point to decimal counter ld (hl),0 ; zero counter call isdigit ; do we have a digit? jr nz,RomCL ; (no, must be Roman) ; ; Here we're going from decimal to Roman ; DecCL: cp ' ' ; space, tab, or null marks end of number jr z,DecCL1 cp TAB jr z,DecCL1 cp 0 jr z,DecCL1 call DEcho ; check and echo decimal digit jp p,Exit inc de ld a,(de) jr DecCL ; DecCL1: call PrtEqu ; print space, equal, space ld a,0 ; mark end of string ld (bc),a call CvtDec ; output Roman jp Exit ; ; Here we're going from Roman to decimal ; RomCL: cp ' ' ; space, tab, or null marks end of number jr z,RomCL1 cp TAB jr z,RomCL1 cp 0 jr z,RomCL1 call REcho ; check and echo each Roman character jp c,Exit inc de ld a,(de) jr RomCL ; RomCL1: call PrtEqu ; print space, equal, space call CvtRom ; output decimal jp Exit ; NoTail: ld hl,MsgSOn ; print sign-on message call epstr ld hl,Entry ; print load address call phl4hc ld hl,MsgSO1 call epstr ld hl,MsgInf ; print interactive instructions call epstr Ready: call PrtPpt ; print prompt call cin ; get first character and 7Fh ; strip high bit cp CtrlC ; abort? jr z,Exit ; (yes) cp CR ; carriage return? jr z,Ready ; (yes, start over) call isdigit ; decimal number? jr z,DoDec ; (yes) call RInp1 ; get number from keyboard jr c,Ready call CvtRom ; get encoded number, convert and print it jr Ready ; DoDec: call DInp1 ; get number from keyboard jr c,Ready call CvtDec ; encode and output it jr Ready ; Exit: ld sp,(Stack) ; restore old stack pointer ret ; ; ** From decimal to Roman routines ** ; ; DInput -- get decimal input from console into NumBuf, checking validity ; DInput: call cin and 7Fh ; strip parity bit cp CtrlC ; abort? jr z,Exit ; (yes) cp CR ; is it a carriage return? jr z,DInp2 ; (yes) DInp1: call DEcho ; (no, echo it to screen) jp m,DInput ; (continue) ret c ; (error) ; DInp2: call PrtEqu ; print space, equal, space ld a,0 ; mark end of string ld (bc),a or a ; and clear flag ret ; ; DEcho -- check and echo each decimal digit ; DEcho: call ccout ; echo character cp '0' ; is it zero or more? jp m,IllChr ; (no) cp ':' ; is it nine or less? jp p,IllChr ; (no) inc (hl) ; count it ld (bc),a ; store it inc bc ; advance pointer ld a,(hl) ; check count cp 5 ; is it less than 5 ret m ; (yes) jp TooLge ; print "too large" ; ; CvtDec -- encodes numbers and then outputs Roman numerals ; CvtDec: ld bc,NumBuf ; point to decimal buffer ld de,RomTbl ; point to Roman numerals ld hl,NumCnt ; point to decimal counter ld a,(hl) ; check count cp 4 ; is it 4? jr nz,CvtDc1 ; (no, continue) ld a,(bc) ; get first character cp '4' ; is it a "4"? jp m,CvtDc1 ; (no, continue) ld hl,Msg2Lg ; print "too large" call epstr ret ; CvtDc1: ld l,(hl) ; get count to HL ld h,0 add hl,hl ; double it add hl,de ; add to list ex de,hl ; and restore list ld hl,RomBuf ; point to Roman character buffer ; ; encode character stream in NumBuf ; Encode: ld a,(bc) ; get a character inc bc ; update pointer cp 0 ; is it end marker? ret z ; (yep) cp '9' ; is it a nine? jr z,Nine ; (it is) cp '5' ; is it five or more? jp p,Five ; (you bet) cp '4' ; is it four? jr z,Four ; (uh-huh) EncAgn: cp '0' ; a zero? jr nz,One ; (nope) EncExt: dec de ; decrement Roman pointer dec de ld a,(NumCnt) ; decrement count dec a ld (NumCnt),a jr nz,Encode ; (loop until zero) ld a,0 ; mark end call StoRom ; ..and store it jr PrtRom ; we're done ; ; One -- 1 = I, 10 = X, 100 = C, 1000 = M or more ; One: push af ; save number ld a,(de) ; get Roman character call StoRom ; store it pop af ; get back number dec a ; subtract one jr EncAgn ; ..and try again ; ; Four -- 4 = IV, 40 = XL, 400 = CD ; Four: ld a,(de) ; get Roman character I, X, or C call StoRom ; store it inc de ; get next Roman character V, L, or D ld a,(de) call StoRom ; store it dec de ; restore pointer jr EncExt ; ..and exit ; ; Five -- 5 = V, 50 = L, 500 = D or more ; Five: push af ; save number inc de ; move pointer ld a,(de) ; get Roman character V, L, or D call StoRom ; store it dec de ; restore pointer pop af ; get back number sub 5 ; subtract five jr EncAgn ; ..and try again ; ; Nine -- 9 = IX, 90 = XC, OR 900 = CM ; Nine: ld a,(de) ; get Roman character I, X, or C call StoRom ; store it inc de ; move pointer inc de ld a,(de) ; get Roman character X, C, or M call StoRom ; store it dec de ; restore pointer dec de jr EncExt ; ..and exit ; ; ; StoRom -- store Roman character in RomBuf for output ; StoRom: ld (hl),a ; store data in buffer inc hl ; move pointer ret ; ; PrtRom -- Print Roman numeral at console ; PrtRom: ld hl,RomBuf ; point to Roman buffer call epstr ; and output it ret ; ; ** From Roman to decimal routines ** ; ; RInput -- get Roman input from console into NumBuf, checking validity ; RInput: call cin and 7Fh ; strip high bit cp CtrlC ; abort? jp z,Exit ; (yes) cp CR ; a carriage return? jr z,RInp2 ; (yes) RInp1: call caps ; upper-case it call REcho ; echo it to screen jr nc,RInput ; (continue) ret ; RInp2: call PrtEqu ; print space, equal, space or a ; clear carry ret ; ; REcho -- checks and echoes Roman numerals ; REcho: call ccout ; echo character push hl push bc call ChkRom ; encode it (I=1, V=2, etc.) ld a,b pop bc pop hl jp nz,IllChr ; (illegal character) inc (hl) ; count it ld (bc),a ; store it inc bc ; advance pointer ld a,(hl) ; check counter cp 18 jr nc,TooLge ; number too long or a ; clear carry ret ; ; ChkRom -- checks for valid Roman numeral. If so, a code ('I'=1, ; 'V'=2, etc.) is returned in B and zero flag is set. Returns NZ ; if character is illegal. ; ChkRom: ld b,7 ; number of possible matches ld hl,RomNum ; point to table CRomLp: cp (hl) ; loop through Roman numerals ret z ; (match, so return with position in B) inc hl djnz CRomLp xor a dec a ; return NZ on error ret ; ; CvtRom -- Converts encoded Roman numerals to Arabic decimal numbers. ; Address of input buffer in BC. If conversion is successful, number ; is printed. ; CvtRom: push bc ; move last buffer address to HL pop hl xor a ld c,a ; initialize last character ld (SubFlg),a ld a,(NumCnt) ; load count into B or a ret z ; (nothing to print) ld b,a CvtRLp: dec hl ld a,(hl) ; get next value in A dec a ; decrement it (I=0, etc.) cp c ; compare to last character in C jr c,SubIt ; if less, then subtract, otherwise add jr nz,AddIt ; if it's not the same, add it ex de,hl ; save pointer in DE ld hl,SubFlg ; point to subtract flag inc (hl) ; did we subtract last time? dec (hl) ex de,hl ; recover pointer jr nz,InvRom ; (yes, this can't be) ; AddIt: push hl call GetVal ; get decimal value ld hl,SubFlg ; point to subtract flag ld (hl),0 ; ..and reset it ld hl,(Total) ; add decimal value to the total add hl,de ld (Total),hl pop hl djnz CvtRLp jr PrtDec ; SubIt: push hl call GetVal ; get decimal value ld hl,SubFlg ; point to subtract flag ld (hl),0FFh ; ..and set it or a ; clear carry ld hl,(Total) ; ..and subtract it from the total sbc hl,de ld (Total),hl pop hl djnz CvtRLp ; PrtDec: ld hl,(Total) ; print decimal total ld de,4000 ; ..if it's not out of range call comphd jp nc,TooLge ; (too big) call phlfdc ret ; ; GetVal -- Gets decimal value of Roman numeral from table ; GetVal: ld c,a ; save copy in C add a,c ; double value in A ld e,a ; put it in DE ld d,0 ld hl,DecTbl ; point to table add hl,de ; get table address ld e,(hl) ; load table value inc hl ld d,(hl) ret ; ; ** General routines ** ; ; Error messages ; InvRom: ld hl,MsgInv ; "invalid Roman numeral" jr OutMsg TooLge: ld hl,Msg2Lg ; "too large" jr OutMsg ; IllChr: ld hl,MsgIll ; "illegal character" OutMsg: call epstr or a ; reset sign flag scf ; set carry (error flag) ret ; ; PrtPpt -- set up and print interactive prompt ; PrtPpt: ld hl,MsgPpt ; print prompt call epstr ld hl,0 ; initialize decimal total ld (Total),hl ld bc,NumBuf ; point to decimal buffer ld hl,NumCnt ; point to counter ld (hl),0 ; clear counter ret ; ; PrtEqu -- prints space, equal, space ; PrtEqu: ld a,' ' call cout ld a,'=' call cout ld a,' ' call cout ret ; ; EatSpc -- eats spaces and tabs ; EatSpc: ld a,(de) inc de cp ' ' ; space? jr z,EatSpc cp TAB jr z,EatSpc dec de ret ; ; Help -- print help message and exit ; Help: ld hl,MsgSOn ; print sign-on call epstr ld hl,Entry ; print load address call phl4hc ld hl,MsgSO1 call epstr ld hl,MsgUse ; print usage call epstr ld hl,(Z3EAdr) ; ZCPR3? ld a,h or l jr z,NoEFcb ; (nope) call getefcb ; get program name jr z,NoEFcb ; (no external fcb) ld b,8 FNLp: inc hl ; print program name ld a,(hl) and 7Fh cp ' ' ; space, we're through call nz,ccout djnz FNLp jr Help2 ; NoEFcb: ld hl,MsgNam call epstr Help2: ld hl,MsgUs1 call epstr jp Exit ; ; Uninitialized storage . . . ; DSEG ; SubFlg: ds 1 ; subtract flag NumBuf: ds 18 ; decimal buffer RomBuf: ds 18 ; Roman buffer NumCnt: ds 1 ; number character count Total: ds 2 ; decimal running total ds 60 ; stack Stack: ds 2 ; old stack pointer ; end