/*
 * $Id$
 *
    Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "std.h"
#include <qse/awk/stdawk.h>
#include <qse/cmn/str.h>
#include <qse/cmn/mbwc.h>
#include <qse/cmn/time.h>
#include <qse/cmn/path.h>
#include <qse/cmn/htb.h>
#include <qse/cmn/env.h>
#include <qse/si/sio.h>
#include <qse/si/pio.h>
#include <qse/si/nwio.h>
#include <qse/si/fs.h>
#include "../cmn/mem-prv.h"

#include <stdarg.h>
#include <stdlib.h>
#include <math.h>
#if defined(HAVE_QUADMATH_H)
#	include <quadmath.h>
#endif

#if defined(_WIN32)
#	include <windows.h>
#	include <tchar.h>
#	if defined(QSE_HAVE_CONFIG_H) && defined(QSE_ENABLE_LIBLTDL)
#		include <ltdl.h>
#		define USE_LTDL
#	endif
#elif defined(__OS2__)
#	define INCL_DOSMODULEMGR
#	define INCL_DOSPROCESS
#	define INCL_DOSERRORS
#    include <os2.h>
#elif defined(__DOS__)
	/* nothing to include */
#else
#	include <unistd.h>
#	if defined(QSE_ENABLE_LIBLTDL)
#		include <ltdl.h>
#		define USE_LTDL
#	elif defined(HAVE_DLFCN_H)
#		include <dlfcn.h>
#		define USE_DLFCN
#	else
#		error UNSUPPORTED DYNAMIC LINKER
#	endif
#endif

#if !defined(QSE_HAVE_CONFIG_H)
#	if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
#		define HAVE_POW
#		define HAVE_FMOD
#	endif
#endif

typedef struct xtn_t
{
	struct
	{
		struct 
		{
			qse_awk_parsestd_t* x;
			qse_size_t          xindex;

			union
			{
				/* nothing to maintain here for file */

				struct 
				{
					const qse_char_t* ptr;
					const qse_char_t* end;
				} str;
				struct
				{
					const qse_mchar_t* ptr;
					const qse_mchar_t* end;
				} mbs;
				struct
				{
					const qse_wchar_t* ptr;
					const qse_wchar_t* end;
				} wcs;
			} u;
		} in;

		struct
		{
			qse_awk_parsestd_t* x;
			union
			{
				struct
				{
					qse_sio_t* sio;
				} file;
				struct 
				{
					qse_str_t* buf;
				} str;
				struct 
				{
					qse_mbs_t* buf;
				} mbs;
				struct 
				{
					qse_wcs_t* buf;
				} wcs;
			} u;
		} out;
	} s; /* script/source handling */

	int gbl_argc;
	int gbl_argv;
	int gbl_environ;

	qse_awk_ecb_t ecb;
	int stdmod_up;
} xtn_t;

typedef struct rxtn_t
{
	struct
	{
		struct {
			const qse_char_t*const* files;
			qse_size_t index;
			qse_size_t count;
		} in; 

		struct 
		{
			const qse_char_t*const* files;
			qse_size_t index;
			qse_size_t count;
		} out;

		qse_cmgr_t* cmgr;
	} c;  /* console */

	int cmgrtab_inited;
	qse_htb_t cmgrtab;

	qse_awk_rtx_ecb_t ecb;
} rxtn_t;

typedef struct ioattr_t
{
	qse_cmgr_t* cmgr;
	qse_char_t cmgr_name[64]; /* i assume that the cmgr name never exceeds this length */
	qse_ntime_t tmout[4];
} ioattr_t;

#if defined(QSE_HAVE_INLINE)
static QSE_INLINE xtn_t* GET_XTN(qse_awk_t* awk) { return (xtn_t*)((qse_uint8_t*)qse_awk_getxtn(awk) - QSE_SIZEOF(xtn_t)); }
static QSE_INLINE rxtn_t* GET_RXTN(qse_awk_rtx_t* rtx) { return (rxtn_t*)((qse_uint8_t*)qse_awk_rtx_getxtn(rtx) - QSE_SIZEOF(rxtn_t)); }
#else
#define GET_XTN(awk) ((xtn_t*)((qse_uint8_t*)qse_awk_getxtn(awk) - QSE_SIZEOF(xtn_t)))
#define GET_RXTN(rtx) ((rxtn_t*)((qse_uint8_t*)qse_awk_rtx_getxtn(rtx) - QSE_SIZEOF(rxtn_t)))
#endif


static ioattr_t* get_ioattr (qse_htb_t* tab, const qse_char_t* ptr, qse_size_t len);

qse_awk_flt_t qse_awk_stdmathpow (qse_awk_t* awk, qse_awk_flt_t x, qse_awk_flt_t y)
{
#if defined(QSE_USE_AWK_FLTMAX) && defined(HAVE_POWQ)
	return powq (x, y);
#elif defined(HAVE_POWL) && (QSE_SIZEOF_LONG_DOUBLE > QSE_SIZEOF_DOUBLE)
	return powl (x, y);
#elif defined(HAVE_POW)
	return pow (x, y);
#elif defined(HAVE_POWF)
	return powf (x, y);
#else
	#error ### no pow function available ###
#endif
}

qse_awk_flt_t qse_awk_stdmathmod (qse_awk_t* awk, qse_awk_flt_t x, qse_awk_flt_t y)
{
#if defined(QSE_USE_AWK_FLTMAX) && defined(HAVE_FMODQ)
	return fmodq (x, y);
#elif defined(HAVE_FMODL) && (QSE_SIZEOF_LONG_DOUBLE > QSE_SIZEOF_DOUBLE)
	return fmodl (x, y);
#elif defined(HAVE_FMOD)
	return fmod (x, y);
#elif defined(HAVE_FMODF)
	return fmodf (x, y);
#else
	#error ### no fmod function available ###
#endif
}

/* [IMPORTANT]
 * qse_awk_stdmodXXXX() functions must not access the extension
 * area of 'awk'. they are used in StdAwk.cpp which instantiates
 * an awk object with qse_awk_open() instead of qse_awk_openstd(). */

int qse_awk_stdmodstartup (qse_awk_t* awk)
{
#if defined(USE_LTDL)

	/* lt_dlinit() can be called more than once and 
	 * lt_dlexit() shuts down libltdl if it's called as many times as
	 * corresponding lt_dlinit(). so it's safe to call lt_dlinit()
	 * and lt_dlexit() at the library level. */
	return lt_dlinit() != 0? -1: 0;

#else
	return 0;
#endif

}

void qse_awk_stdmodshutdown (qse_awk_t* awk)
{
#if defined(USE_LTDL)
	lt_dlexit ();
#else
	/* do nothing */
#endif
}

static void* std_mod_open_checked (qse_awk_t* awk, const qse_awk_mod_spec_t* spec)
{
	xtn_t* xtn = GET_XTN(awk);

	if (!xtn->stdmod_up)
	{
		/* qse_awk_stdmodstartup() must have failed upon start-up.
		 * return failure immediately */
		qse_awk_seterrnum (awk, QSE_AWK_ENOIMPL, QSE_NULL);
		return QSE_NULL;
	}

	return qse_awk_stdmodopen (awk, spec);
}

void* qse_awk_stdmodopen (qse_awk_t* awk, const qse_awk_mod_spec_t* spec)
{
#if defined(USE_LTDL)
	void* h;
	lt_dladvise adv;
	qse_mchar_t* modpath;
	const qse_char_t* tmp[4];
	int count;

	count = 0;
	if (spec->prefix) tmp[count++] = spec->prefix;
	tmp[count++] = spec->name;
	if (spec->postfix) tmp[count++] = spec->postfix;
	tmp[count] = QSE_NULL;

	#if defined(QSE_CHAR_IS_MCHAR)
	modpath = qse_mbsadup (tmp, QSE_NULL, qse_awk_getmmgr(awk));
	#else
	modpath = qse_wcsatombsdupwithcmgr(tmp, QSE_NULL, qse_awk_getmmgr(awk), qse_awk_getcmgr(awk));
	#endif
	if (!modpath)
	{
		qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
		return QSE_NULL;
	}

	if (lt_dladvise_init (&adv) != 0)
	{
		/* the only failure of lt_dladvise_init() seems to be caused
		 * by memory allocation failured */
		qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
		return QSE_NULL;
	}

	lt_dladvise_ext (&adv);
	/*lt_dladvise_resident (&adv); useful for debugging with valgrind */

	h = lt_dlopenadvise (modpath, adv);

	lt_dladvise_destroy (&adv);

	QSE_MMGR_FREE (qse_awk_getmmgr(awk), modpath);

	return h;

#elif defined(_WIN32)

	HMODULE h;
	qse_char_t* modpath;
	const qse_char_t* tmp[4];
	int count;

	count = 0;
	if (spec->prefix) tmp[count++] = spec->prefix;
	tmp[count++] = spec->name;
	if (spec->postfix) tmp[count++] = spec->postfix;
	tmp[count] = QSE_NULL;

	modpath = qse_stradup (tmp, QSE_NULL, qse_awk_getmmgr(awk));
	if (!modpath)
	{
		qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
		return QSE_NULL;
	}

	h = LoadLibrary (modpath);

	QSE_MMGR_FREE (qse_awk_getmmgr(awk), modpath);
	
	QSE_ASSERT (QSE_SIZEOF(h) <= QSE_SIZEOF(void*));
	return h;

#elif defined(__OS2__)

	HMODULE h;
	qse_mchar_t* modpath;
	const qse_char_t* tmp[4];
	int count;
	char errbuf[CCHMAXPATH];
	APIRET rc;

	count = 0;
	if (spec->prefix) tmp[count++] = spec->prefix;
	tmp[count++] = spec->name;
	if (spec->postfix) tmp[count++] = spec->postfix;
	tmp[count] = QSE_NULL;

	#if defined(QSE_CHAR_IS_MCHAR)
	modpath = qse_mbsadup (tmp, QSE_NULL, qse_awk_getmmgr(awk));
	#else
	modpath = qse_wcsatombsdupwithcmgr(tmp, QSE_NULL, qse_awk_getmmgr(awk), qse_awk_getcmgr(awk));
	#endif
	if (!modpath)
	{
		qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
		return QSE_NULL;
	}

	/* DosLoadModule() seems to have severe limitation on 
	 * the file name it can load (max-8-letters.xxx) */
	rc = DosLoadModule (errbuf, QSE_COUNTOF(errbuf) - 1, modpath, &h);
	if (rc != NO_ERROR) h = QSE_NULL;

	QSE_MMGR_FREE (qse_awk_getmmgr(awk), modpath);

	QSE_ASSERT (QSE_SIZEOF(h) <= QSE_SIZEOF(void*));
	return h;

#elif defined(__DOS__) && defined(QSE_ENABLE_DOS_DYNAMIC_MODULE)

	/* the DOS code here is not generic enough. it's for a specific
	 * dos-extender only. the best is not to use dynamic loading
	 * when building for DOS. */
	void* h;
	qse_mchar_t* modpath;
	const qse_char_t* tmp[4];
	int count;

	count = 0;
	if (spec->prefix) tmp[count++] = spec->prefix;
	tmp[count++] = spec->name;
	if (spec->postfix) tmp[count++] = spec->postfix;
	tmp[count] = QSE_NULL;

	#if defined(QSE_CHAR_IS_MCHAR)
	modpath = qse_mbsadup(tmp, QSE_NULL, qse_awk_getmmgr(awk));
	#else
	modpath = qse_wcsatombsdupwithcmgr(tmp, QSE_NULL, qse_awk_getmmgr(awk), qse_awk_getcmgr(awk));
	#endif
	if (!modpath)
	{
		qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
		return QSE_NULL;
	}

	h = LoadModule (modpath);

	QSE_MMGR_FREE (qse_awk_getmmgr(awk), modpath);
	
	QSE_ASSERT (QSE_SIZEOF(h) <= QSE_SIZEOF(void*));
	return h;

#elif defined(USE_DLFCN)

	void* h;
	qse_mchar_t* modpath;
	const qse_char_t* tmp[4];
	int count;

	count = 0;
	if (spec->prefix) tmp[count++] = spec->prefix;
	tmp[count++] = spec->name;
	if (spec->postfix) tmp[count++] = spec->postfix;
	tmp[count] = QSE_NULL;

	#if defined(QSE_CHAR_IS_MCHAR)
	modpath = qse_mbsadup(tmp, QSE_NULL, qse_awk_getmmgr(awk));
	#else
	modpath = qse_wcsatombsdupwithcmgr(tmp, QSE_NULL, qse_awk_getmmgr(awk), qse_awk_getcmgr(awk));
	#endif
	if (!modpath)
	{
		qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
		return QSE_NULL;
	}

	h = dlopen(modpath, RTLD_NOW);
	if (!h)
	{
		qse_awk_seterrfmt (awk, QSE_AWK_ESYSERR, QSE_NULL, QSE_T("%hs"), dlerror());
	}

	QSE_MMGR_FREE (qse_awk_getmmgr(awk), modpath);

	return h;

#else
	qse_awk_seterrnum (awk, QSE_AWK_ENOIMPL, QSE_NULL);
	return QSE_NULL;
#endif
}

