
; Public Domain

; test install command: install; printf "%04X\r\n" 26; printf uninstall
; test run command: "%04X\r\n" 26; .; .

%include "lmacros3.mac"
%include "eld.mac"
%include "eldcall.mac"
%include "elddata.mac"

	cpu 8086

	addsection RELOCATEDDATA, nobits vstart=_ELD_RELOC_VSTART
relocateddata:

	addsection HEADER, start=0

	istruc ELD_HEADERX
at eldhxHeader
		; ELD executable header
	istruc ELD_HEADER
at eldhSignature,	db "ELD1"
			db 0,0,0
			db 26
at eldhCodeOffset,	dd CODEOFFSET
at eldhCodeImageLength,	dw code_size
at eldhCodeAllocLength,	dw 0
at eldhDataOffset,	dd DATAOFFSET
at eldhDataImageLength,	dw data_size
at eldhDataAllocLength,	dw total_data_size - data_size
at eldhCodeEntrypoint,	dw linker - code
at eldhReserved
at eldhExtensionSize,	dw header_extension_end - $$
	iend
at eldhxDescriptionOffset,	dd description
at eldhxHelpOffset,		dd DATAOFFSET + msg.help - datastart
header_extension_end:
	iend

description:		asciz "Print formatted strings."


	align 16, db 0

CODEOFFSET equ $ - $$
	addsection CODE, follows=HEADER vstart=_ELD_CODE_VSTART
%define CODEFIXUP - code + 0
code:
		; ELD instance header
	istruc ELD_INSTANCE
at eldiStartCode
at eldiEndCode
at eldiStartData
at eldiEndData
at eldiIdentifier,	fill 8, 32, db "PRINTF"
at eldiListing,		asciz _ELD_LISTING
	iend


	numdef ELD_BUG, 0
	numdef ELD_BUG_RELOC, _ELD_BUG
	numdef ELD_BUG_LENGTH, _ELD_BUG
	numdef ELD_BUG_RELOC_DATA, _ELD_BUG
	numdef ELD_BUG_LENGTH_DATA, _ELD_BUG

%rep _ELD_BUG_RELOC
	dw msg.printf			; missing internaldatarelocation
%endrep

%if _ELD_BUG_LENGTH && _ELD_CODE_VSTART
times 64 db 0
%endif


command:
	jmp strict short .entry
.chain:
	extcall cmd3_not_ext, required	; must NOT be extcallcall
	times 10 - ($ - command) nop
.entry:
	push si
	cmp al, '-'
	jne @F
	extcallcall skipcomma
@@:
	dec si

	mov dx, msg.printf
internaldatarelocation
	extcallcall isstring?
	je .ours
	pop si
	dec si
	lodsb
	jmp .chain

.ours:
	pop ax
	extcallcall skipcomma
	dec si
reloc2	mov word [relocateddata], relocateddata
linkdatarelocation lastcmd, -4
linkdatarelocation dmycmd
	mov dx, relocateddata
linkdatarelocation msg.uninstall
	extcallcall isstring?
	je uninstall
	lodsb
	call run
	extcallcall cmd3

uninstall:
	lodsb
	extcallcall chkeol

	mov es, word [relocateddata]
linkdatarelocation extdssel
	extcallcall ispm
	jz @F
	mov es, word [relocateddata]
linkdatarelocation extseg
@@:
	push es
	pop ds
	xor bx, bx		; = 0 (no prior, modify ext_command_handler)
	mov di, command		; di -> us
internalcoderelocation
	mov si, word [ss:relocateddata]
linkdatarelocation ext_command_handler
				; si -> first
	test si, si		; none installed ?
	jz .error		; error -->

.loop:
	cmp di, si		; found ?
	je .bx			; yes, use bx -->
	mov bx, si		; bx -> prior handler
	lodsw			; skip entrypoint jmp strict short
	lodsb			; get first byte of chainer
	cmp al, 0E9h		; expecting jmp near ?
	jne .error		; no, error -->
	lodsw			; get rel16 displacement
	add si, ax		; -> next handler
	jmp .loop

