; InkSpector Assembler Example / Documentation
; Mark Incley
; 03.01.2010
;
; Like many Z80 assemblers, Inkspector follows the convention of having labels in the left-most
; column, with instructions and directives in subsequent columns. Labels are case-sensitive
; but instructions and directives are not. I use the age-old convention of using directives
; in upper-case.

; TARGET (or DEVICE for compatibility with SjASMPlus) specifies the machine being targetted.
; If TARGET is not specified, "NONE" is assumed, and SAVESNAP, SAVETAP, SAVETAPE and
; IMPORT SYSVARS cannot be used.
;
; Devices of ZXSPECTRUM48, ZXSPECTRUM128, ZX80 and ZX81 are currently supported.
;
		TARGET ZXSPECTRUM48

; IMPORT SYSVARS creates a set of labels from the system variable names for the current machine
; (specified with TARGET)

		IMPORT SYSVARS			; Brings in about 70 system variable names
		ASSERT KSTATE==$5C00		; Just to prove they're there, test a handful
		ASSERT FRAMES==$5C78
		ASSERT P_RAMT==$5CB4

; SAVETAPE <filename> generates a tape image file containing a loader to load and execute the
; assembled program. This is the preferred way to execute programs that require the system
; OS to be active. A loading screen may be incorporated into the generated tape image by
; specifying the name of a 6912 byte raw SCREEN$ file as the 2nd argument. The assembler
; can generate .tap, .tzx or .pzx tape images. The type of tape image created is determined
; by the filename's extension. The start address of the program is specified by the END directive.
;
;	 e.g. SAVETAPE "mayhem.tap","mayhem.scr"
;
; If SAVETAPE is not specified, the assembled program will be transferred to the emulated machine
; and executed. As mentioned below, executing programs this way (i.e. without SAVETAPE) will not
; work for programs that require the machine's OS to be active.
;
		SAVETAPE	"example.tzx"

; For compatibility with SjASMPlus, the SAVETAP directive is also supported. The format is
; SAVETAP <filename>[,start-address]. If the start-address is omitted, it is taken from the
; END directive.

; TAPEINFO <category: text> specifies information to be added to the generated tape image (.tzx and .pzx images only)
;
; The category must be one of the ones as set out in the TZX specification (also used by the .pzx specification), i.e.
;	Title, Publisher, Author, Year, Language, Type, Price, Protection, Origin or Comment
;
		TAPEINFO	"Title: Example of Inkspector's fab tape generator"
		TAPEINFO	"Publisher: Andy Sturmer"
		TAPEINFO	"Author: Roger Joseph Manning Jr."
		TAPEINFO	"Year: 1989"
		;TAPEINFO	"Language: English"		; NB not necessary if language is English
		TAPEINFO	"Type: Demo"
		TAPEINFO	"Price: 0.00"
		TAPEINFO	"Protection: None"
		TAPEINFO	"Origin: Original"
		TAPEINFO	"Comment: Bellybutton"
		TAPEINFO	"Comment: Spilt Milk"
		TAPEINFO	"Comment: And no more"

; TAPELOADEREXEC PRINT|RAND|LET specifies how the generated tape loader will start your program:
;
;	PRINT:	PRINT USR <start-addr>
;	RAND:	RANDOMISE USR <start-addr>
;	LET:	LET R=USR <start=addr>

		TAPELOADEREXEC PRINT

; SAVESNAP <filename>[,start address] writes the contents of the machine containing the assembled data
; to any supported snapshot type (determined by the filename extension). More than one SAVESNAP
; directive may be used.
;
; Because the underlying machine's OS isn't given chance to boot up before executing the assembled
; code, SAVESNAP is only of use when writing system-independent programs that do not rely on the
; machine's OS having booted up.
;
; For compatibility with SjASMPlus, SAVESNA is an alias for SAVESNAP.
; As with SjASMPlus, the optional 2nd argument specifies the starting address of the program,
; or the value specified after the END directive if not.
;
		SAVESNAP "example.z80"			; Start address assumed to be that specified by the END directive
		SAVESNAP "example.szx",ProgramStart	; Start address specified

; EXECUTE <mode> instructs the emulator how to execute the assembled program:
;
;	EXECUTE IMMEDIATE 	The emulator executes the assembled program in memory immediately
; 				without loading a tape image, even if one has been generated.
;
;	EXECUTE TAPE		The emulator executes the program by loading the generated tape
;				image. If there is no tape image, a warning is shown and an
;				immediate execution is attempted.
;
; 	EXECUTE AUTO		If a tape image has been generated, behaves as EXECUTE TAPE
;				otherwise as EXECUTE IMMEDIATE. The is the default execution mode.
;
; Generally you would want to use EXECUTE IMMEDIATE when the generated program is self-contained.
; i.e. does not rely on the system's OS having booted up and initialised. If you are writing a
; program that does require the OS to be active, use EXECUTE TAPE which allows the emulator to
; boot up the OS then load the generated tape image automatically.

		EXECUTE IMMEDIATE