void qse_awk_stdmodclose (qse_awk_t* awk, void* handle)
{
#if defined(USE_LTDL)
	lt_dlclose (handle);
#elif defined(_WIN32)
	FreeLibrary ((HMODULE)handle);
#elif defined(__OS2__)
	DosFreeModule ((HMODULE)handle);
#elif defined(__DOS__) && defined(QSE_ENABLE_DOS_DYNAMIC_MODULE)
	FreeModule (handle);
#elif defined(USE_DLFCN)
	dlclose (handle);
#else
	/* nothing to do */
#endif
}

void* qse_awk_stdmodsym (qse_awk_t* awk, void* handle, const qse_char_t* name)
{
	void* s;
	qse_mchar_t* mname;

#if defined(QSE_CHAR_IS_MCHAR)
	mname = (qse_mchar_t*)name;
#else
	mname = qse_wcstombsdupwithcmgr(name, QSE_NULL, qse_awk_getmmgr(awk), qse_awk_getcmgr(awk));
	if (!mname)
	{
		qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
		return QSE_NULL;
	}
#endif

#if defined(USE_LTDL)
	s = lt_dlsym (handle, mname);

#elif defined(_WIN32)
	s = GetProcAddress ((HMODULE)handle, mname);

#elif defined(__OS2__)
	if (DosQueryProcAddr ((HMODULE)handle, 0, mname, (PFN*)&s) != NO_ERROR) s = QSE_NULL;

#elif defined(__DOS__) && defined(QSE_ENABLE_DOS_DYNAMIC_MODULE)
	s = GetProcAddress (handle, mname);

#elif defined(USE_DLFCN)
	s = dlsym (handle, mname);

#else
	s = QSE_NULL;
#endif

#if defined(QSE_CHAR_IS_MCHAR)
	/* nothing to do */
#else
	QSE_MMGR_FREE (qse_awk_getmmgr(awk), mname);
#endif

	return s;
}

static int add_globals (qse_awk_t* awk);
static int add_functions (qse_awk_t* awk);

qse_awk_t* qse_awk_openstd (qse_size_t xtnsize, qse_awk_errnum_t* errnum)
{
	return qse_awk_openstdwithmmgr (QSE_MMGR_GETDFL(), xtnsize, errnum);
}

static void fini_xtn (qse_awk_t* awk)
{
	xtn_t* xtn = GET_XTN(awk);
	if (xtn->stdmod_up)
	{
		qse_awk_stdmodshutdown (awk);
		xtn->stdmod_up = 0;
	}
}

static void clear_xtn (qse_awk_t* awk)
{
	/* nothing to do */
}

qse_awk_t* qse_awk_openstdwithmmgr (qse_mmgr_t* mmgr, qse_size_t xtnsize, qse_awk_errnum_t* errnum)
{
	qse_awk_t* awk;
	qse_awk_prm_t prm;
	xtn_t* xtn;

	prm.math.pow = qse_awk_stdmathpow;
	prm.math.mod = qse_awk_stdmathmod;

	prm.modopen = std_mod_open_checked;
	prm.modclose = qse_awk_stdmodclose;
	prm.modsym = qse_awk_stdmodsym;

	/* create an object */
	awk = qse_awk_open(mmgr, QSE_SIZEOF(xtn_t) + xtnsize, &prm, errnum);
	if (!awk) return QSE_NULL;

	/* adjust the object size by the sizeof xtn_t so that qse_getxtn() returns the right pointer. */
	awk->_instsize += QSE_SIZEOF(xtn_t);

#if defined(USE_DLFCN)
	if (qse_awk_setopt(awk, QSE_AWK_MODPOSTFIX, QSE_T(".so")) <= -1) 
	{
		if (errnum) *errnum = qse_awk_geterrnum(awk);
		goto oops;
	}
#endif

	/* initialize extension */
	xtn = GET_XTN(awk);
	/* the extension area has been cleared in qse_awk_open().
	 * QSE_MEMSET (xtn, 0, QSE_SIZEOF(*xtn));*/

	/* add intrinsic global variables and functions */
	if (add_globals(awk) <= -1 || add_functions(awk) <= -1) 
	{
		if (errnum) *errnum = qse_awk_geterrnum(awk);
		goto oops;
	}

	if (qse_awk_stdmodstartup(awk) <= -1) 
	{
		xtn->stdmod_up = 0;
		/* carry on regardless of failure */
	}
	else
	{
		xtn->stdmod_up = 1;
	}

	xtn->ecb.close = fini_xtn;
	xtn->ecb.clear = clear_xtn;
	qse_awk_pushecb (awk, &xtn->ecb);

	return awk;

oops:
	if (awk) qse_awk_close (awk);
	return QSE_NULL;
}

static qse_sio_t* open_sio (qse_awk_t* awk, const qse_char_t* file, int flags)
{
	qse_sio_t* sio;
	sio = qse_sio_open (qse_awk_getmmgr(awk), 0, file, flags);
	if (sio == QSE_NULL)
	{
		qse_cstr_t errarg;
		errarg.ptr = (qse_char_t*)file;
		errarg.len = qse_strlen(file);
		qse_awk_seterrnum (awk, QSE_AWK_EOPEN, &errarg);
	}
	return sio;
}

static qse_sio_t* open_sio_rtx (qse_awk_rtx_t* rtx, const qse_char_t* file, int flags)
{
	qse_sio_t* sio;
	sio = qse_sio_open (qse_awk_rtx_getmmgr(rtx), 0, file, flags);
	if (sio == QSE_NULL)
	{
		qse_cstr_t errarg;
		errarg.ptr = (qse_char_t*)file;
		errarg.len = qse_strlen(file);
		qse_awk_rtx_seterrnum (rtx, QSE_AWK_EOPEN, &errarg);
	}
	return sio;
}

static qse_cstr_t sio_std_names[] =
{
	{ QSE_T("stdin"),   5 },
	{ QSE_T("stdout"),  6 },
	{ QSE_T("stderr"),  6 }
};

static qse_sio_t* open_sio_std (qse_awk_t* awk, qse_sio_std_t std, int flags)
{
	qse_sio_t* sio;
	sio = qse_sio_openstd (qse_awk_getmmgr(awk), 0, std, flags);
	if (sio == QSE_NULL) qse_awk_seterrnum (awk, QSE_AWK_EOPEN, &sio_std_names[std]);
	return sio;
}

static qse_sio_t* open_sio_std_rtx (qse_awk_rtx_t* rtx, qse_sio_std_t std, int flags)
{
	qse_sio_t* sio;

	sio = qse_sio_openstd (qse_awk_rtx_getmmgr(rtx), 0, std, flags);
	if (sio == QSE_NULL) qse_awk_rtx_seterrnum (rtx, QSE_AWK_EOPEN, &sio_std_names[std]);
	return sio;
}

/*** PARSESTD ***/

static int open_parsestd (qse_awk_t* awk, qse_awk_sio_arg_t* arg, xtn_t* xtn, qse_size_t index)
{
	qse_awk_parsestd_t* psin = &xtn->s.in.x[index];

	switch (psin->type)
	{
		/* normal source files */

		case QSE_AWK_PARSESTD_FILE:
			if (psin->u.file.path == QSE_NULL ||
			    (psin->u.file.path[0] == QSE_T('-') &&
			     psin->u.file.path[1] == QSE_T('\0')))
			{
				/* no path name or - -> stdin */
				qse_sio_t* tmp;

				tmp = open_sio_std (awk, QSE_SIO_STDIN, QSE_SIO_READ | QSE_SIO_IGNOREMBWCERR);
				if (tmp == QSE_NULL) return -1;

				if (index >= 1 && xtn->s.in.x[index-1].type == QSE_AWK_PARSESTD_FILE)
					qse_sio_close (arg->handle);

				arg->handle = tmp;
			}
			else
			{
				qse_sio_t* tmp;

				tmp = open_sio (awk, psin->u.file.path, QSE_SIO_READ | QSE_SIO_IGNOREMBWCERR | QSE_SIO_KEEPPATH);
				if (tmp == QSE_NULL) return -1;

				if (index >= 1 && xtn->s.in.x[index-1].type == QSE_AWK_PARSESTD_FILE)
					qse_sio_close (arg->handle);
				arg->handle = tmp;
			}
			if (psin->u.file.cmgr) qse_sio_setcmgr (arg->handle, psin->u.file.cmgr);
			return 0;

		case QSE_AWK_PARSESTD_STR:
			if (index >= 1 && xtn->s.in.x[index-1].type == QSE_AWK_PARSESTD_FILE)
				qse_sio_close (arg->handle);

			xtn->s.in.u.str.ptr = psin->u.str.ptr;
			xtn->s.in.u.str.end = psin->u.str.ptr + psin->u.str.len;
			return 0;

		case QSE_AWK_PARSESTD_MBS:
			if (index >= 1 && xtn->s.in.x[index-1].type == QSE_AWK_PARSESTD_FILE)
				qse_sio_close (arg->handle);

			xtn->s.in.u.mbs.ptr = psin->u.mbs.ptr;
			xtn->s.in.u.mbs.end = psin->u.mbs.ptr + psin->u.mbs.len;
			return 0;

		case QSE_AWK_PARSESTD_WCS:
			if (index >= 1 && xtn->s.in.x[index-1].type == QSE_AWK_PARSESTD_FILE)
				qse_sio_close (arg->handle);

			xtn->s.in.u.wcs.ptr = psin->u.wcs.ptr;
			xtn->s.in.u.wcs.end = psin->u.wcs.ptr + psin->u.wcs.len;
			return 0;

		default:
			qse_awk_seterrnum (awk, QSE_AWK_EINTERN, QSE_NULL);
			return -1;
	}
}

