diff --git a/cpmtris/Makefile b/cpmtris/Makefile new file mode 100644 index 0000000..6e10aaa --- /dev/null +++ b/cpmtris/Makefile @@ -0,0 +1,14 @@ +# makefile for cpmtris + +all: cpmtris.com + +SRC=cpmtris.z rand.z conio.z + +cpmtris.com: $(SRC) + cat $(SRC) >out.z + zmac/zmac out.z + mv out.bin cpmtris.com + +clean: + $(RM) *~ *.lst *.bin out.z + diff --git a/cpmtris/README.md b/cpmtris/README.md new file mode 100644 index 0000000..f66c9ea --- /dev/null +++ b/cpmtris/README.md @@ -0,0 +1,45 @@ +# cpmtris + +A Tetris clone for Z80-based CP/M machines +(c) 1996,1999 Russell Marks, GPLv2 + +Originally part of the ZCN repository: https://github.com/jamesots/zcn + +I modified cpmtris so that "vt100" and "8 MHz" are the default values. + +``cpmtris.com`` is the assembled binary that uses these settings. + +See README.orig for full documentation. + +## Commands + +| Action | Key | +| ---------- | --- | +| Rotate | s | +| Left/Right | k/l | +| Drop | d | + +## Command-Line Options + +``cpmtris [termtype [mhz*10]]`` + +``termtype`` specifies the terminal type: + +| Type | Terminal | +| ---- | --------------------------- | +| 0 | as defined in patch area | +| 1 | VT100 (default) | +| 2 | VT52 (PCW, +3, etc.) | +| 3 | ZCN (this is auto-detected) | +| 4 | VC404 | + +``mhz*10`` defines the speed of the machine "times 10", e.g. for a +3.5 MHz machine, use "35" or for 8 MHz (default) use "80". + +E.g.: ``cpmtris 1 80`` for a VT100 8 MHz machine, which is the default. + +## ZMAC + +The ZMAC Z80 macro assembler, which is used for cpmtris, is also available in the ``zmac/`` folder. +When assembling cpmtris using the Makefile, zmac is used (but needs to be compiled first) + diff --git a/cpmtris/README.orig b/cpmtris/README.orig new file mode 100644 index 0000000..8ae8fa8 --- /dev/null +++ b/cpmtris/README.orig @@ -0,0 +1,101 @@ +* -*- outline -*- + + Cpmtris v1.1 - (c) 1996,1999 Russell Marks + + A tetris clone for Z80-based CP/M machines. + + +* License + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +[The GNU GPL is in the file 'COPYING'.] + + +* About cpmtris + +Cpmtris is a free tetris clone for CP/M machines. It works as is on +VT52s (PCW and +3) and ZCN boxes, works with VT100s with 'cpmtris 1', +and is patchable for other machines and terminals. + +It's a conversion of my Psion series 3 OPL tetris program, via my ZX81 +tetris. Both are free like cpmtris, and available on request. + + +* Playing cpmtris + +It's tetris. Surely you've played tetris!? + +Ok, the basics. Blocks fall from the top of the screen. You have to +fit them together to make solid horizontal lines, as a horizontal line +of blocks disappears to make room for more blocks. And why is this the +case? Well, if the pile of blocks reaches the top of the screen, it's +game over. So it quickly gets important to fit the shapes together +well, and not leave any gaps. + +You can rotate the blocks with 's', move them left or right with 'k' +and 'l', and 'drop' them with 'd'. + +Cpmtris does not (yet) speed up. It starts pretty fast and stays that +way. If you want to play it faster, specify a MHz rating on the +command-line (see below) lower than your machine's actually is. + +* Command-Line Options + +In Unix terms, the synopsis (usage) is 'cpmtris [termtype [mhz10]]'. + +If 'termtype' is specified, it must be a single digit corresponding to +a terminal type. If 'mhz10' is specified, it should be ten times the +speed (in MHz) of the Z80 the machine has. For example, on a 3.5MHz +machine you'd use '35'. You must have a 'termtype' specified to use +'mhz10'. + +To explain this 'terminal type' stuff: It's traditional for +full-screen CP/M programs to either include a configuration program, +or (more usually) document patches which need to be made to support +different terminals. Cpmtris does have a patch area (100h-17fh), but +also has built-in support for a few terminals. These are: + +Type Terminal +0 as defined in patch area +1 VT100 +2 VT52 (PCW, +3, etc.) - this is the default +3 ZCN (this is auto-detected) +4 VC404 + +The idea behind being able to specify both terminal type and speed +when you run cpmtris is that it's more convenient if you sometimes use +your machine via a terminal or terminal emulator. With most programs +you have to devise some method of patching them when you want to use +something other than the default configuration. With cpmtris, if (for +example) I'm using my ZCN box via kermit on my Linux box - not +uncommon - then it's just a matter of using 'cpmtris 1'. + +See the source (cpmtris.z) for details of the patch area. + + +* Acknowledgements + +The original text-mode PC tetris game (which a surprising number of +people have never played, it seems). It's inspired more clones than +any other game since space invaders, I think. :-) + +Rush's "Presto" album. I listened to it constantly while hacking up +cpmtris. + + +* Contacting the Author + +You can email me at russell.marks@ntlworld.com. diff --git a/cpmtris/conio.z b/cpmtris/conio.z new file mode 100644 index 0000000..e60300c --- /dev/null +++ b/cpmtris/conio.z @@ -0,0 +1,65 @@ +;conio - console I/O routines +;mostly just repackaged BDOS stuff + +;for cpm zmac: +;ZZglobal putchar +;ZZglobal putbyte +;ZZglobal getchar +;ZZglobal getch +;ZZglobal kbhit + + +;putchar - put char in a +;puts CR before any LF output +;f corrupt, others preserved +putchar: +cp 10 +jr nz,putbyte +ld a,13 +call putbyte +ld a,10 +;FALLS THROUGH + +;putbyte - put char in a without translation +;preserves all regs +putbyte: +push af +push bc +push de +push hl +ld e,a +ld c,2 +call 5 +pop hl +pop de +pop bc +pop af +ret + + +;getchar - get char into a (without echo) +;preserves all but f +getch: ;same +getchar: +push bc +push de +push hl +getchlp: +call kbhit +jr nc,getchlp +ld e,0ffh +ld c,6 +call 5 +pop hl +pop de +pop bc +ret + + +;kbhit - like the common DOS C function +;returns c if key pressed, nc if not +kbhit: +ld c,11 +call 5 +rra +ret diff --git a/cpmtris/cpmtris.com b/cpmtris/cpmtris.com new file mode 100644 index 0000000..4ea44c0 Binary files /dev/null and b/cpmtris/cpmtris.com differ diff --git a/cpmtris/cpmtris.z b/cpmtris/cpmtris.z new file mode 100644 index 0000000..a30cd25 --- /dev/null +++ b/cpmtris/cpmtris.z @@ -0,0 +1,1379 @@ +;Cpmtris v1.0 - a tetris clone for Z80-based CP/M machines. +;Copyright (C) 1996 Russell Marks. See 'README' for license details. + +;conversion of my OPL tetris for the psion series 3 via my ZX81 tetris +; (both also free and available on request) + + +org 0100h + +jp indirect + +defb 0 + +;cpmtris is, in this version, patched to run on a 8 MHz +;80x24 VT100 machine + +;104h - number of columns +; must have at least 50 columns +; you don't really need to set this; it isn't used for anything. +cols: defb 80 + +;105h - number of lines +; must have at least 10 lines +lines: defb 24 + +;106h - 10*Mhz of your Z80. +; e.g. on a 3.5MHz Spectrum +3 it would be 35. +; most CP/M machines have speeds around the 3 to 4 MHz mark, +; so if you don't know the speed of your box just use 35. +mhz10: defb 80 + +;107h - terminal type +;you can roll your own terminal routines, but there's hard-coded +;support for a few terminals which saves you the effort. they are: +; type 1 - VT100 +; type 2 - VT52, PCW, Spectrum +3, etc. +; type 3 - ZCN +; type 4 - VC404 (volker-craig) +;set this to zero if you're writing your own clear screen and cursor +;move routines. +;note that the terminal type does not influence the number of lines, +;columns and the 'mhz10' setting - all those must still be set (above). +termtype: defb 1 + +;108h-10fh - reserved (currently unused) +defb 0,0,0,0,0,0,0,0 + +;the routines below are allowed to corrupt af/bc/de/hl, +; but other registers must remain intact. You can use these routines +; from them: +;the routine at 180h prints the num. in de in decimal, preserving all regs. +;the routine at 183h prints the char in a, preserving all regs. + +;110h - clear screen +;this example is for a VT100. +hackcls: +ld de,mcls +ld c,9 +jp 5 +mcls: defb 27,'[H',27,'[J$' + +defb 0 + +;120h - move to (x,y), where (0,0) is top-left +;entry: (b,c)=(x,y) +;this example is for a VT100. +hackmove: +ld a,27 +call 0183h +ld a,'[' +call 0183h +ld e,c +inc e +ld d,0 +call 0180h +ld a,';' +call 0183h +ld e,b +inc e +;d still 0 +call 0180h +ld a,'H' +jp 0183h + +;140h - extra patch space, in case the above isn't enough +;all the space up to 17fh is usable +defb 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 +defb 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 +defb 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 +defb 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 + +;180h - print de as decimal number (for VT100 cursor move) +;saves af/bc/de/hl +jp dispdec2 + +;183h - print char in a +;saves all regs +jp putchar + +;we jump to here first, so that the 1st jump always stays the same +; no matter what hacking is done in future, and any binary patches +; made by overwriting the first record still work. +indirect: +jp main + +dispdec2: +push af +push bc +push de +push hl +call dispdec +pop hl +pop de +pop bc +pop af +ret + +;the shapes +shps: +defb 1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0 +defb 1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0 +defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + +defb 1,0,0,0,0,1,1,0,1,0,0,0,0,1,1,0 +defb 1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0 +defb 0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0 +defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + +defb 0,1,0,0,1,1,0,0,0,1,0,0,1,1,0,0 +defb 1,1,0,0,0,1,1,0,1,1,0,0,0,1,1,0 +defb 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 +defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + +defb 0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0 +defb 1,1,1,1,0,1,0,0,1,1,1,1,0,1,0,0 +defb 0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0 +defb 0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0 + +defb 0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0 +defb 1,1,1,0,0,1,1,0,1,1,1,0,1,1,0,0 +defb 0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0 +defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + +defb 0,1,1,0,1,0,0,0,0,1,0,0,0,0,0,0 +defb 0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0 +defb 0,1,0,0,0,0,0,0,1,1,0,0,0,0,1,0 +defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + +defb 1,1,0,0,0,0,0,0,0,1,0,0,0,0,1,0 +defb 0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0 +defb 0,1,0,0,1,0,0,0,0,1,1,0,0,0,0,0 +defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + +;map of the 10x21 screen (and some spare) +scrn: +defw 0,0,0,0,0, 0,0,0,0,0 +defw 0,0,0,0,0, 0,0,0,0,0 +defw 0,0,0,0,0, 0,0,0,0,0 +defw 0,0,0,0,0, 0,0,0,0,0 +defw 0,0,0,0,0, 0,0,0,0,0 + +defw 0,0,0,0,0, 0,0,0,0,0 +defw 0,0,0,0,0, 0,0,0,0,0 +defw 0,0,0,0,0, 0,0,0,0,0 +defw 0,0,0,0,0, 0,0,0,0,0 +defw 0,0,0,0,0, 0,0,0,0,0 + +defw 0,0,0,0,0, 0,0,0,0,0 + + +mtitle: defb 'Cpmtris by RJM 96',0 +mkeys1: defb 'K - left, L - right',0 +mkeys2: defb 'S - rotate, D - drop',0 +mgamovr: defb '*** GAME OVER! ***',0 +mlines: defb 'Lines: 0',0 +mprskey: defb 'Again? (y/n)',0 +mbotline: defb '--==--==--==--==--==',0 + + +ybot equ 20 ;bottom of scrn +timout equ 20 ;timeout in frames before block drops one place + +shp: defb 0 ;current shape +frm: defb 0 ;current frame (rotation) +xy: +y: defb 0 +x: defb 0 +timc: defb 0 +tmp: defb 0 +linesdun: defw 0 +xstatpos: defb 30 + +;terminal-related stuff +compact: defb 0 ;if 1 use doubled-up pixels (" '.:") + +pause: defw 0 + +;for ZCN we make a new font which is basically a copy of ZCN's but +;with some UDGs on the end. +zcnfont equ 0eac0h +newzcnfont equ 08000h +zcnchars: defb 128,129,130,131 +zcncdat: +defb 000h,022h,000h,000h,022h,000h +defb 000h,022h,000h,077h,0ffh,0ffh +defb 077h,0ffh,0ffh,000h,022h,000h +defb 077h,0ffh,0ffh,077h,0ffh,0ffh + + +main: +;if on ZCN, we'll be using the 'compact' 2-pixel-per-char mode. +;since we can (admittedly fairly kludgely) hack up a few udgs, do +;that. (looks a lot nicer.) +ld a,(066h) +cp 0f7h +call z,zcninit + +call srand +call chkargs +call playgame +jr main + + +chkargs: +ld a,(05dh) +cp 32 +ret z + +;set terminal type if 0<=a<=3 +sub 48 +ret c +cp 5 +ret nc +ld (termtype),a +cp 3 +jr z,carg1 +;set to 24 lines if termtype specified and not ZCN type +ld a,24 +ld (lines),a +carg1: + +ld a,(06dh) +cp 32 +ret z + +;set mhz10 +ld hl,06dh +call atoi +ld a,l +ld (mhz10),a +ret + +ld d,h +ld e,l +call dispdec + +ld a,' ' +call putchar + +ld a,'$' +ld (070h),a +ld de,06dh +ld c,9 +call 5 +jp 0 + + + +playgame: +ld a,(lines) +cp 20 +jr nc,notcpt + +ld a,1 +ld (compact),a + +notcpt: +;work out frame pause +ld a,(mhz10) +ld h,0 +ld l,a +ld de,72 +call multiply +ld (pause),hl + +call cls + +;show title/keys +ld a,(xstatpos) +ld b,a +ld c,0 +ld hl,mtitle +call mvaddstr +ld a,(lines) +cp 20 +jr c,nokeyhlp +ld a,(xstatpos) +ld b,a +ld c,18 +ld hl,mkeys1 +call mvaddstr +ld a,(xstatpos) +ld b,a +ld c,20 +ld hl,mkeys2 +call mvaddstr +nokeyhlp: + +call initscr +ld hl,0 +ld (linesdun),hl + +ld a,(xstatpos) +ld b,a +ld c,7 +ld hl,mlines +call mvaddstr + +blklp: +;gen new blk +ld hl,7 +call rand +ld a,l +ld (shp),a + +call rand16 +ld a,l +and 3 +ld (frm),a + +xor a +ld (y),a +ld a,4 +ld (x),a + +ld a,timout +ld (timc),a + +;if hit now, game over +ld bc,(xy) +ld a,(frm) +call hitshp +jp c,endgam + +ld a,1 +call drawshp + +;frame loop +hitlp: +call inkey + +cp 'k' ;left (k) +jr nz,skip1 + +xor a +call drawshp + +ld bc,(xy) +dec b +ld a,(frm) +call hitshp +jr c,skip0a +ld a,(x) +dec a +ld (x),a +skip0a: +ld a,1 +call drawshp +jp skipn + +skip1: +cp 'l' ;right (l) +jr nz,skip2 + +xor a +call drawshp + +ld bc,(xy) +inc b +ld a,(frm) +call hitshp +jr c,skip1a +ld a,(x) +inc a +ld (x),a +skip1a: +ld a,1 +call drawshp +jp skipn + +skip2: +cp 's' ;rot (s) +jr nz,skip3 + +ld a,(frm) +inc a +and 3 +ld (tmp),a + +xor a +call drawshp + +ld bc,(xy) +ld a,(tmp) +call hitshp +jr c,skip2a +ld a,(tmp) +ld (frm),a +skip2a: +ld a,1 +call drawshp +jp skipn + +skip3: +cp 'd' ;drop (d) +jr nz,skip4 + +xor a +call drawshp + +droplp: +ld bc,(xy) +inc c +ld a,(frm) +call hitshp +jr c,skip3a +ld a,(y) +inc a +ld (y),a +jr droplp + +skip3a: +ld a,1 +call drawshp +jr endhit + +skip4: +cp 27 +jp z,abortgam + +ld a,(timc) +dec a +ld (timc),a +jr nz,skipn + +xor a +call drawshp + +ld bc,(xy) +inc c +ld a,(frm) +call hitshp +jr nc,skip5 + +ld a,1 +call drawshp +jr endhit + +skip5: +ld a,(y) +inc a +ld (y),a +ld a,timout +ld (timc),a +ld a,1 +call drawshp + +skipn: +;pause between frames +ld bc,(pause) +pauselp: +dec bc +ld a,b +or c +jr nz,pauselp +jp hitlp + +endhit: +call dolines +ld a,(xstatpos) +add a,7 +ld b,a +ld c,7 +call move +ld de,(linesdun) +call dispdec +jp blklp + +endgam: +;draw last hit block. +;this is messy, but if you get a 1x4 block it looks +;incredibly unfair otherwise. +ld a,1 +call drawshp + +abortgam: +ld a,(xstatpos) +ld b,a +ld c,5 +ld hl,mgamovr +call mvaddstr + +ld a,(lines) +ld e,a +ld a,15 +cp e +jr c,agam1 +ld a,3 +agam1: +ld c,a +dec c +ld a,(xstatpos) +ld b,a +ld hl,mprskey +call mvaddstr +call stashcur + +;wait for no key +mpklp: +call inkey +and a +jr nz,mpklp + +;wait for key +mpklp2: +call inkey +and a +jr z,mpklp2 + +;if key was y, ret +cp 'y' +ret z + +;if key wasn't n, repeat +cp 'n' +jr nz,mpklp2 + +;otherwise, quit... + +bombout: +call cls +jp 0 + + +;draw/undraw blk on 10x20 scrn +;entry: (b,c)=(x,y), a=1 to draw, 0 to undraw +drawblk: +;do scrn() +ld h,0 +ld l,c +call mul10 +ld d,0 +ld e,b +add hl,de +ld de,scrn +add hl,de +ld (hl),a + +ld e,a +ld a,(compact) +and a +ld a,e +jr nz,compblk + +;actually draw +push af +rlc b +call move +pop af +and a +jr z,dblkskip + +;think hard before changing these, they're also used by the +; scrolling stuff. at the very least, it's important to +; preserve the z flag. +dblkon: +ld a,'[' +call putchar +ld a,']' +call putchar +ret + +dblkoff: +dblkskip: +ld a,' ' +call putchar +ld a,' ' +call putchar +ret + + +;multiply hl by 10 +mul10: +push de +add hl,hl ;*2 +ld d,h +ld e,l +add hl,hl ;*4 +add hl,hl ;*8 +add hl,de ;*10 +pop de +ret + + +;this draws a 'compact' block, which can be used on a machine +;with less than 20 lines. It makes the play area only 10x10 onscreen. +;this is really intended to support ZCN boxes, but should work +;for other things too. +;it's called from drawblk when needed. +;entry: hl points to byte set in scrn, (b,c)=(x,y). +cbchars: defb ' .',39,':' ;i.e. " .':" +compblk: +;scrn() has already been done, we just have to draw/undraw. +and a +rr c +rl e +;so c=y/2 and bit 0 of e=y&1 +push de +push hl +call move +pop hl +pop de +;work out what the char there should be, from looking at scrn(). +;build up the 0-3 offset in cbchars in d. +ld a,(hl) +rra +rl d + +ld bc,10 +bit 0,e +jr z,cblkskip +ld bc,-10 +cblkskip: +add hl,bc +ld a,(hl) +rra +rl d + +;if y&1=1, bits 0 and 1 of d are the wrong way round. +;fix that here. +bit 0,e +jr z,cblk2 +rr d +rr b ;bit 0 -> b +rr d +rr c ;bit 1 -> c +rl b +rl d ;b -> bit 1 +rl c +rl d ;c -> bit 0 + +cblk2: +;get correct char and print it. +ld a,d +and 3 +ld h,0 +ld l,a +ld de,cbchars +add hl,de +ld a,(hl) +jp putchar + + +;draw shape +;entry: a=1 to draw, 0 to undraw +dsasav: defb 0 +drawshp: +ld (dsasav),a + +;let hl=offset in shp() +ld a,(frm) +ld h,0 +ld l,a +add hl,hl +add hl,hl +ld de,shps +add hl,de +ex de,hl +ld a,(shp) +ld h,0 +ld l,a +add hl,hl +add hl,hl +add hl,hl +add hl,hl +add hl,hl +add hl,hl +add hl,de + +ld de,(xy) + +ld c,4 +dsylp: + +ld a,(x) +ld d,a +ld b,4 +dsxlp: +ld a,(hl) +and a +jr z,dsskip + +push bc +push de +push hl +ld b,d +ld c,e +ld a,(dsasav) +call drawblk +pop hl +pop de +pop bc + +dsskip: +inc hl +inc d +djnz dsxlp + +push de +ld de,12 +add hl,de +pop de +inc e +dec c +jr nz,dsylp + +ld a,(dsasav) +and a +ret z + +;if we're drawing rather than undrawing, move cursor out of the way +;FALLS THROUGH + +;move cursor to safe pos out of the way +stashcur: +ld b,22 +ld c,0 +call move +ret + + +;entry: (b,c)=(x,y), a=frm +hsxy: +hsy: defb 0 +hsx: defb 0 +hitshp: +ld (hsxy),bc + +;let hl=offset in shp() +ld h,0 +ld l,a +add hl,hl +add hl,hl +ld de,shps +add hl,de +ex de,hl +ld a,(shp) +ld h,0 +ld l,a +add hl,hl +add hl,hl +add hl,hl +add hl,hl +add hl,hl +add hl,hl +add hl,de + +ld de,(hsxy) + +ld c,4 +hsylp: + +ld a,(hsx) +ld d,a +ld b,4 +hsxlp: +ld a,(hl) +and a +jr z,hsskip + +;if <0 or >9, always hit +ld a,d +cp 10 +ccf +ret c + +;now proper check +push de +push hl +ld h,0 +ld l,e +call mul10 +ld e,d +ld d,0 +add hl,de +ld de,scrn +add hl,de +ld a,(hl) +pop hl +pop de +and a +scf +ret nz + +hsskip: +inc hl +inc d +djnz hsxlp + +push de +ld de,12 +add hl,de +pop de +inc e +dec c +jr nz,hsylp + +and a +ret + + +initscr: +ld hl,scrn +ld de,scrn+1 +ld bc,210 +ld (hl),0 +ldir + +ld hl,ybot +call mul10 +ld de,scrn +add hl,de +ld d,h +ld e,l +inc de +ld bc,9 +ld (hl),1 +ldir + +;draw blank play area if needed +ld a,(compact) +and a +jr z,iscrncmp + +ld c,10 +iscrylp: +ld b,10 +iscrxlp: +push bc +dec b +dec c +call move +ld a,(cbchars) +call putchar +pop bc +djnz iscrxlp +dec c +jr nz,iscrylp + +iscrncmp: +;only draw bottom line if (lines)>=ybot+1 (21) +ld a,(lines) +ld c,ybot +inc c +cp c +ret c + +dec c +ld b,0 +ld hl,mbotline +call mvaddstr +ret + + +dolines: +ld c,ybot-1 +dlylp: +;e used as total +ld e,0 +ld b,10 +dlxlp: +push de +ld h,0 +ld l,c +call mul10 +ld de,scrn +add hl,de +ld d,0 +ld e,b +dec e +add hl,de +pop de +ld a,(hl) +add a,e +ld e,a +djnz dlxlp + +ld a,e +and a +ret z + +cp 10 +jr nz,dlskip + +push bc +call scrlscr +pop bc +inc c + +dlskip: +dec c +jr nz,dlylp +ret + + +;scroll up from line c +scrlscr: +dec c + +ssylp: +push bc + +;copy scrn() array line +ld h,0 +ld l,c +call mul10 +ld de,scrn +add hl,de +push hl +ld de,10 +add hl,de +pop de +ex de,hl +ld bc,10 +ldir + +pop bc +push bc + +;copy to screen +;right now, hl points to scrn() data for this line, +; and c+1 is the line to draw it on. +inc c +push bc +push hl +ld a,(compact) +and a +call z,move +pop hl +pop bc + +ld b,10 +ssxlp: +ld a,(compact) +and a +jr nz,ssskip1 + +;normal +ld a,(hl) +and a +call nz,dblkon +call z,dblkoff +jr ssskip2 + +ssskip1: +;compact +push bc +ld a,10 +sub b +ld b,a +push hl +call compblk +pop hl +pop bc + +ssskip2: +inc hl +djnz ssxlp + +pop bc + +dec c +jr nz,ssylp + +ld hl,(linesdun) +inc hl +ld (linesdun),hl +ret + + + +;move to (b,c) and display asciiz at hl +mvaddstr: +push hl +call move +pop hl +mvaslp: +ld a,(hl) +and a +ret z +call putchar +inc hl +jr mvaslp + + +;get non-blocking input. +;returns a=char or 0 if none pressed +inkey: +call kbhit +ld a,0 +ret nc +jp getch + + + +;the built-in terminal support +cls: +ld a,(termtype) +and a +jp z,hackcls + +cp 1 +jr nz,cls2 + +;VT100 +ld de,mclsvt100 +ld c,9 +jp 5 + +mclsvt100: defb 27,'[H',27,'[J$' + +cls2: +cp 2 +jr nz,cls3 + +;VT52 +ld de,mclsvt52 +ld c,9 +jp 5 + +mclsvt52: defb 27,'H',27,'J$' + +cls3: +cp 3 +jr nz,cls4 + +;ZCN +ld a,1 +jp putchar + +cls4: +cp 4 +ret nz + +;VC404 +ld a,24 +jp putchar + + +move: +ld a,(termtype) +and a +jp z,hackmove + +cp 1 +jr nz,move2 + +;VT100 +ld a,27 +call putchar +ld a,'[' +call putchar +ld e,c +inc e +ld d,0 +call dispdec2 +ld a,';' +call putchar +ld e,b +inc e +;d still 0 +call dispdec2 +ld a,'H' +jp putchar + +move2: +cp 2 +jr nz,move3 + +;VT52 +ld a,27 +call putchar +ld a,'Y' +call putchar +ld a,32 +add a,c +call putchar +ld a,32 +add a,b +jp putchar + +move3: +cp 3 +jr nz,move4 + +;ZCN +ld a,16 +call putchar +ld a,32 +add a,c +call putchar +ld a,32 +add a,b +jp putchar + +move4: +cp 4 +ret nz + +;VC404 +ld a,16 +call putchar +ld a,32 +add a,c +call putchar +ld a,32 +add a,b +jp putchar + + +zcninit: +;we know it's ZCN, so set termtype etc. +ld a,3 +ld (termtype),a +ld a,120 +ld (cols),a +ld a,10 +ld (lines),a +ld a,46 +ld (mhz10),a + +ld hl,zcnchars +ld de,cbchars +ld bc,4 +ldir +ld hl,zcnfont +ld de,newzcnfont +ld bc,96*6 +ldir +ld hl,zcncdat +ld bc,4*6 +ldir +;enable the new font +ld de,newzcnfont-192 +ld c,142 +call 5 + +;might as well turn off the cursor, too. +ld a,4 +call putchar +ret + + + +;stuff from ZX81 version of zcnlib's maths.z +;this doesn't require undocumented Z80 ops, which are causing +;probs on the 'cpm' CP/M emulator I'm testing this on. + +;ZX81-friendly (non-ix-using) version; probably slower, but at least +; it works :-) + +;divide +;gives z1=x/y and z2=x mod y +;entry: hl=x, de=y +;exit: hl=z1 (result), de=z2 (remainder) +;af/bc corrupt +dividey: defw 0 +divide: +ld b,h +ld c,l +;see if we're trying to divide by zero +ld a,d +or e +ret z + +ld (dividey),de + +ld de,0 +ld hl,0 +ld a,16 + +dvlp: +push af +and a +rl l +rl h + +and a +rl e +rl d + +bit 7,b +jr z,dvs1 + +ld a,1 +or l +ld l,a + +dvs1: +push hl +and a +push de +ld de,(dividey) +sbc hl,de +pop de +jp m,dvs2 + +;nasty! fiddle the stack +ex (sp),hl + +ld a,1 +or e +ld e,a + +dvs2: +pop hl + +and a +rl c +rl b + +pop af +dec a +jr nz,dvlp + +;finally! got the results. +ex de,hl +;exit: hl=result, de=remainder +ret + + +;this from zcnlib's maths.z + +;this does z=x*y +;entry: hl=x, de=y +;exit: hl=z +;af/bc/de corrupt +multiply: +ld b,h +ld c,l +ld hl,0 +ld a,16 +mulp: +and a +rr d +rr e +jr nc,musp +add hl,bc +musp: +and a +rl c +rl b +dec a +jr nz,mulp +ret + + +numtmp: defb '0000000000000000$' ;16 zeroes and '$' + +;convert number in de to ascii in internal buffer +;entry: de=number +;exit: de=addr of number in internal buffer, '$' terminated +itoa: +ld b,10 +;FALLS THROUGH + +;call here for routine with functionality as above but with b=base + +;convert number in de to ascii, in given base (unsigned) +;entry: de=number, b=base (from 2 to 36) +;exit: af/bc/de/hl corrupt +itoabase: +ld hl,numtmp+16 +ld a,'$' +ld (hl),a + +dispnlp: +push bc +push hl +ex de,hl + +ld e,b +ld d,0 +call divide + +ld a,e +add a,48 +cp 58 +jr c,dispn1 +add a,7 ;compensate for >=10 +dispn1: + +ex de,hl ;so de now is result of division +pop hl +pop bc +dec hl +ld (hl),a +ld a,d +or e +jp nz,dispnlp + +ex de,hl +ret + + +;display number in de, in decimal +dispdec: +call itoa +ld c,9 +jp 5 + + +;like C func. entry: hl=addr of number, exit: hl=actual number +atoi: +ld b,10 +;FALLS THROUGH + +;convert number of specified base as ASCII at hl to number. +;the ASCII number should be terminated by a non-digit in this base. +;supports bases from 2 to 36 +;entry: hl=address of first digit of ASCII number, +; b=base (e.g. 10 for decimal) +;exit: hl=number +atoibase: +ld de,0 ;total of number so far. +atoilp: +ld a,(hl) +;uppercase it +call atoiislw +jr nc,atoi0 +res 5,a +atoi0: +sub 48 +jr c,atoidone ;if not digit, end +cp 10 +jr c,atoi0a +sub 7 ;compensate if we're using letters +atoi0a: +cp b +jr nc,atoidone ;if not digit, end +;otherwise, multiply our running total by base and add this. +push hl +push bc +push af +ld l,b +ld h,0 +call multiply +pop af +ld e,a +ld d,0 +add hl,de +ex de,hl +pop bc +pop hl +inc hl +jr atoilp + +atoidone: +;got number in de - put it in hl. +ex de,hl +ret + +atoiislw: +cp 'a' +ccf +ret nc +cp '{' +ret diff --git a/cpmtris/rand.z b/cpmtris/rand.z new file mode 100644 index 0000000..3e3ce90 --- /dev/null +++ b/cpmtris/rand.z @@ -0,0 +1,96 @@ +;rand - simple random number generator +;requires maths.z +;algorithm adapted from the one I used for 8086 kirsten. + +;for cpm zmac: +;ZZglobal seed +;ZZglobal srand +;ZZglobal rand16 +;ZZglobal rand + +;uses these: +;ZZglobal multiply +;ZZglobal divide + + +seed: defb 'ZX81' ;don't ask :-) + + +;set random number seed from R +;entry: none, exit: none +;af/hl corrupt +srand: +ld a,r +ld h,a +ld a,r +ld l,a +ld (seed),hl +ld a,r +ld h,a +ld a,r +ld l,a +ld (seed+2),hl +ret + + +;get random number in range 0 to hl-1 inclusive +;entry: hl=range size +;exit: hl=random number in range 0 to range_size-1 +;af/bc/de corrupt +;this is equivalent to 'rand()%range_size' in C; the Linux 'rand' +;man page suggests this alternative if you need a better distribution, +;which may be a good idea for numbers bigger than a few thousand: +; +; > To ensure a good distribution for a subrange of values, +; > use code like the below: +; > i = RAND_MAX / my_range +; > i *= my_range +; > while ((j = rand()) >= i) continue; +; > return j % i; +; > (code example based on code from Karl Lehenbauer's fortune +; > cookie program, which credits Ken Arnold, Unix Review, +; > October 1987). +; +;replace rand() above with a call to rand16, and RAND_MAX with FFFFh. +;there are multiply/divide/mod routines in maths.z. Note that using the +;above algorithm will be significantly slower than calling this +;routine, probably half the speed at best. +rand: +push hl +call rand16 +pop de +call divide +ex de,hl +ret + + + +;get random number between 0 and 65535 inclusive +;entry: none +;exit: hl=random number +;af/bc/de corrupt +rand16: +ld hl,(seed+2) +ld d,l +add hl,hl +add hl,hl +ld c,h +ld hl,(seed) +ld b,h +rl b +ld e,h +rl e +rl d +add hl,bc +ld (seed),hl +ld hl,(seed+2) +adc hl,de +res 7,h +ld a,h +and 080h +jr nz,rand16a +inc h +rand16a: +ld (seed+2),hl +ld hl,(seed) ;now hl=16-bit rand. num. +ret diff --git a/cpmtris/zmac/COPYRIGHT b/cpmtris/zmac/COPYRIGHT new file mode 100644 index 0000000..53d222b --- /dev/null +++ b/cpmtris/zmac/COPYRIGHT @@ -0,0 +1,76 @@ +The copyright situation regarding zmac is not totally clear. +This is what Russell Marks had to say when I asked him: + +" +> [...] I would +> like to make double sure that zmac is indeed public domain and so +> freely distributable and modifyable. Can you confirm this? Have the +> various authors of zmac over the years agreed to give away their +> copyright? + +This is a difficult area, partly because I'm not a lawyer. :-) None of +them seemed to explicitly give away their copyright, but equally, none +of them asserted it either. If you really want to be sure that it's +all entirely public domain, the sort of thing that would definitely +stand up in court, you'll have to try and contact all three previous +contributors (my changes are definitely PD, you wouldn't have to worry +about that). I have vague memories of seeing both Colin Kelley and +John Providenza's names mentioned in more recent things than zmac, but +I don't know any contact addresses for them (nor for Bruce Norskog). + +OTOH, if you're just after some general reassurance that it's ok to +distribute a hacked version, I'd say it's fine - if nothing else, +Colin Kelley posted it to comp.sources.unix in 1987, so he must have +thought it freely distributable and modifiable (the latter because he +made changes himself). I think an earlier version had featured in +simtel's "UNIX-C" directory too. + +Also, this point from the copyright FAQ may be relevant: + +> 2.2) What is "public domain?" +> +> In contrast to copyright is "public domain." A work in the public domain +> is one that can be freely used by anyone for any purpose. +> +> It used to be that if a work was published without notice, it lost all +> copyright, and entered the public domain. That's no longer true, and now +> public domain is more the exception than the rule. +> +> There are still a number of ways that a work may be public domain. +[...] +> - The copyright might have been forfeited. For example, the work +> may have been published without notice prior to the change in +> the law that eliminated the notice requirement (March 1, 1988, +> the effective date of the Berne Convention Implementation Act, +> PL 100-568, 102 Stat. 2853). + +zmac was published without a copyright notice in 1987 (and, one +assumes, long before then as well). This is the main reason I describe +zmac as public domain - the 1987 posting was published source with no +copyright notice and no license being distributed for free by one of +the authors. That smells of PD to me, even without the above. :-) I +just don't see what else it could really be. But getting real, +legally-sound confirmation of this would be a challenge. + +I know this isn't the "yep, no problem, and here are all the authors' +current addresses so you can check for yourself" you may have been +looking for, but there it is. + +FWIW, I distribute zmac with ZCN (my GPL'd CP/M-like OS for the +Amstrad NC100). In case that contributes anything to this. :-) +" + +[Needless to say, I too forfeit any copyright claim on my changes!] + +I am therefore assuming that zmac is public domain (note, however, +that the Act above is probably an American one and different +conditions may apply in e.g. the European Union). I am not a +lawyer, though, and I would strongly suggest you don't abuse this. +In particular, I wouldn't use zmac or portions thereof in any +commercial product. + +If any of the authors of zmac requests so, I will immediately +withdraw this release. + + +Mark RISON, , 1999-07-19 diff --git a/cpmtris/zmac/ChangeLog b/cpmtris/zmac/ChangeLog new file mode 100644 index 0000000..09a98b9 --- /dev/null +++ b/cpmtris/zmac/ChangeLog @@ -0,0 +1,336 @@ +2000-07-02 Mark RISON + + * Version 1.3. + + * Improve man page (including portability fixes by Tim). + + * From Matthew, Tim and Russell: compiler warning avoidance. + +2000-06-25 Mark RISON + + * Version 1.3b4. + + * Allow benign EQU redefinition. + + * Cut down on warnings in pass 1. + + * Find and (hopefully!) fix nasty bug in error recovery in arglists. + + * Allow JP HL/IX/IY syntax. A warning is issued. + + * Revamp man page. + + * From Tim Mann: resolve r/r conflict for parentheses in expressions. + +2000-06-18 Mark RISON + + * Version 1.3b3. + + * BeOS now supported (just treat as a Un*x variant!). + + * Upgrade to bison 1.28 so alloca () no longer a problem. + + * Document the operator zoo. + + * Allow normal parentheses in expressions. + (FIXME: is there a way to tell bison the r/r conflict is OK?) + + * From Tim Mann: sort out operator precedence; make it C-like. + +2000-05-25 Mark RISON + + * Version 1.3b2. + + * Fix error handling fix (with Tim's help!). + + * From Tim Mann: add relationals and synonyms for NOT, MOD, SHL, SHR. + +2000-03-18 Mark RISON + + * Version 1.3b1. + + * Fix error handling. + + * Allow a leading colon or no special character at all + to introduce a label. + + * Allow $ and # to introduce hex constants. + + * Allow COND and ENDC as synoyms for IF and ENDIF. + + * More ANSI C pedantry. + + * From Tim Mann: remove the nasty and buggy hack to allow AND, + OR and XOR to be used as binary operators and do it properly. + + * From Tim Mann: fix bug which prevented & from being used as + a binary operator when it was allowed to introduce a hex constant. + + * From Matthew Phillips: try filename as given before trying + to add a '.z' extension. + +2000-02-27 Mark RISON + + * Version 1.2. + + * Stop trying to add an extension to INCLUDEd files + (this stupid idea was a misfeature introduced in 1.1.1.1). + Instead, define canonical form, which is automatically + decanonicalised according to the local OS requirements + (see the man page for more details). + + * Add support for RISC OS. + + * Increase the maximum number of IFs to 1024. + + * Issue warning when SLL is used. + + * Further ANSI C-ify. + (FIXME: anyone know how to convince bison not to use alloca ()?) + + * Tweak command-line error reporting. + + * Remove legacy non-ANSI C garbage. + + * From Matthew Phillips: add support for outputting AMSDOS header. + + * From Matthew Phillips: add support for RISC OS DDE throwback. + +2000-02-11 Russell Marks + + * zmac.1: updated man page. + + * zmac.y: changed `-O' warning output to look more like gcc + (`foo.z:N: warning: wibble' rather than `foo.z:N: wibble + warning'). + + * zmac.y: changed to use my old getoptn() getopt clone instead of + `real' getopt(), as I know Mark likes his portability :-) (and it + probably wouldn't work on DOS otherwise, which even I would like + it to...). + + * zmac.y: merged in changes (relative to 1.1) from my current + zmac. These were: + + * zmac.y: minor changes to avoid egcs/gcc `ambiguous else' + warnings. + + * zmac.y: changes from Chris Smith [slightly changed to fit zmac + 1.1.1.x]. `-o' now specifies output file; `-x' specifies listing + file (`-x -' does the same as the old `-o' option); the + undocumented `sll' opcode is now allowed; "Cannot open foo file" + errors now give the filename; a `.z' extension is only added to + source filename if it has no extension; args are now parsed with + getopt(). + + * Makefile: fix missing dependencies for zmac. + +1999-10-03 Mark RISON + + * Version 1.1.1.2. + + * Fix bug in nested ELSEs. + + * Pad .bin file when multiple ORGs are used. + + * `-O' option invokes optimisation suggester + for JP, RLC/RRC/RL/RR/SLA A and LD A, 0. + + * Reinstate 0x02, 0x0a, 0x12 and 0x1a as valid 8080 opcodes. + + * Add zmac.c to .zip file (for those who don't have yacc/bison). + + * Make makefile no longer depend on GNU make implicit rules. + +1999-07-18 Mark RISON + + * Version 1.1.1.1 (nee 1.1.1). + + * Extend symbol significance from 15 to 40 chars. + A warning is issued regarding truncated symbols. + + * Make continuous listing be the default. + + * Add ELSE to IF/ENDIF construct. + + * Allow INCLUDE filename to be delimited by " or '. + + * Allow labels to be defined using .name (as well as name:). + + * Allow ADD/SBC/ADD s syntax (implicit "A, "). + A warning is issued. + + * Issue warning for SUB/AND/XOR/OR/CP A, s syntax (explicit "A, "). + + * Issue warning for CMP and JMP opcodes, and V and NV condition codes. + + * Catch division by zero in expressions. + + * Allow hex constants to be specified using a leading '&'. + (FIXME: what's all this testing of nextchar () against '$' for?) + + * Add DB, DEFM and TEXT as synonyms for DEFB. + + * Add DS and RMEM as synonyms for DEFS. + + * Add DW as synonym for DEFW. + + * Add READ as synonym for INCLUDE. + + * Allow strings to be specified using " too. + + * Allow AND, OR and XOR to be used as binary operators. + (FIXME: a bit of a nasty hack; can it be done more cleanly?) + + * Make file handing consistent for source and INCLUDE: + for both, try to open with ".z" first, then with no extension. + +1998-12-15 Russell Marks + + * Version 1.1. + + * Added support for --help and --version. + + * zmac.1: mentioned `title' pseudo-op. + + * Converted revision history from zmac.y into Emacs change log + format (roughly). + + * Now outputs errors more like a C compiler (one per line, + prefixed with filename and line-in-file), useful for `M-x compile' + in Emacs. New `-S' option makes it show the error-causing line + like it used to. + + * Made it output current file for error reports. + + * Added `void' to prototypes for funcs with no args. + + * Removed unnecessary `char *ctime()' and `fflush(stderr)'. + + * Made it support filenames longer than 14 chars! + +1998-02-26 Russell Marks + + * Version 1.0 (for the sake of argument). + + * Made usage message say what the program is. :-) + +1998-02-12 Russell Marks + + * Added verbose inline errors in listing and made the original + terse one-letter ones available via new `-t' option. + + * Added `-c' option to produce continuous listing (no page breaks + or page headers). + + * Corrected plurals in listing so you don't get things like "1 + symbols" any more. + + * Added `-z' option to output 8080-compatible code only - i.e., to + flag Z80-specific ops as errors. + +1998-02-11 Russell Marks + + * Fixed LIST-without-args segfault. + + * Allowed decimal constants to be up to 65535 (rather than up to + 32767 before; I checked and there was no reason for the previous + exception for decimal even on systems with 16-bit ints). + + * Removed the `-d' debug option. + + * Removed the `-t' option. It did everything as normal *except* + for not reporting errors. I can't imagine any valid reason for + wanting that. + + * Made exit status 1 if there were errors in the assembly. + + * Fixed `equ' bug - it now detects forward refs to `equ's with + forward refs and flags them as errors (because of course you'd + need three passes to resolve them). The fix is pretty + nasty-looking but it was really the only way I could see to do it. + Some assemblers (e.g. Hisoft's GENS4) don't allow forward refs in + `equ's at all, so count yourselves lucky! :-) + + * Changed symbol table listing so it fits in 80 columns. + + * Fixed `if' to not depend on internals of yacc parser (which + appear to have changed, anyway). + + * Added support for normal `rst' format (0, 8, 10h, 18h...; still + allows the previous 0, 1, 2, 3...). + + * Made default output binary (as .bin), added `-h' to get .hex. + + * ANSIfied the thing (painful!) and cleaned up a little. + +1987-02-05 Colin Kelley + + * Added 'cmp' as a synonym for 'cp', 'jmp' as a synonym for 'jp', + and added tolerance of accumulator specification for arithmetic + and logical instructions. (For example, 'or a,12' is now accepted, + same as 'or 12'.) + +1987-01-18 Colin Kelley + + * Added MIO code to emulate 'mfile' using malloc()'d memory. This + was needed to get the code to work when compiled under MSC 4.0 on + a PC, and it's probably faster anyway. + +1986-10-02 Colin Kelley + + * Added some more typecasts to keep lint a little happier. Removed + several unused variables. Changed most vars declared as char to + int, since many of them were being compared with -1! I still don't + know what's going on with est[][] being malloc'd and free'd + everywhere...it looks pretty fishy... + +1986-09-20 Colin Kelley + + * Converted to run on a Pyramid. This meant changing yylval to be + a %union, and then putting in the appropriate typecasts where ints + are pointers are used interchangeably. The current version still + probably won't run on machines where + sizeof(int) != sizeof(char *). + Also changed emit() to use varargs, and got rid of the old + style = in front of yacc action code. + +1984-08-27 John Providenza + + * Added PHASE/DEPHASE commands. + +1984-05-22 John Providenza + + * Added include files ala ormac. + +1984-05-11 John Providenza + + * Added code to print unused labels out with the symbol table. + Also sped up the macro processor by using stdio. + +1983-06-07 John Providenza + + * Fixed bug in the ADD IX,... instruction. + +1983-02-15 John Providenza + + * Fixed 'getlocal' to return better local labels. It used to crash + after 6 invocations. + +1982-03-15 John Providenza + + * Changed maximum number of characters in a label to 15 from 7. + Note that 'putsymtab' uses this value inside of a quoted string, + so we use 15. + + * Added underscore as a character type in the lex table 'numpart' + (0x5F). + +1982-03-08 John Providenza + + * Converted to run on Vax, updated syntax to conform better to the + Zilog standard. + +1978-04-?? Bruce Norskog + + * Initial version. diff --git a/cpmtris/zmac/MAXAM b/cpmtris/zmac/MAXAM new file mode 100644 index 0000000..ce9424f --- /dev/null +++ b/cpmtris/zmac/MAXAM @@ -0,0 +1,43 @@ +Notes on MAXAM compatibility +============================ + +MAXAM is an assembler for Amstrad CPC machines +(a CP/M version also exists). + +zmac tries to be MAXAM-compatible. + +Note the following major differences with MAXAM 1.14, though: + +- zmac allows ' and " strings to be specified using '''' and """". +MAXAM only allows '"' and "'", which zmac also allows. + +- zmac will truncate symbols to 40 characters. +MAXAM has no such restriction. + +- MAXAM allows strings to be terminated by end-of-line. +zmac does not. + +- When using the READ directive, MAXAM will take the first +non-whitespace character as the filename delimiter. zmac only +allows ' or " as a filename delimiter. + +- AMSDOS restrictions prevent MAXAM from supporting nested +READs, which zmac allows. AMSDOS restrictions also prevent +MAXAM from supporting directories. + +- zmac has a pretty decent expression parser. +MAXAM does not (no parentheses, no precedence). + +- zmac handles multiple ORGs by inserting padding bytes in +the .bin file, but of course this means retrograde ORGs are +not possible. + +- MAXAM allows multiple statements on the same line, separated +by colons. zmac does not. + +- zmac has many synonyms for operators, directives, labels, hex +constants, etc., which MAXAM does not support. The easiest +thing is not to find out about them! + + +Mark RISON, , 2000-05-25 diff --git a/cpmtris/zmac/Makefile b/cpmtris/zmac/Makefile new file mode 100644 index 0000000..618333c --- /dev/null +++ b/cpmtris/zmac/Makefile @@ -0,0 +1,47 @@ +# Makefile for zmac + +# Edit any defs which are wrong - these should be OK for Linux though. + +CC = gcc + +YACC = bison -y + +MV = mv + +ZIP = zip + +CFLAGS = -O -Wall + +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin +MANDIR = $(PREFIX)/man/man1 + + +all: zmac + +zmac: zmac.c mio.c getoptn.c + $(CC) $(CFLAGS) -o zmac zmac.c mio.c getoptn.c + +zmac.c: zmac.y + $(YACC) zmac.y + $(MV) y.tab.c zmac.c + +dev: /tmp/zmac + +/tmp/zmac: zmac.c mio.c getoptn.c + $(CC) $(CFLAGS) --ansi --pedantic --extra-warnings --all-warnings -o /tmp/zmac zmac.c mio.c getoptn.c + chmod 777 /tmp/zmac + +install: zmac + install -s -m 511 zmac $(BINDIR) + install -m 444 zmac.1 $(MANDIR) + +zip: + $(RM) zmac.zip + man -l -7 zmac.1 | sed 's/.//g' >zmac.doc + $(ZIP) zmac.zip zmac.y zmac.c mio.c mio.h getoptn.c getoptn.h zmac.1 \ + zmac.doc Makefile README NEWS COPYRIGHT ChangeLog MAXAM RISCOS \ + file_id.diz + +clean: + $(RM) *.o y.tab.c zmac.c *~ zmac diff --git a/cpmtris/zmac/NEWS b/cpmtris/zmac/NEWS new file mode 100644 index 0000000..5711470 --- /dev/null +++ b/cpmtris/zmac/NEWS @@ -0,0 +1,76 @@ +* Changes between versions -*- mode:indented-text; mode:outline-minor -*- + +This file (NEWS) documents the significant changes between all +versions of zmac (since version 1.0), with the newest changes first. + + +* Changes in zmac 1.3 + +- Allow ':label' and 'label', and '$' and '#' for hex constants. + +- Add relationals. + +- Add more synonyms. + +- Fix logical operator bugs. + +- Fix error handling. + +- Make operator precedence C-like. + +- Allow normal parentheses in expressions. + +- Revamp man page and add plain text version. + + +* Changes in zmac 1.2 + +- Make filename handling more portable. + +- Introduce -x and new -o options (old -o is now '-x -'). + +- Improve ANSI C-ness. + +- Add support for writing out files with AMSDOS headers. + +- Add support for RISC OS throwback. + + +* Changes in zmac 1.1.1.2 + +- Fix nested ELSE bug. + +- Pad .bin file when multiple ORGs used. + +- Add optimisation suggester. + +- Tweak list of valid 8080 opcodes. + + +* Changes in zmac 1.1.1.1 + +- Add ELSE, READ, DB, DS, DW, DEFM, TEXT and RMEM directives. + +- Allow & to introduce a hex constant, " as a string delimiter +and . to introduce labels. + +- Allow AND, OR and XOR to be used as binary operators. + +- Warn for truncated symbols and non-standard syntax. + + +* Changes in zmac 1.1 + +- Fixed the nasty assumption that filenames were never more than 14 + chars (which was *never* valid, as filenames can include the path to + the file). + +- Error reports now conform to the canonical format for compilers and + the like (as the GNU coding standards put it, + "SOURCE-FILE-NAME:LINENO: MESSAGE"). This is a lot more sensible + than the previous OVERALL-LINENO: MESSAGE\nERROR-LINE\n (and means + you can easily jump to errors when using Emacs' `M-x compile'), but + you can get SOURCE-FILE-NAME:LINENO: MESSAGE\nERROR-LINE\n if you + use the `-S' option. + +- Now supports GNU-ish `--help' and `--version' options. diff --git a/cpmtris/zmac/README b/cpmtris/zmac/README new file mode 100644 index 0000000..bbe5798 --- /dev/null +++ b/cpmtris/zmac/README @@ -0,0 +1,145 @@ +zmac 1.3, a Z80 macro cross-assembler. +Public domain by Bruce Norskog, John Providenza and Colin Kelley. +Cleaned up somewhat and documented by Russell Marks. +Tweaked by Mark RISON, Chris Smith, Matthew Phillips and Tim Mann. + + +Description +----------- + +zmac is a rather good Z80 macro cross-assembler which hardly anyone +seems to know about, but which I've used for about four or five years +now for various things. + +I'd taken to, whenever I uploaded any of my Z80 sources, including a +diff to get zmac minimally working (being a relatively old program, it +made some assumptions which were a little odd in a modern context) and +a pointer to where the shar archives could be found. (FWIW, that's +comp.sources.unix volume 9.) But eventually, I decided it'd be a Good +Thing if I got a more reasonable zmac package together, especially +since I couldn't ever remember seeing a Z80 cross-assembler on +sunsite. Honestly, why bother wasting space on junk like the kernel +and gcc when you could be using it for *useful* stuff like Z80 +assemblers? :-) + +So I made the code ANSI C and got it through gcc's `-Wall', fixed a +couple of obscure bugs I'd found in it, wrote a man page, and added +the option of only accepting 8080-compatible instructions so you can +write programs in Z80 which will also run on the 8080 and 8085 (see +the man page for details), and a couple of other minor features. And +that's essentially what's here. + +The documentation is in the file zmac.doc (derived from the Un*x +man page in the file zmac.1 ). + + +Installation on Un*x +-------------------- + +You'll need an ANSI C compiler. gcc is fine - so should most other +compilers be, these days. (You'll also need bison or yacc, if you +want to build from scratch, but that shouldn't be a problem.) + +Check/edit the Makefile first. (It should be fine, though.) Then do +`make', then (as root) `make install'. This installs the man page +as well as the executable. + + +Installation on BeOS +-------------------- + +You'll need an ANSI C compiler. gcc is fine. + +You should be able to use the Makefile provided, otherwise just +do whatever you need to do to compile zmac.c, mio.c and getoptn.c +together. + +Then put the executable in /boot/home/config/bin/ or +/boot/beos/bin/ . + + +Installation on DOS +------------------- + +You'll need an ANSI C compiler. + +You may be able to use the Makefile provided, if you're using +djgpp, otherwise just do whatever you need to do to compile +zmac.c, mio.c and getoptn.c together; in both cases make sure +the preprocessor token MSDOS is defined. + +Then put the executable somewhere in your PATH. + + +Installation on RISC OS +----------------------- + +You'll need an ANSI C compiler. Acorn C/C++ is fine, and +so should gcc and lcc be. + +You may be able to use the Makefile provided, otherwise just +do whatever you need to do to compile zmac.c, mio.c and getoptn.c +together; in both cases make sure the preprocessor token __riscos +or __riscos__ is defined (this is the case for Acorn C/C++, +gcc and lcc). + +Then put the executable somewhere in your Run$Path. + +[Also see the file RISCOS.] + + +Installation on other OSes +-------------------------- + +You'll need an ANSI C compiler. + +You'll probably need to add filename-handling support for +your OS. This involves adding appropriate definitions +of OS_DIR_SEP and OS_EXT_SEP and adding appropriate code +to decanonicalise (). In those cases where directories in +pathnames are syntactically distinct to leafnames (e.g. VMS) +changes will also be needed in suffix () and suffix_if_none (). + +You may be able to use the Makefile provided, otherwise just +do whatever you need to do to compile zmac.c, mio.c and getoptn.c +together. + +Then put the executable somewhere appropriate, and feed your +changes back to so they can become part +of the source source! + + +History +------- + +Bruce Norskog wrote zmac in 1978, "modeled after the ... macro +cross-assembler for the Intel 8080 by Ken Borgendale." + +John Providenza made some changes and bugfixes in the early 80s. + +Colin Kelley did much the same in the mid-80s. + +And of course, I've just messed about with it in the late 90s, +together with Mark RISON, Chris Smith, Matthew Phillips +and Tim Mann. :-) + +I assigned the first version I hacked on the version number 1.0 just +for the sake of argument (previous versions didn't have a version +number). A version number of about 4.0 or 5.0 would probably be a +better reflection of what the code's been through over the years... + + +Contacting me +------------- + +You can email me at . However, +Mark RISON now maintains zmac, so you should generally +email him instead at . + +The latest version of zmac is currently available from + + http://www.nenie.org/cpcip/index.html#zmac + +Share and enjoy! + +-Rus (with a few tweaks by Mark). diff --git a/cpmtris/zmac/README.1st b/cpmtris/zmac/README.1st new file mode 100644 index 0000000..c843b62 --- /dev/null +++ b/cpmtris/zmac/README.1st @@ -0,0 +1,5 @@ +This copy omits zmac.c to save space, but is otherwise identical +to zmac 1.3 as released. The C is normally built from zmac.y, so +this shouldn't cause any problems. + +-Rus. diff --git a/cpmtris/zmac/RISCOS b/cpmtris/zmac/RISCOS new file mode 100644 index 0000000..e9ce602 --- /dev/null +++ b/cpmtris/zmac/RISCOS @@ -0,0 +1,19 @@ +Notes for compilation under RISC OS +=================================== + +zmac 1.2 has been compiled and tested on RISC OS 3.71, using +Acorn C/C++ Release 5. It has not been compiled with gcc, lcc +or Easy C. + +To compile with Acorn C/C++ you will need to rename the five +source and header files to place them in directories called +"c" and "h" and create the object directory "o". Ignore +the supplied Makefile -- it is very easy to build a RISC OS +one using Acorn !Make. Do not forget to link in C:o.stubs. + +A full RISC OS binary distribution, with desktop front end, +is available for download from + + http://users.ox.ac.uk/~chri0264/software/ + +Matthew Phillips, , 2000-03-12 diff --git a/cpmtris/zmac/file_id.diz b/cpmtris/zmac/file_id.diz new file mode 100644 index 0000000..f25f57a --- /dev/null +++ b/cpmtris/zmac/file_id.diz @@ -0,0 +1,3 @@ +Z80 macro cross-assembler + +Cross-assembleur macro Z80 diff --git a/cpmtris/zmac/getoptn.c b/cpmtris/zmac/getoptn.c new file mode 100644 index 0000000..2756991 --- /dev/null +++ b/cpmtris/zmac/getoptn.c @@ -0,0 +1,58 @@ +/* getoptn.c - a getopt() clone, so that cmdline option parsing will + * work on non-Unix systems. + * + * PD by RJM + */ + +#include +#include +#include "getoptn.h" + + +/* equivalents of optopt, opterr, optind, and optarg */ +int optnopt=0,optnerr=0,optnind=1; +char *optnarg=NULL; + +/* holds offset in current argv[] value */ +static unsigned int optnpos=1; + + +/* This routine assumes that the caller is pretty sane and doesn't + * try passing an invalid 'optstring' or varying argc/argv. + */ +int getoptn(int argc,char *argv[],char *optstring) +{ +char *ptr; + +/* check for end of arg list */ +if(optnind==argc || *(argv[optnind])!='-' || strlen(argv[optnind])<=1) + return(-1); + +if((ptr=strchr(optstring,argv[optnind][optnpos]))==NULL) + return('?'); /* error: unknown option */ +else + { + optnopt=*ptr; + if(ptr[1]==':') + { + if(optnind==argc-1) return(':'); /* error: missing option */ + optnarg=argv[optnind+1]; + optnpos=1; + optnind+=2; + return(optnopt); /* return early, avoiding the normal increment */ + } + } + +/* now increment position ready for next time. + * no checking is done for the end of args yet - this is done on + * the next call. + */ +optnpos++; +if(optnpos>=strlen(argv[optnind])) + { + optnpos=1; + optnind++; + } + +return(optnopt); /* return the found option */ +} diff --git a/cpmtris/zmac/getoptn.h b/cpmtris/zmac/getoptn.h new file mode 100644 index 0000000..c72613d --- /dev/null +++ b/cpmtris/zmac/getoptn.h @@ -0,0 +1,5 @@ +/* getoptn.h */ + +extern int optnopt,optnerr,optnind; +extern char *optnarg; +extern int getoptn(int argc,char *argv[],char *optstring); diff --git a/cpmtris/zmac/mio.c b/cpmtris/zmac/mio.c new file mode 100644 index 0000000..ea72fe4 --- /dev/null +++ b/cpmtris/zmac/mio.c @@ -0,0 +1,126 @@ +/* + * mio.c - Colin Kelley 1-18-87 + * routines to emulate temporary file handling with memory instead + * + * rjm 980212 - changed to use memcpy rather than bcopy + */ + +#include +#include +#include + +#define UNUSED(var) ((void) var) + +#define MALLOC_SIZE 10000 +#define JUNK_FPTR stdin /* junk ptr to return (unused) */ + +static unsigned char *mhead; /* pointer to start of malloc()d area */ +static unsigned char *mend; /* pointer to current (just beyond) EOF*/ +static unsigned char *mptr; /* pointer to current position */ +static unsigned int msize; /* size of chunk mhead points to */ + +FILE *mfopen(char *filename,char *mode) +{ + UNUSED (filename); + UNUSED (mode); + if ((mhead = malloc(MALLOC_SIZE)) == NULL) { + msize = 0; + return (NULL); + } + msize = MALLOC_SIZE; + mend = mptr = mhead; + return (JUNK_FPTR); /* not used */ +} + +int mfclose(FILE *f) +{ + UNUSED (f); + if (mhead) { + free(mhead); + return (0); + } + else + return (-1); +} + +int mfputc(unsigned int c,FILE *f) +{ + unsigned char *p; + UNUSED (f); + while (mptr >= mhead + msize) { + if ((p = realloc(mhead,msize+MALLOC_SIZE)) == (unsigned char *)-1) { + fputs("mio: out of memory\n",stderr); + return (-1); + } + else { + msize += MALLOC_SIZE; + mptr = (unsigned char *) (p + (unsigned int)(mptr - mhead)); + mhead = p; + } + } + *mptr = c & 255; + mend = ++mptr; + return c; +} + +int mfgetc(FILE *f) +{ + UNUSED (f); + if (mptr >= mend) /* no characters left */ + return (-1); + else + return (*mptr++); +} + +int mfseek(FILE *f,long loc,int origin) +{ + UNUSED (f); + if (origin != 0) { + fputs("mseek() only implemented with 0 origin",stderr); + return (-1); + } + mptr = mhead + loc; + return (0); +} + +int mfread(char *ptr, unsigned int size, unsigned int nitems, FILE *f) +{ + unsigned int i = 0; + UNUSED (f); + while (i < nitems) { + if ((mptr + size) > mend) + break; + memcpy(ptr,mptr,size); + ptr += size; + mptr += size; + i++; + } + return (i); +} + +int mfwrite(char *ptr, int size, int nitems, FILE *f) +{ + int i = 0; + unsigned char *p; + UNUSED (f); + while (i < nitems) { + while (mptr + size >= mhead + msize) { + if ((p = realloc(mhead,msize+MALLOC_SIZE)) == (unsigned char *)-1){ + fputs("mio: out of memory\n",stderr); + return (-1); + } + else { + msize += MALLOC_SIZE; + mptr = (unsigned char *) (p + (unsigned int)(mptr - mhead)); + mhead = p; + } + } + if ((mptr + size) > mhead + msize) + break; + memcpy(mend,ptr,size); + ptr += size; + mend += size; + mptr = mend; + } + return (i); +} diff --git a/cpmtris/zmac/mio.h b/cpmtris/zmac/mio.h new file mode 100644 index 0000000..66d751d --- /dev/null +++ b/cpmtris/zmac/mio.h @@ -0,0 +1,9 @@ +/* prototypes for mio.h */ + +extern FILE *mfopen(char *filename,char *mode); +extern int mfclose(FILE *f); +extern int mfputc(unsigned int c,FILE *f); +extern int mfgetc(FILE *f); +extern int mfseek(FILE *f,long loc,int origin); +extern int mfread(char *ptr, unsigned int size, unsigned int nitems, FILE *f); +extern int mfwrite(char *ptr, int size, int nitems, FILE *f); diff --git a/cpmtris/zmac/zmac.1 b/cpmtris/zmac/zmac.1 new file mode 100644 index 0000000..1aad03c --- /dev/null +++ b/cpmtris/zmac/zmac.1 @@ -0,0 +1,412 @@ +.\" -*- nroff -*- +.\" +.\" zmac - Z80 macro cross-assembler +.\" Public domain by Bruce Norskog and others. +.\" +.\" zmac.1 - man page +.\" +.\" (version number below is purely for the sake of argument; +.\" it's probably about the millionth version IRL :-)) +.\" +.TH zmac 1 "2000-07-02" "Version 1.3" "Development Tools" +.\" +.\"------------------------------------------------------------------ +.\" +.SH NAME +zmac \- Z80 macro cross-assembler +.\" +.\"------------------------------------------------------------------ +.\" +.SH SYNOPSIS +.PD 0 +.B zmac +.RB [ --help ] +.RB [ --version ] +.RB [ -AbcdefghilLmnOpsStTz ] +.RB [ -o +.IR outfile ] +.RB [ -x +.IR listfile ] +.RI [ filename ] +.PD 1 +.\" +.\"------------------------------------------------------------------ +.\" +.SH DESCRIPTION +zmac is a Z80 macro cross-assembler. It has all the features you'd +expect. It assembles the specified input file (with a `.z' extension +if there is no pre-existing extension and the file as given doesn't +exist) and produces raw binary output +in a `.bin' file. (It can optionally produce CP/M-style Intel hex +format - see below.) By default it also produces a nicely-formatted +listing of the m/c alongside the source, in a `.lst' file. +.PP +As well as normal Z80 programs, zmac lets you write 8080 programs in +Z80 assembly. (See the `-z' option below.) +.\" +.\"------------------------------------------------------------------ +.\" +.SH OPTIONS +Most of the options control aspects of the listing. +.TP +.I --help +Display a list of options and a terse description of what the options +do. +.TP +.I --version +Print version number. +.TP +.I -A +Add an AMSDOS header to the generated binary file (this option +automatically disables generation of hex output). +.TP +.I -b +Don't generate the m/c output at all. +.TP +.I -c +Make the listing continuous, i.e. don't generate any page breaks or +page headers. Can make things less confusing if you're going to +consult the listing online rather than printing it. This is the default. +.TP +.I -d +Make the listing discontinuous. +.TP +.I -e +Omit the `error report' section in the listing. +.TP +.I -f +List instructions not assembled due to `if' expressions being +false. (Normally these are not shown in the listing.) +.TP +.I -g +List only the first line of equivalent hex for a source line. +.TP +.I -h +Output CP/M-ish Intel hex format (using extension `.hex') rather than +the default of a simple binary file (extension `.bin'). +.TP +.I -i +Don't list files included with `include'. +.TP +.I -l +Don't generate a listing at all. +.TP +.I -L +Generate listing no matter what. Overrides any conflicting options. +.TP +.I -m +List macro expansions. +.TP +.I -n +Omit line numbers from listing. +.TP +.I -o +Output assembled code to +.IR outfile . +.TP +.I -O +Suggest possible optimisations. (These are shown as warnings.) +.TP +.I -p +Use a few linefeeds for page break in listing rather than ^L. +.TP +.I -s +Omit the symbol table from the listing. +.TP +.I -S +When reporting an error, show the line which caused it. Without this +option zmac reports errors in the canonical format, one-per-line (but +see the first item in the +.B BUGS +section). +.TP +.I -t +Give terse (single-letter) error codes in listing (with a key at the +end). (Normally the full error message is given.) +.TP +.I -T +Enable DDE throwback for reporting warnings and errors. This option +only exists for RISC OS builds. +.TP +.I -x +Output listing to +.IR listfile . +(It outputs to stdout if you specify the file as `-'.) +This has no effect if used with +.IR -l . +.TP +.I -z +Accept 8080-compatible instructions only; flag any Z80-specific ones +as errors. This lets you write 8080 programs in Z80 assembly, without +having to worry about the deeply nasty 8080 assembly syntax. :-) +.\" +.\"------------------------------------------------------------------ +.\" +.SH "INPUT FORMAT" +zmac uses the standard Zilog mnemonics, and the pseudo-ops are also +largely as you'd expect. +.PP +Input can be upper or lowercase. +.PP +Comments start with `;' and carry on +to the end of the line. +.PP +Labels are declared with +`label:', `.label', `:label' or just `label' - +indentation is unimportant. (Labels can be up to 40 chars long.) +.PP +Number constants can take a trailing h or a leading &, $ or # for hex, +a trailing b for binary, a trailing o or q for octal, or a trailing +d for decimal. +.PP +Here is how other things work: +.TP +.B defb 42 +A byte. `ascii', `byte', `db', `defm' and `text' are synonyms. +.TP +.B defb 'foobar' +An ASCII character string (not NUL-terminated). +Double quotes can also be used. +.TP +\fBdefb 'Who needs MS-DOG when you have *CP/M*?', 13, 10, '$'\fP +Strings, bytes, and comp.os.msdos.programmer troll. :-) +.TP +.B defw 2112 +A word (16 bits). `word' and `dw' are synonyms. +.TP +.B defs 500 +Insert 500 zero bytes. `block', `ds' and `rmem' are synonyms. +.TP +.B org +Set the address to assemble to. +.TP +.B equ +Define a symbol to have a fixed value. The symbol can be used before it +is defined. A symbol defined with `equ' or as a label can be defined only +once, except that a symbol defined with `equ' may be redefined to the +same value. +.TP +.B defl +Define a symbol to have a changeable value. The symbol cannot be used +before it is defined, and it can be redefined to a different value later +with another `defl'. +.TP +.B end +Ends the input. Any lines after an `end' are silently ignored. +If an arg is given, it declares the entry address for the program. +This has no effect if zmac is writing a raw binary file. In an Intel +hex file, it generates an S-record directing 0 bytes of data to be loaded +at the given address. +.TP +\fBif\fP ... [ \fBelse\fP ... ] \fBendif\fP +For conditional assembly. If you do `if foo' and foo evaluates to +zero, all the lines up until the next corresponding `else' or `endif' +are completely ignored. Conversely, if foo evaluates to non-zero, any +lines from a corresponding `else' to the `endif' are ignored. Ifs can +be nested. `cond'/`endc' are synonyms for `if'/`endif'. +.TP +\fBrsym\fP and \fBwsym\fP +Read/write a symbol file. These simply load/save the currently defined +symbols from/to the file specified (in a non-portable format). `rsym' +takes place at the point it is encountered in the file (on the first +pass); `wsym' is delayed until assembly has finished. +.TP +.B include +Include a file. Like C's (well, cpp's) #include, but the filename arg +lacks the angle brackets or quotes (though quotes may be used). +`read' is a synonym. +.\" +.\"------------------------------------------------------------------ +.\" +.SH "ODDITIES" +There are the following oddities: +.TP +.B cmp +Same as `cp'. +.TP +.B jmp +Same as `jp'. +.TP +.B jp hl +Same as `jp (hl)'. Ditto for ix and iy. +.TP +.B ld hl, 'LH' +Oh, yes! Ditto for bc, de, sp, ix and iy. +.TP +\fBmin\fP and \fBmax\fP +Same as `defl' except that the symbol is defined as the +smaller or bigger of two comma-separated expressions. +.TP +\fBv\fP and \fBnv\fP +These are alternatives to `pe' and `po' respectively in `jp' +and `call' instructions, meaning that tests on the Z80's +(parity/)overflow flag can be written in the same +format (`x'/`nx') as for those on the zero or carry flags. +.\" +.\"------------------------------------------------------------------ +.\" +.SH "LISTING PSEUDO-OPS" +There are several pseudo-ops for controlling the listing. None of +these ops appear in the listing themselves: +.TP +.B eject +Start a new listing page. +.TP +.B nolist +Do nothing. This can be used to have a comment in the source but not +the listing, I suppose. +.TP +.B "elist, flist, glist, mlist" +These have the same effect as the similarly-named command-line +options, though possibly with the sense reversed depending on the +default. Use an arg >0 (or no arg) to enable, and an arg <0 to +disable. +.TP +.B list +Sets whether to list or not. You can use this to avoid listing certain +parts of the source. Takes same optional arg as `elist', etc.. +.TP +.B title +Set title (used in listing and symbol file). +.TP +.B space +Output arg blank lines in the listing, or one line if no arg is given. +.\" +.\"------------------------------------------------------------------ +.\" +.SH "EXPRESSIONS" +Expressions are reasonably full-featured; here is the complete +list of operators, highest-precedence first. Operators separated +only by a space are synonyms; for example, `~' is the same as `not'. +.IP +! (logical), ~ not (bitwise), + (unary), - (unary) +.IP +*, /, % mod +.IP ++, - +.IP +<< shl, >> shr +.IP +< lt, > gt, <= le, >= ge +.IP +== = eq, != <> ne +.IP +& and (bitwise) +.IP +^ xor (bitwise) +.IP +| or (bitwise) +.PP +You can use normal parentheses or square brackets to override +the precedence rules; use square brackets where parentheses would +conflict with Z80 mnemonic syntax. +.\" +.\"------------------------------------------------------------------ +.\" +.SH "MACROS" +The following defines a macro named m with zero or more formal parameters +p1, p2, ..., pn, zero or more local symbols ?s1, ?s2, ..., ?sm, and +body b1, b2, ...: +.IP +m macro p1, p2, ..., pn, ?s1, ?s2, ..., ?sm +.IP + b1 +.IP + b2 +.IP + ... +.IP + endm +.PP +The macro is called by writing: +.IP +m v1, v2, ..., vn +.PP +A macro call expands to the text of the macro's body, with each +occurrence of a formal parameter pk replaced by the corresponding +value vk, and with each local symbol ?sk replaced by a new, unique +symbol invented for this call. Invented symbols begin with `?', +so you should avoid using such symbols elsewhere in your program. +.PP +zmac currently does not check that you have provided the right number +of parameters when calling a macro. If you provide too few, unmatched +formals are replaced with the empty string. If you provide too +many, the additional values begin to replace local symbols as if +they were ordinary parameters. (This could be considered a feature.) +After the local symbols are all replaced, additional parameters +are silently ignored. +.\" +.\"------------------------------------------------------------------ +.\" +.SH "FILENAMES" +To allow source files to be portable, a canonical format should be +used for filenames in source files (i.e. in include, rsym or wsym +statements). This canonical format is basically the Un*x +format: `/' as the directory separator, `.' as the extension separator, +".." as the parent directory, "." as the current directory, +and a leading `/' as the root directory +(this should be avoided as it makes moving the source non-trivial). +.PP +For maximum portability, no element of such a filename should have +more than 10 characters, contain characters other than 0-9, a-z, +underscore and `.', or contain more than one `.'. +.PP +Filenames passed as command-line arguments (i.e. for the source +specification, or for the +.IR -o +or +.IR -x +options), however, are assumed to +be in the local (non-canonical) format; this includes the +extension separator (whether supplied or added) and any drive specifiers. +.PP +The OSes which are currently supported are Un*x, BeOS, DOS and RISC OS. +.\" +.\"------------------------------------------------------------------ +.\" +.SH "MISCELLANEOUS" +In the symbol table listing, the `=' prefix is given for those symbols +defined by `equ' or `defl', and the `+' suffix is given for those +which were not used. +.\" +.\"------------------------------------------------------------------ +.\" +.SH "EXIT STATUS" +.TP +.B 0 +No errors. +.TP +.B 1 +One or more errors were found during assembly, or zmac exited with a +fatal error. +.\" +.\"------------------------------------------------------------------ +.\" +.SH BUGS +zmac reports each error in a line separately. This is probably a good +thing, but tends to effectively result in the same overall problem +being reported twice (e.g. a reference to an undefined symbol causes +both an undeclared error and a value error). +.PP +The man page isn't what you'd call extensive. This shouldn't be too +surprising as I had to RTFS to WTFM. :-) +.PP +What do phase/dephase do (they seem to be some way of temporarily +moving the program location -- for overlays?)? +.\" +.\"------------------------------------------------------------------ +.\" +.SH "SEE ALSO" +.IR as "(1)" +.\" +.\"------------------------------------------------------------------ +.\" +.SH AUTHOR +Bruce Norskog (in 1978!). +.PP +Updates and bugfixes over the years by John Providenza, Colin Kelley, +and more recently by Russell Marks, Mark RISON, Chris Smith, +Matthew Phillips and Tim Mann. +.PP +Russell Marks wrote most of the man page, with tweaks by Mark RISON +and Tim Mann. diff --git a/cpmtris/zmac/zmac.doc b/cpmtris/zmac/zmac.doc new file mode 100644 index 0000000..65249d4 --- /dev/null +++ b/cpmtris/zmac/zmac.doc @@ -0,0 +1,462 @@ + + + +zmac(1) Development Tools zmac(1) + + +NAME + zmac - Z80 macro cross-assembler + +SYNOPSIS + zmac [--help] [--version] [-AbcdefghilLmnOpsStTz] [-o out- + file] [-x listfile] [filename] + +DESCRIPTION + zmac is a Z80 macro cross-assembler. It has all the fea- + tures you'd expect. It assembles the specified input file + (with a `.z' extension if there is no pre-existing exten- + sion and the file as given doesn't exist) and produces raw + binary output in a `.bin' file. (It can optionally produce + CP/M-style Intel hex format - see below.) By default it + also produces a nicely-formatted listing of the m/c along- + side the source, in a `.lst' file. + + As well as normal Z80 programs, zmac lets you write 8080 + programs in Z80 assembly. (See the `-z' option below.) + +OPTIONS + Most of the options control aspects of the listing. + + --help Display a list of options and a terse description + of what the options do. + + --version + Print version number. + + -A Add an AMSDOS header to the generated binary file + (this option automatically disables generation of + hex output). + + -b Don't generate the m/c output at all. + + -c Make the listing continuous, i.e. don't generate + any page breaks or page headers. Can make things + less confusing if you're going to consult the list- + ing online rather than printing it. This is the + default. + + -d Make the listing discontinuous. + + -e Omit the `error report' section in the listing. + + -f List instructions not assembled due to `if' expres- + sions being false. (Normally these are not shown in + the listing.) + + -g List only the first line of equivalent hex for a + source line. + + -h Output CP/M-ish Intel hex format (using extension + `.hex') rather than the default of a simple binary + + + +Version 1.3 2000-07-02 1 + + + + + +zmac(1) Development Tools zmac(1) + + + file (extension `.bin'). + + -i Don't list files included with `include'. + + -l Don't generate a listing at all. + + -L Generate listing no matter what. Overrides any con- + flicting options. + + -m List macro expansions. + + -n Omit line numbers from listing. + + -o Output assembled code to outfile. + + -O Suggest possible optimisations. (These are shown as + warnings.) + + -p Use a few linefeeds for page break in listing + rather than ^L. + + -s Omit the symbol table from the listing. + + -S When reporting an error, show the line which caused + it. Without this option zmac reports errors in the + canonical format, one-per-line (but see the first + item in the BUGS section). + + -t Give terse (single-letter) error codes in listing + (with a key at the end). (Normally the full error + message is given.) + + -T Enable DDE throwback for reporting warnings and + errors. This option only exists for RISC OS + builds. + + -x Output listing to listfile. (It outputs to stdout + if you specify the file as `-'.) This has no + effect if used with -l. + + -z Accept 8080-compatible instructions only; flag any + Z80-specific ones as errors. This lets you write + 8080 programs in Z80 assembly, without having to + worry about the deeply nasty 8080 assembly syntax. + :-) + +INPUT FORMAT + zmac uses the standard Zilog mnemonics, and the pseudo-ops + are also largely as you'd expect. + + Input can be upper or lowercase. + + Comments start with `;' and carry on to the end of the + line. + + + +Version 1.3 2000-07-02 2 + + + + + +zmac(1) Development Tools zmac(1) + + + Labels are declared with `label:', `.label', `:label' or + just `label' - indentation is unimportant. (Labels can be + up to 40 chars long.) + + Number constants can take a trailing h or a leading &, $ + or # for hex, a trailing b for binary, a trailing o or q + for octal, or a trailing d for decimal. + + Here is how other things work: + + defb 42 + A byte. `ascii', `byte', `db', `defm' and `text' + are synonyms. + + defb 'foobar' + An ASCII character string (not NUL-terminated). + Double quotes can also be used. + + defb 'Who needs MS-DOG when you have *CP/M*?', 13, 10, '$' + Strings, bytes, and comp.os.msdos.programmer troll. + :-) + + defw 2112 + A word (16 bits). `word' and `dw' are synonyms. + + defs 500 + Insert 500 zero bytes. `block', `ds' and `rmem' + are synonyms. + + org Set the address to assemble to. + + equ Define a symbol to have a fixed value. The symbol + can be used before it is defined. A symbol defined + with `equ' or as a label can be defined only once, + except that a symbol defined with `equ' may be + redefined to the same value. + + defl Define a symbol to have a changeable value. The + symbol cannot be used before it is defined, and it + can be redefined to a different value later with + another `defl'. + + end Ends the input. Any lines after an `end' are + silently ignored. If an arg is given, it declares + the entry address for the program. This has no + effect if zmac is writing a raw binary file. In an + Intel hex file, it generates an S-record directing + 0 bytes of data to be loaded at the given address. + + if ... [ else ... ] endif + For conditional assembly. If you do `if foo' and + foo evaluates to zero, all the lines up until the + next corresponding `else' or `endif' are completely + ignored. Conversely, if foo evaluates to non-zero, + + + +Version 1.3 2000-07-02 3 + + + + + +zmac(1) Development Tools zmac(1) + + + any lines from a corresponding `else' to the + `endif' are ignored. Ifs can be nested. + `cond'/`endc' are synonyms for `if'/`endif'. + + rsym and wsym + Read/write a symbol file. These simply load/save + the currently defined symbols from/to the file + specified (in a non-portable format). `rsym' takes + place at the point it is encountered in the file + (on the first pass); `wsym' is delayed until assem- + bly has finished. + + include + Include a file. Like C's (well, cpp's) #include, + but the filename arg lacks the angle brackets or + quotes (though quotes may be used). `read' is a + synonym. + +ODDITIES + There are the following oddities: + + cmp Same as `cp'. + + jmp Same as `jp'. + + jp hl Same as `jp (hl)'. Ditto for ix and iy. + + ld hl, 'LH' + Oh, yes! Ditto for bc, de, sp, ix and iy. + + min and max + Same as `defl' except that the symbol is defined as + the smaller or bigger of two comma-separated + expressions. + + v and nv + These are alternatives to `pe' and `po' respec- + tively in `jp' and `call' instructions, meaning + that tests on the Z80's (parity/)overflow flag can + be written in the same format (`x'/`nx') as for + those on the zero or carry flags. + +LISTING PSEUDO-OPS + There are several pseudo-ops for controlling the listing. + None of these ops appear in the listing themselves: + + eject Start a new listing page. + + nolist Do nothing. This can be used to have a comment in + the source but not the listing, I suppose. + + elist, flist, glist, mlist + These have the same effect as the similarly-named + command-line options, though possibly with the + + + +Version 1.3 2000-07-02 4 + + + + + +zmac(1) Development Tools zmac(1) + + + sense reversed depending on the default. Use an arg + >0 (or no arg) to enable, and an arg <0 to disable. + + list Sets whether to list or not. You can use this to + avoid listing certain parts of the source. Takes + same optional arg as `elist', etc.. + + title Set title (used in listing and symbol file). + + space Output arg blank lines in the listing, or one line + if no arg is given. + +EXPRESSIONS + Expressions are reasonably full-featured; here is the com- + plete list of operators, highest-precedence first. Opera- + tors separated only by a space are synonyms; for example, + `~' is the same as `not'. + + ! (logical), ~ not (bitwise), + (unary), - (unary) + + *, /, % mod + + +, - + + << shl, >> shr + + < lt, > gt, <= le, >= ge + + == = eq, != <> ne + + & and (bitwise) + + ^ xor (bitwise) + + | or (bitwise) + + You can use normal parentheses or square brackets to over- + ride the precedence rules; use square brackets where + parentheses would conflict with Z80 mnemonic syntax. + +MACROS + The following defines a macro named m with zero or more + formal parameters p1, p2, ..., pn, zero or more local sym- + bols ?s1, ?s2, ..., ?sm, and body b1, b2, ...: + + m macro p1, p2, ..., pn, ?s1, ?s2, ..., ?sm + + b1 + + b2 + + ... + + endm + + + +Version 1.3 2000-07-02 5 + + + + + +zmac(1) Development Tools zmac(1) + + + The macro is called by writing: + + m v1, v2, ..., vn + + A macro call expands to the text of the macro's body, with + each occurrence of a formal parameter pk replaced by the + corresponding value vk, and with each local symbol ?sk + replaced by a new, unique symbol invented for this call. + Invented symbols begin with `?', so you should avoid using + such symbols elsewhere in your program. + + zmac currently does not check that you have provided the + right number of parameters when calling a macro. If you + provide too few, unmatched formals are replaced with the + empty string. If you provide too many, the additional + values begin to replace local symbols as if they were + ordinary parameters. (This could be considered a fea- + ture.) After the local symbols are all replaced, addi- + tional parameters are silently ignored. + +FILENAMES + To allow source files to be portable, a canonical format + should be used for filenames in source files (i.e. in + include, rsym or wsym statements). This canonical format + is basically the Un*x format: `/' as the directory separa- + tor, `.' as the extension separator, ".." as the parent + directory, "." as the current directory, and a leading `/' + as the root directory (this should be avoided as it makes + moving the source non-trivial). + + For maximum portability, no element of such a filename + should have more than 10 characters, contain characters + other than 0-9, a-z, underscore and `.', or contain more + than one `.'. + + Filenames passed as command-line arguments (i.e. for the + source specification, or for the -o or -x options), how- + ever, are assumed to be in the local (non-canonical) for- + mat; this includes the extension separator (whether sup- + plied or added) and any drive specifiers. + + The OSes which are currently supported are Un*x, BeOS, DOS + and RISC OS. + +MISCELLANEOUS + In the symbol table listing, the `=' prefix is given for + those symbols defined by `equ' or `defl', and the `+' suf- + fix is given for those which were not used. + +EXIT STATUS + 0 No errors. + + 1 One or more errors were found during assembly, or + zmac exited with a fatal error. + + + +Version 1.3 2000-07-02 6 + + + + + +zmac(1) Development Tools zmac(1) + + +BUGS + zmac reports each error in a line separately. This is + probably a good thing, but tends to effectively result in + the same overall problem being reported twice (e.g. a ref- + erence to an undefined symbol causes both an undeclared + error and a value error). + + The man page isn't what you'd call extensive. This + shouldn't be too surprising as I had to RTFS to WTFM. :-) + + What do phase/dephase do (they seem to be some way of tem- + porarily moving the program location -- for overlays?)? + +SEE ALSO + as(1) + +AUTHOR + Bruce Norskog (in 1978!). + + Updates and bugfixes over the years by John Providenza, + Colin Kelley, and more recently by Russell Marks, Mark + RISON, Chris Smith, Matthew Phillips and Tim Mann. + + Russell Marks wrote most of the man page, with tweaks by + Mark RISON and Tim Mann. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Version 1.3 2000-07-02 7 + + diff --git a/cpmtris/zmac/zmac.y b/cpmtris/zmac/zmac.y new file mode 100644 index 0000000..2f0be9a --- /dev/null +++ b/cpmtris/zmac/zmac.y @@ -0,0 +1,3886 @@ +%{ +/* + * zmac -- macro cross-assembler for the Zilog Z80 microprocessor + * + * Bruce Norskog 4/78 + * + * Last modification 2000-07-01 by mgr + * + * This assembler is modeled after the Intel 8080 macro cross-assembler + * for the Intel 8080 by Ken Borgendale. The major features are: + * 1. Full macro capabilities + * 2. Conditional assembly + * 3. A very flexible set of listing options and pseudo-ops + * 4. Symbol table output + * 5. Error report + * 6. Elimination of sequential searching + * 7. Commenting of source + * 8. Facilities for system definiton files + * + * (Revision history is now in ChangeLog. -rjm) + */ + +#define ZMAC_VERSION "1.3" +/* #define ZMAC_BETA "b4" */ + +#include +#include +#include +#include +#include +#include "mio.h" +#include "getoptn.h" + +#define UNUSED(var) ((void) var) + +#if defined (__riscos__) && !defined (__riscos) +#define __riscos +#endif + +#ifdef __riscos +#include "swis.h" +#define DDEUtils_Prefix 0x42580 +#define DDEUtils_ThrowbackStart 0x42587 +#define DDEUtils_ThrowbackSend 0x42588 +#define DDEUtils_ThrowbackEnd 0x42589 +#endif + +#ifndef OS_DIR_SEP +#if defined (MSDOS) +#define OS_DIR_SEP '\\' +#elif defined (__riscos) +#define OS_DIR_SEP '.' +#else +#define OS_DIR_SEP '/' +#endif +#endif + +#ifndef OS_EXT_SEP +#if defined (__riscos) +#define OS_EXT_SEP '/' +#else +#define OS_EXT_SEP '.' +#endif +#endif + +/* + * DEBUG turns on pass reporting. + * Macro debug and Token debug enables. +#define DEBUG +#define M_DEBUG +#define T_DEBUG + */ + +#define ITEMTABLESIZE 2000 +#define TEMPBUFSIZE 200 +#define LINEBUFFERSIZE 200 +#define EMITBUFFERSIZE 200 +#define MAXSYMBOLSIZE 40 +#define IFSTACKSIZE 20 +#define MAXIFS 1024 +#define TITLELEN 50 +#define BINPERLINE 16 +#define PARMMAX 25 +#define MAXEXP 25 +#define SYMMAJIC 07203 +#define NEST_IN 8 + + +#define loop for(;;) + +void yyerror(char *err) +{ + UNUSED (err); /* we will do our own error printing */ + /* printf ("Oops! %s\n", err); */ +} + +struct item { + char *i_string; + int i_value; + int i_token; + int i_uses; + int i_equbad; +}; + +FILE *fout, + *fbuf, + *fin[NEST_IN], + *now_file ; + +int pass2; /* set when pass one completed */ +int dollarsign ; /* location counter */ +int olddollar ; /* kept to put out binary */ + +/* program counter save for PHASE/DEPHASE */ +int phdollar, phbegin, phaseflag ; + +char *src_name[NEST_IN] ; +int linein[NEST_IN] ; +int now_in ; + + +#define bflag 0 /* balance error */ +#define eflag 1 /* expression error */ +#define fflag 2 /* syntax error */ +#define iflag 3 /* bad digits */ +#define mflag 4 /* multiply defined */ +#define pflag 5 /* phase error */ +#define uflag 6 /* undeclared used */ +#define vflag 7 /* value out of range */ +#define oflag 8 /* phase/dephase error */ +#define frflag 9 /* double forward ref. via equ error */ +#define zflag 10 /* Z80-only instruction (when `-z' option in use) */ +#define orgflag 11 /* retrograde org error (when `-h' option not in use) */ + +#define FLAGS 12 /* number of flags */ + +char err[FLAGS]; +int keeperr[FLAGS]; +char errlet[FLAGS]="BEFIMPUVORZG"; +char *errname[FLAGS]={ + "Balance", + "Expression", + "Syntax", + "Digit", + "Mult. def.", + "Phase", + "Undeclared", + "Value", + "Phase/Dephase", + "Forward ref. to EQU with forward ref.", + "Z80-specific instruction", + "Retrograde ORG" +}; +char *warnname[]={ + "Symbol length exceeded", + "Non-standard syntax", + "Could replace JP with JR", + "Could replace LD A, 0 with XOR A if flags unimportant", + "Could replace RLC A with RLCA if S, Z and P/V flags unimportant", + "Could replace RRC A with RRCA if S, Z and P/V flags unimportant", + "Could replace RL A with RLA if S, Z and P/V flags unimportant", + "Could replace RR A with RRA if S, Z and P/V flags unimportant", + "Could replace SLA A with ADD A, A if H and P/V flags unimportant" +}; + +/* for "0 symbols", "1 symbol", "2 symbols", etc. */ +#define DO_PLURAL(x) (x),((x)==1)?"":"s" + +char linebuf[LINEBUFFERSIZE]; +char *lineptr; +char *linemax = linebuf+LINEBUFFERSIZE; + +char outbin[BINPERLINE]; +char *outbinp = outbin; +char *outbinm = outbin+BINPERLINE; + +char emitbuf[EMITBUFFERSIZE]; +char *emitptr; + +char ifstack[IFSTACKSIZE]; +char *ifptr; +char *ifstmax = ifstack+IFSTACKSIZE-1; + + +char expif[MAXIFS]; +char *expifp; +char *expifmax = expif+MAXIFS; + +char hexadec[] = "0123456789ABCDEF" ; +char *expstack[MAXEXP]; +int expptr; + + +int nitems; +int linecnt; +int nbytes; +int invented; + + +char tempbuf[TEMPBUFSIZE]; +char *tempmax = tempbuf+TEMPBUFSIZE-1; + +char inmlex; +char arg_flag; +char quoteflag; +int parm_number; +int exp_number; +char symlong[] = "Symbol too long"; + +int disp; +#define FLOC PARMMAX +#define TEMPNUM PARMMAX+1 +char **est; +char **est2; + +char *floc; +int mfptr; +FILE *mfile; + + +char *title; +char titlespace[TITLELEN]; +char *timp; +char *sourcef; +/* changed to cope with filenames longer than 14 chars -rjm 1998-12-15 */ +char src[1024]; +char bin[1024]; +char mtmp[1024]; +char listf[1024]; +char writesyms[1024]; +#ifdef __riscos +char riscos_thbkf[1024]; +#endif + +char bopt = 1, + edef = 1, + eopt = 1, + fdef = 0, + fopt = 0, + gdef = 1, + gopt = 1, + iopt = 0 , /* list include files */ + lstoff = 0, + lston = 0, /* flag to force listing on */ + lopt = 0, + mdef = 0, + mopt = 0, + nopt = 1, /* line numbers on as default */ + oldoopt = 0, + popt = 1, /* form feed as default page eject */ + sopt = 0, /* turn on symbol table listing */ + output_hex = 0, /* `-h', output .hex rather than .bin -rjm */ + output_8080_only = 0, /* `-z', output 8080-compat. ops only -rjm */ + show_error_line = 0, /* `-S', show line which caused error -rjm */ + terse_lst_errors = 0, /* `-t', terse errors in listing -rjm */ + continuous_listing = 1, /* `-d', discontinuous - with page breaks */ + suggest_optimise = 0, /* `-O', suggest optimisations -mgr */ +#ifdef __riscos + riscos_thbk = 0, /* `-T', RISC OS throwback -mep */ +#endif + output_amsdos = 0, /* `-A', AMSDOS binary file output -mep */ + saveopt; + +char xeq_flag = 0; +int xeq; + +time_t now; +int line; +int page = 1; + +int had_errors = 0; /* if program had errors, do exit(1) */ +#ifdef __riscos +int riscos_throwback_started = 0; +#endif +int not_seen_org = 1; +int first_org_store = 0; + +struct stab { + char t_name[MAXSYMBOLSIZE+1]; + int t_value; + int t_token; +}; + +/* + * push back character + */ +int peekc; + + +/* function prototypes */ +int addtoline(int ac); +int iflist(void); +int yylex(void); +int tokenofitem(int deftoken); +int nextchar(void); +int skipline(int ac); +void usage(void); +int main(int argc, char *argv[]); +int getarg(void); +int getm(void); +void yyerror(char *err); +void emit(int num, ...); +void emit1(int opcode,int regvalh,int data16,int type); +void emitdad(int rp1,int rp2); +void emitjr(int opcode,int expr); +void emitjp(int opcode,int expr); +void putbin(int v); +void flushbin(void); +void puthex(char byte, FILE *buf); +void list(int optarg); +void lineout(void); +void eject(void); +void space(int n); +void lsterr1(void); +void lsterr2(int lst); +void errorprt(int errnum); +void warnprt(int warnnum, int warnoff); +void list1(void); +void interchange(int i, int j); +void custom_qsort(int m, int n); +void setvars(void); +void error(char *as); +void fileerror(char *as,char *filename); +void justerror(char *as); +void putsymtab(void); +void erreport(void); +void mlex(void); +void suffix_if_none(char *str,char *suff); +void suffix(char *str,char *suff); +void decanonicalise(char *str); +void putm(char c); +void popsi(void); +char *getlocal(int c, int n); +void insymtab(char *name); +void outsymtab(char *name); +void copyname(char *st1, char *st2); +void next_source(char *sp); +void doatexit (void); +#ifdef __riscos +void riscos_set_csd(char *sp); +void riscos_throwback(int severity, char *file, int line, char *error); +#endif + + + + +/* + * add a character to the output line buffer + */ +int addtoline(int ac) +{ + /* check for EOF from stdio */ + if (ac == -1) + ac = 0 ; + if (inmlex) + return(ac); + if (lineptr >= linemax) + error("line buffer overflow"); + *lineptr++ = ac; + return(ac); +} + + +/* + * put values in buffer for outputing + */ + +void emit(int bytes, ...) +{ + va_list ap; + unsigned char *oldemitptr=(unsigned char *)emitptr; + int c; + + va_start(ap,bytes); + + while (--bytes >= 0) + if (emitptr >= &emitbuf[EMITBUFFERSIZE]) + error("emit buffer overflow"); + else { + *emitptr++ = va_arg(ap,int); + } + + if (output_8080_only) { + /* test for Z80-specific ops. These start with one of + * sixteen byte values, listed below. The values were + * taken from "A Z80 Workshop Manual" by E. A. Parr. -rjm + */ + /* As far as I can tell from my own literature + * review, 0x02, 0x0a, 0x12 and 0x1a are valid + * 8080 opcodes (LDAX/STAX B/D) -mgr + */ + c=*oldemitptr; + if (/* c==0x02 || */ c==0x08 || /* c==0x0a || */ c==0x10 || + /* c==0x12 || */ c==0x18 || /* c==0x1a || */ c==0x20 || + c==0x28 || c==0x30 || c==0x38 || c==0xcb || + c==0xd9 || c==0xdd || c==0xed || c==0xfd) + err[zflag]++; + } + + va_end(ap); +} + +/* for emitted data - as above, without 8080 test. + * Duplicating the code was easier than putting an extra arg in all + * those emit()s. :-} Hopefully this isn't too unbearably nasty. -rjm + */ +void dataemit(int bytes, ...) +{ + va_list ap; + + va_start(ap,bytes); + + while (--bytes >= 0) + if (emitptr >= &emitbuf[EMITBUFFERSIZE]) + error("emit buffer overflow"); + else { + *emitptr++ = va_arg(ap,int); + } + va_end(ap); +} + + +void emit1(int opcode,int regvalh,int data16,int type) +{ + if ((regvalh & 0x8000)) { /* extra brackets to silence -Wall */ + if ((type & 1) == 0 && (disp > 127 || disp < -128)) + err[vflag]++; + switch(type) { + case 0: + if (opcode & 0x8000) + emit(4, regvalh >> 8, opcode >> 8, disp, opcode); + else + emit(3, regvalh >> 8, opcode, disp); + break; + case 1: + emit(2, regvalh >> 8, opcode); + break; + case 2: + if (data16 > 255 || data16 < -128) + err[vflag]++; + emit(4, regvalh >> 8, opcode, disp, data16); + break; + case 5: + emit(4, regvalh >> 8, opcode, data16, data16 >> 8); + } + } else + switch(type) { + case 0: + if (opcode & 0100000) + emit(2, opcode >> 8, opcode); + else + emit(1, opcode); + break; + case 1: + if (opcode & 0100000) + emit(2, opcode >> 8, opcode); + else + emit(1, opcode); + break; + case 2: + if (data16 > 255 || data16 < -128) + err[vflag]++; + emit(2, opcode, data16); + break; + case 3: + if (data16 >255 || data16 < -128) + err[vflag]++; + emit(2, opcode, data16); + break; + case 5: + if (opcode & 0100000) + emit(4, opcode >> 8, opcode, data16, data16 >> 8); + else + emit(3, opcode, data16, data16 >> 8); + } +} + + + + +void emitdad(int rp1,int rp2) +{ + if (rp1 & 0x8000) + emit(2,rp1 >> 8, rp2 + 9); + else + emit(1,rp2 + 9); +} + + +void emitjr(int opcode,int expr) +{ + disp = expr - dollarsign - 2; + if (disp > 127 || disp < -128) + err[vflag]++; + emit(2, opcode, disp); +} + + + +void emitjp(int opcode,int expr) +{ + if (suggest_optimise && pass2 && opcode <= 0xda && !output_8080_only) { + disp = expr - dollarsign - 2; + if (disp <= 127 && disp >= -128) + warnprt (2, 0); + } + emit(3, opcode, expr, expr >> 8); +} + + + + +/* + * put out a byte of binary + */ +void putbin(int v) +{ + if(!pass2 || !bopt) return; + *outbinp++ = v; + if (outbinp >= outbinm) flushbin(); +} + + + +/* + * output one line of binary in INTEL standard form + */ +void flushbin() +{ + char *p; + int check=outbinp-outbin; + + if (!pass2 || !bopt) + return; + nbytes += check; + if (check) { + if (output_hex) { + putc(':', fbuf); + puthex(check, fbuf); + puthex(olddollar>>8, fbuf); + puthex(olddollar, fbuf); + puthex(0, fbuf); + } + check += (olddollar >> 8) + olddollar; + olddollar += (outbinp-outbin); + for (p=outbin; p> 4) & 017], buf); + putc(hexadec[byte & 017], buf); +} + +/* + * put out a line of output -- also put out binary + */ +void list(int optarg) +{ + char * p; + int i; + int lst; + + if (!expptr) + linecnt++; + addtoline('\0'); + if (pass2) { + lst = iflist(); + if (lst) { + lineout(); + if (nopt) + fprintf(fout, "%4d:\t", linein[now_in]); + puthex(optarg >> 8, fout); + puthex(optarg, fout); + fputs(" ", fout); + for (p = emitbuf; (p < emitptr) && (p - emitbuf < 4); p++) { + puthex(*p, fout); + } + for (i = 4 - (p-emitbuf); i > 0; i--) + fputs(" ", fout); + putc('\t', fout); + fputs(linebuf, fout); + } + + if (bopt) { + for (p = emitbuf; p < emitptr; p++) + putbin(*p); + } + + + p = emitbuf+4; + while (lst && gopt && p < emitptr) { + lineout(); + if (nopt) putc('\t', fout); + fputs(" ", fout); + for (i = 0; (i < 4) && (p < emitptr);i++) { + puthex(*p, fout); + p++; + } + putc('\n', fout); + } + + + lsterr2(lst); + } else + lsterr1(); + dollarsign += emitptr - emitbuf; + emitptr = emitbuf; + lineptr = linebuf; +} + + + +/* + * keep track of line numbers and put out headers as necessary + */ +void lineout() +{ + if (continuous_listing) { + line = 1; + return; + } + if (line == 60) { + if (popt) + putc('\014', fout); /* send the form feed */ + else + fputs("\n\n\n\n\n", fout); + line = 0; + } + if (line == 0) { + fprintf(fout, "\n\n%s %s\t%s\t Page %d\n\n\n", + &timp[4], &timp[20], title, page++); + line = 4; + } + line++; +} + + +/* + * cause a page eject + */ +void eject() +{ + if (pass2 && !continuous_listing && iflist()) { + if (popt) { + putc('\014', fout); /* send the form feed */ + } else { + while (line < 65) { + line++; + putc('\n', fout); + } + } + } + line = 0; +} + + +/* + * space n lines on the list file + */ +void space(int n) +{ + int i ; + if (pass2 && iflist()) + for (i = 0; i 4) + errorprt(i); + } + + fflush(fout); /* to avoid putc(har) mix bug */ +} + +/* + * print diagnostic to error terminal + */ +void errorprt(int errnum) +{ + had_errors=1; + fprintf(stderr,"%s:%d: %s error\n", + src_name[now_in], linein[now_in], errname[errnum]); + if(show_error_line) + fprintf(stderr, "%s\n", linebuf); +#ifdef __riscos + if (riscos_thbk) + riscos_throwback (1, src_name[now_in], linein[now_in], errname[errnum]); +#endif +} + + +/* + * print warning to error terminal + */ +void warnprt(int warnnum, int warnoff) +{ + fprintf(stderr,"%s:%d: warning: %s\n", + src_name[now_in], linein[now_in] + warnoff, warnname[warnnum]); + /* Offset needed if warning issued while line is being parsed */ +#ifdef __riscos + if (riscos_thbk) + riscos_throwback (0, src_name[now_in], linein[now_in] + warnoff, warnname[warnnum]); +#endif + /* if(show_error_line) + Can't show line because it isn't necessarily complete + fprintf(stderr, "%s\n", linebuf); */ +} + + +/* + * list without address -- for comments and if skipped lines + */ +void list1() +{ + int lst; + + addtoline('\0'); + lineptr = linebuf; + if (!expptr) linecnt++; + if (pass2) + { + if ((lst = iflist())) { + lineout(); + if (nopt) + fprintf(fout, "%4d:\t", linein[now_in]); + fprintf(fout, "\t\t%s", linebuf); + lsterr2(lst); + } + } else + lsterr1(); +} + + +/* + * see if listing is desired + */ +int iflist() +{ + int i, j; + + if (lston) + return(1) ; + if (lopt) + return(0); + if (*ifptr && !fopt) + return(0); + if (!lstoff && !expptr) + return(1); + j = 0; + for (i=0; i STRING +%token NOOPERAND +%token ARITHC +%token ADD +%token LOGICAL +%token AND +%token OR +%token XOR +%token BIT +%token CALL +%token INCDEC +%token DJNZ +%token EX +%token IM +%token PHASE +%token DEPHASE +%token IN +%token JP +%token JR +%token LD +%token OUT +%token PUSHPOP +%token RET +%token SHIFT +%token RST +%token REGNAME +%token ACC +%token C +%token RP +%token HL +%token INDEX +%token AF +%token SP +%token MISCREG +%token F +%token COND +%token SPCOND +%token NUMBER +%token UNDECLARED +%token END +%token ORG +%token DEFB +%token DEFS +%token DEFW +%token EQU +%token DEFL +%token LABEL +%token EQUATED +%token WASEQUATED +%token DEFLED +%token MULTDEF +%token MOD +%token SHL +%token SHR +%token NOT +%token LT +%token GT +%token EQ +%token LE +%token GE +%token NE +%token IF +%token ELSE +%token ENDIF +%token ARGPSEUDO +%token LIST +%token MINMAX +%token MACRO +%token MNAME +%token OLDMNAME +%token ARG +%token ENDM +%token MPARM +%token ONECHAR +%token TWOCHAR + +%type label.part symbol +%type reg evenreg realreg mem pushable bcdesp bcdehlsp mar condition +%type spcondition noparenexpr parenexpr expression lxexpression + +%left '|' OR +%left '^' XOR +%left '&' AND +%left '=' EQ NE +%left '<' '>' LT GT LE GE +%left SHL SHR +%left '+' '-' +%left '*' '/' '%' MOD +%right '!' '~' NOT UNARY + +%% + + +statements: + /* Empty file! */ +| + statements statement +; + + +statement: + label.part '\n' { + if ($1) list(dollarsign); + else list1(); + } +| + label.part operation '\n' { + list(dollarsign); + } +| + symbol EQU expression '\n' { + /* a forward reference to a label in the expression cannot + * be fixed in a forward reference to the EQU; + * it would need three passes. -rjm + */ + if(!pass2 && equ_bad_label) { + /* this indicates that the equ has an incorrect + * (i.e. pass 1) value. + */ + $1->i_equbad = 1; + } else { + /* but if 2nd pass or no forward reference, it's ok. */ + $1->i_equbad = 0; + } + equ_bad_label=0; + switch($1->i_token) { + case UNDECLARED: case WASEQUATED: + $1->i_token = EQUATED; + $1->i_value = $3; + break; + case EQUATED: + if ($1->i_value == $3) + break; /* Allow benign redefinition -mgr */ + /* Drop-through intentional */ + default: + err[mflag]++; + $1->i_token = MULTDEF; + } + list($3); + } +| + symbol DEFL expression '\n' { + switch($1->i_token) { + case UNDECLARED: case DEFLED: + $1->i_token = DEFLED; + $1->i_value = $3; + break; + default: + err[mflag]++; + $1->i_token = MULTDEF; + } + list($3); + } +| + symbol MINMAX expression ',' expression '\n' { + switch ($1->i_token) { + case UNDECLARED: case DEFLED: + $1->i_token = DEFLED; + if ($2->i_value) /* max */ + list($1->i_value = ($3 > $5? $3:$5)); + else list($1->i_value = ($3 < $5? $3:$5)); + break; + default: + err[mflag]++; + $1->i_token = MULTDEF; + list($1->i_value); + } + } +| + IF expression '\n' { + /* all $2's here were yypc[2].ival before. + * I think the idea was perhaps to allow constants + * only...? Anyway, it now allows any expression - + * which would seem to make sense given the definition + * above, right? :-) -rjm + */ + if (ifptr >= ifstmax) + error("Too many ifs"); + else { + if (pass2) { + *++ifptr = *expifp++; + if (*ifptr != !($2)) err[pflag]++; + } else { + if (expifp >= expifmax) + error("Too many ifs!"); + *expifp++ = !($2); + *++ifptr = !($2); + } + } + saveopt = fopt; + fopt = 1; + list($2); + fopt = saveopt; + } +| + ELSE '\n' { + /* FIXME: it would be nice to spot repeated ELSEs, but how? */ + *ifptr = !*ifptr; + saveopt = fopt; + fopt = 1; + list1(); + fopt = saveopt; + } +| + ENDIF '\n' { + if (ifptr == ifstack) err[bflag]++; + else --ifptr; + list1(); + } +| + label.part END '\n' { + list(dollarsign); + peekc = 0; + } +| + label.part END expression '\n' { + xeq_flag++; + xeq = $3; + list($3); + peekc = 0; + } +| + label.part DEFS expression '\n' { + if ($3 < 0) err[vflag]++; + list(dollarsign); + if ($3) { + flushbin(); + dollarsign += $3; + olddollar = dollarsign; + + /* if it's not hex output though, we also need + * to output zeroes as appropriate. -rjm + */ + if(!output_hex && pass2) { + int f; + for (f=0;f<($3);f++) + fputc(0, fbuf); + } + } + } +| + ARGPSEUDO arg_on ARG arg_off '\n' { + list1(); + switch ($1->i_value) { + + case 0: /* title */ + lineptr = linebuf; + cp = tempbuf; + title = titlespace; + while ((*title++ = *cp++) && (title < &titlespace[TITLELEN])); + *title = 0; + title = titlespace; + break; + + case 1: /* rsym */ + if (pass2) break; + insymtab(tempbuf); + break; + + case 2: /* wsym */ + strcpy(writesyms, tempbuf); + break; + + case 3: /* include file */ + if (*tempbuf == '"' || *tempbuf == '\'') + { + if (tempbuf[strlen (tempbuf) - 1] == '"' || tempbuf[strlen (tempbuf) - 1] == '\'') + tempbuf[strlen (tempbuf) - 1] = 0; + next_source(tempbuf + 1) ; + } + else + { + next_source(tempbuf) ; + } + break ; + } + } +| + ARGPSEUDO arg_on arg_off '\n' { + fprintf(stderr,"ARGPSEUDO error\n"); + err[fflag]++; + list(dollarsign); + } +| + LIST '\n' { + list_tmp1=$1->i_value; + list_tmp2=1; + goto dolopt; + } +| + LIST expression '\n' { + list_tmp1=$1->i_value; + list_tmp2=$2; + dolopt: + linecnt++; + if (pass2) { + lineptr = linebuf; + switch (list_tmp1) { + case 0: /* list */ + if (list_tmp2 < 0) lstoff = 1; + if (list_tmp2 > 0) lstoff = 0; + break; + + case 1: /* eject */ + if (list_tmp2) eject(); + break; + + case 2: /* space */ + if ((line + list_tmp2) > 60) eject(); + else space(list_tmp2); + break; + + case 3: /* elist */ + eopt = edef; + if (list_tmp2 < 0) eopt = 0; + if (list_tmp2 > 0) eopt = 1; + break; + + case 4: /* fopt */ + fopt = fdef; + if (list_tmp2 < 0) fopt = 0; + if (list_tmp2 > 0) fopt = 1; + break; + + case 5: /* gopt */ + gopt = gdef; + if (list_tmp2 < 0) gopt = 1; + if (list_tmp2 > 0) gopt = 0; + break; + + case 6: /* mopt */ + mopt = mdef; + if (list_tmp2 < 0) mopt = 0; + if (list_tmp2 > 0) mopt = 1; + } + } + } +| + UNDECLARED MACRO parm.list '\n' { + $1->i_token = MNAME; + $1->i_value = mfptr; +#ifdef M_DEBUG + fprintf (stderr, "[UNDECLARED MACRO %s]\n", $1->i_string); +#endif + mfseek(mfile, (long)mfptr, 0); + list1(); + mlex() ; + parm_number = 0; + } +| + OLDMNAME MACRO { + $1->i_token = MNAME; +#ifdef M_DEBUG + fprintf (stderr, "[OLDNAME MACRO %s]\n", $1->i_string); +#endif + while (yychar != ENDM && yychar) { + while (yychar != '\n' && yychar) + yychar = yylex(); + list1(); + yychar = yylex(); + } + while (yychar != '\n' && yychar) yychar = yylex(); + list1(); + yychar = yylex(); + } +| + label.part MNAME al arg.list '\n' { +#ifdef M_DEBUG + fprintf (stderr, "[MNAME %s]\n", $2->i_string); +#endif + $2->i_uses++ ; + arg_flag = 0; + parm_number = 0; + list(dollarsign); + expptr++; + est = est2; + est2 = NULL; + est[FLOC] = floc; + est[TEMPNUM] = (char *)(long)exp_number++; + floc = (char *)(long)($2->i_value); + mfseek(mfile, (long)floc, 0); + } +| + error { + err[fflag]++; + quoteflag = 0; + arg_flag = 0; + parm_number = 0; + + if (est2) + { + int i; + for (i=0; ii_token) { + case UNDECLARED: + if (pass2) + err[pflag]++; + else { + $2->i_token = LABEL; + $2->i_value = dollarsign; + } + break; + case LABEL: + if (!pass2) { + $2->i_token = MULTDEF; + err[mflag]++; + } else if ($2->i_value != dollarsign) + err[pflag]++; + break; + default: + err[mflag]++; + $2->i_token = MULTDEF; + } + } +| + symbol maybecolon { + switch($1->i_token) { + case UNDECLARED: + if (pass2) + err[pflag]++; + else { + $1->i_token = LABEL; + $1->i_value = dollarsign; + } + break; + case LABEL: + if (!pass2) { + $1->i_token = MULTDEF; + err[mflag]++; + } else if ($1->i_value != dollarsign) + err[pflag]++; + break; + default: + err[mflag]++; + $1->i_token = MULTDEF; + } + } +; + + +operation: + NOOPERAND + { emit1($1->i_value, 0, 0, 1); } +| + JP expression + { emitjp(0303, $2); } +| + CALL expression + { emit(3, 0315, $2, $2 >> 8); } +| + RST expression + { int a = $2, doneerr=0; + /* added support for normal RST form -rjm */ + if (a >= 8) { + if ((a&7)!=0) doneerr=1,err[vflag]++; + a >>= 3; + } + if ((a > 7 || a < 0) && !doneerr) /* don't give two errs... */ + err[vflag]++; + emit(1, $1->i_value + (a << 3)); + } +| + ADD expression + { emit1(0306, 0, $2, 3); if (pass2) warnprt (1, 0); } +| + ADD ACC ',' expression + { emit1(0306, 0, $4, 3); } +| + ARITHC expression + { emit1(0306 + ($1->i_value << 3), 0, $2, 3); if (pass2) warnprt (1, 0); } +| + ARITHC ACC ',' expression + { emit1(0306 + ($1->i_value << 3), 0, $4, 3); } +| + LOGICAL expression + { emit1(0306 | ($1->i_value << 3), 0, $2, 3); } +| + AND expression + { emit1(0306 | ($1->i_value << 3), 0, $2, 3); } +| + OR expression + { emit1(0306 | ($1->i_value << 3), 0, $2, 3); } +| + XOR expression + { emit1(0306 | ($1->i_value << 3), 0, $2, 3); } +| + LOGICAL ACC ',' expression /* -cdk */ + { emit1(0306 | ($1->i_value << 3), 0, $4, 3); if (pass2) warnprt (1, 0); } +| + AND ACC ',' expression /* -cdk */ + { emit1(0306 | ($1->i_value << 3), 0, $4, 3); if (pass2) warnprt (1, 0); } +| + OR ACC ',' expression /* -cdk */ + { emit1(0306 | ($1->i_value << 3), 0, $4, 3); if (pass2) warnprt (1, 0); } +| + XOR ACC ',' expression /* -cdk */ + { emit1(0306 | ($1->i_value << 3), 0, $4, 3); if (pass2) warnprt (1, 0); } +| + ADD reg + { emit1(0200 + ($2 & 0377), $2, 0, 0); if (pass2) warnprt (1, 0); } +| + ADD ACC ',' reg + { emit1(0200 + ($4 & 0377), $4, 0, 0); } +| + ARITHC reg + { emit1(0200 + ($1->i_value << 3) + ($2 & 0377), $2, 0, 0); if (pass2) warnprt (1, 0); } +| + ARITHC ACC ',' reg + { emit1(0200 + ($1->i_value << 3) + ($4 & 0377), $4, 0, 0); } +| + LOGICAL reg + { emit1(0200 + ($1->i_value << 3) + ($2 & 0377), $2, 0, 0); } +| + AND reg + { emit1(0200 + ($1->i_value << 3) + ($2 & 0377), $2, 0, 0); } +| + OR reg + { emit1(0200 + ($1->i_value << 3) + ($2 & 0377), $2, 0, 0); } +| + XOR reg + { emit1(0200 + ($1->i_value << 3) + ($2 & 0377), $2, 0, 0); } +| + LOGICAL ACC ',' reg /* -cdk */ + { emit1(0200 + ($1->i_value << 3) + ($4 & 0377), $4, 0, 0); if (pass2) warnprt (1, 0); } +| + AND ACC ',' reg /* -cdk */ + { emit1(0200 + ($1->i_value << 3) + ($4 & 0377), $4, 0, 0); if (pass2) warnprt (1, 0); } +| + OR ACC ',' reg /* -cdk */ + { emit1(0200 + ($1->i_value << 3) + ($4 & 0377), $4, 0, 0); if (pass2) warnprt (1, 0); } +| + XOR ACC ',' reg /* -cdk */ + { emit1(0200 + ($1->i_value << 3) + ($4 & 0377), $4, 0, 0); if (pass2) warnprt (1, 0); } +| + SHIFT reg + { + if (suggest_optimise && pass2 && ($2 & 0377) == 7 && $1->i_value <= 4) + warnprt ($1->i_value + 4, 0); + if (pass2 && $1->i_value == 6) + warnprt (1, 0); + emit1(0145400 + ($1->i_value << 3) + ($2 & 0377), $2, 0, 0); + } +| + INCDEC reg + { emit1($1->i_value + (($2 & 0377) << 3) + 4, $2, 0, 0); } +| + ARITHC HL ',' bcdehlsp + { if ($1->i_value == 1) + emit(2,0355,0112+$4); + else + emit(2,0355,0102+$4); + } +| + ADD mar ',' bcdesp + { emitdad($2,$4); } +| + ADD mar ',' mar + { + if ($2 != $4) { + fprintf(stderr,"ADD mar, mar error\n"); + err[fflag]++; + } + emitdad($2,$4); + } +| + INCDEC evenreg + { emit1(($1->i_value << 3) + ($2 & 0377) + 3, $2, 0, 1); } +| + PUSHPOP pushable + { emit1($1->i_value + ($2 & 0377), $2, 0, 1); } +| + BIT expression ',' reg + { + if ($2 < 0 || $2 > 7) + err[vflag]++; + emit1($1->i_value + (($2 & 7) << 3) + ($4 & 0377), $4, 0, 0); + } +| + JP condition ',' expression + { emitjp(0302 + $2, $4); } +| + JP '(' mar ')' + { emit1(0351, $3, 0, 1); } +| + JP mar + { emit1(0351, $2, 0, 1); if (pass2) warnprt (1, 0); } +| + CALL condition ',' expression + { emit(3, 0304 + $2, $4, $4 >> 8); } +| + JR expression + { emitjr(030,$2); } +| + JR spcondition ',' expression + { emitjr($1->i_value + $2, $4); } +| + DJNZ expression + { emitjr($1->i_value, $2); } +| + RET + { emit(1, $1->i_value); } +| + RET condition + { emit(1, 0300 + $2); } +| + LD reg ',' reg + { + if (($2 & 0377) == 6 && ($4 & 0377) == 6) { + fprintf(stderr,"LD reg, reg error\n"); + err[fflag]++; + } + emit1(0100 + (($2 & 7) << 3) + ($4 & 7),$2 | $4, 0, 0); + } +| + LD reg ',' noparenexpr + { + if (suggest_optimise && pass2 && $4 == 0 && ($2 & 0377) == 7) + warnprt (3, 0); + emit1(6 + (($2 & 0377) << 3), $2, $4, 2); + } +| + LD reg ',' '(' RP ')' + { if ($2 != 7) { + fprintf(stderr,"LD reg, (RP) error\n"); + err[fflag]++; + } + else emit(1, 012 + $5->i_value); + } +| + LD reg ',' parenexpr + { + if ($2 != 7) { + fprintf(stderr,"LD reg, (expr) error\n"); + err[fflag]++; + } + else emit(3, 072, $4, $4 >> 8); + } +| + LD '(' RP ')' ',' ACC + { emit(1, 2 + $3->i_value); } +| + LD parenexpr ',' ACC + { emit(3, 062, $2, $2 >> 8); } +| + LD reg ',' MISCREG + { + if ($2 != 7) { + fprintf(stderr,"LD reg, MISCREG error\n"); + err[fflag]++; + } + else emit(2, 0355, 0127 + $4->i_value); + } +| + LD MISCREG ',' ACC + { emit(2, 0355, 0107 + $2->i_value); } +| + LD evenreg ',' lxexpression + { emit1(1 + ($2 & 060), $2, $4, 5); } +| + LD evenreg ',' parenexpr + { + if (($2 & 060) == 040) + emit1(052, $2, $4, 5); + else + emit(4, 0355, 0113 + $2, $4, $4 >> 8); + } +| + LD parenexpr ',' evenreg + { + if (($4 & 060) == 040) + emit1(042, $4, $2, 5); + else + emit(4, 0355, 0103 + $4, $2, $2 >> 8); + } +| + LD evenreg ',' mar + { + if ($2 != 060) { + fprintf(stderr,"LD evenreg error\n"); + err[fflag]++; + } + else + emit1(0371, $4, 0, 1); + } +| + EX RP ',' HL + { + if ($2->i_value != 020) { + fprintf(stderr,"EX RP, HL error\n"); + err[fflag]++; + } + else + emit(1, 0353); + } +| + EX AF ',' AF setqf '\'' clrqf + { emit(1, 010); } +| + EX '(' SP ')' ',' mar + { emit1(0343, $6, 0, 1); } +| + IN realreg ',' parenexpr + { + if ($2 != 7) { + fprintf(stderr,"IN reg, (expr) error\n"); + err[fflag]++; + } + else { + if ($4 < 0 || $4 > 255) + err[vflag]++; + emit(2, $1->i_value, $4); + } + } +| + IN realreg ',' '(' C ')' + { emit(2, 0355, 0100 + ($2 << 3)); } +| + IN F ',' '(' C ')' + { emit(2, 0355, 0160); } +| + OUT parenexpr ',' ACC + { + if ($2 < 0 || $2 > 255) + err[vflag]++; + emit(2, $1->i_value, $2); + } +| + OUT '(' C ')' ',' realreg + { emit(2, 0355, 0101 + ($6 << 3)); } +| + IM expression + { + if ($2 > 2 || $2 < 0) + err[vflag]++; + else + emit(2, $1->i_value >> 8, $1->i_value + (($2 + ($2 > 0)) << 3)); + } +| + PHASE expression + { + if (phaseflag) { + err[oflag]++; + } else { + phaseflag = 1; + phdollar = dollarsign; + dollarsign = $2; + phbegin = dollarsign; + } + } +| + DEPHASE + { + if (!phaseflag) { + err[oflag]++; + } else { + phaseflag = 0; + dollarsign = phdollar + dollarsign - phbegin; + } + } +| + ORG expression + { + if (not_seen_org) + first_org_store=yyvsp[0].ival; + not_seen_org=0; + if (phaseflag) { + err[oflag]++; + dollarsign = phdollar + dollarsign - phbegin; + phaseflag = 0; + } + if ($2-dollarsign) { + flushbin(); + if (pass2 && !output_hex && dollarsign != 0) { + if (yyvsp[0].ival < dollarsign) { + err[orgflag]++; + } else { + int f; + for (f=0;f<(yyvsp[0].ival - dollarsign);f++) + fputc(0, fbuf); + } + } + olddollar = $2; + dollarsign = $2; + } + } +| + DEFB db.list +| + DEFW dw.list +| + ENDM +; + + +parm.list: +| + parm.element +| + parm.list ',' parm.element +; + + +parm.element: + UNDECLARED + { + $1->i_token = MPARM; + if (parm_number >= PARMMAX) + error("Too many parameters"); + $1->i_value = parm_number++; + } +; + + +arg.list: + /* empty */ +| + arg.element +| + arg.list ',' arg.element +; + + +arg.element: + ARG + { + cp = malloc(strlen(tempbuf)+1); +#ifdef M_DEBUG + fprintf (stderr, "[Arg%u(%p): %s]\n", parm_number, cp, tempbuf); +#endif + est2[parm_number++] = cp; + strcpy(cp, tempbuf); + } +; +reg: + realreg +| + mem +; +realreg: + REGNAME + { + $$ = $1->i_value; + } +| + ACC + { + $$ = $1->i_value; + } +| + C + { + $$ = $1->i_value; + } +; +mem: + '(' HL ')' + { + $$ = 6; + } +| + '(' INDEX expression ')' + { + disp = $3; + $$ = ($2->i_value & 0177400) | 6; + } +| + '(' INDEX ')' + { + disp = 0; + $$ = ($2->i_value & 0177400) | 6; + } +; +evenreg: + bcdesp +| + mar +; +pushable: + RP + { + $$ = $1->i_value; + } +| + AF + { + $$ = $1->i_value; + } +| + mar +; +bcdesp: + RP + { + $$ = $1->i_value; + } +| + SP + { + $$ = $1->i_value; + } +; +bcdehlsp: + bcdesp +| + HL + { + $$ = $1->i_value; + } +; +mar: + HL + { + $$ = $1->i_value; + } +| + INDEX + { + $$ = $1->i_value; + } +; +condition: + spcondition +| + COND + { + $$ = $1->i_value; + } +; +spcondition: + SPCOND + { + $$ = $1->i_value; + } +| + C + { $$ = 030; } +; +db.list: + db.list.element +| + db.list ',' db.list.element +; +db.list.element: + TWOCHAR + { + dataemit(2, $1, $1>>8); + } +| + STRING + { + cp = $1; + while (*cp != '\0') + dataemit(1,*cp++); + } +| + expression + { + if ($1 < -128 || $1 > 255) + err[vflag]++; + dataemit(1, $1 & 0377); + } +; + + +dw.list: + dw.list.element +| + dw.list ',' dw.list.element +; + + +dw.list.element: + expression + { + dataemit(2, $1, $1>>8); + } +; + + + +lxexpression: + noparenexpr +| + TWOCHAR +; + +expression: + parenexpr +| + noparenexpr +; + +parenexpr: + '(' expression ')' + { $$ = $2; } +; + +noparenexpr: + LABEL + { $$ = $1->i_value; $1->i_uses++ ; } +| + NUMBER +| + ONECHAR +| + EQUATED + { $$ = $1->i_value; $1->i_uses++ ; } +| + WASEQUATED + { + $$ = $1->i_value; $1->i_uses++ ; + if ($1->i_equbad) { + /* forward reference to equ with a forward + * reference of its own cannot be resolved + * in two passes. -rjm + */ + err[frflag]++; + } + } +| + DEFLED + { $$ = $1->i_value; $1->i_uses++ ; } +| + '$' + { $$ = dollarsign; } +| + UNDECLARED + { + err[uflag]++; + equ_bad_label=1; + $$ = 0; + } +| + MULTDEF + { $$ = $1->i_value; } +| + expression '+' expression + { $$ = $1 + $3; } +| + expression '-' expression + { $$ = $1 - $3; } +| + expression '/' expression + { if ($3 == 0) err[eflag]++; else $$ = $1 / $3; } +| + expression '*' expression + { $$ = $1 * $3; } +| + expression '%' expression + { if ($3 == 0) err[eflag]++; else $$ = $1 % $3; } +| + expression MOD expression + { if ($3 == 0) err[eflag]++; else $$ = $1 % $3; } +| + expression '&' expression + { $$ = $1 & $3; } +| + expression AND expression + { $$ = $1 & $3; } +| + expression '|' expression + { $$ = $1 | $3; } +| + expression OR expression + { $$ = $1 | $3; } +| + expression '^' expression + { $$ = $1 ^ $3; } +| + expression XOR expression + { $$ = $1 ^ $3; } +| + expression SHL expression + { $$ = $1 << $3; } +| + expression SHR expression + { $$ = (($1 >> 1) & 077777) >> ($3 - 1); } +| + expression '<' expression + { $$ = $1 < $3; } +| + expression '=' expression + { $$ = $1 == $3; } +| + expression '>' expression + { $$ = $1 > $3; } +| + expression LT expression + { $$ = $1 < $3; } +| + expression EQ expression + { $$ = $1 == $3; } +| + expression GT expression + { $$ = $1 > $3; } +| + expression LE expression + { $$ = $1 <= $3; } +| + expression GE expression + { $$ = $1 >= $3; } +| + expression NE expression + { $$ = $1 != $3; } +| + '[' expression ']' + { $$ = $2; } +| + NOT expression + { $$ = ~$2; } +| + '~' expression + { $$ = ~$2; } +| + '!' expression + { $$ = !$2; } +| + '+' expression %prec UNARY + { $$ = $2; } +| + '-' expression %prec UNARY + { $$ = -$2; } +; + +symbol: + UNDECLARED +| + LABEL +| + MULTDEF +| + EQUATED +| + WASEQUATED +| + DEFLED +; + + +al: + { int i; + if (expptr >= MAXEXP) + error("Macro expansion level"); + est2 = (char **) malloc((PARMMAX +4) * sizeof(char *)); + expstack[expptr] = (char *)est2 ; + for (i=0; i= tempmax) + error(symlong); + *p++ = (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c; + while ((c = nextchar()) == '$') + ; + } while (charclass[c]==LETTER || charclass[c]==DIGIT); + if (p - tempbuf > MAXSYMBOLSIZE) + { + if (pass2) warnprt (0, 1); + p = tempbuf + MAXSYMBOLSIZE; + } + *p++ = '\0'; + peekc = c; + return(tokenofitem(UNDECLARED)); + case HEXIN: + { + int corig = c; + if (*ifptr) return (skipline(c)); + while ((c = nextchar ()) == '$'); + if (!numpart[c]) + { + peekc = c; + return (corig); + } + leadinghex = 1; + /* fall through */ + } + case DIGIT: + if (*ifptr) return (skipline(c)); + p = tempbuf; + do { + if (p >= tempmax) + error(symlong); + *p++ = (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c; + while ((c = nextchar()) == '$'); + } + while(numpart[c]); + peekc = c; + if (leadinghex) + { + *p++ = 'h'; + } + *p-- = '\0'; + switch(*p) { + case 'o': + case 'q': + radix = 8; + limit = 020000; + *p = '\0'; + break; + case 'd': + radix = 10; + limit = 6553; + *p = '\0'; + break; + case 'h': + radix = 16; + limit = 010000; + *p = '\0'; + break; + case 'b': + radix = 2; + limit = 077777; + *p = '\0'; + break; + default: + radix = 10; + limit = 6553; + p++; + break; + } + + /* + * tempbuf now points to the number, null terminated + * with radix 'radix'. + */ + yylval.ival = 0; + p = tempbuf; + do { + c = *p - (*p > '9' ? ('a' - 10) : '0'); + if (c >= radix) + { + err[iflag]++; + yylval.ival = 0; + break; + } + if (yylval.ival < limit || + (radix == 10 && yylval.ival == 6553 && c < 6) || + (radix == 2 && yylval.ival == limit)) + yylval.ival = yylval.ival * radix + c; + else { + err[vflag]++; + yylval.ival = 0; + break; + } + } + while(*++p != '\0'); + return(NUMBER); + default: + if (*ifptr) + return(skipline(c)); + switch(c) { + int corig; + case ';': + return(skipline(c)); + case '\'': + if (quoteflag) return('\''); + case '"': + corig = c; + p = tempbuf; + p[1] = 0; + do switch(c = nextchar()) { + case '\0': + case '\n': + err[bflag]++; + goto retstring; + case '\'': + case '"': + if (c == corig && (c = nextchar()) != corig) { + retstring: + peekc = c; + *p = '\0'; + if ((p-tempbuf) >2) { + yylval.cval = tempbuf; + return(STRING); + } else if (p-tempbuf == 2) { + p = tempbuf; + yylval.ival = *p++ ; + yylval.ival |= *p<<8; + return(TWOCHAR); + } else { + p = tempbuf; + yylval.ival = *p++; + return(ONECHAR); + } + } + default: + *p++ = c; + } while (p < tempmax); + /* + * if we break out here, our string is longer than + * our input line + */ + error("string buffer overflow"); + case '<': + corig = c; + switch (c = nextchar ()) { + case '=': + return LE; + case '<': + return SHL; + case '>': + return NE; + default: + peekc = c; + return corig; + } + /* break; suppress "unreachable" warning for tcc */ + case '>': + corig = c; + switch (c = nextchar ()) { + case '=': + return GE; + case '>': + return SHR; + default: + peekc = c; + return corig; + } + /* break; suppress "unreachable" warning for tcc */ + case '!': + corig = c; + switch (c = nextchar ()) { + case '=': + return NE; + default: + peekc = c; + return corig; + } + /* break; suppress "unreachable" warning for tcc */ + case '=': + corig = c; + switch (c = nextchar ()) { + case '=': + return EQ; + default: + peekc = c; + return corig; + } + /* break; suppress "unreachable" warning for tcc */ + default: + return(c); + } + } +} + +/* + * return the token associated with the string pointed to by + * tempbuf. if no token is associated with the string, associate + * deftoken with the string and return deftoken. + * in either case, cause yylval to point to the relevant + * symbol table entry. + */ + +int tokenofitem(int deftoken) +{ + char *p; + struct item * ip; + int i; + int r, l, u, hash; + + +#ifdef T_DEBUG + fputs("'tokenofitem entry' ", stderr) ; + fputs(tempbuf, stderr) ; +#endif + if (strcmp (tempbuf, "cmp") == 0 || + strcmp (tempbuf, "jmp") == 0 || + strcmp (tempbuf, "v") == 0 || + strcmp (tempbuf, "nv") == 0) + if (pass2) warnprt (1, 1); + /* + * binary search + */ + l = 0; + u = (sizeof keytab/sizeof keytab[0])-1; + while (l <= u) { + i = (l+u)/2; + ip = &keytab[i]; + if ((r = strcmp(tempbuf, ip->i_string)) == 0) + goto found; + if (r < 0) + u = i-1; + else + l = i+1; + } + + /* + * hash into item table + */ + hash = 0; + p = tempbuf; + while (*p) hash += *p++; + hash %= ITEMTABLESIZE; + ip = &itemtab[hash]; + + loop { + if (ip->i_token == 0) + break; + if (strcmp(tempbuf, ip->i_string) == 0) + goto found; + if (++ip >= itemmax) + ip = itemtab; + } + + if (!deftoken) { + i = 0 ; + goto token_done ; + } + if (++nitems > ITEMTABLESIZE-20) + error("item table overflow"); + ip->i_string = malloc(strlen(tempbuf)+1); + ip->i_token = deftoken; + ip->i_uses = 0; + ip->i_equbad = 0; + strcpy(ip->i_string, tempbuf); + +found: + if (*ifptr) { + if (ip->i_token == ENDIF) { + i = ENDIF ; + goto token_done ; + } + if (ip->i_token == ELSE) { + /* We must only honour the ELSE if it is not + in a nested failed IF/ELSE */ + char forbid = 0; + char *ifstackptr; + for (ifstackptr = ifstack; ifstackptr != ifptr; ++ifstackptr) { + if (*ifstackptr) { + forbid = 1; + break; + } + } + if (!forbid) { + i = ELSE; + goto token_done; + } + } + if (ip->i_token == IF) { + if (ifptr >= ifstmax) + error("Too many ifs"); + else *++ifptr = 1; + } + i = skipline(' '); + goto token_done ; + } + yylval.itemptr = ip; + i = ip->i_token; + if (i == EQU) equ_bad_label=0; +token_done: +#ifdef T_DEBUG + fputs("\t'tokenofitem exit'\n", stderr) ; +#endif + return(i) ; +} + + +/* + * interchange two entries in the item table -- used by custom_qsort + */ +void interchange(int i, int j) +{ + struct item *fp, *tp; + struct item temp; + + fp = &itemtab[i]; + tp = &itemtab[j]; + temp.i_string = fp->i_string; + temp.i_value = fp->i_value; + temp.i_token = fp->i_token; + temp.i_uses = fp->i_uses; + temp.i_equbad = fp->i_equbad; + + fp->i_string = tp->i_string; + fp->i_value = tp->i_value; + fp->i_token = tp->i_token; + fp->i_uses = tp->i_uses; + fp->i_equbad = tp->i_equbad; + + tp->i_string = temp.i_string; + tp->i_value = temp.i_value; + tp->i_token = temp.i_token; + tp->i_uses = temp.i_uses; + tp->i_equbad = temp.i_equbad; +} + + + +/* + * quick sort -- used by putsymtab to sort the symbol table + */ +void custom_qsort(int m, int n) +{ + int i, j; + + if (m < n) { + i = m; + j = n+1; + loop { + do i++; while(strcmp(itemtab[i].i_string, + itemtab[m].i_string) < 0); + do j--; while(strcmp(itemtab[j].i_string, + itemtab[m].i_string) > 0); + if (i < j) interchange(i, j); else break; + } + interchange(m, j); + custom_qsort(m, j-1); + custom_qsort(j+1, n); + } +} + + + +/* + * get the next character + */ +int nextchar() +{ + int c, ch; + static char *earg; + + if (peekc != -1) { + c = peekc; + peekc = -1; + return(c); + } + +start: + if (earg) { + if (*earg) + return(addtoline(*earg++)); + earg = 0; + } + + if (expptr) { + if ((ch = getm()) == '\1') { /* expand argument */ + ch = getm() - 'A'; + if (ch >= 0 && ch < PARMMAX && est[ch]) + earg = est[ch]; + goto start; + } + if (ch == '\2') { /* local symbol */ + ch = getm() - 'A'; + if (ch >= 0 && ch < PARMMAX && est[ch]) { + earg = est[ch]; + goto start; + } + earg = getlocal(ch, (int)(long)est[TEMPNUM]); + goto start; + } + + return(addtoline(ch)); + } + ch = getc(now_file) ; + /* if EOF, check for include file */ + if (ch == EOF) { + while (ch == EOF && now_in) { + fclose(fin[now_in]) ; + free(src_name[now_in]) ; + now_file = fin[--now_in] ; + ch = getc(now_file) ; + } + if (linein[now_in] < 0) { + lstoff = 1 ; + linein[now_in] = -linein[now_in] ; + } else { + lstoff = 0 ; + } + if (pass2 && iflist()) { + lineout() ; + fprintf(fout, "**** %s ****\n", src_name[now_in]) ; + } + } + if (ch == '\n') + { + linein[now_in]++ ; + } + + return(addtoline(ch)) ; +} + + +/* + * skip to rest of the line -- comments and if skipped lines + */ +int skipline(int ac) +{ + int c; + + c = ac; + while (c != '\n' && c != '\0') + c = nextchar(); + return('\n'); +} + + + +void usage() +{ + printf( +"zmac " ZMAC_VERSION +#ifdef ZMAC_BETA + ZMAC_BETA +#endif +", a Z80 macro cross-assembler.\n" +"Public domain by Bruce Norskog and others.\n" +"\n" +#ifdef __riscos +"usage: zmac [--help] [--version] [-AbcdefghilLmnOpsStTz]\n" +#else +"usage: zmac [--help] [--version] [-AbcdefghilLmnOpsStz]\n" +#endif +" [-o outfile] [-x listfile] [filename]\n" +"\n" +" --help give this usage help.\n" +" --version report version number.\n" +" -A output AMSDOS binary file rather than default binary file.\n" +" -b don't generate the m/c output at all.\n" +" -c make the listing continuous, i.e. don't generate any\n" +" page breaks or page headers. (This is the default.)\n" +" -d make the listing discontinuous.\n" +" -e omit the `error report' section in the listing.\n" +" -f list instructions not assembled due to `if' expressions being\n" +" false. (Normally these are not shown in the listing.)\n" +" -g list only the first line of equivalent hex for a source line.\n" +" -h output CP/M-ish Intel hex format (using extension `.hex')\n" +" rather than default binary file (extension `.bin').\n" +" -i don't list files included with `include'.\n" +" -l don't generate a listing at all.\n" +" -L generate listing; overrides any conflicting options.\n" +" -m list macro expansions.\n" +" -n omit line numbers from listing.\n" +" -o assemble output to `outfile'.\n" +" -O suggest possible optimisations (as warnings).\n" +" -p use linefeeds for page break in listing rather than ^L.\n" +" -s omit the symbol table from the listing.\n" +" -S show relevant line when reporting errors.\n" +" -t give terse (single-letter) error codes in listing.\n" +#ifdef __riscos +" -T enable DDE throwback for reporting warnings and errors.\n" +#endif +" -x generate listing to `listfile' (`-' for stdout).\n" +" -z accept 8080-compatible instructions only; flag any\n" +" Z80-specific ones as errors.\n"); +} + + + +int main(int argc, char *argv[]) +{ + struct item *ip; + int i, c; + +#ifdef ZMAC_BETA + printf ("*** THIS IS A BETA VERSION; NOT FOR GENERAL DISTRIBUTION ***\n"); +#endif + + if(argc==1) + usage(),exit(0); + + if(argc>=2) { + if(strcmp(argv[1],"--help")==0) + usage(),exit(0); + else if(strcmp(argv[1],"--version")==0) + puts("zmac " ZMAC_VERSION +#ifdef ZMAC_BETA + ZMAC_BETA +#endif + ),exit(0); + } + + fout = stdout ; + fin[0] = stdin ; + now_file = stdin ; + + *bin = *listf = 0; + optnerr = 0; + + while((c = getoptn(argc,argv, +#ifdef __riscos + "AbcdefghilLmno:OpsStTx:z" +#else + "AbcdefghilLmno:OpsStx:z" +#endif + )) != EOF) { + switch(c) { + + case 'A': /* AMSDOS binary -mep */ + output_amsdos = 1; + output_hex = 0; + break; + + case 'b': /* no binary */ + bopt = 0; + break; + + case 'c': /* continuous listing */ + continuous_listing = 1; + break; + + case 'd': /* discontinuous listing */ + continuous_listing = 0; + break; + + case 'e': /* error list only */ + eopt = 0; + edef = 0; + break; + + case 'f': /* print if skipped lines */ + fopt++; + fdef++; + break; + + case 'g': /* do not list extra code */ + gopt = 0; + gdef = 0; + break; + + case 'h': /* output .hex not .bin -rjm */ + output_hex = 1; + output_amsdos = 0; + break; + + case 'i': /* do not list include files */ + iopt = 1 ; + break; + + case 'l': /* no list */ + lopt++; + break; + + case 'L': /* force listing of everything */ + lston++; + break; + + case 'm': /* print macro expansions */ + mdef++; + mopt++; + break; + + case 'n': /* put line numbers off */ + nopt-- ; + break; + + case 'o': /* specify m/c output file */ + strcpy(bin, optnarg); + break; + + case 'O': /* suggest optimisations */ + suggest_optimise = 1; + break; + + case 'p': /* put out four \n's for eject */ + popt-- ; + break; + + case 's': /* don't produce a symbol list */ + sopt++; + break; + + case 'S': /* show line which caused error */ + show_error_line = 1; + break; + + case 't': /* terse error messages in listing */ + terse_lst_errors = 1; + break; + +#ifdef __riscos + case 'T': /* RISC OS throwback -mep */ + riscos_thbk = 1; + break; +#endif + + case 'x': /* specify listing file */ + if(strcmp(optnarg, "-") == 0) + oldoopt++; /* list to stdout (old `-o') */ + else + strcpy(listf, optnarg); + break; + + case 'z': /* 8080-compatible ops only */ + output_8080_only = 1; + break; + + case '?': + default: /* error */ + justerror("Unknown option or missing argument"); + break; + + } + } + + if(optnind != argc-1) justerror("Missing, extra or mispositioned argument"); + + atexit (doatexit); + + sourcef = argv[optnind]; + strcpy(src, sourcef); + + if ((now_file = fopen(src, "r")) == NULL) + { + /* If filename has no pre-existing suffix, then try .z */ + suffix_if_none (src, "z"); + if ((now_file = fopen(src, "r")) == NULL) + fileerror("Cannot open source file", src); + } + now_in = 0 ; + fin[now_in] = now_file ; + src_name[now_in] = src ; +#ifdef __riscos + riscos_set_csd(src); /* -mep */ +#endif + + /* If we haven't got a bin file filename, then create one from the + * source filename (.hex extension if option -h is specified). + */ + if (*bin == 0) { + strcpy(bin, sourcef); + if (output_hex) + suffix(bin,"hex"); + else + suffix(bin,"bin"); + } + if (bopt) + if (( fbuf = fopen(bin, output_hex ? "w" : "wb")) == NULL) + fileerror("Cannot create binary file", bin); + if (output_amsdos) + for(i=0; i<128; i++) + putc(0,fbuf); /* -mep */ + + if (!lopt && !oldoopt) { + /* If we've not got a filename for the listing file + * (-x option) then create one from the source filename + * (.lst extension) + */ + if( *listf == 0 ) { + strcpy(listf, sourcef); + suffix(listf,"lst"); + } + if ((fout = fopen(listf, "w")) == NULL) + fileerror("Cannot create list file", listf); + } else + fout = stdout ; + + strcpy(mtmp, sourcef); + suffix(mtmp,"tmp"); + mfile = mfopen(mtmp,"w+b") ; + if (mfile == NULL) { + fileerror("Cannot create temp file", mtmp); + } + + /* + * get the time + */ + time(&now); + timp = ctime(&now); + timp[16] = 0; + timp[24] = 0; + + title = sourcef; + /* + * pass 1 + */ +#ifdef DEBUG + fputs("DEBUG-pass 1\n", stderr) ; +#endif + setvars(); + yyparse(); + pass2++; + ip = &itemtab[-1]; + while (++ip < itemmax) { + /* reset use count */ + ip->i_uses = 0 ; + + /* set macro names, equated and defined names */ + switch (ip->i_token) { + case MNAME: + ip->i_token = OLDMNAME; + break; + + case EQUATED: + ip->i_token = WASEQUATED; + break; + + case DEFLED: + ip->i_token = UNDECLARED; + break; + } + } + setvars(); + fseek(now_file, (long)0, 0); + +#ifdef DEBUG + fputs("DEBUG- pass 2\n", stderr) ; +#endif + yyparse(); + + + if (bopt) { + flushbin(); + if (output_hex) { + putc(':', fbuf); + if (xeq_flag) { + puthex(0, fbuf); + puthex(xeq >> 8, fbuf); + puthex(xeq, fbuf); + puthex(1, fbuf); + puthex(255-(xeq >> 8)-xeq, fbuf); + } else + for (i = 0; i < 10; i++) + putc('0', fbuf); + putc('\n', fbuf); + } + if (output_amsdos) { + char leafname[] = "FILENAMEBIN"; + unsigned int chk; + unsigned int filelen = dollarsign - first_org_store; + if (filelen & 0x7f) + { + putc (0x1a, fbuf); /* CP/M EOF char */ + } + rewind(fbuf); + chk=0; + putc(0,fbuf); + for(i=0;i<11;i++) { + putc(leafname[i],fbuf); + chk+=leafname[i]; + } + for(i=0;i<6;i++) + putc(0,fbuf); + putc(2,fbuf); /* Unprotected binary */ + chk+=2; + putc(0,fbuf); + putc(0,fbuf); + putc(first_org_store & 0xFF,fbuf); + chk+=first_org_store & 0xFF; + putc(first_org_store >> 8,fbuf); + chk+=first_org_store >> 8; + putc(0,fbuf); + putc(filelen & 0xFF,fbuf); + chk+=filelen & 0xFF; + putc(filelen >> 8,fbuf); + chk+=filelen >> 8; + /* Next bit should be entry address really */ + putc(first_org_store & 0xFF,fbuf); + chk+=first_org_store & 0xFF; + putc(first_org_store >> 8,fbuf); + chk+=first_org_store >> 8; + for(i=28;i<64;i++) + putc(0,fbuf); + putc(filelen & 0xFF,fbuf); + chk+=filelen & 0xFF; + putc(filelen >> 8,fbuf); + chk+=filelen >> 8; + putc(0,fbuf); /* this would be used if length>64K */ + putc(chk & 0xFF,fbuf); + putc(chk >> 8,fbuf); + } + fflush(fbuf); + } + + if (!lopt) + fflush(fout); + if (*writesyms) + outsymtab(writesyms); + if (eopt) + erreport(); + if (!lopt && !sopt) + putsymtab(); + if (!lopt) { + eject(); + fflush(fout); + } + exit(had_errors); + return(had_errors); /* main () does return int, after all... */ +} + + +void doatexit (void) +{ +#ifdef __riscos + if (riscos_throwback_started) + { + _swix(DDEUtils_ThrowbackEnd,0); + } + _swix(DDEUtils_Prefix,1,0); /* Unset CSD */ +#endif +} + + +/* + * set some data values before each pass + */ +void setvars() +{ + int i; + + peekc = -1; + linein[now_in] = linecnt = 0; + exp_number = 0; + emitptr = emitbuf; + lineptr = linebuf; + ifptr = ifstack; + expifp = expif; + *ifptr = 0; + dollarsign = 0; + olddollar = 0; + phaseflag = 0; + for (i=0; ii_token == UNDECLARED) { + nitems--; + continue; + } + if (fp->i_token == 0) + continue; + tp++; + if (tp != fp) { + tp->i_string = fp->i_string; + tp->i_value = fp->i_value; + tp->i_token = fp->i_token; + tp->i_uses = fp->i_uses ; + tp->i_equbad = fp->i_equbad ; + } + } + + tp++; + tp->i_string = "{"; + + /* sort the table */ + custom_qsort(0, nitems-1); + + title = "** Symbol Table **"; + + rows = (nitems+3) / 3; + if (rows+5+line > 60) + eject(); + lineout(); + fprintf(fout,"\n\n\nSymbol Table:\n\n"); + line += 4; + + for (i=0; ii_token; + c = ' ' ; + if (t == EQUATED || t == DEFLED) + c = '=' ; + if (tp->i_uses == 0) + c1 = '+' ; + else + c1 = ' ' ; + fprintf(fout, "%-15s%c%4x%c ", + tp->i_string, c, tp->i_value & 0xffff, c1); + } + } + lineout(); + putc('\n', fout); + } +} + + + + +/* + * put out error report + */ +void erreport() +{ + int i, numerr; + + if (line > 50) eject(); + lineout(); + numerr = 0; + for (i=0; i 55) eject(); + lineout(); + fprintf(fout, "\n%6d\tsymbol%s\n", DO_PLURAL(nitems)); + fprintf(fout, "%6d\tbyte%s\n", DO_PLURAL(nbytes)); + line += 2; + if (mfptr) { + if (line > 53) eject(); + lineout(); + fprintf(fout, "\n%6d\tmacro call%s\n", DO_PLURAL(exp_number)); + fprintf(fout, "%6d\tmacro byte%s\n", DO_PLURAL(mfptr)); + fprintf(fout, "%6d\tinvented symbol%s\n", + DO_PLURAL(invented/2)); + line += 3; + } +} + + +/* + * lexical analyser for macro definition + */ +void mlex() +{ + char *p; + int c; + int t; + + /* + * move text onto macro file, changing formal parameters + */ +#ifdef M_DEBUG + fprintf(stderr,"enter 'mlex'\n") ; +#endif + inmlex++; + + c = nextchar(); +loop { + switch(charclass[c]) { + + case DIGIT: + while (numpart[c]) { + putm(c); + c = nextchar(); + } + continue; + + case STARTER: + case LETTER: + t = 0; + p = tempbuf+MAXSYMBOLSIZE+2; + do { + if (p >= tempmax) + error(symlong); + *p++ = c; + if (t < MAXSYMBOLSIZE) + tempbuf[t++] = (c >= 'A' && c <= 'Z') ? + c+'a'-'A' : c; + else + if (pass2) warnprt (0, 1); + c = nextchar(); + } while (charclass[c]==LETTER || charclass[c]==DIGIT); + + tempbuf[t] = 0; + *p++ = '\0'; + p = tempbuf+MAXSYMBOLSIZE+2; + t = tokenofitem(0); + if (t != MPARM) while (*p) putm(*p++); + else { + if (*(yylval.itemptr->i_string) == '?') putm('\2'); + else putm('\1'); + putm(yylval.itemptr->i_value + 'A'); + } + if (t == ENDM) goto done; + continue; + + case F_END: + if (expptr) { + popsi(); + c = nextchar(); + continue; + } + + goto done; + + default: + if (c == '\n') { + linecnt++; + } + if (c != '\1') putm(c); + c = nextchar(); + } +} + + /* + * finish off the file entry + */ +done: + while(c != EOF && c != '\n' && c != '\0') c = nextchar(); + linecnt++; + putm('\n'); + putm('\n'); + putm(0); + + for (c=0; c linebuf) lineptr--; + } +} + + + +/* + * return a unique name for a local symbol + * c is the parameter number, n is the macro number. + */ + +char *getlocal(int c, int n) +{ + static char local_label[10]; + + invented++; + if (c >= 26) + c += 'a' - '0'; + sprintf(local_label, "?%c%04d", c+'a', n) ; + return(local_label); +} + + + +/* + * read in a symbol table + */ +void insymtab(char *name) +{ + struct stab *t; + int s, i; + FILE *sfile; + + t = (struct stab *) tempbuf; + decanonicalise (name); + if ((sfile = fopen(name, "rb")) == NULL) + return; + fread((char *)t, 1, sizeof *t, sfile); + if (t->t_value != SYMMAJIC) + { + fclose (sfile); + return; + } + + s = t->t_token; + for (i=0; ii_token = t->t_token; + yylval.itemptr->i_value = t->t_value; + if (t->t_token == MACRO) + yylval.itemptr->i_value += mfptr; + } + + while ((s = fread(tempbuf, 1, TEMPBUFSIZE, sfile)) > 0) { + mfptr += s; + mfwrite(tempbuf, 1, s, mfile) ; + } + + fclose (sfile); +} + + + +/* + * write out symbol table + */ +void outsymtab(char *name) +{ + struct stab *t; + struct item *ip; + int i; + FILE *sfile; + + t = (struct stab *) tempbuf; + decanonicalise (name); + if ((sfile = fopen(name, "wb")) == NULL) + return; + for (ip=itemtab; ipi_token == UNDECLARED) { + ip->i_token = 0; + nitems--; + } + } + + copyname(title, (char *)t); + t->t_value = SYMMAJIC; + t->t_token = nitems; + fwrite((char *)t, 1, sizeof *t, sfile); + + for (ip=itemtab; ipi_token != 0) { + t->t_token = ip->i_token; + t->t_value = ip->i_value; + copyname(ip->i_string, (char *)t); + fwrite((char *)t, 1, sizeof *t, sfile); + } + } + + mfseek(mfile, (long)0, 0); + while((i = mfread(tempbuf, 1, TEMPBUFSIZE, mfile) ) > 0) + fwrite(tempbuf, 1, i, sfile); + + fclose (sfile); +} + + + +/* + * copy a name into the symbol file + */ +void copyname(char *st1, char *st2) +{ + char *s1, *s2; + int i; + + i = (MAXSYMBOLSIZE+2) & ~01; + s1 = st1; + s2 = st2; + + while((*s2++ = *s1++)) i--; /* -Wall-ishness :-) -RJM */ + while(--i > 0) *s2++ = '\0'; +} + +/* get the next source file */ +void next_source(char *sp) +{ + + if(now_in == NEST_IN -1) + error("Too many nested includes") ; + decanonicalise (sp); + if ((now_file = fopen(sp, "r")) == NULL) { +#ifdef __riscos + if (riscos_thbk) + riscos_throwback(2,src_name[now_in],linein[now_in],"Cannot open include file"); +#endif + fileerror("Cannot open include file", sp) ; + } + if (pass2 && iflist()) { + lineout() ; + fprintf(fout, "**** %s ****\n",sp) ; + } + + /* save the list control flag with the current line number */ + if (lstoff) + linein[now_in] = - linein[now_in] ; + + /* no list if include files are turned off */ + lstoff |= iopt ; + + /* save the new file descriptor. */ + fin[++now_in] = now_file ; + /* start with line 0 */ + linein[now_in] = 0 ; + /* save away the file name */ + src_name[now_in] = malloc(strlen(sp)+1) ; + strcpy(src_name[now_in],sp) ; +} + +#ifdef __riscos +/* + * On entry sp should point to the full pathname of a file in RISC OS form. + * Searches for the last dot, and sets the local CSD to that path. + * Does not corrupt the string. + */ +void riscos_set_csd(char *sp) +{ + char *s1 = strrchr (sp, '.'); + + if (s1 != NULL) + { + *s1=0; + _swix(DDEUtils_Prefix,1,sp); + *s1='.'; + } +} + +void riscos_throwback(int severity, char *file, int line, char *error) +{ + if (riscos_throwback_started==0) + { + riscos_throwback_started=1; + *riscos_thbkf=0; + _swix(DDEUtils_ThrowbackStart,0); + } + if (strcmp(file, riscos_thbkf)!=0) + { + _swix(DDEUtils_ThrowbackSend,4+1,0,file); /* Notify of a change of file */ + strcpy(riscos_thbkf,file); + } + _swix(DDEUtils_ThrowbackSend,32+16+8+4+1,1,file,line,severity,error); +} +#endif