/*
 *	getline.c
 *
 *	区切り文字までの文字列入力を読み込む
 *
 *	* Mon Nov 10 23:59:55 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 *	- POSIX標準のgetline(),getdelim()互換関数を実装しました。
 *	  宣言は、include/stdio.hに有ります。
 *	- Windows用プロジェクトで利用する場合は、getline.cをプロジェクトに追加してください。
 *	  Windows用プロジェクトには、P/ECE用のinclude/stdio.hをインクルードできないので、
 *	  include/stdio.hの関数宣言を、各アプリケーションのextclip.h等にコピーしてください。
 *	* Tue Nov 11 20:41:40 JST 2014 Naoyuki Sawa
 *	- getdelim()関数において、'前回に読み込んだ文字'の初期値が、確実にdelimと異なる値になるように、修正しました。
 *	- 修正前はcの初期値をEOF(=-1)としており、万一、delimにEOFが指定された場合に、一文字も読み込まずに終了する問題が有りました。
 *	  実際にはdelimにEOFを指定することは仕様外なのですが、もしかすると'ファイル全体を読み込みたい'という意図でEOFが指定される可能性が有ります。
 *	  (ちなみにその目的ならば、EOFでも'\0'でも何でも良く、通常のテキスト中に現れない文字ならば、何を指定しても同じなのですが。)
 *	- 修正後は、cの初期値を、指定されたdelimのビット反転としました。
 *	  これで、delimにどのような値が指定されたとしても、確実に1ループ目は一致しません。
 *	  尚、コードサイズ的にも修正前と変わらず1命令です。修正前「ld.w %r3,-1」⇒修正後「not %r3,%r14」
 *	- getline(),getdelim()をアセンブラ化しました。
 *	* Sat Mar 19 21:19:21 JST 2016 Naoyuki Sawa
 *	- /clip/include/sys/types.h(又は/clip/clipstdw.h)にssize_tの定義を追加した事に伴い、これまでssize_tの代りにintを使っていた部分を正式なssize_tに修正しました。
 *	  2016/03/19時点でint⇒ssize_tに修正した箇所は、getline(),getdelim(),mq_receive()です。
 */
#include "clip.h"

/****************************************************************************
 *	getline,getdelim
 ****************************************************************************/