static qse_ssize_t sf_in_open (qse_awk_t* awk, qse_awk_sio_arg_t* arg, xtn_t* xtn)
{
	if (arg->prev == QSE_NULL)
	{
		/* handle normal source input streams specified 
		 * to qse_awk_parsestd() */

		qse_ssize_t x;

		QSE_ASSERT (arg == &awk->sio.arg);

		x = open_parsestd (awk, arg, xtn, 0);
		if (x >= 0) 
		{
			xtn->s.in.xindex = 0; /* update the current stream index */
			/* perform some manipulation about the top-level input information */
			if (xtn->s.in.x[0].type == QSE_AWK_PARSESTD_FILE)
			{
				arg->name = xtn->s.in.x[0].u.file.path;
				if (arg->name == QSE_NULL) arg->name = sio_std_names[QSE_SIO_STDIN].ptr;
			}
			else arg->name = QSE_NULL;
		}

		return x;
	}
	else
	{
		/* handle the included source file - @include */
		const qse_char_t* path;
		qse_char_t fbuf[64];
		qse_char_t* dbuf = QSE_NULL;
		qse_fattr_t fattr;

		QSE_ASSERT (arg->name != QSE_NULL);

		path = arg->name;
		if (arg->prev->handle)
		{
			const qse_char_t* outer;

			outer = qse_sio_getpath(arg->prev->handle);
			if (outer)
			{
				const qse_char_t* base;

				base = qse_basename(outer);
				if (base != outer && arg->name[0] != QSE_T('/'))
				{
					qse_size_t tmplen, totlen, dirlen;
			
					dirlen = base - outer;
					totlen = qse_strlen(arg->name) + dirlen;
					if (totlen >= QSE_COUNTOF(fbuf))
					{
						dbuf = qse_awk_allocmem(awk, QSE_SIZEOF(qse_char_t) * (totlen + 1));
						if (!dbuf) return -1;
						path = dbuf;
					}
					else path = fbuf;

					tmplen = qse_strncpy ((qse_char_t*)path, outer, dirlen);
					qse_strcpy ((qse_char_t*)path + tmplen, arg->name);
				}
			}
		}

		arg->handle = qse_sio_open (
			qse_awk_getmmgr(awk), 0, path, QSE_SIO_READ | QSE_SIO_IGNOREMBWCERR | QSE_SIO_KEEPPATH
		);

		if (dbuf) qse_awk_freemem (awk, dbuf);
		if (!arg->handle)
		{
			qse_cstr_t ea;
			ea.ptr = (qse_char_t*)arg->name;
			ea.len = qse_strlen(ea.ptr);
			qse_awk_seterrnum (awk, QSE_AWK_EOPEN, &ea);
			return -1;
		}

		/* TODO: use the system handle(file descriptor) instead of the path? */
		/*syshnd = qse_sio_gethnd(arg->handle);*/
		if (qse_get_file_attr_with_mmgr(path, 0, &fattr, qse_awk_getmmgr(awk)) >= 0)
		{
			struct
			{
				qse_uintptr_t ino;
				qse_uintptr_t dev;
			} tmp;
			tmp.ino = fattr.ino;
			tmp.dev = fattr.dev;
			QSE_MEMCPY (&arg->unique_id[0], &tmp, (QSE_SIZEOF(tmp) > QSE_SIZEOF(arg->unique_id)? QSE_SIZEOF(arg->unique_id): QSE_SIZEOF(fattr.ino)));
		}

		return 0;
	}
}

static qse_ssize_t sf_in_close (qse_awk_t* awk, qse_awk_sio_arg_t* arg, xtn_t* xtn)
{
	if (arg->prev == QSE_NULL)
	{
		switch (xtn->s.in.x[xtn->s.in.xindex].type)
		{
			case QSE_AWK_PARSESTD_FILE:
				QSE_ASSERT (arg->handle != QSE_NULL);
				qse_sio_close (arg->handle);
				break;

			case QSE_AWK_PARSESTD_STR:
			case QSE_AWK_PARSESTD_MBS:
			case QSE_AWK_PARSESTD_WCS:
				/* nothing to close */
				break;

			default:
				/* nothing to close */
				break;
		}
	}
	else
	{
		/* handle the included source file - @include */
		QSE_ASSERT (arg->handle != QSE_NULL);
		qse_sio_close (arg->handle);
	}

	return 0;
}

static qse_ssize_t sf_in_read (qse_awk_t* awk, qse_awk_sio_arg_t* arg, qse_char_t* data, qse_size_t size, xtn_t* xtn)
{
	if (arg->prev == QSE_NULL)
	{
		qse_ssize_t n;

		QSE_ASSERT (arg == &awk->sio.arg);

	again:
		switch (xtn->s.in.x[xtn->s.in.xindex].type)
		{
			case QSE_AWK_PARSESTD_FILE:
				QSE_ASSERT (arg->handle != QSE_NULL);
				n = qse_sio_getstrn (arg->handle, data, size);
				if (n <= -1)
				{
					qse_cstr_t ea;
					ea.ptr = (qse_char_t*)xtn->s.in.x[xtn->s.in.xindex].u.file.path;
					if (ea.ptr == QSE_NULL) ea.ptr = sio_std_names[QSE_SIO_STDIN].ptr;
					ea.len = qse_strlen(ea.ptr);
					qse_awk_seterrnum (awk, QSE_AWK_EREAD, &ea);
				}
				break;

			case QSE_AWK_PARSESTD_STR:
			parsestd_str:
				n = 0;
				while (n < size && xtn->s.in.u.str.ptr < xtn->s.in.u.str.end)
				{
					data[n++] = *xtn->s.in.u.str.ptr++;
				}
				break;

			case QSE_AWK_PARSESTD_MBS:
			#if defined(QSE_CHAR_IS_MCHAR)
				goto parsestd_str;
			#else
			{
				int m;
				qse_size_t mbslen, wcslen;

				mbslen = xtn->s.in.u.mbs.end - xtn->s.in.u.mbs.ptr;
				wcslen = size;
				if ((m = qse_mbsntowcsnwithcmgr(xtn->s.in.u.mbs.ptr, &mbslen, data, &wcslen, qse_awk_getcmgr(awk))) <= -1 && m != -2)
				{
					qse_awk_seterrnum (awk, QSE_AWK_EINVAL, QSE_NULL);
					n = -1;
				}
				else
				{
					xtn->s.in.u.mbs.ptr += mbslen;
					n = wcslen;
				}
				break;
			}
			#endif

			case QSE_AWK_PARSESTD_WCS:
			#if defined(QSE_CHAR_IS_MCHAR)
			{
				int m;
				qse_size_t mbslen, wcslen;

				wcslen = xtn->s.in.u.wcs.end - xtn->s.in.u.wcs.ptr;
				mbslen = size;
				if ((m = qse_wcsntombsnwithcmgr(xtn->s.in.u.wcs.ptr, &wcslen, data, &mbslen, qse_awk_getcmgr(awk))) <= -1 && m != -2)
				{
					qse_awk_seterrnum (awk, QSE_AWK_EINVAL, QSE_NULL);
					n = -1;
				}
				else
				{
					xtn->s.in.u.wcs.ptr += wcslen;
					n = mbslen;
				}
				break;
			}
			#else
				goto parsestd_str;
			#endif

			default:
				/* this should never happen */
				qse_awk_seterrnum (awk, QSE_AWK_EINTERN, QSE_NULL);
				n = -1;
				break;
		}

		if (n == 0)
		{
		 	/* reached end of the current stream. */
			qse_size_t next = xtn->s.in.xindex + 1;
			if (xtn->s.in.x[next].type != QSE_AWK_PARSESTD_NULL)
			{
				/* open the next stream if available. */
				if (open_parsestd (awk, arg, xtn, next) <= -1) n = -1;
				else 
				{
					xtn->s.in.xindex = next; /* update the next to the current */

					/* update the I/O object name */
					if (xtn->s.in.x[next].type == QSE_AWK_PARSESTD_FILE)
					{
						arg->name = xtn->s.in.x[next].u.file.path;
						if (arg->name == QSE_NULL) arg->name = sio_std_names[QSE_SIO_STDIN].ptr;
					}
					else
						arg->name = QSE_NULL;

					arg->line = 0; /* reset the line number */
					arg->colm = 0;
					goto again;
				}
			}
		}

		return n;
	}
	else
	{
		/* handle the included source file - @include */
		qse_ssize_t n;

		QSE_ASSERT (arg->name != QSE_NULL);
		QSE_ASSERT (arg->handle != QSE_NULL);

		n = qse_sio_getstrn (arg->handle, data, size);
		if (n <= -1)
		{
			qse_cstr_t ea;
			ea.ptr = (qse_char_t*)arg->name;
			ea.len = qse_strlen(ea.ptr);
			qse_awk_seterrnum (awk, QSE_AWK_EREAD, &ea);
		}
		return n;
	}
}

static qse_ssize_t sf_in (qse_awk_t* awk, qse_awk_sio_cmd_t cmd, qse_awk_sio_arg_t* arg, qse_char_t* data, qse_size_t size)
{
	xtn_t* xtn = GET_XTN(awk);

	switch (cmd)
	{
		case QSE_AWK_SIO_OPEN:
			return sf_in_open(awk, arg, xtn);

		case QSE_AWK_SIO_CLOSE:
			return sf_in_close(awk, arg, xtn);

		case QSE_AWK_SIO_READ:
			return sf_in_read(awk, arg, data, size, xtn);

		default:
			qse_awk_seterrnum (awk, QSE_AWK_EINTERN, QSE_NULL);
			return -1;
	}
}