.bx:
	test bx, bx		; any prior ?
	jnz .bxnz		; yes -->
	scasw			; skip entrypoint jmp strict short
	cmp byte [di], 0E8h	; is it a call to cmd3_not_ext ?
	jne @F			; no -->
				; yes, reset ext_command_handler to zero
.setbx:
	mov word [ss:relocateddata], bx
linkdatarelocation ext_command_handler
	jmp .done

@@:
	cmp byte [di], 0E9h	; validate
	jne .error		; failure -->
	inc di			; -> rel16 displacement
	mov bx, word [di]	; get displacement
	scasw			; -> after jmp near
	add bx, di		; -> next handler
	jmp .setbx		; set ext_command_handler to next

.bxnz:
	mov si, bx		; -> prior handler with us as downlink
	xchg di, si		; si -> ours, di -> prior
	cmpsw			; skip entrypoint jmp strict short
	movsb			; copy 0E8h/0E9h
	lodsw			; ax = near rel16 displacement
	add ax, si		; add in our base (= absolute offset)
	sub ax, di
	dec ax
	dec ax			; subtract new base (= relative displacement)
	stosw			; store new rel16 displacement
	movsw			; jmp strict short
	movsw			; linkcall target
	movsb			; trailer
.done:
	clropt [code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as free
	mov dx, msg.uninstall_done
internaldatarelocation
@@:
	push ss
	pop ds
	extcallcall putsz
	extcallcall cmd3	; return

.error:
	mov ax, 0E01h
	extcallcall setrc
	mov dx, msg.uninstall_error
internaldatarelocation
	jmp @B


run:
	lframe none
	lequ 16, line_out_reserved
	lequ 16, buffersize
	lvar ?buffersize, buffer
	lvar word, type
	lvar word, width
	lvar word, maxwidth
	lvar word, pad
	lvar word, pad_after_length
	lvar word, pad_after
	lvar word, otherprefix
	lvar word, preprefixlength
	lvar word, postprefixlength
	lvar word, prefixes
	lenter
	push ss
	pop es

	extcallcall skipwh0
	cmp al, '"'
	je @F
	cmp al, "'"
	je @F
	mov ax, 0E4Eh
	extcallcall setrc
	mov dx, msg.no_quote
internaldatarelocation
.putsz_end:
	extcallcall putsz
	jmp end

@@:
	lvar word, quote
	 push ax
	mov ah, al
	lvar word, startstring
	 push si			; -> text

.unquote_loop:
	lodsb
	cmp al, '\'
	je .unquote_backslash
	cmp al, ah
	je .unquote_check
	extcallcall iseol?.notsemicolon
	jne .unquote_loop

.unexpected_eol:
	mov dx, msg.no_unquote
internaldatarelocation
	mov ax, 0E4Fh
	extcallcall setrc
	jmp .putsz_end

.unquote_backslash:
	lodsb
	extcallcall iseol?.notsemicolon
	je .unexpected_eol
	jmp .unquote_loop

.unquote_check:
	lodsb				; si -> after next text
	cmp al, ah
	je .unquote_loop
	dec si				; -> next text
	dec si				; -> closing quote mark
	lvar word, endstring
	 push si			; -> closing quote mark, behind text
	mov byte [si], 0
	lvar word, nextparam
	inc si
	 push si			; -> after closing quote mark

	mov si, [bp + ?startstring]
	mov di, relocateddata
linkdatarelocation line_out

.loop:
	call .dump_if_close
	lodsb
.loop_al:
	cmp al, 0
	je .end
	cmp al, '\'
	je .backslash
	cmp al, '%'
	je .percent
	cmp al, byte [bp + ?quote]
	jne .store
.quote:
	inc si
.store:
	stosb
	jmp .loop


.backslash:
	lodsb
	cmp al, 0
	je .end
	cmp al, 'A'
	jb .store
	cmp al, 'Z'
	jbe @F
	cmp al, 'a'
	jb .store
	cmp al, 'z'
	ja .store
	sub al, 'a' - 'A' - 26
@@:
	sub al, 'A'
	mov bx, backslashletters
internaldatarelocation
	xlatb
	cmp al, 0
	jnz .store
	lodsb
	extcallcall getnyb
	jc .loop_al
	xchg bx, ax
	lodsb
	extcallcall getnyb
	jc .onedigit
	add bl, bl
	add bl, bl
	add bl, bl
	add bl, bl
	add bl, al
	db __TEST_IMM8		; (skip dec)
.onedigit:
	dec si
	xchg bx, ax
	jmp .store

.percent:
	xor bx, bx
	mov word [bp + ?preprefixlength], bx
	mov word [bp + ?postprefixlength], bx
	mov word [bp + ?pad_after], bx
	mov word [bp + ?pad_after_length], bx
	mov word [bp + ?otherprefix], bx
	mov word [bp + ?pad], 32
.loop_percent:
@@:
	lodsb
	cmp al, 0
	je .end
	cmp al, '%'
	je .store

	cmp al, '0'
	jb @F
	jne .notzero
	test bx, bx
	jnz .notzero
	rol byte [bp + ?pad + 1], 1
	jc .notzero
	mov byte [bp + ?pad], al
	jmp @B

.notzero:
	cmp al, '9'
	ja @F
	sub al, '0'
	add bx, bx		; times 2
	mov dx, bx
	add bx, bx		; times 4
	add bx, bx		; times 8
	add bx, dx		; times 10
	mov ah, 0
	add bx, ax
	jmp @B

@@:
	cmp al, '.'
	jne @F
	mov byte [bp + ?pad + 1], -1
	mov word [bp + ?width], bx
	xor bx, bx
	jmp @BB
@@:
	cmp al, '-'
	jne @F
	mov byte [bp + ?pad_after], -1
	jmp @BBB
@@:
	cmp al, '+'
	jne @F
	mov word [bp + ?otherprefix], '+' << 8 | 0FFh
	jmp .loop_percent
@@:
	cmp al, ' '
	jne @F
	mov word [bp + ?otherprefix], ' ' << 8 | 0FFh
	jmp .loop_percent
@@:
	cmp al, '*'
	jne @F
	push si
	mov si, word [bp + ?nextparam]
	extcallcall skipwhite
	extcallcall getword
	dec si
	mov word [bp + ?nextparam], si
	mov bx, dx
	pop si
	jmp .loop_percent
@@:
	mov ah, al
	and ah, 20h
	mov word [bp + ?type], ax
	rol byte [bp + ?pad + 1], 1
	jc @F
	mov word [bp + ?width], bx
	jmp @FF
@@:
	mov word [bp + ?maxwidth], bx
@@:
	extcallcall uppercase
	cmp al, 'X'
	je .hex
	cmp al, 'U'
	je .unsigned
	cmp al, 'D'
	je .signed
	cmp al, 'I'
	je .signed
	cmp al, 'C'
	je .codepoint
	cmp al, 'S'
	je .string
	cmp al, 'B'
	je .bytes
	mov dx, msg.unknown
internaldatarelocation
	mov ax, 0E50h
	extcallcall setrc
	jmp .putsz_end


.string:
	push si
houdini
	mov si, word [bp + ?nextparam]
	extcallcall skipcomma
	dec si
	mov dx, msg.start
internaldatarelocation
	extcallcall isstring?
	je .string_start
	mov dx, msg.range
internaldatarelocation
	extcallcall isstring?
	je .range_string
	mov dx, msg.asciz
internaldatarelocation
	extcallcall isstring?
	je .asciz_string
	extcallcall error

.asciz_string:
	extcallcall skipcomma
	xor cx, cx
	mov bx, word [relocateddata]
linkdatarelocation reg_ds
	extcallcall getrangeX
	testopt [relocateddata], 8000h
linkdatarelocation internalflags, -3
	jz @F
	extcallcall ispm
	jnz @F
subcpu 386
	inc ecx
	sub ecx, edx
	push edx
	 push di
	mov es, bx
	mov edi, edx			; es:edi -> data
	mov al, 0			; al = 0
	a32 repne scasb			; edi -> behind NUL
	mov ecx, edi			; ecx -> behind NUL
	 push ss
	 pop es
	 pop di				; restore
	sub ecx, edx			; = length
	dec ecx
	test ecx, 0FFFF_0000h
	jnz .toolong
	dec si
	call .string_have_length
	pop esi
subcpureset
	jmp @FF

@@:
	inc cx
	sub cx, dx
	push dx
	 push di
	mov es, bx
	mov di, dx			; es:di -> data
	mov al, 0			; al = 0
	repne scasb			; di -> behind NUL
	mov cx, di			; cx -> behind NUL
	 push ss
	 pop es
	 pop di				; restore
	sub cx, dx			; = length
	dec cx
	dec si
	call .string_have_length
	pop si

@@:
	jmp .asciz_common


.range_string:
	extcallcall skipcomma
	xor cx, cx
	mov bx, word [relocateddata]
linkdatarelocation reg_ds
	extcallcall getrangeX
	testopt [relocateddata], 8000h
linkdatarelocation internalflags, -3
	jz @F
	extcallcall ispm
	jnz @F
subcpu 386
	inc ecx
	sub ecx, edx
	test ecx, 0FFFF_0000h
	jnz .toolong
	push edx
	dec si
	call .string_have_length
	pop esi
subcpureset
	jmp @FF

@@:
	inc cx
	sub cx, dx
	push dx
	dec si
	call .string_have_length
	pop si

@@:
.asciz_common:
	mov ax, cx
	add ax, di			; -> behind buffer needed
	jc @F				; if > 64 KiB -->
	cmp ax, strict word relocateddata
linkdatarelocation line_out_end		; in buffer ?
	jbe @FFF			; yes, just copy all -->
@@:
	mov ax, di			; -> behind used data
	neg ax				; minus current
	add ax, strict word relocateddata
linkdatarelocation line_out_end		; ax = end - current = free
	sub cx, ax			; cx = remainder
	jbe @F
	xchg cx, ax			; cx = chunk size, ax = remainder
	call .movs_bx_esi
	xchg cx, ax			; cx = remainder
	call .dump			; dump as much as possible,
					;  resets di
	jmp @B

@@:
	add cx, ax			; reset cx
@@:
	call .movs_bx_esi		; always fits
	pop si
.loop_pad_after:
	call .pad_after
	jmp .loop

.movs_bx_esi:
	testopt [relocateddata], 8000h
linkdatarelocation internalflags, -3
	mov ds, bx			; modify ds after variable read
	jz @F
	extcallcall ispm
	jnz @F
	a32			; (a32 rep movsb)
@@:
	rep movsb			; copy from user memory
	 push ss
	 pop ds				; reset ds
	retn


.string_start:
	extcallcall skipcomma
	xor cx, cx
	push si
.string_find_loop:
	dec si
	mov dx, msg.end
internaldatarelocation
	extcallcall isstring?
	je .string_found
	lodsb
	cmp al, '"'
	je .string_find_quoted
	cmp al, "'"
	je .string_find_quoted
	call getbyte
	inc cx
	jz .toolong
.string_find_loop_skip:
	extcallcall skipcomma
	jmp .string_find_loop

.string_find_quoted:
	mov ah, al
.string_find_quoted_loop:
	lodsb
	extcallcall iseol?.notsemicolon
	jz .unexpected_eol
	cmp al, ah
	je .string_find_quoted_unquote
@@:
	inc cx
	jz .toolong
	jmp .string_find_quoted_loop

.toolong:
	mov dx, msg.toolong
internaldatarelocation
	mov ax, 0E51h
	extcallcall setrc
	jmp .putsz_end

.string_find_quoted_unquote:
	lodsb
	cmp al, ah
	je @B
	jmp .string_find_loop_skip

.string_found:
	call .string_have_length
	pop si
.string_out_loop:
	dec si
	mov dx, msg.end
internaldatarelocation
	extcallcall isstring?
	je .string_out_done
	lodsb
	cmp al, '"'
	je .string_out_quoted
	cmp al, "'"
	je .string_out_quoted
	call getbyte
	xchg ax, dx
	call .string_stosb
.string_out_loop_skip:
	extcallcall skipcomma
	jmp .string_out_loop

.string_out_quoted:
	mov ah, al
.string_out_quoted_loop:
	lodsb
	extcallcall iseol?.notsemicolon
	jz .unexpected_eol
	cmp al, ah
	je .string_out_quoted_unquote
@@:
	call .string_stosb
	jmp .string_out_quoted_loop

.string_out_quoted_unquote:
	lodsb
	cmp al, ah
	je @B
	jmp .string_out_loop_skip

.string_stosb:
	stosb
	call .dump_if_close
	dec cx
	jz .string_out_done.pop
	retn


.string_have_length:
	extcallcall skipcomma
	dec si
	mov word [bp + ?nextparam], si
	rol byte [bp + ?pad + 1], 1
	jnc @F
	cmp cx, word [bp + ?maxwidth]
	jbe @F
	mov cx, word [bp + ?maxwidth]
@@:
	mov ax, word [bp + ?width]
	sub ax, cx
	jbe @F
	push cx
	xchg cx, ax
	mov al, 32
	call .pad
	pop cx
@@:
	retn

.string_out_done.pop:
	pop si
.string_out_done:
	pop si
	jmp .loop_pad_after


.codepoint:
houdini
	push si
	mov si, word [bp + ?nextparam]
	extcallcall skipwhite
	extcallcall getbyte
	dec si
	mov word [bp + ?nextparam], si
	mov cx, word [bp + ?width]
	sub cx, 1
	jbe @F
	mov al, 32
	call .pad
@@:
	xchg ax, dx
	stosb
	pop si
	jmp .loop_pad_after


.signed:
	push si
	push di
	mov si, word [bp + ?nextparam]
	extcallcall skipwhite
	extcallcall getexpression
	dec si
	mov word [bp + ?nextparam], si
	lea di, [bp + ?buffer + ?buffersize - 1]
	test bh, bh
	jns .unsigned_common
	cmp byte [bp + ?pad], '0'
	jne @F
	mov word [bp + ?preprefixlength], 1
	mov byte [bp + ?prefixes], '-'
	jmp @FF

@@:
	mov word [bp + ?postprefixlength], 1
	mov byte [bp + ?prefixes + 1], '-'
@@:
	neg bx
	neg dx
	sbb bx, byte 0		; neg bx:dx
	jmp @F


.unsigned:
	push si
	push di
	mov si, word [bp + ?nextparam]
	extcallcall skipwhite
	extcallcall getexpression
	dec si
	mov word [bp + ?nextparam], si
	lea di, [bp + ?buffer + ?buffersize - 1]
.unsigned_common:
	call .setotherprefix

@@:
	std
@@:
	xchg ax, bx
	push dx
	xor dx, dx
	mov cx, 10
	div cx			; dx = remainder, ax = quotient high
	xchg bx, ax		; bx = quotient high
	pop ax			; dx:ax = remainder:low word
	div cx			; dx = remainder, bx:ax = quotient
	xchg ax, dx		; bx:dx = quotient
	add al, '0'
	stosb
	mov cx, dx
	or cx, bx
	jnz @B
	jmp .common_number


.setotherprefix:
	rol byte [bp + ?otherprefix], 1
	jnc @FF
	mov al, [bp + ?otherprefix + 1]
	cmp byte [bp + ?pad], '0'
	jne @F
	cmp word [bp + ?preprefixlength], 1
	je @FF
	inc word [bp + ?preprefixlength]
	mov byte [bp + ?prefixes], al
	jmp @FF

@@:
	cmp word [bp + ?postprefixlength], 1
	je @F
	inc word [bp + ?postprefixlength]
	mov byte [bp + ?prefixes + 1], al
@@:
	retn


.bytes:
	push si
	mov si, word [bp + ?nextparam]
	extcallcall skipwhite
	extcallcall getexpression
	dec si
	mov word [bp + ?nextparam], si
	xchg bx, ax			; ax:dx
	xchg dx, ax			; dx:ax

	mov bx, word [bp + ?width]	; bx = width
	rol byte [bp + ?pad_after], 1	; has to use padding after ?
	jc @F				; yes, use complex code -->

	mov cx, bx
	add cx, di			; -> behind buffer needed
	jc @F				; if > 64 KiB use complex code -->
	cmp cx, strict word relocateddata
linkdatarelocation line_out_end		; in buffer ?
	ja @F				; no, use complex code -->
					; yes, just write directly
	mov cx, 1			; multiplier = 1
	extcallcall disp_dxax_times_cx_width_bx_size.store
	pop si
	jmp .loop

@@:
	 push di
	lea di, [bp + ?buffer]		; es:di -> buffer (> 4 + 4 Bytes)
	xor bx, bx			; clear width
	  push di
	mov cx, 1			; multiplier = 1
	extcallcall disp_dxax_times_cx_width_bx_size.store
	  pop bx			; -> buffer start
	neg bx				; minus start
	add bx, di			; end minus start
	 pop di				; restore output pointer
	mov cx, word [bp + ?width]
	mov al, 32
	sub cx, bx			; = how much to pad
	jbe @F				; if negative or zero -->
	call .pad
@@:
	lea si, [bp + ?buffer]		; -> buffer
	mov cx, bx			; = length
	rep movsb			; write it
	pop si
	jmp .loop_pad_after		; handle padding after


.hex:
	push si
	push di
	mov si, word [bp + ?nextparam]
	extcallcall skipwhite
	extcallcall getexpression
	dec si
	mov word [bp + ?nextparam], si
	call .setotherprefix
	lea di, [bp + ?buffer + ?buffersize - 1]
	std
@@:
	mov al, dl
	extcallcall hexnyb
	mov al, byte [bp + ?type + 1]
	or byte [di + 1], al
	mov cx, 4
@@:
	shr bx, 1
	rcr dx, 1
	loop @B
	test bx, bx
	jnz @BB
	test dx, dx
	jnz @BB
.common_number:
	cld
	inc di
	lea bx, [bp + ?buffer + ?buffersize]
	sub bx, di
	mov si, di
	pop di
	add bx, word [bp + ?preprefixlength]
	add bx, word [bp + ?postprefixlength]
	mov cx, word [bp + ?preprefixlength]
	mov al, byte [bp + ?prefixes]
	rep stosb
	mov cx, word [bp + ?width]
	mov al, byte [bp + ?pad]
	sub cx, bx
	jbe @F
	call .pad
@@:
	mov cx, word [bp + ?postprefixlength]
	mov al, byte [bp + ?prefixes + 1]
	rep stosb
	mov cx, bx
	sub cx, word [bp + ?preprefixlength]
	sub cx, word [bp + ?postprefixlength]
	rep movsb
	pop si
	jmp .loop_pad_after


.dump_if_close:
	cmp di, strict word relocateddata - ?line_out_reserved
linkdatarelocation line_out_end
	jb @F
.dump:
	push ax
	push bx
	push cx
	extcallcall putsline
	mov di, relocateddata
linkdatarelocation line_out
	pop cx
	pop bx
	pop ax
@@:
	retn


.pad_after:
	mov cx, word [bp + ?pad_after_length]
	mov al, 32
	rol byte [bp + ?pad_after], 1
	jnc .pad_retn
	jmp @F

.pad:
	mov word [bp + ?pad_after_length], cx
	rol byte [bp + ?pad_after], 1
	jc .pad_retn
@@:
	push dx
	mov dx, cx
	add dx, di			; -> behind buffer needed
	jc @F				; if > 64 KiB -->
	cmp dx, strict word relocateddata - (?line_out_reserved - 1)
linkdatarelocation line_out_end		; in buffer ?
	jbe @FFF			; yes, just copy all -->
@@:
	mov dx, di			; -> behind used data
	neg dx				; minus current
	add dx, strict word relocateddata - (?line_out_reserved - 1)
linkdatarelocation line_out_end		; dx = end - current = free
	sub cx, dx			; cx = remainder
	jbe @F
	xchg cx, dx			; cx = chunk size, dx = remainder
	rep stosb
	xchg cx, dx			; cx = remainder
	call .dump			; dump as much as possible,
					;  resets di
	jmp @B

@@:
	add cx, dx			; reset cx
@@:
	rep stosb			; always fits
	pop dx
.pad_retn:
	retn


.end:
	call .dump

end:
	lleave
	retn


	eldcall_dump_callcall ELDCALL_CALLCALL_LIST

endinstalled equ ($ + CODEFIXUP + 15) & ~15


start:
	mov bx, es
	 push ss
	 pop es
	call skipcomma
	extcall iseol?
	je help
	dec si
	mov dx, relocateddata
linkdatarelocation msg.install
	call isstring?
	je install
	mov dx, msg.keyword_help
internaldatarelocation
	call isstring?
	lodsb
	je help
	call run
@@:
	call uninstall_oneshot
	xor ax, ax
	retf

help:
	call chkeol
	mov dx, msg.help
internaldatarelocation
	call putsz
	jmp @B


uninstall_oneshot:
	testopt [ss:relocateddata], 1
linkdatarelocation options7, -3
	jnz @F

	mov ax, word [cs:code + eldiEndCode]
internalcoderelocation
	sub ax, word [cs:code + eldiStartCode]
internalcoderelocation
	sub word [relocateddata], ax
linkdatarelocation extseg_used

	mov ax, word [cs:code + eldiEndData]
internalcoderelocation
	sub ax, word [cs:code + eldiStartData]
internalcoderelocation
	sub word [relocateddata], ax
linkdatarelocation extdata_used
@@:
	retn


DATAOFFSET equ CODEOFFSET + code_size
	addsection DATA, follows=CODE vstart=_ELD_DATA_VSTART
%define DATAFIXUP - datastart + 0
datastart:


%rep _ELD_BUG_RELOC_DATA
	dw msg.printf			; missing internaldatarelocation
%endrep

%if _ELD_BUG_LENGTH_DATA && _ELD_CODE_VSTART
times 64 db 0
%endif


%define LETTERSLOOKUP ""
%assign LETTER 'A'
%rep 26
 %xdefine LETTERSLOOKUP LETTERSLOOKUP, LETTER
 %assign LETTER LETTER + 1
%endrep
%assign LETTER 'a'
%rep 26
 %xdefine LETTERSLOOKUP LETTERSLOOKUP, LETTER
 %assign LETTER LETTER + 1
%endrep

	%imacro backslashletter 2.nolist
%assign %%letter %1 - 'a'
%assign LETTERBEFORE %%letter
%assign LETTERAFTER 26 - 1 - %%letter
%assign LETTER %2
%define LETTERSLOOKUPNEW ""
	letterappend LETTERSLOOKUP
%xdefine LETTERSLOOKUP LETTERSLOOKUPNEW
	%endmacro

%imacro letterappend 53.nolist
%rotate 1
%rep LETTERBEFORE
 %xdefine LETTERSLOOKUPNEW LETTERSLOOKUPNEW, %1
 %rotate 1
%endrep
%xdefine LETTERSLOOKUPNEW LETTERSLOOKUPNEW, LETTER
%rotate 1
%rep LETTERAFTER
 %xdefine LETTERSLOOKUPNEW LETTERSLOOKUPNEW, %1
 %rotate 1
%endrep
%rep LETTERBEFORE
 %xdefine LETTERSLOOKUPNEW LETTERSLOOKUPNEW, %1
 %rotate 1
%endrep
%xdefine LETTERSLOOKUPNEW LETTERSLOOKUPNEW, LETTER
%rotate 1
%rep LETTERAFTER
 %xdefine LETTERSLOOKUPNEW LETTERSLOOKUPNEW, %1
 %rotate 1
%endrep
%endmacro

backslashletter 'b', 8
backslashletter 't', 9
backslashletter 'n', 10
backslashletter 'r', 13
backslashletter 's', 32
backslashletter 'f', 12
backslashletter 'x', 0

backslashletters:
	db LETTERSLOOKUP


msg:
.printf:		asciz "PRINTF"
.start:			asciz "START"
.end:			asciz "END"
.range:			asciz "RANGE"
.asciz:			asciz "ASCIZ"
.uninstall_done:	db "PRINTF uninstalled."
%if _ELD_RECLAIM_HINT
			db " (Don't forget to use reclaim.eld)"
%endif
			asciz 13,10
.uninstall_error:	asciz "PRINTF unable to uninstall!",13,10
.no_quote:		asciz "PRINTF error: No starting quote mark!",13,10
.no_unquote:		asciz "PRINTF error: No ending quote mark!",13,10
.unknown:		asciz "PRINTF error: Unknown percent specifier!",13,10
.toolong:		asciz "PRINTF error: Too long string or width!",13,10


uinit_data: equ $

.installed:		asciz "PRINTF installed.",13,10
.keyword_help:		asciz "HELP"
.help:		db "This ELD can be installed residently or used transiently.",13,10
		db 13,10
		db "First parameter is a quoted string. This is the format string.",13,10
		db 13,10
		db "Supported escape codes:",13,10
		db 9,"\b",9,"Backslash (8)",13,10
		db 9,"\t",9,"Tab (9)",13,10
		db 9,"\n",9,"Line Feed (10). Usually wanted in a sequence \r\n",13,10
		db 9,"\r",9,"Carriage Return (13)",13,10
		db 9,"\s",9,"Blank (32)",13,10
		db 9,"\f",9,"Form Feed (12)",13,10
		db 9,"\xNN",9,"Byte value in one or two hexadecimal digits",13,10
		db 13,10
		db "Supported format codes:",13,10
		db 9,"%%",9,"Literal percent sign",13,10
		db 9,"%X, %x",9,"Hexadecimal number (Capitalised, uncapitalised)",13,10
		db 9,"%U",9,"Unsigned number",13,10
		db 9,"%D, %I",9,"Signed number",13,10
		db 9,"%C",9,"Text byte",13,10
		db 9,"%S",9,"Text string",13,10
		db 9,"%B",9,"Format as Byte/kB/KiB size",13,10
		db 13,10
		db "Text strings are provided in one of three forms:",13,10
		db 9,"START list-parameter END",13,10
		db 9,"RANGE range-parameter",13,10
		db 9,"ASCIZ range-parameter",13,10
		asciz

	align 16, db 0
init_data_end:
data_size equ $ - datastart
transient_data_size equ data_size

	absolute uinit_data

	alignb 16
uinit_data_end:
resident_data_end:
resident_data_size equ resident_data_end - datastart

%if uinit_data_end >= init_data_end
 total_data_size equ $ - datastart
%else
 total_data_size equ init_data_end - datastart
%endif
%assign _DATA_SIZE total_data_size


	usesection CODE

install:
	lodsb
	extcall chkeol

	houdini
	mov es, bx		; => ext seg (writable)

	mov ax, endresident - endinstalled
	sub word [es:code + eldiEndCode], ax
internalcoderelocation		; adjust size
	sub word [relocateddata], ax
linkdatarelocation extseg_used	; adjust size

%if (transient_data_size - resident_data_size) > 0
	mov ax, transient_data_size - resident_data_size
	sub word [es:code + eldiEndData], ax
internalcoderelocation		; adjust size
	sub word [relocateddata], ax
linkdatarelocation extdata_used	; adjust size
%endif

	mov bx, word [relocateddata]
linkdatarelocation ext_command_handler
				; -> prior
	mov di, command		; -> our handler
internalcoderelocation
	test bx, bx		; installing as first ?
	jz .only_first		; yes, simple --> (leave as extcall cmd3_not_ext)
	scasw			; skip entrypoint jmp strict short
	mov al, 0E9h		; = jmp near opcode
	stosb			; store
	xchg ax, bx		; ax -> next handler
	sub ax, di
	dec ax
	dec ax			; ax = ax - (di + 2)
	stosw			; store our downlink as rel16 displacement

.only_first:
	setopt [es:code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as resident
	mov word [relocateddata], command
linkdatarelocation ext_command_handler, -4
internalcoderelocation		; -> our entrypoint

	testopt [relocateddata], 4
linkdatarelocation options7, -3
	jnz @F
	mov dx, msg.installed
internaldatarelocation
	call putsz
@@:
	xor ax, ax
	retf


%include "eldlink.asm"

	align 16
code_size equ $ - code