//「Linux Programmer's Manual - GETLINE」(http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/getline.3.html)より引用:
//□説明
//│getline()はstreamから1行全てを読み込み、テキストが含まれているバッファのアドレスを*lineptrに格納する。
//│バッファはヌル文字(\0)で終端される。改行文字が見つかった場合は、改行文字もバッファに格納される。
//│*lineptrがNULLにセットされ、*nが呼び出し前に0にセットされた場合、getline()は行の内容を格納するためのバッファを確保する。
//│このバッファは、getline()が失敗した場合であっても、ユーザーのプログラムで解放すべきである。
//│別の方法として、getline()を呼び出す際に、*lineptrにmalloc(3)で確保した大きさ*nバイトのバッファへのポインタを入れて渡すこともできる。
//│読み込んだ行を保持するのに十分なバッファがない場合、getline()はrealloc(3)を使ってバッファのサイズを変更し、必要に応じて*lineptrと*nを更新する。
//│どちらの場合でも、呼び出しに成功したときには、*lineptrと*nがバッファのアドレスと割り当てたサイズを反映した値に更新される。
//│getdelim()はgetline()と同じように動作するが、改行文字以外の区切り文字を引き数delimに指定することができる。
//│getline()と同様に、ファイル終端に達するまでに入力行に区切り文字が見付からない場合は、区切り文字をバッファに追加しない。
//□返り値
//│成功した場合、getline()とgetdelim()は読み込んだ文字数を返す。
//│文字数には区切り文字は含まれるが、終端に使うヌルバイト('\0')は含まれない。
//│この値によって、読み込んだ行に含まれるヌルバイトを操作することができる。
//│どちらの関数も、行の読み込みに失敗した場合には-1を返す(ファイルの終端に達した場合にも-1を返す)。
//│エラーが発生した場合には、errnoにエラーの原因を示す値が設定される。
//□準拠
//│getline()とgetdelim()は、どちらも元はGNUによる拡張であったが、POSIX.1-2008で標準化された。
//□例
//│#include <stdio.h>
//│#include <stdlib.h>
//│int main() {
//│  FILE* fp;
//│  char* line = NULL;
//│  size_t len = 0;
//│  ssize_t read;
//│  fp = fopen("/etc/motd", "r");
//│  if(fp == NULL) { exit(EXIT_FAILURE); }
//│  while((read = getline(&line, &len, fp)) != -1) {
//│    printf("Retrieved line of length %zu :\n", read);	←長さ修飾子'z'はglibc 2.1の拡張である。VC++6.0やP/ECEでは動作しないので注意せよ。
//│    printf("%s", line);
//│  }
//│  free(line);
//│  exit(EXIT_SUCCESS);
//│}
//[note]
// - 戻り値は'ssize_t'型，引数nは'size_t'へのポインタである事に注意せよ。
//   言い換えると、戻り値は'int'型，引数nは'unsigned'型へのポインタである。
// - VC++6.0や、P/ECEの環境では、'ssize_t'型が定義されていない。
//   'ssize_t'型は実質'int'型と同じであるので、当関数は'int'型として実装することにした。
// * Sat Mar 19 21:19:21 JST 2016 Naoyuki Sawa
// - /clip/include/sys/types.h(又は/clip/clipstdw.h)にssize_tの定義を追加した事に伴い、これまでssize_tの代りにintを使っていた部分を正式なssize_tに修正しました。
//   2016/03/19時点でint⇒ssize_tに修正した箇所は、getline(),getdelim(),mq_receive()です。
#ifndef PIECE
ssize_t getline(char** lineptr, size_t* n, FILE* stream) {
	return getdelim(lineptr, n, '\n', stream);
}
ssize_t getdelim(char** lineptr, size_t* n, int delim, FILE* stream) {
	size_t len = 0;		//読み込んだ文字数。デリミタ文字も、文字数に含める。終端の'\0'は、文字数に含めない。
//{{2014/11/11修正:getdelim()関数において、'前回に読み込んだ文字'の初期値が、確実にdelimと異なる値になるように、修正しました。
//	int c = EOF;		//前回に読み込んだ文字。1ループ目は一致しないよう、delimに指定できない値を設定する。
//↓2014/11/11修正:getdelim()関数において、'前回に読み込んだ文字'の初期値が、確実にdelimと異なる値になるように、修正しました。
// * Tue Nov 11 20:41:40 JST 2014 Naoyuki Sawa
// - getdelim()関数において、'前回に読み込んだ文字'の初期値が、確実にdelimと異なる値になるように、修正しました。
// - 修正前はcの初期値をEOF(=-1)としており、万一、delimにEOFが指定された場合に、一文字も読み込まずに終了する問題が有りました。
//   実際にはdelimにEOFを指定することは仕様外なのですが、もしかすると'ファイル全体を読み込みたい'という意図でEOFが指定される可能性が有ります。
//   (ちなみにその目的ならば、EOFでも'\0'でも何でも良く、通常のテキスト中に現れない文字ならば、何を指定しても同じなのですが。)
// - 修正後は、cの初期値を、指定されたdelimのビット反転としました。
//   これで、delimにどのような値が指定されたとしても、確実に1ループ目は一致しません。
//   尚、コードサイズ的にも修正前と変わらず1命令です。修正前「ld.w %r3,-1」⇒修正後「not %r3,%r14」
	int c = ~delim;		//前回に読み込んだ文字。1ループ目は一致しないよう、delimと異なる値を設定する。
//}}2014/11/11修正:getdelim()関数において、'前回に読み込んだ文字'の初期値が、確実にdelimと異なる値になるように、修正しました。
	/* lineptr,及び,nの指定は必須である。NULLポインタを指定してはいけない。 */
	if(!lineptr || !n) { DIE(); }
	/* バッファアドレスにNULLが指定された場合、バッファサイズに指定された値は無意味であると見なして、0にする。 */
	if(!*lineptr) { *n = 0; }
//誤り	/* バッファサイズに指定された値が0である場合、バッファアドレスに指定されたアドレスは無意味であると見なして、NULLにする。 */	//┬左記の処理は誤りである。おこなってはいけない。呼び出し側アプリケーションの意図は、「サイズ0のメモリブロックをrealloc()して欲しい」という事であるからだ。
//誤り	if(!*n) { *lineptr = NULL; }													//┘もし左記の処理をおこなうと、指定されたメモリブロックとは別のメモリブロックを新たに確保してしまい、指定されたのメモリブロックが開放漏れする問題が生じる。
	/* 実装を少し工夫して、バッファを拡張する処理を一箇所にまとめました。
	 * - 素直に実装すると、読み込んだ文字を格納する所と、終端の'\0'を格納する所の、二箇所にバッファを拡張する処理が必要になります。
	 *   以下の実装では、バッファを拡張する処理をループの先頭に持ってくることにより、バッファを拡張する処理を一箇所で済ませました。
	 *   今後もしアセンブラ化する場合に、コードサイズの節約が行い易くなると思います。 */
	for(;;) {
		/* バッファサイズに、次に格納する文字のスペースが無ければ、バッファを拡張する。 */
		if(*n <= len) {
#ifdef  WIN32
			*n += 8/*調整可*/;	//┐速度よりも省メモリを優先して、バッファサイズの拡張単位を、最小限とすることにした。
#else //WIN32					//│Win32のメモリアロケータは8バイトアライメント,P/ECEのメモリアロケータは4バイトアライメントである。
			*n += 4/*調整可*/;	//┘厳密には'1'でも動作するのだが、最低限の速度を考慮して、各OSのアロケータのアライメント単位で拡張することにした。
#endif//WIN32
			*lineptr = realloc(*lineptr, *n);
			if(!*lineptr) { DIE(); }
		}
		/* *前回*に読み込んだ文字が、デリミタ文字だったならば、抜ける。 */
		if(c == delim) { break; }
		/* 1文字読み込み、ファイル終端ならば、抜ける。 */
		if((c = fgetc(stream)) == EOF) { break; }
		/* 読み込んだ文字をバッファに格納し、読み込んだ文字数を増やす。 */			//デリミタ文字もバッファに格納し、読み込んだ文字数に含めることに注意せよ。
		(*lineptr)[len++] = c;
	}
	/* 終端の'\0'を格納する。'\0'を格納するスペースは、ループを抜ける前に確保済みである。 */	//'\0'は読み込んだ文字数に含めず、lenを増やさないことに注意せよ。
	(*lineptr)[len] = '\0';
	/* 読み込んだ文字数を返す。1文字も読み込めずにファイル終端に達したならば、EOFを返す。 */
#ifdef  __GNUC__
	return len ?     : EOF;
#else //__GNUC__
	return len ? len : EOF;
#endif//__GNUC__
}
#else //PIECE
//アセンブラ化した目的は、コードサイズの低減です。
//コードサイズが、(C言語版=148バイト)⇒(Asm版=132バイト)に小さくなりました。
//処理速度は、ファイルIOやメモリ確保の処理に要する時間が大きいので、元々期待できません。
ssize_t getline(char** lineptr, size_t* n, FILE* stream);
ssize_t getdelim(char** lineptr, size_t* n, int delim, FILE* stream);
asm("
		.code
		.align		1
		.global		getline
		.global		getdelim
getline:
		ld.w		%r15, %r14		;//%r15 := stream
		ld.w		%r14, 10		;//%r14 := '￥ｎ'
getdelim:
		pushn		%r3
		xsub		%sp, %sp, 16
		cmp		%r12, 0			;//if(!lineptr) { DIE() }
		jreq		getdelim_DIE
		cmp		%r13, 0			;//if(!n) { DIE() }
		jreq		getdelim_DIE
		ld.w		%r0, [%r12]		;//%r0      := lineptrValue = *lineptr
		ld.w		%r1, [%r13]		;//%r1      := nValue = *n
		ld.w		%r2, 0			;//%r2      := len = 0
		not		%r3, %r14		;//%r3      := c = ~delim
		xld.w		[%sp+0], %r12		;//[%sp+0]  := lineptr
		xld.w		[%sp+4], %r13		;//[%sp+4]  := n
		xld.w		[%sp+8], %r14		;//[%sp+8]  := delim
		xld.w		[%sp+12], %r15		;//[%sp+12] := stream
		;//%r0      := lineptrValue
		;//%r1      := nValue
		;//%r2      := len
		;//%r3      := c
		;//[%sp+0]  := lineptr	//固定値
		;//[%sp+4]  := n	//固定値
		;//[%sp+8]  := delim	//固定値
		;//[%sp+12] := stream	//固定値
		cmp		%r0, 0			;//if(!lineptrValue) { nValue = 0 }
		jrne		2
		 ld.w		%r1, %r0
getdelim_LOOP:						;//for(;;) {
		cmp		%r1, %r2		;//  if(nValue <= len) {
		jrugt		getdelim_L10
		add		%r1, 4			;//    %r1  := nValue += 4
		ld.w		%r12, %r0		;//    %r12 :=                        lineptrValue
		xcall.d		realloc			;//    %r10 := lineptrValue = realloc(lineptrValue, nValue)
		ld.w		%r13, %r1		;//    %r13 :=                                      nValue	*delay*
		cmp		%r10, 0			;//    if(!lineptrValue) { DIE() }
		jreq		getdelim_DIE
		ld.w		%r0, %r10		;//    %r0  := lineptrValue
getdelim_L10:						;//  }
		xld.w		%r9, [%sp+8]		;//  %r9  := delim
		cmp		%r3, %r9		;//  if(c == delim) { goto BREAK }
		jreq		getdelim_BREAK
		xld.w		%r12, [%sp+12]		;//  %r12 := stream
		xcall		fgetc			;//  %r10 := c = fgetc(stream)
		cmp		%r10, -1		;//  if(c == EOF) { goto BREAK }
		jreq		getdelim_BREAK
		add		%r2, %r0		;//  %r2  := &lineptrValue[len]
		ld.b		[%r2]+, %r10		;//           lineptrValue[len++] = c
		sub		%r2, %r0		;//  %r2  :=               len
		jp.d		getdelim_LOOP		;//}
		ld.w		%r3, %r10		;//  %r3  := c							*delay*
getdelim_BREAK:
		xld.w		%r9, [%sp+0]		;//%r9  := lineptr
		ld.w		[%r9], %r0		;//       *lineptr = lineptrValue
		xld.w		%r9, [%sp+4]		;//%r9  := n
		ld.w		[%r9], %r1		;//       *n = nValue
		add		%r0, %r2		;//%r0  := &lineptrValue[len]
		ld.b		[%r0], %r8		;//         lineptrValue[len] = '￥０'
		ld.w		%r10, %r2		;//%r10 := len
		xadd		%sp, %sp, 16
		popn		%r3
		cmp		%r10, 1			;//%psr(C) := len ?   0 :  1	//┐
		ret.d					;//				//├%r10が(0)の場合のみ(-1)に変更するテクニックを使いました。
		sbc		%r10, %r8		;//%r10    := len ? len : -1	//┘				*delay*
");
static void __attribute__((noreturn,unused))/*asmブロックから参照*/ getdelim_DIE() { DIE(); }
#endif//PIECE