static qse_ssize_t sf_out (qse_awk_t* awk, qse_awk_sio_cmd_t cmd, qse_awk_sio_arg_t* arg, qse_char_t* data, qse_size_t size)
{
	xtn_t* xtn = GET_XTN(awk);

	switch (cmd)
	{
		case QSE_AWK_SIO_OPEN:
		{
			switch (xtn->s.out.x->type)
			{
				case QSE_AWK_PARSESTD_FILE:
					if (xtn->s.out.x->u.file.path == QSE_NULL ||
					    (xtn->s.out.x->u.file.path[0] == QSE_T('-') &&
					     xtn->s.out.x->u.file.path[1] == QSE_T('\0')))
					{
						/* no path name or - -> stdout */
						xtn->s.out.u.file.sio = open_sio_std (
							awk, QSE_SIO_STDOUT, 
							QSE_SIO_WRITE | QSE_SIO_IGNOREMBWCERR | QSE_SIO_LINEBREAK
						);
						if (xtn->s.out.u.file.sio == QSE_NULL) return -1;
					}
					else
					{
						xtn->s.out.u.file.sio = open_sio (
							awk, xtn->s.out.x->u.file.path, 
							QSE_SIO_WRITE | QSE_SIO_CREATE | 
							QSE_SIO_TRUNCATE | QSE_SIO_IGNOREMBWCERR
						);
						if (xtn->s.out.u.file.sio == QSE_NULL) return -1;
					}

					if (xtn->s.out.x->u.file.cmgr)
						qse_sio_setcmgr (xtn->s.out.u.file.sio, xtn->s.out.x->u.file.cmgr);
					return 1;

				case QSE_AWK_PARSESTD_STR:
					xtn->s.out.u.str.buf = qse_str_open(qse_awk_getmmgr(awk), 0, 512);
					if (xtn->s.out.u.str.buf == QSE_NULL)
					{
						qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
						return -1;
					}
					return 1;

				case QSE_AWK_PARSESTD_MBS:
					xtn->s.out.u.mbs.buf = qse_mbs_open(qse_awk_getmmgr(awk), 0, 512);
					if (xtn->s.out.u.mbs.buf == QSE_NULL)
					{
						qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
						return -1;
					}
					return 1;

				case QSE_AWK_PARSESTD_WCS:
					xtn->s.out.u.wcs.buf = qse_wcs_open(qse_awk_getmmgr(awk), 0, 512);
					if (xtn->s.out.u.wcs.buf == QSE_NULL)
					{
						qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
						return -1;
					}
					return 1;
			}

			break;
		}


		case QSE_AWK_SIO_CLOSE:
		{
			switch (xtn->s.out.x->type)
			{
				case QSE_AWK_PARSESTD_FILE:
					qse_sio_close (xtn->s.out.u.file.sio);
					return 0;

				case QSE_AWK_PARSESTD_STR:
				case QSE_AWK_PARSESTD_MBS:
				case QSE_AWK_PARSESTD_WCS:
					/* i don't close xtn->s.out.u.str.buf intentionally here.
					 * it will be closed at the end of qse_awk_parsestd() */
					return 0;
			}

			break;
		}

		case QSE_AWK_SIO_WRITE:
		{
			switch (xtn->s.out.x->type)
			{
				case QSE_AWK_PARSESTD_FILE:
				{
					qse_ssize_t n;
					QSE_ASSERT (xtn->s.out.u.file.sio != QSE_NULL);
					n = qse_sio_putstrn (xtn->s.out.u.file.sio, data, size);
					if (n <= -1)
					{
						qse_cstr_t ea;
						ea.ptr = (qse_char_t*)xtn->s.out.x->u.file.path;
						if (ea.ptr == QSE_NULL) ea.ptr = sio_std_names[QSE_SIO_STDOUT].ptr;
						ea.len = qse_strlen(ea.ptr);
						qse_awk_seterrnum (awk, QSE_AWK_EWRITE, &ea);
					}
					return n;
				}

				case QSE_AWK_PARSESTD_STR:
				parsestd_str:
					if (size > QSE_TYPE_MAX(qse_ssize_t)) size = QSE_TYPE_MAX(qse_ssize_t);
					if (qse_str_ncat(xtn->s.out.u.str.buf, data, size) == (qse_size_t)-1)
					{
						qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
						return -1;
					}
					return size;

				case QSE_AWK_PARSESTD_MBS:
				#if defined(QSE_CHAR_IS_MCHAR)
					goto parsestd_str;
				#else
				{
					qse_size_t mbslen, wcslen;
					qse_size_t orglen;

					wcslen = size;
					if (qse_wcsntombsnwithcmgr(data, &wcslen, QSE_NULL, &mbslen, qse_awk_getcmgr(awk)) <= -1)
					{
						qse_awk_seterrnum (awk, QSE_AWK_EINVAL, QSE_NULL);
						return -1;
					}

					if (mbslen > QSE_TYPE_MAX(qse_ssize_t)) mbslen = QSE_TYPE_MAX(qse_ssize_t);

					orglen = qse_mbs_getlen(xtn->s.out.u.mbs.buf);
					if (qse_mbs_setlen(xtn->s.out.u.mbs.buf, orglen + mbslen) == (qse_size_t)-1)
					{
						qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
						return -1;
					}

					wcslen = size;
					qse_wcsntombsnwithcmgr(data, &wcslen, QSE_MBS_CPTR(xtn->s.out.u.mbs.buf, orglen), &mbslen, qse_awk_getcmgr(awk));
					size = wcslen;

					return size;
				}
				#endif

				case QSE_AWK_PARSESTD_WCS:
				#if defined(QSE_CHAR_IS_MCHAR)
				{
					qse_size_t mbslen, wcslen;
					qse_size_t orglen;

					mbslen = size;
					if (qse_mbsntowcsnwithcmgr(data, &mbslen, QSE_NULL, &wcslen, qse_awk_getcmgr(awk)) <= -1)
					{
						qse_awk_seterrnum (awk, QSE_AWK_EINVAL, QSE_NULL);
						return -1;
					}

					if (wcslen > QSE_TYPE_MAX(qse_ssize_t)) wcslen = QSE_TYPE_MAX(qse_ssize_t);

					orglen = qse_mbs_getlen(xtn->s.out.u.wcs.buf);
					if (qse_wcs_setlen(xtn->s.out.u.wcs.buf, orglen + wcslen) == (qse_size_t)-1)
					{
						qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
						return -1;
					}

					mbslen = size;
					qse_mbsntowcsnwithcmgr(data, &mbslen, QSE_MBS_CPTR(xtn->s.out.u.wcs.buf, orglen), &wcslen, qse_awk_getcmgr(awk));
					size = mbslen;

					return size;
				}
				#else
					goto parsestd_str;
				#endif
			}

			break;
		}

		default:
			/* other code must not trigger this function */
			break;
	}

	qse_awk_seterrnum (awk, QSE_AWK_EINTERN, QSE_NULL);
	return -1;
}

int qse_awk_parsestd (qse_awk_t* awk, qse_awk_parsestd_t in[], qse_awk_parsestd_t* out)
{
	qse_awk_sio_t sio;
	xtn_t* xtn = GET_XTN(awk);
	int n;

	if (in == QSE_NULL || (in[0].type != QSE_AWK_PARSESTD_FILE && 
	                       in[0].type != QSE_AWK_PARSESTD_STR &&
	                       in[0].type != QSE_AWK_PARSESTD_MBS &&
	                       in[0].type != QSE_AWK_PARSESTD_WCS))
	{
		/* the input is a must. at least 1 file or 1 string 
		 * must be specified */
		qse_awk_seterrnum (awk, QSE_AWK_EINVAL, QSE_NULL);
		return -1;
	}

	sio.in = sf_in;
	xtn->s.in.x = in;

	if (out == QSE_NULL) sio.out = QSE_NULL;
	else
	{
		if (out->type != QSE_AWK_PARSESTD_FILE &&
		    out->type != QSE_AWK_PARSESTD_STR &&
		    out->type != QSE_AWK_PARSESTD_MBS &&
		    out->type != QSE_AWK_PARSESTD_WCS)
		{
			qse_awk_seterrnum (awk, QSE_AWK_EINVAL, QSE_NULL);
			return -1;
		}
		sio.out = sf_out;
		xtn->s.out.x = out;
	}

	n = qse_awk_parse(awk, &sio);

	if (out)
	{
		switch (out->type)
		{
			case QSE_AWK_PARSESTD_STR:
				if (n >= 0)
				{
					QSE_ASSERT (xtn->s.out.u.str.buf != QSE_NULL);
					qse_str_yield (xtn->s.out.u.str.buf, &out->u.str, 0);
				}
				if (xtn->s.out.u.str.buf) qse_str_close (xtn->s.out.u.str.buf);
				break;

			case QSE_AWK_PARSESTD_MBS:
				if (n >= 0)
				{
					QSE_ASSERT (xtn->s.out.u.mbs.buf != QSE_NULL);
					qse_mbs_yield (xtn->s.out.u.mbs.buf, &out->u.mbs, 0);
				}
				if (xtn->s.out.u.mbs.buf) qse_mbs_close (xtn->s.out.u.mbs.buf);
				break;

			case QSE_AWK_PARSESTD_WCS:
				if (n >= 0)
				{
					QSE_ASSERT (xtn->s.out.u.wcs.buf != QSE_NULL);
					qse_wcs_yield (xtn->s.out.u.wcs.buf, &out->u.wcs, 0);
				}
				if (xtn->s.out.u.wcs.buf) qse_wcs_close (xtn->s.out.u.wcs.buf);
				break;
		}
	}

	return n;
}

/*** RTX_OPENSTD ***/

static qse_ssize_t nwio_handler_open (qse_awk_rtx_t* rtx, qse_awk_rio_arg_t* riod, int flags, qse_nwad_t* nwad, qse_nwio_tmout_t* tmout)
{
	qse_nwio_t* handle;

	handle = qse_nwio_open (
		qse_awk_rtx_getmmgr(rtx), 0, nwad, 
		flags | QSE_NWIO_TEXT | QSE_NWIO_IGNOREMBWCERR |
		QSE_NWIO_REUSEADDR | QSE_NWIO_READNORETRY | QSE_NWIO_WRITENORETRY,
		tmout
	);
	if (handle == QSE_NULL) return -1;

#if defined(QSE_CHAR_IS_WCHAR)
	{
		qse_cmgr_t* cmgr = qse_awk_rtx_getiocmgrstd(rtx, riod->name);
		if (cmgr) qse_nwio_setcmgr (handle, cmgr);
	}
#endif

	riod->handle = (void*)handle;
	riod->uflags = 1; /* nwio indicator */
	return 1;
}

static qse_ssize_t nwio_handler_rest (qse_awk_rtx_t* rtx, qse_awk_rio_cmd_t cmd, qse_awk_rio_arg_t* riod, void* data, qse_size_t size)
{
	switch (cmd)
	{
		case QSE_AWK_RIO_OPEN:
			qse_awk_rtx_seterrnum (rtx, QSE_AWK_EINTERN, QSE_NULL);
			return -1;

		case QSE_AWK_RIO_CLOSE:
			qse_nwio_close ((qse_nwio_t*)riod->handle);
			return 0;

		case QSE_AWK_RIO_READ:
			return qse_nwio_read((qse_nwio_t*)riod->handle, data, size);

		case QSE_AWK_RIO_WRITE:
			return qse_nwio_write((qse_nwio_t*)riod->handle, data, size);

		case QSE_AWK_RIO_WRITE_BYTES:
			return qse_nwio_writebytes((qse_nwio_t*)riod->handle, data, size);

		case QSE_AWK_RIO_FLUSH:
			/*if (riod->mode == QSE_AWK_RIO_PIPE_READ) return -1;*/
			return qse_nwio_flush((qse_nwio_t*)riod->handle);

		case QSE_AWK_RIO_NEXT:
			break;
	}

	qse_awk_rtx_seterrnum (rtx, QSE_AWK_EINTERN, QSE_NULL);
	return -1;
}

static int parse_rwpipe_uri (const qse_char_t* uri, int* flags, qse_nwad_t* nwad)
{
	static struct
	{
		const qse_char_t* prefix;
		qse_size_t        len;
		int               flags;
	} x[] =
	{
		{ QSE_T("tcp://"),  6, QSE_NWIO_TCP },
		{ QSE_T("udp://"),  6, QSE_NWIO_UDP },
		{ QSE_T("tcpd://"), 7, QSE_NWIO_TCP | QSE_NWIO_PASSIVE },
		{ QSE_T("udpd://"), 7, QSE_NWIO_UDP | QSE_NWIO_PASSIVE }
	};
	int i;


	for (i = 0; i < QSE_COUNTOF(x); i++)
	{
		if (qse_strzcmp (uri, x[i].prefix, x[i].len) == 0)
		{
			if (qse_strtonwad (uri + x[i].len, nwad) <= -1) return -1;
			*flags = x[i].flags;
			return 0;
		}
	}

	return -1;
}

