;; -----------------------------------------------------------------------
;;
;;   Copyright 1994-2008 H. Peter Anvin - All Rights Reserved
;;
;;   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, Inc., 53 Temple Place Ste 330,
;;   Boston MA 02111-1307, USA; either version 2 of the License, or
;;   (at your option) any later version; incorporated herein by reference.
;;
;; -----------------------------------------------------------------------

;;
;; getc.inc
;;
;; Simple file handling library (open, getc, ungetc)
;;
;; WARNING: This interface uses the real_mode_seg/comboot_seg.
;;

MAX_GETC_LG2	equ 4			; Max number of file nesting
MAX_GETC	equ (1 << MAX_GETC_LG2)
bytes_per_getc_lg2	equ 16-MAX_GETC_LG2
bytes_per_getc		equ (1 << bytes_per_getc_lg2)
secs_per_getc	equ bytes_per_getc/SECTOR_SIZE
MAX_UNGET	equ 5			; Max bytes that can be pushed back

		struc getc_file
gc_file		resw 1			; File pointer
gc_bufbytes	resw 1			; Bytes left in buffer
gc_bytes	resd 1			; Bytes left in file
gc_bufdata	resw 1			; Pointer to data in buffer
gc_unget_cnt	resb 1			; Character pushed back count
gc_unget_buf	resb MAX_UNGET		; Character pushed back buffer
		endstruc
getc_file_lg2	equ 4			; Size of getc_file as a power of 2

%ifndef DEPEND
%if (getc_file_size != (1 << getc_file_lg2))
%error "getc_file_size != (1 << getc_file_lg2)"
%endif
%endif

;
; open,getc:	Load a file a character at a time for parsing in a manner
;		similar to the C library getc routine.
;		Up to MAX_GETC files can be open at the same time,
;		they are accessed in a stack-like fashion.
;
;		All routines assume CS == DS.
;
;		open:	Input:	mangled filename in DS:DI
;			Output: ZF set on file not found or zero length
;
;		openfd:	Input:	file handle in SI
;			Output:	ZF set on getc stack overflow
;
;		getc:	Output: CF set on end of file
;				Character loaded in AL
;
;		close:	Output:	CF set if nothing open
;
open:
		call searchdir
		jz openfd.ret
openfd:
		push bx

		mov bx,[CurrentGetC]
		sub bx,getc_file_size
		cmp bx,GetCStack
		jb .stack_full		; Excessive nesting
		mov [CurrentGetC],bx

		mov [bx+gc_file],si	; File pointer
		mov [bx+gc_bytes],ax	; Bytes available
		mov [bx+gc_bytes+2],dx
		xor ax,ax
		mov [bx+gc_bufbytes],ax		; Buffer empty
		mov [bx+gc_unget_cnt],al	; ungetc buffer empty

		inc ax			; ZF <- 0
		pop bx
.ret:		ret

.stack_full:
		call close_file
		xor ax,ax		; ZF <- 1
		pop bx
		ret

getc:
		push bx
		push si
		push di
		push es

		mov di,[CurrentGetC]
		movzx bx,byte [di+gc_unget_cnt]
		and bx,bx
		jnz .have_unget

		mov si,real_mode_seg	; Borrow the real_mode_seg
		mov es,si

.got_data:
		sub word [di+gc_bufbytes],1
		jc .get_data		; Was it zero already?
		mov si,[di+gc_bufdata]
		mov al,[es:si]
		inc si
		mov [di+gc_bufdata],si
.done:
		clc
.ret:
		pop es
		pop di
		pop si
		pop bx
		ret
.have_unget:
		dec bx
		mov al,[di+bx+gc_unget_buf]
		mov [di+gc_unget_cnt],bl
		jmp .done

.get_data:
		pushad
		; Compute start of buffer
		mov bx,di
		sub bx,GetCStack
		shl bx,bytes_per_getc_lg2-getc_file_lg2

		mov [di+gc_bufdata],bx
		mov si,[di+gc_file]
		mov ecx,[di+gc_bytes]
		jecxz .empty
		cmp ecx,bytes_per_getc
		jna .sizeok
		mov ecx,bytes_per_getc
.sizeok:
		mov [di+gc_bufbytes],cx
		sub [di+gc_bytes],ecx
		add cx,SECTOR_SIZE-1
		shr cx,SECTOR_SHIFT
		call getfssec
		mov [di+gc_file],si
		popad
		jmp .got_data

.empty:
		; CX == 0 at this point; gc_bufbytes was clobbered
		; by the subtract; we need to restore it to zero so
		; we will continue to get EOF on any further attempts
		; to read the file.
		mov [di+gc_bufbytes],cx
		popad
		xor al,al		; Return a predictable zero
		stc
		jmp .ret

close:
		push bx
		push si
		mov bx,[CurrentGetC]
		mov si,[bx+gc_file]
		call close_file
		add bx,getc_file_size
		mov [CurrentGetC],bx
		pop si
		pop bx
		ret

;
; ungetc:	Push a character (in AL) back into the getc buffer
;		Note: if more than MAX_UNGET bytes are pushed back, all
;		hell will break loose.
;
ungetc:
		push di
		push bx
		mov di,[CurrentGetC]
		movzx bx,[di+gc_unget_cnt]
		mov [bx+di+gc_unget_buf],al
		inc bx
		mov [di+gc_unget_cnt],bl
		pop bx
		pop di
		ret