; SAVESYMBOLS <filename> will write out all symbols to the specified file. The file format used
; is the same that SjASMPlus's LABELSLIST directive produces, i.e.
;
; 		[PN]:ADDR <Label Name>
;
;	Where PN is a two digit page number and ADDR is the address in hexadecimal using 4 digits.
;
; The value of identifiers (created with DEFINE or DEFL) are shown without any page number (PN) and
; start with the colon.
;
		SAVESYMBOLS "example.sym"

; SAVELISTING <filename> causes the assembler to perform an extra pass of the source code to
; produce a listing containing each line of the assembled code along with generated machine code.
;
		SAVELISTING "example.lst"

; SAVEBIN <filename> saves the generated assembly as a raw binary file
;
		SAVEBIN	"example.bin"

; SAVEHEX <filename> saves the generated assembly as an Intel hex format file

		SAVEHEX	"example.hex"

; NB SAVESYMBOLS, SAVELISTING, SAVEBIN and SAVEHEX default to using
; current-filename>.ext if no filename is specified, where ext
; is .sym, .lst, .bin and .hex respectively.

; Specifying UNREFS displays a list of all unreferenced items in the assembly. If SAVELISTING
; has been used, the same list is appended to the listing file too.
; NB unreferenced labels imported using IMPORT SYSVARS will not be displayed.
;
		UNREFS

; ORG sets the address to assemble to. Multiple ORGs may be used throughout the code.
;
		ORG	$8000
;
; DISPLAY prints a message in the message window for the GUI, or console output for the CLI tool.
;
		DISPLAY "Hello there!"
;
; DEFINE creates an identifier
; NB creating an identifier is different to the EQU directive, which sets a label's value and is
; therefore limited to 16-bit values.
;
LabelValue	EQU	$1234			; Cannot be a value less than 0 or greater than 65535
		ASSERT LabelValue == $1234

		DEFINE	IntM2VectorTableAddr 	$8200
		DEFINE	IntM2VectorTableByte	$81
		DEFINE	A_Big_Value_That_Would_Be_Too_Large_For_A_Label 1234567890
		DEFINE	Author			"Mark Incley"
		DEFINE	I_Have_No_Value_But_I_Am_Still_Valid
		DEFINE	I_Am_An_Unloved_Identifier_Which_Will_Not_Be_Referenced	; This will show up in the UNREFS list
;
; DEFL works as DEFINE, but unlike DEFINE, the identifier may be redefined
;
Count		DEFL	1
Count 		DEFL 	Count + 1	; This wouldn't work if Count had been created with DEFINE
Count		=	Count + 1	; You can use = instead of DEFL
;
; ASSERT allows you to stop the assembly if an expression is false
;
		ASSERT	Count == 3
		ASSERT 	A_Big_Value_That_Would_Be_Too_Large_For_A_Label == 1234567890
		ASSERT $ == $8000	; NB $ = the current assembly address
		ASSERT 1 != 0
		ASSERT LabelValue == $1234

;
; IF / ELSE
;
		IF Count == 2
		DISPLAY "The value of Count is indeed 2"
		ELSE
		DISPLAY "The value of Count must be 1"
		ENDIF
;
; IFDEF
;
		IFDEF	I_Have_No_Value_But_I_Am_Still_Valid
		DISPLAY "This rubbish was written by ", Author
		ENDIF

; The identifier __INKSPECTOR__ is defined automatically with a value of the version number, to allow assembly to query, e.g.
		IFDEF __INKSPECTOR__
		DISPLAY	"I'm being assembled by Inkspector ",__INKSPECTOR__,"!"
		ENDIF
;
; IFNDEF
;
		IFNDEF I_Have_No_Value_But_I_Am_Still_Valid
		rst 0		; *snigger*
		ELSE
		DISPLAY "Phew!"
		ENDIF

; Declaring a STRUCT
; Every line containing an instruction (e.g. "DB") must have a label declared
;
		STRUCT	Sprite
x		DB	0		; X coor
y		DB	0		; Y coor
gfx		DW	0		; Pointer to graphic
		ENDS

; Create our STRUCT instance
mySprite	Sprite

;
; Referencing a STRUCT
;
		; Addressing the STRUCT directly gives you its size
		ASSERT	Sprite == 4
		ASSERT	$ == $8000 + Sprite
		ld	hl,Sprite

		; Addressing a STRUCT's member gives you its offset
		ASSERT	Sprite.x == 0
		ASSERT	Sprite.y == 1
		ASSERT	Sprite.gfx == 2
		ld	hl,Sprite.y
		ld	b,(ix+Sprite.y)

		; Addressing a STRUCT variable's member gives you its address
		ASSERT	mySprite.gfx == $8002
		ld	hl,(mySprite.gfx)

;
; REPT repeats the block of code within it a number of times.
; Repeat values of 0 are valid too, which essentially ignores the enclosed block of code.
;

ShowBackBuffer:	ld      hl,$ffff
		ld      de,$57ff
		ld      bc,$1000