static qse_ssize_t pio_handler_open (qse_awk_rtx_t* rtx, qse_awk_rio_arg_t* riod)
{
	qse_pio_t* handle;
	int flags;

	if (riod->mode == QSE_AWK_RIO_PIPE_READ)
	{
		/* TODO: should ERRTOOUT be unset? */
		flags = QSE_PIO_READOUT | QSE_PIO_ERRTOOUT;
	}
	else if (riod->mode == QSE_AWK_RIO_PIPE_WRITE)
	{
		flags = QSE_PIO_WRITEIN;
	}
	else if (riod->mode == QSE_AWK_RIO_PIPE_RW)
	{
		flags = QSE_PIO_READOUT | QSE_PIO_ERRTOOUT | QSE_PIO_WRITEIN;
	}
	else 
	{
		/* this must not happen */
		qse_awk_rtx_seterrnum (rtx, QSE_AWK_EINTERN, QSE_NULL);
		return -1;
	}

	handle = qse_pio_open (
		qse_awk_rtx_getmmgr(rtx),
		0, 
		riod->name, 
		QSE_NULL,
		flags | QSE_PIO_SHELL | QSE_PIO_TEXT | QSE_PIO_IGNOREMBWCERR
	);
	if (handle == QSE_NULL) return -1;

#if defined(QSE_CHAR_IS_WCHAR)
	{
		qse_cmgr_t* cmgr = qse_awk_rtx_getiocmgrstd(rtx, riod->name);
		if (cmgr)	
		{
			qse_pio_setcmgr (handle, QSE_PIO_IN, cmgr);
			qse_pio_setcmgr (handle, QSE_PIO_OUT, cmgr);
			qse_pio_setcmgr (handle, QSE_PIO_ERR, cmgr);
		}
	}
#endif

	riod->handle = (void*)handle;
	riod->uflags = 0; /* pio indicator */
	return 1;
}

static qse_ssize_t pio_handler_rest (qse_awk_rtx_t* rtx, qse_awk_rio_cmd_t cmd, qse_awk_rio_arg_t* riod, void* data, qse_size_t size)
{
	switch (cmd)
	{
		case QSE_AWK_RIO_OPEN:
			qse_awk_rtx_seterrnum (rtx, QSE_AWK_EINTERN, QSE_NULL);
			return -1;

		case QSE_AWK_RIO_CLOSE:
		{
			qse_pio_t* pio = (qse_pio_t*)riod->handle;
			if (riod->mode == QSE_AWK_RIO_PIPE_RW)
			{
				/* specialy treatment is needed for rwpipe.
				 * inspect rwcmode to see if partial closing is
				 * requested. */
				if (riod->rwcmode == QSE_AWK_RIO_CLOSE_READ)
				{
					qse_pio_end (pio, QSE_PIO_IN);
					return 0;
				}
				if (riod->rwcmode == QSE_AWK_RIO_CLOSE_WRITE)
				{
					qse_pio_end (pio, QSE_PIO_OUT);
					return 0;
				}
			}

			qse_pio_close (pio);
			return 0;
		}

		case QSE_AWK_RIO_READ:
			return qse_pio_read ((qse_pio_t*)riod->handle, QSE_PIO_OUT, data, size);

		case QSE_AWK_RIO_WRITE:
			return qse_pio_write((qse_pio_t*)riod->handle, QSE_PIO_IN, data, size);

		case QSE_AWK_RIO_WRITE_BYTES:
			return qse_pio_writebytes((qse_pio_t*)riod->handle, QSE_PIO_IN, data, size);

		case QSE_AWK_RIO_FLUSH:
			/*if (riod->mode == QSE_AWK_RIO_PIPE_READ) return -1;*/
			return qse_pio_flush ((qse_pio_t*)riod->handle, QSE_PIO_IN);

		case QSE_AWK_RIO_NEXT:
			break;
	}

	qse_awk_rtx_seterrnum (rtx, QSE_AWK_EINTERN, QSE_NULL);
	return -1;
}

static qse_ssize_t awk_rio_pipe (qse_awk_rtx_t* rtx, qse_awk_rio_cmd_t cmd, qse_awk_rio_arg_t* riod, void* data, qse_size_t size)
{
	if (cmd == QSE_AWK_RIO_OPEN)
	{
		int flags;
		qse_nwad_t nwad;

		if (riod->mode != QSE_AWK_RIO_PIPE_RW ||
		    parse_rwpipe_uri (riod->name, &flags, &nwad) <= -1) 
		{
			return pio_handler_open (rtx, riod);
		}
		else
		{
			qse_nwio_tmout_t tmout_buf;
			qse_nwio_tmout_t* tmout = QSE_NULL;
			ioattr_t* ioattr;
			rxtn_t* rxtn;

			rxtn = GET_RXTN(rtx);

			ioattr = get_ioattr(&rxtn->cmgrtab, riod->name, qse_strlen(riod->name));
			if (ioattr)
			{
				tmout = &tmout_buf;
				tmout->r = ioattr->tmout[0];
				tmout->w = ioattr->tmout[1];
				tmout->c = ioattr->tmout[2];
				tmout->a = ioattr->tmout[3];
			}

			return nwio_handler_open(rtx, riod, flags, &nwad, tmout);
		}
	}
	else if (riod->uflags > 0)
		return nwio_handler_rest(rtx, cmd, riod, data, size);
	else
		return pio_handler_rest(rtx, cmd, riod, data, size);
}

static qse_ssize_t awk_rio_file (qse_awk_rtx_t* rtx, qse_awk_rio_cmd_t cmd, qse_awk_rio_arg_t* riod, void* data, qse_size_t size)
{
	switch (cmd)
	{
		case QSE_AWK_RIO_OPEN:
		{
			qse_sio_t* handle;
			int flags = QSE_SIO_IGNOREMBWCERR;

			switch (riod->mode)
			{
				case QSE_AWK_RIO_FILE_READ: 
					flags |= QSE_SIO_READ;
					break;
				case QSE_AWK_RIO_FILE_WRITE:
					flags |= QSE_SIO_WRITE | QSE_SIO_CREATE | QSE_SIO_TRUNCATE;
					break;
				case QSE_AWK_RIO_FILE_APPEND:
					flags |= QSE_SIO_APPEND | QSE_SIO_CREATE;
					break;
				default:
					/* this must not happen */
					qse_awk_rtx_seterrnum (rtx, QSE_AWK_EINTERN, QSE_NULL);
					return -1; 
			}

			handle = qse_sio_open (qse_awk_rtx_getmmgr(rtx), 0, riod->name, flags);
			if (handle == QSE_NULL) 
			{
				qse_cstr_t errarg;
				errarg.ptr = riod->name;
				errarg.len = qse_strlen(riod->name);
				qse_awk_rtx_seterrnum (rtx, QSE_AWK_EOPEN, &errarg);
				return -1;
			}

		#if defined(QSE_CHAR_IS_WCHAR)
			{
				qse_cmgr_t* cmgr = qse_awk_rtx_getiocmgrstd(rtx, riod->name);
				if (cmgr) qse_sio_setcmgr(handle, cmgr);
			}
		#endif

			riod->handle = (void*)handle;
			return 1;
		}

		case QSE_AWK_RIO_CLOSE:
			qse_sio_close ((qse_sio_t*)riod->handle);
			riod->handle = QSE_NULL;
			return 0;

		case QSE_AWK_RIO_READ:
			return qse_sio_getstrn((qse_sio_t*)riod->handle, data, size);

		case QSE_AWK_RIO_WRITE:
			return qse_sio_putstrn((qse_sio_t*)riod->handle, data, size);

		case QSE_AWK_RIO_WRITE_BYTES:
			return qse_sio_putmbsn((qse_sio_t*)riod->handle, data, size);

		case QSE_AWK_RIO_FLUSH:
			return qse_sio_flush((qse_sio_t*)riod->handle);

		case QSE_AWK_RIO_NEXT:
			return -1;
	}

	return -1;
}

static int open_rio_console (qse_awk_rtx_t* rtx, qse_awk_rio_arg_t* riod)
{
	rxtn_t* rxtn = GET_RXTN(rtx);
	qse_sio_t* sio;

	if (riod->mode == QSE_AWK_RIO_CONSOLE_READ)
	{
		xtn_t* xtn = (xtn_t*)GET_XTN(rtx->awk);

		if (rxtn->c.in.files == QSE_NULL)
		{
			/* if no input files is specified, 
			 * open the standard input */
			QSE_ASSERT (rxtn->c.in.index == 0);

			if (rxtn->c.in.count == 0)
			{
				sio = open_sio_std_rtx (
					rtx, QSE_SIO_STDIN,
					QSE_SIO_READ | QSE_SIO_IGNOREMBWCERR
				);
				if (sio == QSE_NULL) return -1;

				if (rxtn->c.cmgr) qse_sio_setcmgr (sio, rxtn->c.cmgr);	

				riod->handle = sio;
				rxtn->c.in.count++;
				return 1;
			}

			return 0;
		}
		else
		{
			/* a temporary variable sio is used here not to change 
			 * any fields of riod when the open operation fails */
			const qse_char_t* file;
			qse_awk_val_t* argv;
			qse_htb_t* map;
			qse_htb_pair_t* pair;
			qse_char_t ibuf[128];
			qse_size_t ibuflen;
			qse_awk_val_t* v;
			qse_cstr_t as;

		nextfile:
			file = rxtn->c.in.files[rxtn->c.in.index];

			if (file == QSE_NULL)
			{
				/* no more input file */

				if (rxtn->c.in.count == 0)
				{
					/* all ARGVs are empty strings. 
					 * so no console files were opened.
					 * open the standard input here.
					 *
					 * 'BEGIN { ARGV[1]=""; ARGV[2]=""; }
					 *        { print $0; }' file1 file2
					 */
					sio = open_sio_std_rtx (
						rtx, QSE_SIO_STDIN,
						QSE_SIO_READ | QSE_SIO_IGNOREMBWCERR
					);
					if (sio == QSE_NULL) return -1;

					if (rxtn->c.cmgr) qse_sio_setcmgr (sio, rxtn->c.cmgr);	

					riod->handle = sio;
					rxtn->c.in.count++;
					return 1;
				}

				return 0;
			}

			/* handle special case when ARGV[x] has been altered.
			 * so from here down, the file name gotten from 
			 * rxtn->c.in.files is not important and is overridden 
			 * from ARGV.
			 * 'BEGIN { ARGV[1]="file3"; } 
			 *        { print $0; }' file1 file2
			 */
			argv = qse_awk_rtx_getgbl(rtx, xtn->gbl_argv);
			QSE_ASSERT (argv != QSE_NULL);
			if (QSE_AWK_RTX_GETVALTYPE(rtx, argv) != QSE_AWK_VAL_MAP)
			{
				/* with FLEXMAP on, you can change ARGV to a scalar.
				 *   BEGIN { ARGV="xxx"; }
				 * you must not do this. */
				qse_awk_rtx_seterrfmt (rtx, QSE_AWK_EINVAL, QSE_NULL, QSE_T("phony value in ARGV"));
				return -1;
			}

			map = ((qse_awk_val_map_t*)argv)->map;
			QSE_ASSERT (map != QSE_NULL);
			
			ibuflen = qse_awk_inttostr (
				rtx->awk, rxtn->c.in.index + 1, 10, QSE_NULL,
				ibuf, QSE_COUNTOF(ibuf));

			pair = qse_htb_search (map, ibuf, ibuflen);
			QSE_ASSERT (pair != QSE_NULL);

			v = QSE_HTB_VPTR(pair);
			QSE_ASSERT (v != QSE_NULL);

			as.ptr = qse_awk_rtx_getvalstr (rtx, v, &as.len);
			if (as.ptr == QSE_NULL) return -1;

			if (as.len == 0)
			{
				/* the name is empty */
				qse_awk_rtx_freevalstr (rtx, v, as.ptr);
				rxtn->c.in.index++;
				goto nextfile;
			}

			if (qse_strlen(as.ptr) < as.len)
			{
				/* the name contains one or more '\0' */
				qse_cstr_t errarg;

				errarg.ptr = as.ptr;
				/* use this length not to contains '\0'
				 * in an error message */
				errarg.len = qse_strlen(as.ptr);

				qse_awk_rtx_seterrnum (rtx, QSE_AWK_EIONMNL, &errarg);

				qse_awk_rtx_freevalstr (rtx, v, as.ptr);
				return -1;
			}

			file = as.ptr;

			sio = (file[0] == QSE_T('-') && file[1] == QSE_T('\0'))?
				open_sio_std_rtx (rtx, QSE_SIO_STDIN, 
					QSE_SIO_READ | QSE_SIO_IGNOREMBWCERR):
				open_sio_rtx (rtx, file,
					QSE_SIO_READ | QSE_SIO_IGNOREMBWCERR);
			if (sio == QSE_NULL)
			{
				qse_awk_rtx_freevalstr (rtx, v, as.ptr);
				return -1;
			}

			if (rxtn->c.cmgr) qse_sio_setcmgr (sio, rxtn->c.cmgr);	

			if (qse_awk_rtx_setfilename (
				rtx, file, qse_strlen(file)) <= -1)
			{
				qse_sio_close (sio);
				qse_awk_rtx_freevalstr (rtx, v, as.ptr);
				return -1;
			}

			qse_awk_rtx_freevalstr (rtx, v, as.ptr);
			riod->handle = sio;

			/* increment the counter of files successfully opened */
			rxtn->c.in.count++;
			rxtn->c.in.index++;
			return 1;
		}

	}
	else if (riod->mode == QSE_AWK_RIO_CONSOLE_WRITE)
	{
		if (rxtn->c.out.files == QSE_NULL)
		{
			QSE_ASSERT (rxtn->c.out.index == 0);

			if (rxtn->c.out.count == 0)
			{
				sio = open_sio_std_rtx (
					rtx, QSE_SIO_STDOUT,
					QSE_SIO_WRITE | QSE_SIO_IGNOREMBWCERR | QSE_SIO_LINEBREAK
				);
				if (sio == QSE_NULL) return -1;

				if (rxtn->c.cmgr) qse_sio_setcmgr (sio, rxtn->c.cmgr);	

				riod->handle = sio;
				rxtn->c.out.count++;
				return 1;
			}

			return 0;
		}
		else
		{
			/* a temporary variable sio is used here not to change 
			 * any fields of riod when the open operation fails */
			qse_sio_t* sio;
			const qse_char_t* file;

			file = rxtn->c.out.files[rxtn->c.out.index];

			if (file == QSE_NULL)
			{
				/* no more input file */
				return 0;
			}

			sio = (file[0] == QSE_T('-') && file[1] == QSE_T('\0'))?
				open_sio_std_rtx (
					rtx, QSE_SIO_STDOUT,
					QSE_SIO_WRITE | QSE_SIO_IGNOREMBWCERR | QSE_SIO_LINEBREAK):
				open_sio_rtx (
					rtx, file, 
					QSE_SIO_WRITE | QSE_SIO_CREATE | 
					QSE_SIO_TRUNCATE | QSE_SIO_IGNOREMBWCERR);
			if (sio == QSE_NULL) return -1;

			if (rxtn->c.cmgr) qse_sio_setcmgr (sio, rxtn->c.cmgr);	
			
			if (qse_awk_rtx_setofilename (
				rtx, file, qse_strlen(file)) <= -1)
			{
				qse_sio_close (sio);
				return -1;
			}

			riod->handle = sio;
			rxtn->c.out.index++;
			rxtn->c.out.count++;
			return 1;
		}

	}

	return -1;
}