;
; skipspace:	Skip leading whitespace using "getc".  If we hit end-of-line
;		or end-of-file, return with carry set; ZF = true of EOF
;		ZF = false for EOLN; otherwise CF = ZF = 0.
;
;		Otherwise AL = first character after whitespace
;
skipspace:
.loop:		call getc
		jc .eof
		cmp al,1Ah			; DOS EOF
		je .eof
		cmp al,0Ah
		je .eoln
		cmp al,' '
		jbe .loop
		ret				; CF = ZF = 0
.eof:		cmp al,al			; Set ZF
		stc				; Set CF
		ret
.eoln:		add al,0FFh			; Set CF, clear ZF
		ret

;
; getint:	Load an integer from the getc file.
;		Return CF if error; otherwise return integer in EBX
;
getint:
		mov di,NumBuf
.getnum:	cmp di,NumBufEnd	; Last byte in NumBuf
		jae .loaded
		push di
		call getc
		pop di
		jc .loaded
		stosb
		cmp al,'-'
		jnb .getnum
		call ungetc		; Unget non-numeric
.loaded:	mov byte [di],0
		mov si,NumBuf
		; Fall through to parseint

;
; parseint:	Convert an integer to a number in EBX
;		Get characters from string in DS:SI
;		Return CF on error
;		DS:SI points to first character after number
;
;               Syntaxes accepted: [-]dec, [-]0+oct, [-]0x+hex, val+[KMG]
;
parseint:
                push eax
                push ecx
		push bp
		xor eax,eax		; Current digit (keep eax == al)
		mov ebx,eax		; Accumulator
		mov ecx,ebx		; Base
                xor bp,bp               ; Used for negative flag
.begin:		lodsb
		cmp al,'-'
		jne .not_minus
		xor bp,1		; Set unary minus flag
		jmp short .begin
.not_minus:
		cmp al,'0'
		jb .err
		je .octhex
		cmp al,'9'
		ja .err
		mov cl,10		; Base = decimal
		jmp short .foundbase
.octhex:
		lodsb
		cmp al,'0'
		jb .km		; Value is zero
		or al,20h		; Downcase
		cmp al,'x'
		je .ishex
		cmp al,'7'
		ja .err
		mov cl,8		; Base = octal
		jmp short .foundbase
.ishex:
		mov al,'0'		; No numeric value accrued yet
		mov cl,16		; Base = hex
.foundbase:
                call unhexchar
                jc .km                ; Not a (hex) digit
                cmp al,cl
		jae .km			; Invalid for base
		imul ebx,ecx		; Multiply accumulated by base
                add ebx,eax             ; Add current digit
		lodsb
		jmp short .foundbase
.km:
		dec si			; Back up to last non-numeric
		lodsb
		or al,20h
		cmp al,'k'
		je .isk
		cmp al,'m'
		je .ism
		cmp al,'g'
		je .isg
		dec si			; Back up
.fini:		and bp,bp
		jz .ret		; CF=0!
		neg ebx			; Value was negative
.done:		clc
.ret:		pop bp
                pop ecx
                pop eax
		ret
.err:		stc
		jmp short .ret
.isg:		shl ebx,10		; * 2^30
.ism:		shl ebx,10		; * 2^20
.isk:		shl ebx,10		; * 2^10
		jmp .fini

		section .bss1
		alignb 4
NumBuf		resb 15			; Buffer to load number
NumBufEnd	resb 1			; Last byte in NumBuf

GetCStack	resb getc_file_size*MAX_GETC
.end		equ $

		section .data
CurrentGetC	dw GetCStack.end	; GetCStack empty

;
; unhexchar:    Convert a hexadecimal digit in AL to the equivalent number;
;               return CF=1 if not a hex digit
;
		section .text
unhexchar:
                cmp al,'0'
		jb .ret			; If failure, CF == 1 already
                cmp al,'9'
                ja .notdigit
		sub al,'0'		; CF <- 0
		ret
.notdigit:	or al,20h		; upper case -> lower case
		cmp al,'a'
                jb .ret			; If failure, CF == 1 already
                cmp al,'f'
                ja .err
                sub al,'a'-10           ; CF <- 0
                ret
.err:		stc
.ret:		ret

;
;
; getline:	Get a command line, converting control characters to spaces
;               and collapsing streches to one; a space is appended to the
;               end of the string, unless the line is empty.
;		The line is terminated by ^J, ^Z or EOF and is written
;		to ES:DI.  On return, DI points to first char after string.
;		CF is set if we hit EOF.
;
getline:
		call skipspace
                mov dl,1                ; Empty line -> empty string.
                jz .eof               ; eof
                jc .eoln              ; eoln
		call ungetc
.fillloop:	push dx
		push di
		call getc
		pop di
		pop dx
		jc .ret		; CF set!
		cmp al,' '
		jna .ctrl
		xor dx,dx
.store:		stosb
		jmp short .fillloop
.ctrl:		cmp al,10
		je .ret		; CF clear!
		cmp al,26
		je .eof
		and dl,dl
		jnz .fillloop		; Ignore multiple spaces
		mov al,' '		; Ctrl -> space
		inc dx
		jmp short .store
.eoln:		clc                     ; End of line is not end of file
                jmp short .ret
.eof:		stc
.ret:		pushf			; We want the last char to be space!
		and dl,dl
		jnz .xret
		mov al,' '
		stosb
.xret:		popf
		ret