.loop:		REPT	16
		ldd
		ENDR
		ASSERT (($ - .loop) / 2) == 16
		jp      pe,.loop
		ret
;
; REPTs may also contain local variables
;
		REPT 	4
		add	a,a
		jp 	m,.not_pos

		cpl

.not_pos:	ENDR		; NB does not cause a label already defined error

;
; REPT 0 is valid, but won't produce any code
;
		REPT 	0
		RST 	0	; This instruction will not appear in the assembled code because of the REPT value of zero
		ENDR
;
; MACRO declares a macro with optional parameters which will be replaced when used
;
		MACRO 	SETBORDER border
		ld	a,border
		out	($fe),a
		ENDM

		MACRO	BLACKBORDER
		SETBORDER 0
		ENDM

		MACRO	CALCSCR reg,x,y
		ld	reg,$4000 + ((x) / 8) + (256 * ((y) & 7)) + ($20 * (((y) & $3f) >> 3)) + (((y) >> 6) * $800)
		ENDM

		BLACKBORDER		; Expands to "SETBORDER 0" -> "ld a, 0: out ($fe),a"
		SETBORDER	4	; Expands to "ld a,4: out ($fe),a"
		CALCSCR		hl,0,0	; Expands to "ld hl,$4000"


;
; Inkspector automatically detects labels created within a macro - they do not have to be explicitly
; declared as local.
;
		MACRO	LOCALTEST
		or	a
		jr 	nz,.not_zero

		cpl

.not_zero:	ENDM

		LOCALTEST
		LOCALTEST			; Just prove MACRO local labels don't produce duplicate label errors :)

; Inkspector supports all known undocumented Z80 instructions in addition to the official Zilog ones.
; Here's a random selection for the fun of it

		ld	a,ixl
		adc 	a,iyh
		sll	a
		rl 	(ix+0),d
		rlc 	(iy+0),e
		set 	1,(ix-10),h

; A simple program to draw a jazzy border
ProgramStart:	di
		ld	sp,0
		call	SetupIM2
		ei
		halt

; Set black attributes and all pixels to ink
		ld	hl,$5AFF
		ld	de,$5AFE
		ld	bc,$0300
		ld	(hl),0
		lddr
		ld	bc,$17FF
		ld	(hl),$FF
		lddr

; Let's have a jazzy green border too
		ld	a,4
		out 	($fe),a

		xor	a

.loop:		push	af
		call	Border
		pop	af
		inc	a
		and	7
		ld	b,4
.halt_a_bit:	halt
		djnz	.halt_a_bit

		jr	.loop

Border:		ld	hl,$5800
		ld	c,7
		ld	b,31

.top:		ld	(hl),a
		inc	a
		and	c
		inc	l
		djnz	.top

		ld	de,$20
		ld	b,23

.rhs:		ld	(hl),a
		add	hl,de
		inc	a
		and	c
		djnz	.rhs

		ld	b,31

.bot:		ld	(hl),a
		dec	l
		inc	a
		and	c
		djnz	.bot

		ld	de,-$20
		ld	b,23

.lhs:		ld	(hl),a
		add	hl,de
		inc	a
		and	c
		djnz	.lhs
		ret


SetupIM2:	ld	hl,IntM2VectorTableAddr
		ld	de,IntM2VectorTableAddr+1
		ld	bc,$100
		ld	(hl),IntM2VectorTableByte
		ld	a,h
		ld	i,a
		ldir
		im	2
		ret

		DEFINE Int_Vector	IntM2VectorTableByte + 256 * IntM2VectorTableByte

		IF $ >= Int_Vector
		DISPLAY "$ is encroaching on the IM2 handler at ", Int_Vector
		ENDIF

		ORG Int_Vector
		ei
		ret

; When the assembler writes out snapshots, it uses the Z80's powered-on values
; except for PC, which is set by the END directive (unless an address has been specified).
; To set the value of other registers SETREGISTER may be used.
; This example moves the SP to the end of the Spectrum's attributes, just in case we write
; a .sna format out because that format requires the starting address (PC) is pushed on the
; stack, which *might* just clobber the contents of some memory that we want to keep.

		SETREGISTER	SP,$5B00

; Just before we finish, let's demonstrate the ALIGN directive, which moves the current address
; on to the next multiple of the value specified. If the current address is already a multiple
; of the alignment value, it is not modified.

		ORG 	$8000
		ALIGN 	128
		ASSERT 	$ == $8000

		ORG 	$8001
		ALIGN 	128
		ASSERT 	$ == $8080
		ALIGN 	256
		ASSERT 	$ == $8100
		ALIGN 	$4000
		ASSERT 	$ == $C000
;
; END specifies end of assembly and optionally specifies the starting point of the program
;
		END ProgramStart

; The assembler won't see this "directive" because of the END directive above
		NONSENSE_DIRECTIVE

; The following directives are also available:
;
; INCBIN <filename> 		- include the specified file at the current address
; INCHEX <filename>[,address] 	- include the specified Intel hex format file at the address specified in the file (or address if specified as a second parameter)

;
; End of example.s
;