static qse_ssize_t awk_rio_console (qse_awk_rtx_t* rtx, qse_awk_rio_cmd_t cmd, qse_awk_rio_arg_t* riod, void* data, qse_size_t size)
{
	switch (cmd)
	{
		case QSE_AWK_RIO_OPEN:
			return open_rio_console (rtx, riod);

		case QSE_AWK_RIO_CLOSE:
			if (riod->handle) qse_sio_close ((qse_sio_t*)riod->handle);
			return 0;

		case QSE_AWK_RIO_READ:
		{
			qse_ssize_t nn;

			while ((nn = qse_sio_getstrn((qse_sio_t*)riod->handle, data, size)) == 0)
			{
				int n;
				qse_sio_t* sio = (qse_sio_t*)riod->handle;

				n = open_rio_console(rtx, riod);
				if (n <= -1) return -1;

				if (n == 0) 
				{
					/* no more input console */
					return 0;
				}

				if (sio) qse_sio_close (sio);

				/* reset FNR to 0 here since the caller doesn't know that the file has changed. */
				qse_awk_rtx_setgbl(rtx, QSE_AWK_GBL_FNR, qse_awk_rtx_makeintval(rtx, 0));

			}
		
			return nn;
		}

		case QSE_AWK_RIO_WRITE:
			return qse_sio_putstrn((qse_sio_t*)riod->handle, data, size);

		case QSE_AWK_RIO_WRITE_BYTES:
			return qse_sio_putmbsn((qse_sio_t*)riod->handle, data, size);

		case QSE_AWK_RIO_FLUSH:
			return qse_sio_flush((qse_sio_t*)riod->handle);

		case QSE_AWK_RIO_NEXT:
		{
			int n;
			qse_sio_t* sio = (qse_sio_t*)riod->handle;

			n = open_rio_console (rtx, riod);
			if (n <= -1) return -1;

			if (n == 0) 
			{
				/* if there is no more file, keep the previous handle */
				return 0;
			}

			if (sio) qse_sio_close (sio);
			return n;
		}

	}

	qse_awk_rtx_seterrnum (rtx, QSE_AWK_EINTERN, QSE_NULL);
	return -1;
}

static void fini_rxtn (qse_awk_rtx_t* rtx)
{
	rxtn_t* rxtn = GET_RXTN(rtx);
	/*xtn_t* xtn = (xtn_t*)GET_XTN(rtx->awk);*/

	if (rxtn->cmgrtab_inited)
	{
		qse_htb_fini (&rxtn->cmgrtab);
		rxtn->cmgrtab_inited = 0;
	}
}

static int build_argcv (
	qse_awk_rtx_t* rtx, int argc_id, int argv_id, 
	const qse_char_t* id, const qse_char_t*const icf[])
{
	const qse_char_t*const* p;
	qse_size_t argc;
	qse_awk_val_t* v_argc;
	qse_awk_val_t* v_argv;
	qse_awk_val_t* v_tmp;
	qse_char_t key[QSE_SIZEOF(qse_awk_int_t)*8+2];
	qse_size_t key_len;

	v_argv = qse_awk_rtx_makemapval (rtx);
	if (v_argv == QSE_NULL) return -1;

	qse_awk_rtx_refupval (rtx, v_argv);

	/* make ARGV[0] */
	v_tmp = qse_awk_rtx_makestrvalwithstr (rtx, id);
	if (v_tmp == QSE_NULL) 
	{
		qse_awk_rtx_refdownval (rtx, v_argv);
		return -1;
	}

	/* increment reference count of v_tmp in advance as if 
	 * it has successfully been assigned into ARGV. */
	qse_awk_rtx_refupval (rtx, v_tmp);

	key_len = qse_strxcpy (key, QSE_COUNTOF(key), QSE_T("0"));
	if (qse_htb_upsert (
		((qse_awk_val_map_t*)v_argv)->map,
		key, key_len, v_tmp, 0) == QSE_NULL)
	{
		/* if the assignment operation fails, decrements
		 * the reference of v_tmp to free it */
		qse_awk_rtx_refdownval (rtx, v_tmp);

		/* the values previously assigned into the
		 * map will be freeed when v_argv is freed */
		qse_awk_rtx_refdownval (rtx, v_argv);

		qse_awk_rtx_seterrnum (rtx, QSE_AWK_ENOMEM, QSE_NULL); 
		return -1;
	}

	if (icf)
	{
		for (argc = 1, p = icf; *p; p++, argc++) 
		{
			/* the argument must compose a numeric string if it can be.
			 * so call qse_awk_rtx_makenstrvalwithstr() instead of
			 * qse_awk_rtx_makestrvalwithstr(). */
			v_tmp = qse_awk_rtx_makenstrvalwithstr (rtx, *p);
			if (v_tmp == QSE_NULL)
			{
				qse_awk_rtx_refdownval (rtx, v_argv);
				return -1;
			}

			key_len = qse_awk_inttostr (
				rtx->awk, argc, 10,
				QSE_NULL, key, QSE_COUNTOF(key));
			QSE_ASSERT (key_len != (qse_size_t)-1);

			qse_awk_rtx_refupval (rtx, v_tmp);

			if (qse_htb_upsert (
				((qse_awk_val_map_t*)v_argv)->map,
				key, key_len, v_tmp, 0) == QSE_NULL)
			{
				qse_awk_rtx_refdownval (rtx, v_tmp);
				qse_awk_rtx_refdownval (rtx, v_argv);
				qse_awk_rtx_seterrnum (rtx, QSE_AWK_ENOMEM, QSE_NULL); 
				return -1;
			}
		}
	}
	else argc = 1;

	v_argc = qse_awk_rtx_makeintval(rtx, (qse_awk_int_t)argc);
	if (v_argc == QSE_NULL)
	{
		qse_awk_rtx_refdownval (rtx, v_argv);
		return -1;
	}

	qse_awk_rtx_refupval (rtx, v_argc);

	if (qse_awk_rtx_setgbl(rtx, argc_id, v_argc) <= -1)
	{
		qse_awk_rtx_refdownval (rtx, v_argc);
		qse_awk_rtx_refdownval (rtx, v_argv);
		return -1;
	}

	if (qse_awk_rtx_setgbl(rtx, argv_id, v_argv) <= -1)
	{
		qse_awk_rtx_refdownval (rtx, v_argc);
		qse_awk_rtx_refdownval (rtx, v_argv);
		return -1;
	}

	qse_awk_rtx_refdownval (rtx, v_argc);
	qse_awk_rtx_refdownval (rtx, v_argv);

	return 0;
}
 
static int __build_environ (qse_awk_rtx_t* rtx, int gbl_id, qse_env_char_t* envarr[])
{
	qse_awk_val_t* v_env;
	qse_awk_val_t* v_tmp;

	v_env = qse_awk_rtx_makemapval (rtx);
	if (v_env == QSE_NULL) return -1;

	qse_awk_rtx_refupval (rtx, v_env);

	if (envarr)
	{
		qse_env_char_t* eq;
		qse_char_t* kptr, * vptr;
		qse_size_t klen, count;

		for (count = 0; envarr[count]; count++)
		{
		#if ((defined(QSE_ENV_CHAR_IS_MCHAR) && defined(QSE_CHAR_IS_MCHAR)) || \
		     (defined(QSE_ENV_CHAR_IS_WCHAR) && defined(QSE_CHAR_IS_WCHAR)))
			eq = qse_strchr (envarr[count], QSE_T('='));
			if (eq == QSE_NULL || eq == envarr[count]) continue;

			kptr = envarr[count];
			klen = eq - envarr[count];
			vptr = eq + 1;
		#elif defined(QSE_ENV_CHAR_IS_MCHAR)
			eq = qse_mbschr (envarr[count], QSE_MT('='));
			if (eq == QSE_NULL || eq == envarr[count]) continue;

			*eq = QSE_MT('\0');

			/* mbstowcsdup() may fail for invalid encoding. as the environment 
			 * variaables are not under control, call mbstowcsalldup() instead 
			 * to go on despite encoding failure */
			kptr = qse_mbstowcsalldupwithcmgr(envarr[count], &klen, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx)); 
			vptr = qse_mbstowcsalldupwithcmgr(eq + 1, QSE_NULL, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx));
			if (kptr == QSE_NULL || vptr == QSE_NULL)
			{
				if (kptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), kptr);
				if (vptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), vptr);
				qse_awk_rtx_refdownval (rtx, v_env);

				qse_awk_rtx_seterrnum (rtx, QSE_AWK_ENOMEM, QSE_NULL); 
				return -1;
			}

			*eq = QSE_MT('=');
		#else
			eq = qse_wcschr(envarr[count], QSE_WT('='));
			if (eq == QSE_NULL || eq == envarr[count]) continue;

			*eq = QSE_WT('\0');

			kptr = qse_wcstombsdupwithcmgr(envarr[count], &klen, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx)); 
			vptr = qse_wcstombsdupwithcmgr(eq + 1, QSE_NULL, qse_awk_rtx_getmmgr(rtx), qse_awk_rtx_getcmgr(rtx));
			if (kptr == QSE_NULL || vptr == QSE_NULL)
			{
				if (kptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), kptr);
				if (vptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), vptr);
				qse_awk_rtx_refdownval (rtx, v_env);

				qse_awk_rtx_seterrnum (rtx, QSE_AWK_ENOMEM, QSE_NULL);
				return -1;
			}			

			*eq = QSE_WT('=');
		#endif

			/* the string in ENVIRON should be a numeric string if
			 * it can be converted to a number. call makenstrval()
			 * instead of makestrval() */
			v_tmp = qse_awk_rtx_makenstrvalwithstr (rtx, vptr);
			if (v_tmp == QSE_NULL)
			{
		#if ((defined(QSE_ENV_CHAR_IS_MCHAR) && defined(QSE_CHAR_IS_MCHAR)) || \
		     (defined(QSE_ENV_CHAR_IS_WCHAR) && defined(QSE_CHAR_IS_WCHAR)))
				/* nothing to do */
		#else
				if (vptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), vptr);
				if (kptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), kptr);
		#endif
				qse_awk_rtx_refdownval (rtx, v_env);
				return -1;
			}


			/* increment reference count of v_tmp in advance as if 
			 * it has successfully been assigned into ARGV. */
			qse_awk_rtx_refupval (rtx, v_tmp);

			if (qse_htb_upsert (
				((qse_awk_val_map_t*)v_env)->map,
				kptr, klen, v_tmp, 0) == QSE_NULL)
			{
				/* if the assignment operation fails, decrements
				 * the reference of v_tmp to free it */
				qse_awk_rtx_refdownval (rtx, v_tmp);

		#if ((defined(QSE_ENV_CHAR_IS_MCHAR) && defined(QSE_CHAR_IS_MCHAR)) || \
		     (defined(QSE_ENV_CHAR_IS_WCHAR) && defined(QSE_CHAR_IS_WCHAR)))
				/* nothing to do */
		#else
				if (vptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), vptr);
				if (kptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), kptr);
		#endif

				/* the values previously assigned into the
				 * map will be freeed when v_env is freed */
				qse_awk_rtx_refdownval (rtx, v_env);

				qse_awk_rtx_seterrnum (rtx, QSE_AWK_ENOMEM, QSE_NULL);
				return -1;
			}

		#if ((defined(QSE_ENV_CHAR_IS_MCHAR) && defined(QSE_CHAR_IS_MCHAR)) || \
		     (defined(QSE_ENV_CHAR_IS_WCHAR) && defined(QSE_CHAR_IS_WCHAR)))
				/* nothing to do */
		#else
			if (vptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), vptr);
			if (kptr) QSE_MMGR_FREE (qse_awk_rtx_getmmgr(rtx), kptr);
		#endif
		}
	}

	if (qse_awk_rtx_setgbl (rtx, gbl_id, v_env) == -1)
	{
		qse_awk_rtx_refdownval (rtx, v_env);
		return -1;
	}

	qse_awk_rtx_refdownval (rtx, v_env);
	return 0;
}

static int build_environ (qse_awk_rtx_t* rtx, int gbl_id)
{
	qse_env_t env;
	int xret;

	if (qse_env_init(&env, qse_awk_rtx_getmmgr(rtx), 1) <= -1)
	{
		qse_awk_rtx_seterrnum (rtx, QSE_AWK_ENOMEM, QSE_NULL);
		return -1;
	}

	xret = __build_environ (rtx, gbl_id, qse_env_getarr(&env));

	qse_env_fini (&env);
	return xret;
}

static int make_additional_globals (
	qse_awk_rtx_t* rtx, xtn_t* xtn, 
	const qse_char_t* id, const qse_char_t*const icf[])
{
	if (build_argcv (rtx, xtn->gbl_argc, xtn->gbl_argv, id, icf) <= -1 ||
	    build_environ (rtx, xtn->gbl_environ) <= -1) return -1;

	return 0;
}

static qse_awk_rtx_t* open_rtx_std (
	qse_awk_t*        awk,
	qse_size_t        xtnsize,
	const qse_char_t* id,
	const qse_char_t* icf[],
	const qse_char_t* ocf[],
	qse_cmgr_t*       cmgr)
{
	qse_awk_rtx_t* rtx;
	qse_awk_rio_t rio;
	rxtn_t* rxtn;
	xtn_t* xtn;

	xtn = GET_XTN(awk);

	rio.pipe = awk_rio_pipe;
	rio.file = awk_rio_file;
	rio.console = awk_rio_console;

	rtx = qse_awk_rtx_open(awk, QSE_SIZEOF(rxtn_t) + xtnsize, &rio);
	if (!rtx) return QSE_NULL;

	rtx->_instsize += QSE_SIZEOF(rxtn_t);

	rxtn = GET_RXTN(rtx);

	if (rtx->awk->opt.trait & QSE_AWK_RIO)
	{
		if (qse_htb_init(&rxtn->cmgrtab, qse_awk_getmmgr(awk), 256, 70, QSE_SIZEOF(qse_char_t), 1) <= -1)
		{
			qse_awk_rtx_close (rtx);
			qse_awk_seterrnum (awk, QSE_AWK_ENOMEM, QSE_NULL);
			return QSE_NULL;
		}
		qse_htb_setstyle (&rxtn->cmgrtab, qse_gethtbstyle(QSE_HTB_STYLE_INLINE_COPIERS));
		rxtn->cmgrtab_inited = 1;
	}

	rxtn->ecb.close = fini_rxtn;
	qse_awk_rtx_pushecb (rtx, &rxtn->ecb);

	rxtn->c.in.files = icf;
	rxtn->c.in.index = 0;
	rxtn->c.in.count = 0;
	rxtn->c.out.files = ocf;
	rxtn->c.out.index = 0;
	rxtn->c.out.count = 0;
	rxtn->c.cmgr = cmgr;
	
	/* FILENAME can be set when the input console is opened.
	 * so we skip setting it here even if an explicit console file
	 * is specified.  So the value of FILENAME is empty in the 
	 * BEGIN block unless getline is ever met. 
	 *
	 * However, OFILENAME is different. The output
	 * console is treated as if it's already open upon start-up. 
	 * otherwise, 'BEGIN { print OFILENAME; }' prints an empty
	 * string regardless of output console files specified.
	 * That's because OFILENAME is evaluated before the output
	 * console file is opened. 
	 */
	if (ocf && ocf[0])
	{
		if (qse_awk_rtx_setofilename(rtx, ocf[0], qse_strlen(ocf[0])) <= -1)
		{
			awk->errinf = rtx->errinf; /* transfer error info */
			qse_awk_rtx_close (rtx);
			return QSE_NULL;
		}
	}

	if (make_additional_globals(rtx, xtn, id, icf) <= -1)
	{
		awk->errinf = rtx->errinf; /* transfer error info */
		qse_awk_rtx_close (rtx);
		return QSE_NULL;
	}

	return rtx;
}

qse_awk_rtx_t* qse_awk_rtx_openstdwithmbs (
	qse_awk_t*         awk,
	qse_size_t         xtnsize,
	const qse_mchar_t* id,
	const qse_mchar_t* icf[],
	const qse_mchar_t* ocf[],
	qse_cmgr_t*        cmgr)
{
#if defined(QSE_CHAR_IS_MCHAR)
	return open_rtx_std(awk, xtnsize, id, icf, ocf, cmgr);
#else
	qse_awk_rtx_t* rtx = QSE_NULL;
	qse_size_t wcslen, i;
	qse_wchar_t* wid = QSE_NULL, ** wicf = QSE_NULL, ** wocf = QSE_NULL;

	wid = qse_awk_mbstowcsdup(awk, id, &wcslen);
	if (!wid) return QSE_NULL;

	if (icf)
	{
		for (i = 0; icf[i]; i++) ;

		wicf = (qse_wchar_t**)qse_awk_callocmem(awk, QSE_SIZEOF(*wicf) * (i + 1));
		if (!wicf) goto done;

		for (i = 0; icf[i]; i++)
		{
			wicf[i] = qse_awk_mbstowcsdup(awk, icf[i], &wcslen);
			if (!wicf[i]) goto done;
		}
		wicf[i] = QSE_NULL;
	}

	if (ocf)
	{
		for (i = 0; ocf[i]; i++) ;

		wocf = (qse_wchar_t**)qse_awk_callocmem(awk, QSE_SIZEOF(*wocf) * (i + 1));
		if (!wocf) goto done;

		for (i = 0; ocf[i]; i++)
		{
			wocf[i] = qse_awk_mbstowcsdup(awk, ocf[i], &wcslen);
			if (!wocf[i]) goto done;
		}
		wocf[i] = QSE_NULL;
	}

	rtx = open_rtx_std(awk, xtnsize, wid, (const qse_wchar_t**)wicf, (const qse_wchar_t**)wocf, cmgr);

done:
	if (wocf)
	{
		for (i = 0; wocf[i]; i++) qse_awk_freemem (awk, wocf[i]);
		qse_awk_freemem (awk, wocf);
	}
	if (wicf)
	{
		for (i = 0; wicf[i]; i++) qse_awk_freemem (awk, wicf[i]);
		qse_awk_freemem (awk, wicf);
	}
	if (wid) qse_awk_freemem (awk, wid);

	return rtx;
#endif
}

qse_awk_rtx_t* qse_awk_rtx_openstdwithwcs (
	qse_awk_t*         awk,
	qse_size_t         xtnsize,
	const qse_wchar_t* id,
	const qse_wchar_t* icf[],
	const qse_wchar_t* ocf[],
	qse_cmgr_t*        cmgr)
{
#if defined(QSE_CHAR_IS_MCHAR)
	qse_awk_rtx_t* rtx = QSE_NULL;
	qse_size_t mbslen, i;
	qse_mchar_t* mid = QSE_NULL, ** micf = QSE_NULL, ** mocf = QSE_NULL;

	mid = qse_awk_wcstombsdup(awk, id, &mbslen);
	if (!mid) return QSE_NULL;

	if (icf)
	{
		for (i = 0; icf[i]; i++) ;

		micf = (qse_mchar_t**)qse_awk_callocmem(awk, QSE_SIZEOF(*micf) * (i + 1));
		if (!micf) goto done;

		for (i = 0; icf[i]; i++)
		{
			micf[i] = qse_awk_wcstombsdup(awk, icf[i], &mbslen);
			if (!micf[i]) goto done;
		}
		micf[i] = QSE_NULL;
	}

	if (ocf)
	{
		for (i = 0; ocf[i]; i++) ;

		mocf = (qse_wchar_t**)qse_awk_callocmem(awk, QSE_SIZEOF(*mocf) * (i + 1));
		if (!mocf) goto done;

		for (i = 0; ocf[i]; i++)
		{
			mocf[i] = qse_awk_wcstombsdup(awk, ocf[i], &mbslen);
			if (!mocf[i]) goto done;
		}
		mocf[i] = QSE_NULL;
	}

	rtx = open_rtx_std(awk, xtnsize, mid, (const qse_mchar_t**)micf, (const qse_mchar_t**)mocf, cmgr);

done:
	if (mocf)
	{
		for (i = 0; mocf[i]; i++) qse_awk_freemem (awk, mocf[i]);
		qse_awk_freemem (awk, mocf);
	}
	if (micf)
	{
		for (i = 0; micf[i]; i++) qse_awk_freemem (awk, micf[i]);
		qse_awk_freemem (awk, micf);
	}
	if (mid) qse_awk_freemem (awk, mid);

	return rtx;
#else
	return open_rtx_std(awk, xtnsize, id, icf, ocf, cmgr);
#endif
}


static int timeout_code (const qse_char_t* name)
{
	if (qse_strcasecmp(name, QSE_T("rtimeout")) == 0) return 0;
	if (qse_strcasecmp(name, QSE_T("wtimeout")) == 0) return 1;
	if (qse_strcasecmp(name, QSE_T("ctimeout")) == 0) return 2;
	if (qse_strcasecmp(name, QSE_T("atimeout")) == 0) return 3;
	return -1;
}

static QSE_INLINE void init_ioattr (ioattr_t* ioattr)
{
	int i;
	QSE_MEMSET (ioattr, 0, QSE_SIZEOF(*ioattr));
	for (i = 0; i < QSE_COUNTOF(ioattr->tmout); i++) 
	{
		/* a negative number for no timeout */
		ioattr->tmout[i].sec = -999;
		ioattr->tmout[i].nsec = 0;
	}
}

static ioattr_t* get_ioattr (
	qse_htb_t* tab, const qse_char_t* ptr, qse_size_t len)
{
	qse_htb_pair_t* pair;

	pair = qse_htb_search (tab, ptr, len);
	if (pair) return QSE_HTB_VPTR(pair);

	return QSE_NULL;
}

static ioattr_t* find_or_make_ioattr (
	qse_awk_rtx_t* rtx, qse_htb_t* tab, const qse_char_t* ptr, qse_size_t len)
{
	qse_htb_pair_t* pair;

	pair = qse_htb_search (tab, ptr, len);
	if (pair == QSE_NULL)
	{
		ioattr_t ioattr;

		init_ioattr (&ioattr);

		pair = qse_htb_insert (tab, (void*)ptr, len, (void*)&ioattr, QSE_SIZEOF(ioattr));
		if (pair == QSE_NULL)
		{
			qse_awk_rtx_seterrnum (rtx, QSE_AWK_ENOMEM, QSE_NULL);
			return QSE_NULL;
		}
	}

	return (ioattr_t*)QSE_HTB_VPTR(pair);
}

static int fnc_setioattr (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	rxtn_t* rxtn;
	qse_awk_val_t* v[3];
	qse_char_t* ptr[3];
	qse_size_t len[3];
	int i, ret = 0, fret = 0;
	int tmout;

	rxtn = GET_RXTN(rtx);
	QSE_ASSERT (rxtn->cmgrtab_inited == 1);

	for (i = 0; i < 3; i++)
	{
		v[i] = qse_awk_rtx_getarg (rtx, i);
		ptr[i] = qse_awk_rtx_getvalstr (rtx, v[i], &len[i]);
		if (ptr[i] == QSE_NULL) 
		{
			ret = -1;
			goto done;
		}

		if (qse_strxchr (ptr[i], len[i], QSE_T('\0')))
		{
			fret = -1;
			goto done;
		}
	}

	if ((tmout = timeout_code (ptr[1])) >= 0)
	{
		ioattr_t* ioattr;

		qse_awk_int_t l;
		qse_awk_flt_t r;
		int x;

		/* no error is returned by qse_awk_rtx_strnum() if the strict option 
		 * of the second parameter is 0. so i don't check for an error */
		x = qse_awk_rtx_strtonum(rtx, QSE_AWK_RTX_STRTONUM_MAKE_OPTION(0, 0), ptr[2], len[2], &l, &r);
		if (x == 0) r = (qse_awk_flt_t)l;

		ioattr = find_or_make_ioattr(rtx, &rxtn->cmgrtab, ptr[0], len[0]);
		if (ioattr == QSE_NULL) 
		{
			ret = -1;
			goto done;
		}

		if (x == 0)
		{
			ioattr->tmout[tmout].sec = l;
			ioattr->tmout[tmout].nsec = 0;
		}
		else if (x >= 1)
		{
			qse_awk_flt_t nsec;
			ioattr->tmout[tmout].sec = (qse_awk_int_t)r;
			nsec = r - ioattr->tmout[tmout].sec;
			ioattr->tmout[tmout].nsec = QSE_SEC_TO_NSEC(nsec);
		}
	}
#if defined(QSE_CHAR_IS_WCHAR)
	else if (qse_strcasecmp (ptr[1], QSE_T("codepage")) == 0 ||
	         qse_strcasecmp (ptr[1], QSE_T("encoding")) == 0)
	{
		ioattr_t* ioattr;
		qse_cmgr_t* cmgr;

		if (ptr[2][0] == QSE_T('\0')) 
		{
			cmgr = QSE_NULL;
		}
		else
		{
			cmgr = qse_findcmgr(ptr[2]);
			if (cmgr == QSE_NULL) 
			{
				fret = -1;
				goto done;
			}
		}

		ioattr = find_or_make_ioattr(rtx, &rxtn->cmgrtab, ptr[0], len[0]);
		if (ioattr == QSE_NULL) 
		{
			ret = -1;
			goto done;
		}

		ioattr->cmgr = cmgr;
		qse_strxcpy (ioattr->cmgr_name, QSE_COUNTOF(ioattr->cmgr_name), ptr[2]);
	}
#endif
	else
	{
		/* unknown attribute name */
		fret = -1;
		goto done;
	}

done:
	while (i > 0)
	{
		i--;
		qse_awk_rtx_freevalstr (rtx, v[i], ptr[i]);
	}

	if (ret >= 0)
	{
		v[0] = qse_awk_rtx_makeintval (rtx, (qse_awk_int_t)fret);
		if (v[0] == QSE_NULL) return -1;
		qse_awk_rtx_setretval (rtx, v[0]);
	}

	return ret;
}

static int fnc_getioattr (qse_awk_rtx_t* rtx, const qse_awk_fnc_info_t* fi)
{
	rxtn_t* rxtn;
	qse_awk_val_t* v[2];
	qse_char_t* ptr[2];
	qse_size_t len[2];
	int i, ret = 0;
	int tmout;
	qse_awk_val_t* rv = QSE_NULL;

	ioattr_t* ioattr;
	ioattr_t ioattr_buf;

	rxtn = GET_RXTN(rtx);
	QSE_ASSERT (rxtn->cmgrtab_inited == 1);

	for (i = 0; i < 2; i++)
	{
		v[i] = qse_awk_rtx_getarg (rtx, i);
		ptr[i] = qse_awk_rtx_getvalstr (rtx, v[i], &len[i]);
		if (ptr[i] == QSE_NULL) 
		{
			ret = -1;
			goto done;
		}

		if (qse_strxchr (ptr[i], len[i], QSE_T('\0'))) goto done;
	}

	ioattr = get_ioattr (&rxtn->cmgrtab, ptr[0], len[0]);
	if (ioattr == QSE_NULL) 
	{
		init_ioattr (&ioattr_buf);
		ioattr = &ioattr_buf;
	}

	if ((tmout = timeout_code (ptr[1])) >= 0)
	{
		if (ioattr->tmout[tmout].nsec == 0)
			rv = qse_awk_rtx_makeintval (rtx, ioattr->tmout[tmout].sec);
		else
			rv = qse_awk_rtx_makefltval (rtx, (qse_awk_flt_t)ioattr->tmout[tmout].sec + QSE_NSEC_TO_SEC((qse_awk_flt_t)ioattr->tmout[tmout].nsec));
		if (rv == QSE_NULL) 
		{
			ret = -1;
			goto done;
		}
	}
#if defined(QSE_CHAR_IS_WCHAR)
	else if (qse_strcasecmp (ptr[1], QSE_T("codepage")) == 0 ||
	         qse_strcasecmp (ptr[1], QSE_T("encoding")) == 0)
	{
		rv = qse_awk_rtx_makestrvalwithstr (rtx, ioattr->cmgr_name);
		if (rv == QSE_NULL)
		{
			ret = -1;
			goto done;
		}
	}
#endif
	else
	{
		/* unknown attribute name */
		goto done;
	}

done:
	while (i > 0)
	{
		i--;
		qse_awk_rtx_freevalstr (rtx, v[i], ptr[i]);
	}

	if (ret >= 0)
	{
		if (rv)
		{
			qse_awk_rtx_refupval (rtx, rv);
			ret = qse_awk_rtx_setrefval (rtx, (qse_awk_val_ref_t*)qse_awk_rtx_getarg (rtx, 2), rv);
			qse_awk_rtx_refdownval (rtx, rv);
			if (ret >= 0) qse_awk_rtx_setretval (rtx, QSE_AWK_VAL_ZERO);
		}
		else 
		{
			qse_awk_rtx_setretval (rtx, QSE_AWK_VAL_NEGONE);
		}

	}

	return ret;
}

qse_cmgr_t* qse_awk_rtx_getiocmgrstd (qse_awk_rtx_t* rtx, const qse_char_t* ioname)
{
#if defined(QSE_CHAR_IS_WCHAR)
	rxtn_t* rxtn = GET_RXTN(rtx);
	ioattr_t* ioattr;

	QSE_ASSERT (rxtn->cmgrtab_inited == 1);

	ioattr = get_ioattr(&rxtn->cmgrtab, ioname, qse_strlen(ioname));
	if (ioattr) return ioattr->cmgr;
#endif
	return QSE_NULL;
}

static int add_globals (qse_awk_t* awk)
{
	xtn_t* xtn = GET_XTN(awk);

	xtn->gbl_argc = qse_awk_addgbl(awk, QSE_T("ARGC"));
	xtn->gbl_argv = qse_awk_addgbl(awk, QSE_T("ARGV"));
	xtn->gbl_environ = qse_awk_addgbl(awk,  QSE_T("ENVIRON"));

	return (xtn->gbl_argc <= -1 || 
	        xtn->gbl_argv <= -1 ||
	        xtn->gbl_environ <= -1)? -1: 0;
}

struct fnctab_t 
{
	const qse_char_t* name;
	qse_awk_fnc_spec_t spec;
};

static struct fnctab_t fnctab[] =
{
	/* additional aliases to module functions */
	{ QSE_T("rand"),      { {1, 0, QSE_T("math")},  QSE_NULL,      0           } },
	{ QSE_T("srand"),     { {1, 0, QSE_T("math")},  QSE_NULL,      0           } },
	{ QSE_T("system"),    { {1, 0, QSE_T("sys")},   QSE_NULL ,     0           } }, 

	/* additional functions */
	{ QSE_T("setioattr"), { {3, 3, QSE_NULL},       fnc_setioattr, QSE_AWK_RIO } },
	{ QSE_T("getioattr"), { {3, 3, QSE_T("vvr")},   fnc_getioattr, QSE_AWK_RIO } }
};

static int add_functions (qse_awk_t* awk)
{
	int i;

	for (i = 0; i < QSE_COUNTOF(fnctab); i++)
	{
		if (qse_awk_addfnc (awk, fnctab[i].name, &fnctab[i].spec) == QSE_NULL) return -1;
	}

	return 0;
}

