/*
    Copyright (c) 2006-2020 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 "hawk-prv.h"
#include <hawk-pio.h>
#include <hawk-sio.h>
#include <hawk-xma.h>

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <math.h>
#if defined(HAVE_QUADMATH_H)
#	include <quadmath.h>
#elif defined(HAWK_USE_FLTMAX) && (HAWK_SIZEOF_FLT_T == 16) && defined(HAWK_FLTMAX_REQUIRE_QUADMATH)
	/* the header file doesn't exist while the library is available */
	extern __float128 powq (__float128, __float128);
	extern __float128 fmodq (__float128, __float128);
#endif

#if defined(_WIN32)
#	include <windows.h>
#	include <tchar.h>
#	if defined(HAWK_HAVE_CFG_H) && defined(HAWK_ENABLE_LIBLTDL)
#		include <ltdl.h>
#		define USE_LTDL
#	endif
#	include <io.h>
#elif defined(__OS2__)
#	define INCL_DOSMODULEMGR
#	define INCL_DOSPROCESS
#	define INCL_DOSERRORS
#	include <os2.h>
#elif defined(__DOS__)
	/* nothing to include */
#else
#	include "syscall.h"
#	if defined(HAWK_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

#	if defined(HAVE_CRT_EXTERNS_H)
#		include <crt_externs.h> /* MacOSX/darwin. _NSGetEnviron() */
#	endif
#endif

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

enum logfd_flag_t
{
	LOGFD_TTY = (1 << 0),
	LOGFD_OPENED_HERE = (1 << 1)
};

typedef struct xtn_t
{
	struct
	{
		struct
		{
			hawk_parsestd_t* x;
			hawk_oow_t       xindex;

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

				struct
				{
					const hawk_ooch_t* ptr;
					const hawk_ooch_t* end;
				} oocs;
				struct
				{
					const hawk_bch_t* ptr;
					const hawk_bch_t* end;
				} bcs;
				struct
				{
					const hawk_uch_t* ptr;
					const hawk_uch_t* end;
				} ucs;
			} u;
		} in;

		struct
		{
			hawk_parsestd_t* x;
			union
			{
				struct
				{
					hawk_sio_t* sio;
				} file;
				struct
				{
					hawk_ooecs_t* buf;
				} oocs;
				struct
				{
					hawk_becs_t* buf;
				} bcs;
				struct
				{
					hawk_uecs_t* buf;
				} ucs;
			} u;
		} out;
	} s; /* script/source handling */

	int gbl_argc;
	int gbl_argv;
	int gbl_environ;

	hawk_ecb_t ecb;
	int stdmod_up;

	struct
	{
		int fd;
		int fd_flag; /* bitwise OR'ed fo logfd_flag_t bits */

		struct
		{
			hawk_bch_t buf[4096];
 			hawk_oow_t len;
		} out;
	} log;
} xtn_t;

typedef struct rxtn_t
{
	struct
	{
		struct
		{
			hawk_ooch_t** files;
			hawk_oow_t index;
			hawk_oow_t count; /* number of files opened so far */
		} in;

		struct
		{
			hawk_ooch_t** files;
			hawk_oow_t index;
			hawk_oow_t count;
		} out;

		hawk_cmgr_t* cmgr;
	} c;  /* console */

	int cmgrtab_inited;
	hawk_htb_t cmgrtab;

	hawk_rtx_ecb_t ecb;
} rxtn_t;

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

#if defined(HAWK_HAVE_INLINE)
static HAWK_INLINE xtn_t* GET_XTN(hawk_t* hawk) { return (xtn_t*)((hawk_uint8_t*)hawk_getxtn(hawk) - HAWK_SIZEOF(xtn_t)); }
static HAWK_INLINE rxtn_t* GET_RXTN(hawk_rtx_t* rtx) { return (rxtn_t*)((hawk_uint8_t*)hawk_rtx_getxtn(rtx) - HAWK_SIZEOF(rxtn_t)); }
#else
#define GET_XTN(hawk) ((xtn_t*)((hawk_uint8_t*)hawk_getxtn(hawk) - HAWK_SIZEOF(xtn_t)))
#define GET_RXTN(rtx) ((rxtn_t*)((hawk_uint8_t*)hawk_rtx_getxtn(rtx) - HAWK_SIZEOF(rxtn_t)))
#endif


/* ========================================================================= */

static void* sys_alloc (hawk_mmgr_t* mmgr, hawk_oow_t size)
{
	return malloc(size);
}

static void* sys_realloc (hawk_mmgr_t* mmgr, void* ptr, hawk_oow_t size)
{
	return realloc(ptr, size);
}

static void sys_free (hawk_mmgr_t* mmgr, void* ptr)
{
	free (ptr);
}

static hawk_mmgr_t sys_mmgr =
{
	sys_alloc,
	sys_realloc,
	sys_free,
	HAWK_NULL
};

hawk_mmgr_t* hawk_get_sys_mmgr (void)
{
	return &sys_mmgr;
}

/* ----------------------------------------------------------------------- */


static void* xma_alloc (hawk_mmgr_t* mmgr, hawk_oow_t size)
{
	return hawk_xma_alloc(mmgr->ctx, size);
}

static void* xma_realloc (hawk_mmgr_t* mmgr, void* ptr, hawk_oow_t size)
{
	return hawk_xma_realloc(mmgr->ctx, ptr, size);
}

static void xma_free (hawk_mmgr_t* mmgr, void* ptr)
{
	hawk_xma_free(mmgr->ctx, ptr);
}

int hawk_init_xma_mmgr (hawk_mmgr_t* mmgr, hawk_oow_t memlimit)
{
	hawk_xma_t* xma;

	xma = hawk_xma_open(hawk_get_sys_mmgr(), 0, HAWK_NULL, memlimit);
	if (HAWK_UNLIKELY(!xma)) return -1;

	HAWK_MEMSET(mmgr, 0, HAWK_SIZEOF(*mmgr));
	mmgr->alloc = xma_alloc;
	mmgr->realloc = xma_realloc;
	mmgr->free = xma_free;
	mmgr->ctx = xma;

	return 0;
}

void hawk_fini_xma_mmgr (hawk_mmgr_t* mmgr)
{
	if (mmgr->ctx) hawk_xma_close(mmgr->ctx);
	mmgr->ctx = HAWK_NULL;
}

/* ----------------------------------------------------------------------- */

hawk_flt_t hawk_stdmathpow (hawk_t* hawk, hawk_flt_t x, hawk_flt_t y)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_POWQ)
	return powq(x, y);
#elif defined(HAVE_POWL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_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
}

hawk_flt_t hawk_stdmathmod (hawk_t* hawk, hawk_flt_t x, hawk_flt_t y)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_FMODQ)
	return fmodq(x, y);
#elif defined(HAVE_FMODL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_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]
 * hawk_stdmodXXXX() functions must not access the extension
 * area of 'hawk'. they are used in StdAwk.cpp which instantiates
 * an hawk object with hawk_open() instead of hawk_openstd(). */

int hawk_stdmodstartup (hawk_t* hawk)
{
#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 hawk_stdmodshutdown (hawk_t* hawk)
{
#if defined(USE_LTDL)
	lt_dlexit ();
#else
	/* do nothing */
#endif
}

static void* std_mod_open_checked (hawk_t* hawk, const hawk_mod_spec_t* spec)
{
	xtn_t* xtn = GET_XTN(hawk);

	if (!xtn->stdmod_up)
	{
		/* hawk_stdmodstartup() must have failed upon start-up.
		 * return failure immediately */
		hawk_seterrnum(hawk, HAWK_NULL, HAWK_ENOIMPL);
		return HAWK_NULL;
	}

	return hawk_stdmodopen(hawk, spec);
}

void* hawk_stdmodopen (hawk_t* hawk, const hawk_mod_spec_t* spec)
{
#if defined(USE_LTDL)
	if (spec)
	{
		void* h;
		lt_dladvise adv;
		hawk_bch_t* modpath;
		const hawk_ooch_t* tmp[6];
		int count;
		static hawk_ooch_t ds[] = { '/', '\0' };

		count = 0;
		if (spec->libdir)
		{
			hawk_oow_t len;

			tmp[count++] = spec->libdir;
			len = hawk_count_oocstr(spec->libdir);
			if (len > 0 && spec->libdir[len - 1] != '/') tmp[count++] = ds;
		}
		if (spec->prefix) tmp[count++] = spec->prefix;
		tmp[count++] = spec->name;
		if (spec->postfix) tmp[count++] = spec->postfix;
		tmp[count] = HAWK_NULL;

		#if defined(HAWK_OOCH_IS_BCH)
		modpath = hawk_dupbcstrarr(hawk, tmp, HAWK_NULL);
		#else
		modpath = hawk_dupucstrarrtobcstr(hawk, tmp, HAWK_NULL)
		#endif
		if (!modpath) return HAWK_NULL;

		if (lt_dladvise_init(&adv) != 0)
		{
			hawk_seterrfmt(hawk, HAWK_NULL, HAWK_ESYSERR, HAWK_T("%hs"), lt_dlerror());
			return HAWK_NULL;
		}

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

		h = lt_dlopenadvise(modpath, adv);
		if (!h) hawk_seterrfmt(hawk, HAWK_NULL, HAWK_ESYSERR, HAWK_T("%hs"), lt_dlerror());

		lt_dladvise_destroy (&adv);

		if (modpath) hawk_freemem(hawk, modpath);
		return h;
	}
	else
	{
		void* h;
		h = lt_dlopen(HAWK_NULL);
		if (HAWK_UNLIKELY(!h)) hawk_seterrfmt(hawk, HAWK_NULL, HAWK_ESYSERR, HAWK_T("%hs"), lt_dlerror());
		return h;
	}

#elif defined(_WIN32)

	if (spec)
	{
		HMODULE h;
		hawk_ooch_t* modpath;
		const hawk_ooch_t* tmp[6];
		int count;
		static hawk_ooch_t ds[][2] = { { '\\', '\0' }, { '/', '\0' } };

		count = 0;
		if (spec->libdir)
		{
			hawk_oow_t len;

			tmp[count++] = spec->libdir;
			len = hawk_count_oocstr(spec->libdir);
			if (len > 0 && (spec->libdir[len - 1] != '/' && spec->libdir[len - 1] != '\\'))
			{
				tmp[count++] = ds[hawk_find_oochar_in_oocstr(spec->libdir, '/') != HAWK_NULL];
			}
		}
		if (spec->prefix) tmp[count++] = spec->prefix;
		tmp[count++] = spec->name;
		if (spec->postfix) tmp[count++] = spec->postfix;
		tmp[count] = HAWK_NULL;

		modpath = hawk_dupoocstrarr(hawk, tmp, HAWK_NULL);
		if (!modpath) return HAWK_NULL;

		h = LoadLibrary(modpath);
		if (!h) hawk_seterrnum(hawk, HAWK_NULL, hawk_syserr_to_errnum(GetLastError());

		hawk_freemem(hawk, modpath);

		HAWK_ASSERT(HAWK_SIZEOF(h) <= HAWK_SIZEOF(void*));
		return h;
	}
	else
	{
		HMODULE h;
		h = GetModuleHandle(HAWK_NULL);
		if (!h) hawk_seterrnum(hawk, HAWK_NULL, hawk_syserr_to_errnum(GetLastError());
		return h;
	}

#elif defined(__OS2__)

	if (spec)
	{
		HMODULE h;
		hawk_bch_t* modpath;
		const hawk_ooch_t* tmp[6];
		int count;
		char errbuf[CCHMAXPATH];
		APIRET rc;
		static hawk_ooch_t ds[] = { '\\', '\0' };

		count = 0;
		if (spec->libdir)
		{
			hawk_oow_t len;

			tmp[count++] = spec->libdir;
			len = hawk_count_oocstr(spec->libdir);
			if (len > 0 && spec->libdir[len - 1] != '\\') tmp[count++] = ds;
		}
		if (spec->prefix) tmp[count++] = spec->prefix;
		tmp[count++] = spec->name;
		if (spec->postfix) tmp[count++] = spec->postfix;
		tmp[count] = HAWK_NULL;

		#if defined(HAWK_OOCH_IS_BCH)
		modpath = hawk_dupbcstrarr(hawk, tmp, HAWK_NULL);
		#else
		modpath = hawk_dupucstrarrtobcstr(hawk, tmp, HAWK_NULL);
		#endif
		if (HAWK_UNLIKELY(!modpath)) return HAWK_NULL;

		/* DosLoadModule() seems to have severe limitation on
		 * the file name it can load (max-8-letters.xxx) */
		rc = DosLoadModule(errbuf, HAWK_COUNTOF(errbuf) - 1, modpath, &h);
		if (rc != NO_ERROR)
		{
			h = HAWK_NULL;
			hawk_seterrnum(hawk, HAWK_NULL, hawk_syserr_to_errnum(rc));
		}

		hawk_freemem(hawk, modpath);

		HAWK_ASSERT(HAWK_SIZEOF(h) <= HAWK_SIZEOF(void*));
		return h;
	}
	else
	{
		hawk_seterrnum(hawk, HAWK_NULL, HAWK_ENOIMPL);
		return HAWK_NULL;
	}

#elif defined(__DOS__) && defined(HAWK_ENABLE_DOS_DYNAMIC_MODULE)

	if (spec)
	{
		/* 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;
		hawk_bch_t* modpath;
		const hawk_ooch_t* tmp[6];
		int count;
		static hawk_ooch_t ds[] = { '\\', '\0' };

		count = 0;
		if (spec->libdir)
		{
			hawk_oow_t len;

			tmp[count++] = spec->libdir;
			len = hawk_count_oocstr(spec->libdir);
			if (len > 0 && spec->libdir[len - 1] != '/') tmp[count++] = ds;
		}
		if (spec->prefix) tmp[count++] = spec->prefix;
		tmp[count++] = spec->name;
		if (spec->postfix) tmp[count++] = spec->postfix;
		tmp[count] = HAWK_NULL;

		#if defined(HAWK_OOCH_IS_BCH)
		modpath = hawk_dupbcstrarr(hawk, tmp, HAWK_NULL);
		#else
		modpath = hawk_dupucstrarrtobcstr(hawk, tmp, HAWK_NULL);
		#endif
		if (!modpath) return HAWK_NULL;

		h = LoadModule(modpath);
		if (!h) hawk_seterrnum(hawk, HAWK_NULL, HAWK_ESYSERR);

		hawk_freemem(hawk, modpath);

		HAWK_ASSERT(HAWK_SIZEOF(h) <= HAWK_SIZEOF(void*));
		return h;
	}
	else
	{
		void* h;
		h = GetModuleHandle(HAWK_NULL);
		if (!h) hawk_seterrnum(hawk, HAWK_NULL, HAWK_ESYSERR);
		return h;
	}

#elif defined(USE_DLFCN)
	if (spec)
	{
		void* h;
		hawk_bch_t* modpath;
		const hawk_ooch_t* tmp[6];
		int count;
		static hawk_ooch_t ds[] = { '/', '\0' };

		count = 0;
		if (spec->libdir)
		{
			hawk_oow_t len;

			tmp[count++] = spec->libdir;
			len = hawk_count_oocstr(spec->libdir);
			if (len > 0 && spec->libdir[len - 1] != '/') tmp[count++] = ds;
		}
		if (spec->prefix) tmp[count++] = spec->prefix;
		tmp[count++] = spec->name;
		if (spec->postfix) tmp[count++] = spec->postfix;
		tmp[count] = HAWK_NULL;

		#if defined(HAWK_OOCH_IS_BCH)
		modpath = hawk_dupbcstrarr(hawk, tmp, HAWK_NULL);
		#else
		modpath = hawk_dupucstrarrtobcstr(hawk, tmp, HAWK_NULL);
		#endif
		if (!modpath) return HAWK_NULL;

		h = dlopen(modpath, RTLD_NOW | RTLD_LOCAL);
		if (!h) hawk_seterrfmt(hawk, HAWK_NULL, HAWK_ESYSERR, HAWK_T("%hs"), dlerror());

		hawk_freemem(hawk, modpath);

		return h;
	}
	else
	{
		void* h;
		h = dlopen(NULL, RTLD_NOW | RTLD_LOCAL);
		if (!h) hawk_seterrfmt(hawk, HAWK_NULL, HAWK_ESYSERR, HAWK_T("%hs"), dlerror());
		return h;
	}
#else
	hawk_seterrnum(hawk, HAWK_NULL, HAWK_ENOIMPL);
	return HAWK_NULL;
#endif
}

void hawk_stdmodclose(hawk_t* hawk, void* handle)
{
#if defined(USE_LTDL)
	lt_dlclose(handle);
#elif defined(_WIN32)
	if (handle != GetModuleHandle(HAWK_NULL)) FreeLibrary ((HMODULE)handle);
#elif defined(__OS2__)
	DosFreeModule ((HMODULE)handle);
#elif defined(__DOS__) && defined(HAWK_ENABLE_DOS_DYNAMIC_MODULE)
	if (handle != GetModuleHandle(HAWK_NULL))  FreeModule (handle);
#elif defined(USE_DLFCN)
	dlclose(handle);
#else
	/* nothing to do */
#endif
}

void* hawk_stdmodgetsym (hawk_t* hawk, void* handle, const hawk_ooch_t* name)
{
	void* s;
	hawk_bch_t* mname;

#if defined(HAWK_OOCH_IS_BCH)
	mname = (hawk_bch_t*)name;
#else
	mname = hawk_duputobcstr(hawk, name, HAWK_NULL);
	if (!mname) return HAWK_NULL;
#endif

#if defined(USE_LTDL)
	s = lt_dlsym(handle, mname);
	if (!s) hawk_seterrfmt(hawk, HAWK_NULL, HAWK_ESYSERR, HAWK_T("%hs"), lt_dlerror());

#elif defined(_WIN32)
	s = GetProcAddress((HMODULE)handle, mname);
	if (!s) hawk_seterrnum(hawk, HAWK_NULL, hawk_syserr_to_errnum(GetLastError());

#elif defined(__OS2__)
	{
		APIRET rc;
		rc = DosQueryProcAddr((HMODULE)handle, 0, mname, (PFN*)&s);
		if (rc != NO_ERROR)
		{
			s = HAWK_NULL;
			hawk_seterrnum(hawk, HAWK_NULL, hawk_syserr_to_errnum(rc));
		}
	}

#elif defined(__DOS__) && defined(HAWK_ENABLE_DOS_DYNAMIC_MODULE)
	s = GetProcAddress(handle, mname);
	if (!s) hawk_seterrnum(hawk, HAWK_NULL, HAWK_ESYSERR);

#elif defined(USE_DLFCN)
	s = dlsym(handle, mname);
	if (!s) hawk_seterrfmt(hawk, HAWK_NULL, HAWK_ESYSERR, HAWK_T("%hs"), dlerror());

#else
	s = HAWK_NULL;
#endif

#if defined(HAWK_OOCH_IS_BCH)
	/* nothing to do */
#else
	hawk_freemem(hawk, mname);
#endif

	return s;
}

/* ----------------------------------------------------------------------- */

static HAWK_INLINE void reset_log_to_default (xtn_t* xtn)
{
#if defined(ENABLE_LOG_INITIALLY)
	xtn->log.fd = 2;
	xtn->log.fd_flag = 0;
	#if defined(HAVE_ISATTY)
	if (isatty(xtn->log.fd)) xtn->log.fd_flag |= LOGFD_TTY;
	#endif
#else
	xtn->log.fd = -1;
	xtn->log.fd_flag = 0;
#endif
}

#if defined(EMSCRIPTEN)
EM_JS(int, write_all, (int, const hawk_bch_t* ptr, hawk_oow_t len), {
	// UTF8ToString() doesn't handle a null byte in the middle of an array.
	// Use the heap memory and pass the right portion to UTF8Decoder.
	//console.log ("%s", UTF8ToString(ptr, len));
	console.log ("%s", UTF8Decoder.decode(HEAPU8.subarray(ptr, ptr + len)));
	return 0;
});

#else

static int write_all (int fd, const hawk_bch_t* ptr, hawk_oow_t len)
{
#if defined(EMSCRIPTEN)
	EM_ASM_ ({
		// UTF8ToString() doesn't handle a null byte in the middle of an array.
		// Use the heap memory and pass the right portion to UTF8Decoder.
		//console.log ("%s", UTF8ToString($0, $1));
		console.log ("%s", UTF8Decoder.decode(HEAPU8.subarray($0, $0 + $1)));
	}, ptr, len);
#else
	while (len > 0)
	{
		hawk_ooi_t wr;

		wr = write(fd, ptr, len);

		if (wr <= -1)
		{
		#if defined(EAGAIN) && defined(EWOULDBLOCK) && (EAGAIN == EWOULDBLOCK)
			if (errno == EAGAIN) continue;
		#else
			#if defined(EAGAIN)
			if (errno == EAGAIN) continue;
			#elif defined(EWOULDBLOCK)
			if (errno == EWOULDBLOCK) continue;
			#endif
		#endif

		#if defined(EINTR)
			/* TODO: would this interfere with non-blocking nature of this VM? */
			if (errno == EINTR) continue;
		#endif
			return -1;
		}

		ptr += wr;
		len -= wr;
	}
#endif

	return 0;
}
#endif

static int write_log (hawk_t* hawk, int fd, const hawk_bch_t* ptr, hawk_oow_t len)
{
	xtn_t* xtn = GET_XTN(hawk);

	while (len > 0)
	{
		if (xtn->log.out.len > 0)
		{
			hawk_oow_t rcapa, cplen;

			rcapa = HAWK_COUNTOF(xtn->log.out.buf) - xtn->log.out.len;
			cplen = (len >= rcapa)? rcapa: len;

			HAWK_MEMCPY(&xtn->log.out.buf[xtn->log.out.len], ptr, cplen);
			xtn->log.out.len += cplen;
			ptr += cplen;
			len -= cplen;

			if (xtn->log.out.len >= HAWK_COUNTOF(xtn->log.out.buf))
			{
				int n;
				n = write_all(fd, xtn->log.out.buf, xtn->log.out.len);
				xtn->log.out.len = 0;
				if (n <= -1) return -1;
			}
		}
		else
		{
			hawk_oow_t rcapa;

			rcapa = HAWK_COUNTOF(xtn->log.out.buf);
			if (len >= rcapa)
			{
				if (write_all(fd, ptr, rcapa) <= -1) return -1;
				ptr += rcapa;
				len -= rcapa;
			}
			else
			{
				HAWK_MEMCPY(xtn->log.out.buf, ptr, len);
				xtn->log.out.len += len;
				ptr += len;
				len -= len;
			}
		}
	}

	return 0;
}

static void flush_log (hawk_t* hawk, int fd)
{
	xtn_t* xtn = GET_XTN(hawk);
	if (xtn->log.out.len > 0)
	{
		write_all (fd, xtn->log.out.buf, xtn->log.out.len);
		xtn->log.out.len = 0;
	}
}

static void log_write (hawk_t* hawk, hawk_bitmask_t mask, const hawk_ooch_t* msg, hawk_oow_t len)
{
	xtn_t* xtn = GET_XTN(hawk);
	hawk_bch_t buf[256];
	hawk_oow_t ucslen, bcslen, msgidx;
	int n, logfd;

	if (mask & HAWK_LOG_STDERR) logfd = 2;
	else if (mask & HAWK_LOG_STDOUT) logfd = 1;
	else
	{
		logfd = xtn->log.fd;
#if !defined(EMSCRIPTEN)
		if (logfd <= -1)
		{
			return;
		}
#endif
	}

/* TODO: beautify the log message.
 *       do classification based on mask. */
	if (!(mask & (HAWK_LOG_STDOUT | HAWK_LOG_STDERR)))
	{
	#if defined(_WIN32)
		TIME_ZONE_INFORMATION tzi;
		SYSTEMTIME now;
	#else
		time_t now;
		struct tm tm, *tmp;
	#endif
	#if defined(HAWK_OOCH_IS_UCH)
		char ts[32 * HAWK_BCSIZE_MAX];
	#else
		char ts[32];
	#endif
		hawk_oow_t tslen;

	#if defined(_WIN32)
		/* %z for strftime() in win32 seems to produce a long non-numeric timezone name.
		 * i don't use strftime() for time formatting. */
		GetLocalTime (&now);
		if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID)
		{
			tzi.Bias = -tzi.Bias;
			tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d %+02.2d%02.2d ",
				(int)now.wYear, (int)now.wMonth, (int)now.wDay,
				(int)now.wHour, (int)now.wMinute, (int)now.wSecond,
				(int)(tzi.Bias / 60), (int)(tzi.Bias % 60));
		}
		else
		{
			tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d ",
				(int)now.wYear, (int)now.wMonth, (int)now.wDay,
				(int)now.wHour, (int)now.wMinute, (int)now.wSecond);
		}
	#elif defined(__OS2__)
		now = time(HAWK_NULL);

		#if defined(__WATCOMC__)
		tmp = _localtime(&now, &tm);
		#else
		tmp = localtime(&now);
		#endif

		#if defined(__BORLANDC__)
		/* the borland compiler doesn't handle %z properly - it showed 00 all the time */
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %Z ", tmp);
		#else
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %z ", tmp);
		#endif
		if (tslen == 0)
		{
			tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d ", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
		}

	#elif defined(__DOS__)
		now = time(HAWK_NULL);
		tmp = localtime(&now);
		/* since i know that %z/%Z is not available in strftime, i switch to sprintf immediately */
		tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d ", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
	#else
		now = time(HAWK_NULL);
		#if defined(HAVE_LOCALTIME_R)
		tmp = localtime_r(&now, &tm);
		#else
		tmp = localtime(&now);
		#endif

		#if defined(HAVE_STRFTIME_SMALL_Z)
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %z ", tmp);
		#else
		tslen = strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S %Z ", tmp);
		#endif
		if (tslen == 0)
		{
			tslen = sprintf(ts, "%04d-%02d-%02d %02d:%02d:%02d ", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
		}
	#endif

		write_log(hawk, logfd, ts, tslen);
	}

	if (logfd == xtn->log.fd && (xtn->log.fd_flag & LOGFD_TTY))
	{
		if (mask & HAWK_LOG_FATAL) write_log(hawk, logfd, "\x1B[1;31m", 7);
		else if (mask & HAWK_LOG_ERROR) write_log(hawk, logfd, "\x1B[1;32m", 7);
		else if (mask & HAWK_LOG_WARN) write_log(hawk, logfd, "\x1B[1;33m", 7);
	}

#if defined(HAWK_OOCH_IS_UCH)
	msgidx = 0;
	while (len > 0)
	{
		ucslen = len;
		bcslen = HAWK_COUNTOF(buf);

		/*n = hawk_convootobchars(hawk, &msg[msgidx], &ucslen, buf, &bcslen);*/
		n = hawk_conv_uchars_to_bchars_with_cmgr(&msg[msgidx], &ucslen, buf, &bcslen, hawk_getcmgr(hawk));
		if (n == 0 || n == -2)
		{
			/* n = 0:
			 *   converted all successfully
			 * n == -2:
			 *    buffer not sufficient. not all got converted yet.
			 *    write what have been converted this round. */

			HAWK_ASSERT(ucslen > 0); /* if this fails, the buffer size must be increased */

			/* attempt to write all converted characters */
			if (write_log(hawk, logfd, buf, bcslen) <= -1) break;

			if (n == 0) break;
			else
			{
				msgidx += ucslen;
				len -= ucslen;
			}
		}
		else if (n <= -1)
		{
			/* conversion error but i just stop here but don't treat it as a hard error. */
			break;
		}
	}
#else
	write_log(hawk, logfd, msg, len);
#endif

	if (logfd == xtn->log.fd && (xtn->log.fd_flag & LOGFD_TTY))
	{
		if (mask & (HAWK_LOG_FATAL | HAWK_LOG_ERROR | HAWK_LOG_WARN)) write_log(hawk, logfd, "\x1B[0m", 4);
	}

	flush_log(hawk, logfd);
}
/* ----------------------------------------------------------------------- */

static int add_globals (hawk_t* hawk);
static int add_functions (hawk_t* hawk);

hawk_t* hawk_openstd (hawk_oow_t xtnsize, hawk_errinf_t* errinf)
{
	return hawk_openstdwithmmgr(&sys_mmgr, xtnsize, hawk_get_cmgr_by_id(HAWK_CMGR_UTF8), errinf);
}

static void fini_xtn (hawk_t* hawk, void* ctx)
{
	xtn_t* xtn = GET_XTN(hawk);
	if (xtn->stdmod_up)
	{
		hawk_stdmodshutdown (hawk);
		xtn->stdmod_up = 0;
	}

	if ((xtn->log.fd_flag & LOGFD_OPENED_HERE) && xtn->log.fd >= 0) close(xtn->log.fd);
	reset_log_to_default (xtn);
}

static void clear_xtn (hawk_t* hawk, void* ctx)
{
	/* nothing to do */
}

hawk_t* hawk_openstdwithmmgr (hawk_mmgr_t* mmgr, hawk_oow_t xtnsize, hawk_cmgr_t* cmgr, hawk_errinf_t* errinf)
{
	hawk_t* hawk;
	hawk_prm_t prm;
	xtn_t* xtn;

	prm.math.pow = hawk_stdmathpow;
	prm.math.mod = hawk_stdmathmod;

	prm.modopen = std_mod_open_checked;
	prm.modclose = hawk_stdmodclose;
	prm.modgetsym = hawk_stdmodgetsym;

	prm.logwrite = log_write;

	if (!mmgr) mmgr = &sys_mmgr;
	if (!cmgr) cmgr = hawk_get_cmgr_by_id(HAWK_CMGR_UTF8);

	/* create an object */
	hawk = hawk_open(mmgr, HAWK_SIZEOF(xtn_t) + xtnsize, cmgr, &prm, errinf);
	if (HAWK_UNLIKELY(!hawk)) return HAWK_NULL;

	/* adjust the object size by the sizeof xtn_t so that hawk_getxtn() returns the right pointer. */
	hawk->_instsize += HAWK_SIZEOF(xtn_t);

/*
#if defined(USE_DLFCN)
	if (hawk_setopt(hawk, HAWK_OPT_MODPOSTFIX, HAWK_T(".so")) <= -1)
	{
		if (errinf) hawk_geterrinf(hawk, errinf);
		goto oops;
	}
#endif
*/

	/* initialize extension */
	xtn = GET_XTN(hawk);

	reset_log_to_default (xtn);

	/* add intrinsic global variables and functions */
	if (add_globals(hawk) <= -1 || add_functions(hawk) <= -1)
	{
		if (errinf) hawk_geterrinf(hawk, errinf);
		goto oops;
	}

	if (hawk_stdmodstartup(hawk) <= -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;
	xtn->ecb.ctx = HAWK_NULL;
	hawk_pushecb(hawk, &xtn->ecb);

	return hawk;

oops:
	if (hawk) hawk_close(hawk);
	return HAWK_NULL;
}

static hawk_sio_t* open_sio (hawk_t* hawk, const hawk_ooch_t* file, int flags)
{
	hawk_sio_t* sio;
	sio = hawk_sio_open(hawk_getgem(hawk), 0, file, flags);
	if (sio == HAWK_NULL)
	{
		const hawk_ooch_t* bem = hawk_backuperrmsg(hawk);
		hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EOPEN, HAWK_T("unable to open %js - %js"), file, bem);
	}
	return sio;
}

static hawk_sio_t* open_sio_rtx (hawk_rtx_t* rtx, const hawk_ooch_t* file, int flags)
{
	hawk_sio_t* sio;
	sio = hawk_sio_open(hawk_rtx_getgem(rtx), 0, file, flags);
	if (sio == HAWK_NULL)
	{
		const hawk_ooch_t* bem = hawk_rtx_backuperrmsg(rtx);
		hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EOPEN, HAWK_T("unable to open %js - %js"), file, bem);
	}
	return sio;
}

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

static hawk_sio_t* open_sio_std (hawk_t* hawk, hawk_sio_std_t std, int flags)
{
	hawk_sio_t* sio;
	sio = hawk_sio_openstd(hawk_getgem(hawk), 0, std, flags);
	if (sio == HAWK_NULL)
	{
		const hawk_ooch_t* bem = hawk_backuperrmsg(hawk);
		hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EOPEN, HAWK_T("unable to open %js - %js"), sio_std_names[std].ptr, bem);
	}
	return sio;
}

static hawk_sio_t* open_sio_std_rtx (hawk_rtx_t* rtx, hawk_sio_std_t std, int flags)
{
	hawk_sio_t* sio;

	sio = hawk_sio_openstd(hawk_rtx_getgem(rtx), 0, std, flags);
	if (sio == HAWK_NULL)
	{
		const hawk_ooch_t* bem = hawk_rtx_backuperrmsg(rtx);
		hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EOPEN, HAWK_T("unable to open %js - %js"), sio_std_names[std].ptr, bem);
	}
	return sio;
}

/*** PARSESTD ***/

static int is_psin_file (hawk_parsestd_t* psin)
{
	return psin->type == HAWK_PARSESTD_FILE ||
	       psin->type == HAWK_PARSESTD_FILEB ||
	       psin->type == HAWK_PARSESTD_FILEU;
}

static int open_parsestd (hawk_t* hawk, hawk_sio_arg_t* arg, xtn_t* xtn, hawk_oow_t index)
{
	hawk_parsestd_t* psin = &xtn->s.in.x[index];

	switch (psin->type)
	{
		/* normal source files */
	#if defined(HAWK_OOCH_IS_BCH)
		case HAWK_PARSESTD_FILE:
			HAWK_ASSERT(&psin->u.fileb.path == &psin->u.file.path);
			HAWK_ASSERT(&psin->u.fileb.cmgr == &psin->u.file.cmgr);
	#endif
		case HAWK_PARSESTD_FILEB:
		{
			hawk_sio_t* tmp;
			hawk_ooch_t* path = HAWK_NULL;

			if (psin->u.fileb.path == HAWK_NULL || (psin->u.fileb.path[0] == '-' && psin->u.fileb.path[1] == '\0'))
			{
				/* no path name or - -> stdin */
				path = HAWK_NULL;
				tmp = open_sio_std(hawk, HAWK_SIO_STDIN, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR);
			}
			else
			{
				path = hawk_addsionamewithbchars(hawk, psin->u.fileb.path, hawk_count_bcstr(psin->u.fileb.path));
				if (HAWK_UNLIKELY(!path)) return -1;
				tmp = open_sio(hawk, path, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR | HAWK_SIO_KEEPPATH);
			}
			if (tmp == HAWK_NULL) return -1;

			if (index >= 1 && is_psin_file(&xtn->s.in.x[index - 1])) hawk_sio_close(arg->handle);

			arg->handle = tmp;
			arg->path = path;
			if (psin->u.fileb.cmgr) hawk_sio_setcmgr(arg->handle, psin->u.fileb.cmgr);
			return 0;
		}

	#if defined(HAWK_OOCH_IS_UCH)
		case HAWK_PARSESTD_FILE:
			HAWK_ASSERT(&psin->u.fileu.path == &psin->u.file.path);
			HAWK_ASSERT(&psin->u.fileu.cmgr == &psin->u.file.cmgr);
	#endif
		case HAWK_PARSESTD_FILEU:
		{
			hawk_sio_t* tmp;
			hawk_ooch_t* path;

			if (psin->u.fileu.path == HAWK_NULL || (psin->u.fileu.path[0] == '-' && psin->u.fileu.path[1] == '\0'))
			{
				/* no path name or - -> stdin */
				path = HAWK_NULL;
				tmp = open_sio_std(hawk, HAWK_SIO_STDIN, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR);
			}
			else
			{
				path = hawk_addsionamewithuchars(hawk, psin->u.fileu.path, hawk_count_ucstr(psin->u.fileu.path));
				if (!path) return -1;
				tmp = open_sio(hawk, path, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR | HAWK_SIO_KEEPPATH);
			}
			if (tmp == HAWK_NULL) return -1;

			if (index >= 1 && is_psin_file(&xtn->s.in.x[index - 1])) hawk_sio_close(arg->handle);

			arg->handle = tmp;
			arg->path = path;
			if (psin->u.fileu.cmgr) hawk_sio_setcmgr(arg->handle, psin->u.fileu.cmgr);
			return 0;
		}

		case HAWK_PARSESTD_OOCS:
			if (index >= 1 && is_psin_file(&xtn->s.in.x[index - 1])) hawk_sio_close(arg->handle);
			xtn->s.in.u.oocs.ptr = psin->u.oocs.ptr;
			xtn->s.in.u.oocs.end = psin->u.oocs.ptr + psin->u.oocs.len;
			return 0;

		case HAWK_PARSESTD_BCS:
			if (index >= 1 && is_psin_file(&xtn->s.in.x[index - 1])) hawk_sio_close(arg->handle);
			xtn->s.in.u.bcs.ptr = psin->u.bcs.ptr;
			xtn->s.in.u.bcs.end = psin->u.bcs.ptr + psin->u.bcs.len;
			return 0;

		case HAWK_PARSESTD_UCS:
			if (index >= 1 && is_psin_file(&xtn->s.in.x[index - 1])) hawk_sio_close(arg->handle);
			xtn->s.in.u.ucs.ptr = psin->u.ucs.ptr;
			xtn->s.in.u.ucs.end = psin->u.ucs.ptr + psin->u.ucs.len;
			return 0;

		default:
			hawk_seterrnum(hawk, HAWK_NULL, HAWK_EINTERN);
			return -1;
	}
}

static int fill_sio_arg_unique_id (hawk_t* hawk, hawk_sio_arg_t* arg, const hawk_ooch_t* path)
{
#if defined(_WIN32)
	return -1;

#elif defined(__OS2__)
	return -1;

#elif defined(__DOS__)
	return -1;

#else
	hawk_stat_t st;
	int x;
	struct
	{
		hawk_uintptr_t ino;
		hawk_uintptr_t dev;
	} tmp;

#if defined(HAWK_OOCH_IS_BCH)
	x = HAWK_STAT(path, &st);
	if (x <= -1) return -1;
#else
	hawk_bch_t* bpath;

	bpath= hawk_duputobcstr(hawk, path, HAWK_NULL);
	if (!bpath) return -1;

	x = HAWK_STAT(bpath, &st);
	hawk_freemem(hawk, bpath);
	if (x <= -1) return -1;
#endif

	tmp.ino = st.st_ino;
	tmp.dev = st.st_dev;

	HAWK_MEMCPY(arg->unique_id, &tmp, (HAWK_SIZEOF(tmp) > HAWK_SIZEOF(arg->unique_id)? HAWK_SIZEOF(arg->unique_id): HAWK_SIZEOF(tmp)));
	return 0;
#endif
}

int hawk_stdplainfileexists (hawk_t* hawk, const hawk_ooch_t* file)
{
#if defined(_WIN32)
	DWORD attr;

	attr = GetFileAttributes(file);
	if (attr == INVALID_FILE_ATTRIBUTES) return 0;
	if (attr & FILE_ATTRIBUTE_DIRECTORY) return 0; /* not a plain file. it's a directory */
	return 1;
#else

	struct stat st;
#if defined(HAWK_OOCH_IS_UCH)
	hawk_bch_t* tmp;
	int n;
	tmp = hawk_duputobcstr(hawk, file, HAWK_NULL);
	if (!tmp) return 0;
	n = stat(tmp, &st);
	hawk_freemem(hawk, tmp);
	if (n == -1) return 0;
#else
	if (stat(file, &st) == -1) return 0;
#endif
	if (S_ISDIR(st.st_mode)) return 0; /* not a plain file, it's a directory */
	return 1;
#endif
}

const hawk_ooch_t* hawk_stdgetfileindirs (hawk_t* hawk, const hawk_oocs_t* dirs, const hawk_ooch_t* file)
{
	/*xtn_t* xtn = GET_XTN(hawk);*/
	const hawk_ooch_t* ptr = dirs->ptr;
	const hawk_ooch_t* dirend = dirs->ptr + dirs->len;
	const hawk_ooch_t* colon, * endptr;

	while (1)
	{
		colon = hawk_find_oochar_in_oocstr(ptr, ':');
		endptr = colon? colon: dirend;

		if (hawk_copyoocharstosbuf(hawk, ptr, endptr - ptr, HAWK_SBUF_ID_TMP_3) <= -1 ||
		    hawk_concatoochartosbuf(hawk, '/', 1, HAWK_SBUF_ID_TMP_3) <= -1 ||
		    hawk_concatoocstrtosbuf(hawk, file, HAWK_SBUF_ID_TMP_3) <= -1) break;

		if (hawk_stdplainfileexists(hawk, hawk->sbuf[HAWK_SBUF_ID_TMP_3].ptr))
		{
			return hawk->sbuf[HAWK_SBUF_ID_TMP_3].ptr;
		}

		if (!colon) break;
		ptr = colon + 1;
	}

	return HAWK_NULL;
}

static hawk_ooi_t sf_in_open (hawk_t* hawk, hawk_sio_arg_t* arg, xtn_t* xtn)
{
	if (arg->prev == HAWK_NULL)
	{
		/* handle top-level source input stream specified to hawk_parsestd() */
		hawk_ooi_t x;

		HAWK_ASSERT(arg == &hawk->sio.arg);

		x = open_parsestd(hawk, arg, xtn, 0);
		if (x >= 0) xtn->s.in.xindex = 0; /* update the current stream index */

		return x;
	}
	else
	{
		/* handle the included source file - @include */
		hawk_ooch_t* xpath;

		HAWK_ASSERT(arg->name != HAWK_NULL);

		if (arg->prev->handle)
		{
			const hawk_ooch_t* outer;
			const hawk_ooch_t* path;
			hawk_ooch_t fbuf[64];
			hawk_ooch_t* dbuf = HAWK_NULL;

			path = arg->name;

			outer = hawk_sio_getpath(arg->prev->handle);
			if (outer)
			{
				const hawk_ooch_t* base;

				base = hawk_get_base_name_oocstr(outer);
				if (base != outer && arg->name[0] != '/')
				{
					hawk_oow_t tmplen, totlen, dirlen;

					dirlen = base - outer;
					totlen = hawk_count_oocstr(arg->name) + dirlen;
					if (totlen >= HAWK_COUNTOF(fbuf))
					{
						dbuf = hawk_allocmem(hawk, HAWK_SIZEOF(hawk_ooch_t) * (totlen + 1));
						if (!dbuf) return -1;
						path = dbuf;
					}
					else path = fbuf;

					tmplen = hawk_copy_oochars_to_oocstr_unlimited((hawk_ooch_t*)path, outer, dirlen);
					hawk_copy_oocstr_unlimited ((hawk_ooch_t*)path + tmplen, arg->name);
				}
			}

			if (!hawk_stdplainfileexists(hawk, path) && hawk->opt.includedirs.len > 0 && arg->name[0] != '/')
			{
				const hawk_ooch_t* tmp;
				tmp = hawk_stdgetfileindirs(hawk, &hawk->opt.includedirs, arg->name);
				if (tmp) path = tmp;
			}

			xpath = hawk_addsionamewithoochars(hawk, path, hawk_count_oocstr(path));
			if (dbuf) hawk_freemem(hawk, dbuf);
		}
		else
		{
			const hawk_ooch_t* path;

			path = arg->name;
			if (!hawk_stdplainfileexists(hawk, path) && hawk->opt.includedirs.len > 0 && arg->name[0] != '/')
			{
				const hawk_ooch_t* tmp;
				tmp = hawk_stdgetfileindirs(hawk, &hawk->opt.includedirs, arg->name);
				if (tmp) path = tmp;
			}

			xpath = hawk_addsionamewithoochars(hawk, path, hawk_count_oocstr(path));
		}
		if (!xpath) goto fail;

		arg->handle = hawk_sio_open(hawk_getgem(hawk), 0, xpath, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR | HAWK_SIO_KEEPPATH);
		if (!arg->handle)
		{
			const hawk_ooch_t* bem;
		fail:
			bem = hawk_backuperrmsg(hawk);
			hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EOPEN, HAWK_T("unable to open %js - %js"), arg->name, bem);
			return -1;
		}

		arg->path = xpath;
		/* TODO: use the system handle(file descriptor) instead of the path? */
		/*syshnd = hawk_sio_gethnd(arg->handle);*/
		fill_sio_arg_unique_id(hawk, arg, xpath); /* ignore failure */

		return 0;
	}
}

static hawk_ooi_t sf_in_close(hawk_t* hawk, hawk_sio_arg_t* arg, xtn_t* xtn)
{
	if (arg->prev == HAWK_NULL)
	{
		switch (xtn->s.in.x[xtn->s.in.xindex].type)
		{
			case HAWK_PARSESTD_FILE:
			case HAWK_PARSESTD_FILEB:
			case HAWK_PARSESTD_FILEU:
				HAWK_ASSERT(arg->handle != HAWK_NULL);
				hawk_sio_close(arg->handle);
				break;

			case HAWK_PARSESTD_OOCS:
			case HAWK_PARSESTD_BCS:
			case HAWK_PARSESTD_UCS:
				/* nothing to close */
				break;

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

	return 0;
}

static hawk_ooi_t sf_in_read (hawk_t* hawk, hawk_sio_arg_t* arg, hawk_ooch_t* data, hawk_oow_t size, xtn_t* xtn)
{
	if (arg->prev == HAWK_NULL)
	{
		hawk_ooi_t n;

		HAWK_ASSERT(arg == &hawk->sio.arg);

	again:
		switch (xtn->s.in.x[xtn->s.in.xindex].type)
		{
		#if defined(HAWK_OOCH_IS_UCH)
			case HAWK_PARSESTD_FILE:
		#endif
			case HAWK_PARSESTD_FILEU:
				HAWK_ASSERT(arg->handle != HAWK_NULL);
				n = hawk_sio_getoochars(arg->handle, data, size);
				if (n <= -1)
				{
					const hawk_uch_t* path;
					const hawk_ooch_t* bem = hawk_backuperrmsg(hawk);
					path = xtn->s.in.x[xtn->s.in.xindex].u.fileu.path;
					if (path)
						hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EREAD, HAWK_T("unable to read %ls - %js"), path, bem);
					else
						hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EREAD, HAWK_T("unable to read %js - %js"), sio_std_names[HAWK_SIO_STDIN].ptr, bem);
				}
				break;

		#if defined(HAWK_OOCH_IS_BCH)
			case HAWK_PARSESTD_FILE:
		#endif
			case HAWK_PARSESTD_FILEB:
				HAWK_ASSERT(arg->handle != HAWK_NULL);
				n = hawk_sio_getoochars(arg->handle, data, size);
				if (n <= -1)
				{
					const hawk_bch_t* path;
					const hawk_ooch_t* bem = hawk_backuperrmsg(hawk);
					path = xtn->s.in.x[xtn->s.in.xindex].u.fileb.path;
					if (path)
						hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EREAD, HAWK_T("unable to read %hs - %js"), path, bem);
					else
						hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EREAD, HAWK_T("unable to read %js - %js"), sio_std_names[HAWK_SIO_STDIN].ptr, bem);
				}
				break;

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

			case HAWK_PARSESTD_BCS:
			#if defined(HAWK_OOCH_IS_BCH)
				goto parsestd_str;
			#else
			{
				int m;
				hawk_oow_t mbslen, wcslen;

				mbslen = xtn->s.in.u.bcs.end - xtn->s.in.u.bcs.ptr;
				wcslen = size;
				if ((m = hawk_conv_bchars_to_uchars_with_cmgr(xtn->s.in.u.bcs.ptr, &mbslen, data, &wcslen, hawk_getcmgr(hawk), 0)) <= -1 && m != -2)
				{
					hawk_seterrnum(hawk, HAWK_NULL, HAWK_EINVAL);
					n = -1;
				}
				else
				{
					xtn->s.in.u.bcs.ptr += mbslen;
					n = wcslen;
				}
				break;
			}
			#endif

			case HAWK_PARSESTD_UCS:
			#if defined(HAWK_OOCH_IS_BCH)
			{
				int m;
				hawk_oow_t mbslen, wcslen;

				wcslen = xtn->s.in.u.ucs.end - xtn->s.in.u.ucs.ptr;
				mbslen = size;
				if ((m = hawk_conv_uchars_to_bchars_with_cmgr(xtn->s.in.u.ucs.ptr, &wcslen, data, &mbslen, hawk_getcmgr(hawk))) <= -1 && m != -2)
				{
					hawk_seterrnum(hawk, HAWK_NULL, HAWK_EINVAL);
					n = -1;
				}
				else
				{
					xtn->s.in.u.ucs.ptr += wcslen;
					n = mbslen;
				}
				break;
			}
			#else
				goto parsestd_str;
			#endif

			default:
				/* this should never happen */
				hawk_seterrnum(hawk, HAWK_NULL, HAWK_EINTERN);
				n = -1;
				break;
		}

		if (n == 0)
		{
		 	/* reached end of the current stream. */
			hawk_oow_t next = xtn->s.in.xindex + 1;
			if (xtn->s.in.x[next].type != HAWK_PARSESTD_NULL)
			{
				/* open the next stream if available. */
				if (open_parsestd(hawk, arg, xtn, next) <= -1) n = -1;
				else
				{
					xtn->s.in.xindex = next; /* update the next to the current */
					arg->line = 0; /* reset the line number */
					arg->colm = 0;
					goto again;
				}
			}
		}

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

		HAWK_ASSERT(arg->name != HAWK_NULL);
		HAWK_ASSERT(arg->handle != HAWK_NULL);

		n = hawk_sio_getoochars(arg->handle, data, size);

		if (n <= -1)
		{
			const hawk_ooch_t* bem = hawk_backuperrmsg(hawk);
			hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EREAD, HAWK_T("unable to read %js - %js"), arg->name, bem);
		}
		return n;
	}
}

static hawk_ooi_t sf_in (hawk_t* hawk, hawk_sio_cmd_t cmd, hawk_sio_arg_t* arg, hawk_ooch_t* data, hawk_oow_t size)
{
	xtn_t* xtn = GET_XTN(hawk);

	switch (cmd)
	{
		case HAWK_SIO_CMD_OPEN:
			return sf_in_open(hawk, arg, xtn);

		case HAWK_SIO_CMD_CLOSE:
			return sf_in_close(hawk, arg, xtn);

		case HAWK_SIO_CMD_READ:
			return sf_in_read(hawk, arg, data, size, xtn);

		default:
			hawk_seterrnum(hawk, HAWK_NULL, HAWK_EINTERN);
			return -1;
	}
}

static hawk_ooi_t sf_out (hawk_t* hawk, hawk_sio_cmd_t cmd, hawk_sio_arg_t* arg, hawk_ooch_t* data, hawk_oow_t size)
{
	xtn_t* xtn = GET_XTN(hawk);

	switch (cmd)
	{
		case HAWK_SIO_CMD_OPEN:
		{
			switch (xtn->s.out.x->type)
			{
			#if defined(HAWK_OOCH_IS_BCH)
				case HAWK_PARSESTD_FILE:
					HAWK_ASSERT(&xtn->s.out.x->u.fileb.path == &xtn->s.out.x->u.file.path);
					HAWK_ASSERT(&xtn->s.out.x->u.fileb.cmgr == &xtn->s.out.x->u.file.cmgr);
			#endif
				case HAWK_PARSESTD_FILEB:
					if (xtn->s.out.x->u.fileb.path == HAWK_NULL || (xtn->s.out.x->u.fileb.path[0] == '-' && xtn->s.out.x->u.fileb.path[1] == '\0'))
					{
						/* no path name or - -> stdout */
						xtn->s.out.u.file.sio = open_sio_std(hawk, HAWK_SIO_STDOUT, HAWK_SIO_WRITE | HAWK_SIO_IGNOREECERR | HAWK_SIO_LINEBREAK);
						if (xtn->s.out.u.file.sio == HAWK_NULL) return -1;
					}
					else
					{
					#if defined(HAWK_OOCH_IS_UCH)
						hawk_uch_t* upath;
						upath = hawk_dupbtoucstr(hawk, xtn->s.out.x->u.fileb.path, HAWK_NULL, 1);
						if (!upath) return -1;
						xtn->s.out.u.file.sio = open_sio(hawk, upath, HAWK_SIO_WRITE | HAWK_SIO_CREATE | HAWK_SIO_TRUNCATE | HAWK_SIO_IGNOREECERR);
						hawk_freemem(hawk, upath);
					#else
						xtn->s.out.u.file.sio = open_sio(hawk, xtn->s.out.x->u.fileb.path, HAWK_SIO_WRITE | HAWK_SIO_CREATE | HAWK_SIO_TRUNCATE | HAWK_SIO_IGNOREECERR);
					#endif
						if (xtn->s.out.u.file.sio == HAWK_NULL) return -1;
					}

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

			#if defined(HAWK_OOCH_IS_UCH)
				case HAWK_PARSESTD_FILE:
					HAWK_ASSERT(&xtn->s.out.x->u.fileu.path == &xtn->s.out.x->u.file.path);
					HAWK_ASSERT(&xtn->s.out.x->u.fileu.cmgr == &xtn->s.out.x->u.file.cmgr);
			#endif
				case HAWK_PARSESTD_FILEU:
					if (xtn->s.out.x->u.fileu.path == HAWK_NULL || (xtn->s.out.x->u.fileu.path[0] == '-' && xtn->s.out.x->u.fileu.path[1] == '\0'))
					{
						/* no path name or - -> stdout */
						xtn->s.out.u.file.sio = open_sio_std(hawk, HAWK_SIO_STDOUT, HAWK_SIO_WRITE | HAWK_SIO_IGNOREECERR | HAWK_SIO_LINEBREAK);
						if (xtn->s.out.u.file.sio == HAWK_NULL) return -1;
					}
					else
					{
					#if defined(HAWK_OOCH_IS_UCH)
						xtn->s.out.u.file.sio = open_sio(hawk, xtn->s.out.x->u.fileu.path, HAWK_SIO_WRITE | HAWK_SIO_CREATE | HAWK_SIO_TRUNCATE | HAWK_SIO_IGNOREECERR);
					#else
						hawk_bch_t* bpath;
						bpath = hawk_duputobcstr(hawk, xtn->s.out.x->u.fileu.path, HAWK_NULL);
						if (!bpath) return -1;
						xtn->s.out.u.file.sio = open_sio(hawk, bpath, HAWK_SIO_WRITE | HAWK_SIO_CREATE | HAWK_SIO_TRUNCATE | HAWK_SIO_IGNOREECERR);
						hawk_freemem(hawk, bpath);
					#endif
						if (xtn->s.out.u.file.sio == HAWK_NULL) return -1;
					}

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

				case HAWK_PARSESTD_OOCS:
					xtn->s.out.u.oocs.buf = hawk_ooecs_open(hawk_getgem(hawk), 0, 512);
					if (xtn->s.out.u.oocs.buf == HAWK_NULL) return -1;
					return 1;

				case HAWK_PARSESTD_BCS:
					xtn->s.out.u.bcs.buf = hawk_becs_open(hawk_getgem(hawk), 0, 512);
					if (xtn->s.out.u.bcs.buf == HAWK_NULL) return -1;
					return 1;

				case HAWK_PARSESTD_UCS:
					xtn->s.out.u.ucs.buf = hawk_uecs_open(hawk_getgem(hawk), 0, 512);
					if (xtn->s.out.u.ucs.buf == HAWK_NULL) return -1;
					return 1;

				default:
					goto internal_error;
			}

			break;
		}

		case HAWK_SIO_CMD_CLOSE:
		{
			switch (xtn->s.out.x->type)
			{
				case HAWK_PARSESTD_FILE:
				case HAWK_PARSESTD_FILEB:
				case HAWK_PARSESTD_FILEU:
					hawk_sio_close(xtn->s.out.u.file.sio);
					return 0;

				case HAWK_PARSESTD_OOCS:
				case HAWK_PARSESTD_BCS:
				case HAWK_PARSESTD_UCS:
					/* i don't close xtn->s.out.u.oocs.buf intentionally here.
					 * it will be closed at the end of hawk_parsestd() */
					return 0;

				default:
					goto internal_error;
			}

			break;
		}

		case HAWK_SIO_CMD_WRITE:
		{
			switch (xtn->s.out.x->type)
			{
				case HAWK_PARSESTD_FILE:
				case HAWK_PARSESTD_FILEB:
				case HAWK_PARSESTD_FILEU:
				{
					hawk_ooi_t n;
					HAWK_ASSERT(xtn->s.out.u.file.sio != HAWK_NULL);
					n = hawk_sio_putoochars(xtn->s.out.u.file.sio, data, size);
					if (n <= -1)
					{
						const hawk_ooch_t* ioname;
						const hawk_ooch_t* bem = hawk_backuperrmsg(hawk);
						ioname = xtn->s.out.x->u.file.path;
						if (!ioname) ioname = sio_std_names[HAWK_SIO_STDOUT].ptr;
						hawk_seterrfmt(hawk, HAWK_NULL, HAWK_EWRITE, HAWK_T("unable to write to %js - %js"), ioname, bem);
					}
					return n;
				}

				case HAWK_PARSESTD_OOCS:
				parsestd_str:
					if (size > HAWK_TYPE_MAX(hawk_ooi_t)) size = HAWK_TYPE_MAX(hawk_ooi_t);
					if (hawk_ooecs_ncat(xtn->s.out.u.oocs.buf, data, size) == (hawk_oow_t)-1) return -1;
					return size;

				case HAWK_PARSESTD_BCS:
				#if defined(HAWK_OOCH_IS_BCH)
					goto parsestd_str;
				#else
				{
					hawk_oow_t mbslen, wcslen;
					hawk_oow_t orglen;

					wcslen = size;
					if (hawk_convutobchars(hawk, data, &wcslen, HAWK_NULL, &mbslen) <= -1) return -1;
					if (mbslen > HAWK_TYPE_MAX(hawk_ooi_t)) mbslen = HAWK_TYPE_MAX(hawk_ooi_t);

					orglen = hawk_becs_getlen(xtn->s.out.u.bcs.buf);
					if (hawk_becs_setlen(xtn->s.out.u.bcs.buf, orglen + mbslen) == (hawk_oow_t)-1) return -1;

					wcslen = size;
					hawk_convutobchars(hawk, data, &wcslen, HAWK_BECS_CPTR(xtn->s.out.u.bcs.buf, orglen), &mbslen);
					size = wcslen;

					return size;
				}
				#endif

				case HAWK_PARSESTD_UCS:
				#if defined(HAWK_OOCH_IS_BCH)
				{
					hawk_oow_t mbslen, wcslen;
					hawk_oow_t orglen;

					mbslen = size;
					if (hawk_convbtouchars(hawk, data, &mbslen, HAWK_NULL, &wcslen, 0) <= -1) return -1;
					if (wcslen > HAWK_TYPE_MAX(hawk_ooi_t)) wcslen = HAWK_TYPE_MAX(hawk_ooi_t);

					orglen = hawk_uecs_getlen(xtn->s.out.u.ucs.buf);
					if (hawk_uecs_setlen(xtn->s.out.u.ucs.buf, orglen + wcslen) == (hawk_oow_t)-1) return -1;

					mbslen = size;
					hawk_convbtouchars(hawk, data, &mbslen, HAWK_UECS_CPTR(xtn->s.out.u.ucs.buf, orglen), &wcslen, 0);
					size = mbslen;

					return size;
				}
				#else
					goto parsestd_str;
				#endif

				default:
					goto internal_error;
			}

			break;
		}

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

internal_error:
	hawk_seterrnum(hawk, HAWK_NULL, HAWK_EINTERN);
	return -1;
}

int hawk_parsestd (hawk_t* hawk, hawk_parsestd_t in[], hawk_parsestd_t* out)
{
	hawk_sio_cbs_t sio;
	xtn_t* xtn = GET_XTN(hawk);
	int n;

	if (in == HAWK_NULL || (in[0].type != HAWK_PARSESTD_FILE &&
	                        in[0].type != HAWK_PARSESTD_FILEB &&
	                        in[0].type != HAWK_PARSESTD_FILEU &&
	                        in[0].type != HAWK_PARSESTD_OOCS &&
	                        in[0].type != HAWK_PARSESTD_BCS &&
	                        in[0].type != HAWK_PARSESTD_UCS))
	{
		/* the input is a must. at least 1 file or 1 string
		 * must be specified */
		hawk_seterrnum(hawk, HAWK_NULL, HAWK_EINVAL);
		return -1;
	}

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

	if (out == HAWK_NULL) sio.out = HAWK_NULL;
	else
	{
		if (out->type != HAWK_PARSESTD_FILE &&
		    out->type != HAWK_PARSESTD_FILEB &&
		    out->type != HAWK_PARSESTD_FILEU &&
		    out->type != HAWK_PARSESTD_OOCS &&
		    out->type != HAWK_PARSESTD_BCS &&
		    out->type != HAWK_PARSESTD_UCS)
		{
			hawk_seterrnum(hawk, HAWK_NULL, HAWK_EINVAL);
			return -1;
		}
		sio.out = sf_out;
		xtn->s.out.x = out;
	}

	n = hawk_parse(hawk, &sio);

	if (out)
	{
		switch (out->type)
		{
			case HAWK_PARSESTD_OOCS:
				if (n >= 0)
				{
					HAWK_ASSERT(xtn->s.out.u.oocs.buf != HAWK_NULL);
					hawk_ooecs_yield (xtn->s.out.u.oocs.buf, &out->u.oocs, 0);
				}
				if (xtn->s.out.u.oocs.buf) hawk_ooecs_close(xtn->s.out.u.oocs.buf);
				break;

			case HAWK_PARSESTD_BCS:
				if (n >= 0)
				{
					HAWK_ASSERT(xtn->s.out.u.bcs.buf != HAWK_NULL);
					hawk_becs_yield (xtn->s.out.u.bcs.buf, &out->u.bcs, 0);
				}
				if (xtn->s.out.u.bcs.buf) hawk_becs_close(xtn->s.out.u.bcs.buf);
				break;

			case HAWK_PARSESTD_UCS:
				if (n >= 0)
				{
					HAWK_ASSERT(xtn->s.out.u.ucs.buf != HAWK_NULL);
					hawk_uecs_yield (xtn->s.out.u.ucs.buf, &out->u.ucs, 0);
				}
				if (xtn->s.out.u.ucs.buf) hawk_uecs_close(xtn->s.out.u.ucs.buf);
				break;

			default:
				/* do nothing */
				break;
		}
	}

	return n;
}

static int check_var_assign (hawk_rtx_t* rtx, const hawk_ooch_t* str)
{
	hawk_ooch_t* eq, * dstr;
	int n;

	eq = hawk_find_oochar_in_oocstr(str, '=');
	if (!eq || eq <= str) return 0; /* not assignment */

	dstr = hawk_rtx_dupoocstr(rtx, str, HAWK_NULL);
	if (HAWK_UNLIKELY(!dstr)) return -1;

	eq = dstr + (eq - str);
	*eq = '\0';

	if (hawk_isvalidident(hawk_rtx_gethawk(rtx), dstr))
	{
		hawk_unescape_oocstr(eq + 1);
		n = (hawk_rtx_setgbltostrbyname(rtx, dstr, eq + 1) <= -1)? -1: 1;
	}
	else
	{
		n = 0;
	}

	hawk_rtx_freemem(rtx, dstr);
	return n;
}

/*** RTX_OPENSTD ***/

static ioattr_t* get_ioattr (hawk_htb_t* tab, const hawk_ooch_t* ptr, hawk_oow_t len);

#if defined(ENABLE_NWIO)
static hawk_ooi_t nwio_handler_open (hawk_rtx_t* rtx, hawk_rio_arg_t* riod, int flags, hawk_nwad_t* nwad, hawk_nwio_tmout_t* tmout)
{
	hawk_nwio_t* handle;

	handle = hawk_nwio_open (
		hawk_rtx_getmmgr(rtx), 0, nwad,
		flags | HAWK_NWIO_TEXT | HAWK_NWIO_IGNOREECERR |
		HAWK_NWIO_REUSEADDR | HAWK_NWIO_READNORETRY | HAWK_NWIO_WRITENORETRY,
		tmout
	);
	if (handle == HAWK_NULL) return -1;

#if defined(HAWK_OOCH_IS_UCH)
	{
		hawk_cmgr_t* cmgr = hawk_rtx_getiocmgrstd(rtx, riod->name);
		if (cmgr) hawk_nwio_setcmgr(handle, cmgr);
	}
#endif

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

static hawk_ooi_t nwio_handler_rest (hawk_rtx_t* rtx, hawk_rio_cmd_t cmd, hawk_rio_arg_t* riod, void* data, hawk_oow_t size)
{
	switch (cmd)
	{
		case HAWK_RIO_CMD_OPEN:
			hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
			return -1;

		case HAWK_RIO_CMD_CLOSE:
			hawk_nwio_close((hawk_nwio_t*)riod->handle);
			return 0;

		case HAWK_RIO_CMD_READ:
			return hawk_nwio_read((hawk_nwio_t*)riod->handle, data, size);

		case HAWK_RIO_CMD_READBYTES:
			return hawk_nwio_readbytes(((hawk_nwio_t*)riod->handle, data, size);

		case HAWK_RIO_CMD_WRITE:
			return hawk_nwio_write((hawk_nwio_t*)riod->handle, data, size);

		case HAWK_RIO_CMD_WRITE_BYTES:
			return hawk_nwio_writebytes((hawk_nwio_t*)riod->handle, data, size);

		case HAWK_RIO_CMD_FLUSH:
			/*if (riod->mode == HAWK_RIO_PIPE_READ) return -1;*/
			return hawk_nwio_flush((hawk_nwio_t*)riod->handle);

		case HAWK_RIO_CMD_NEXT:
			break;
	}

	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
	return -1;
}

static int parse_rwpipe_uri (const hawk_ooch_t* uri, int* flags, hawk_nwad_t* nwad)
{
	static struct
	{
		const hawk_ooch_t* prefix;
		hawk_oow_t        len;
		int               flags;
	} x[] =
	{
		{ HAWK_T("tcp://"),  6, HAWK_NWIO_TCP },
		{ HAWK_T("udp://"),  6, HAWK_NWIO_UDP },
		{ HAWK_T("tcpd://"), 7, HAWK_NWIO_TCP | HAWK_NWIO_PASSIVE },
		{ HAWK_T("udpd://"), 7, HAWK_NWIO_UDP | HAWK_NWIO_PASSIVE }
	};
	int i;


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

	return -1;
}
#endif

static hawk_ooi_t pio_handler_open (hawk_rtx_t* rtx, hawk_rio_arg_t* riod)
{
	hawk_pio_t* handle;
	int flags;

	if (riod->mode == HAWK_RIO_PIPE_READ)
	{
		/* TODO: should ERRTOOUT be unset? */
		flags = HAWK_PIO_READOUT | HAWK_PIO_ERRTOOUT;
	}
	else if (riod->mode == HAWK_RIO_PIPE_WRITE)
	{
		flags = HAWK_PIO_WRITEIN;
	}
	else if (riod->mode == HAWK_RIO_PIPE_RW)
	{
		flags = HAWK_PIO_READOUT | HAWK_PIO_ERRTOOUT | HAWK_PIO_WRITEIN;
	}
	else
	{
		/* this must not happen */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
		return -1;
	}

	handle = hawk_pio_open(
		hawk_rtx_getgem(rtx),
		0,
		riod->name,
		flags | HAWK_PIO_SHELL | HAWK_PIO_TEXT | HAWK_PIO_IGNOREECERR
	);
	if (handle == HAWK_NULL) return -1;

#if defined(HAWK_OOCH_IS_UCH)
	{
		hawk_cmgr_t* cmgr = hawk_rtx_getiocmgrstd(rtx, riod->name);
		if (cmgr)
		{
			hawk_pio_setcmgr(handle, HAWK_PIO_IN, cmgr);
			hawk_pio_setcmgr(handle, HAWK_PIO_OUT, cmgr);
			hawk_pio_setcmgr(handle, HAWK_PIO_ERR, cmgr);
		}
	}
#endif

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

static hawk_ooi_t pio_handler_rest (hawk_rtx_t* rtx, hawk_rio_cmd_t cmd, hawk_rio_arg_t* riod, void* data, hawk_oow_t size)
{
	switch (cmd)
	{
		case HAWK_RIO_CMD_OPEN:
			hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
			return -1;

		case HAWK_RIO_CMD_CLOSE:
		{
			hawk_pio_t* pio = (hawk_pio_t*)riod->handle;
			if (riod->mode == HAWK_RIO_PIPE_RW)
			{
				/* specialy treatment is needed for rwpipe.
				 * inspect rwcmode to see if partial closing is
				 * requested. */
				if (riod->rwcmode == HAWK_RIO_CMD_CLOSE_READ)
				{
					hawk_pio_end(pio, HAWK_PIO_IN);
					return 0;
				}
				if (riod->rwcmode == HAWK_RIO_CMD_CLOSE_WRITE)
				{
					hawk_pio_end(pio, HAWK_PIO_OUT);
					return 0;
				}
			}

			hawk_pio_close(pio);
			return 0;
		}

		case HAWK_RIO_CMD_READ:
			return hawk_pio_read((hawk_pio_t*)riod->handle, HAWK_PIO_OUT, data, size);

		case HAWK_RIO_CMD_READ_BYTES:
			return hawk_pio_readbytes((hawk_pio_t*)riod->handle, HAWK_PIO_OUT, data, size);

		case HAWK_RIO_CMD_WRITE:
			return hawk_pio_write((hawk_pio_t*)riod->handle, HAWK_PIO_IN, data, size);

		case HAWK_RIO_CMD_WRITE_BYTES:
			return hawk_pio_writebytes((hawk_pio_t*)riod->handle, HAWK_PIO_IN, data, size);

		case HAWK_RIO_CMD_FLUSH:
			/*if (riod->mode == HAWK_RIO_PIPE_READ) return -1;*/
			return hawk_pio_flush ((hawk_pio_t*)riod->handle, HAWK_PIO_IN);

		case HAWK_RIO_CMD_NEXT:
			break;
	}

	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
	return -1;
}

static hawk_ooi_t hawk_rio_pipe (hawk_rtx_t* rtx, hawk_rio_cmd_t cmd, hawk_rio_arg_t* riod, void* data, hawk_oow_t size)
{
	if (cmd == HAWK_RIO_CMD_OPEN)
	{
	#if defined(ENABLE_NWIO)
		int flags;
		hawk_nwad_t nwad;

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

			rxtn = GET_RXTN(rtx);

			ioattr = get_ioattr(&rxtn->cmgrtab, riod->name, hawk_count_oocstr(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
		return pio_handler_open(rtx, riod);
	#endif
	}
	#if defined(ENABLE_NWIO)
	else if (riod->uflags > 0)
		return nwio_handler_rest(rtx, cmd, riod, data, size);
	#endif
	else
		return pio_handler_rest(rtx, cmd, riod, data, size);
}

static void set_rio_error (hawk_rtx_t* rtx, hawk_errnum_t errnum, const hawk_ooch_t* errmsg, const hawk_ooch_t* path)
{
	const hawk_ooch_t* bem = hawk_rtx_backuperrmsg(rtx);
	hawk_rtx_seterrfmt(rtx, HAWK_NULL, errnum, HAWK_T("%js%js%js - %js"),
		errmsg, (path? HAWK_T(" "): HAWK_T("")), (path? path: HAWK_T("")), bem);
}

static hawk_ooi_t hawk_rio_file (hawk_rtx_t* rtx, hawk_rio_cmd_t cmd, hawk_rio_arg_t* riod, void* data, hawk_oow_t size)
{
	switch (cmd)
	{
		case HAWK_RIO_CMD_OPEN:
		{
			hawk_sio_t* handle;
			int flags = HAWK_SIO_IGNOREECERR;

			switch (riod->mode)
			{
				case HAWK_RIO_FILE_READ:
					flags |= HAWK_SIO_READ;
					break;
				case HAWK_RIO_FILE_WRITE:
					flags |= HAWK_SIO_WRITE | HAWK_SIO_CREATE | HAWK_SIO_TRUNCATE;
					break;
				case HAWK_RIO_FILE_APPEND:
					flags |= HAWK_SIO_APPEND | HAWK_SIO_CREATE;
					break;
				default:
					/* this must not happen */
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
					return -1;
			}

			if (riod->name[0] == '-' && riod->name[1] == '\0')
			{
				if (riod->mode == HAWK_RIO_FILE_READ)
					handle = open_sio_std_rtx(rtx, HAWK_SIO_STDIN, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR);
				else
					handle = open_sio_std_rtx(rtx, HAWK_SIO_STDOUT, HAWK_SIO_WRITE | HAWK_SIO_IGNOREECERR | HAWK_SIO_LINEBREAK);
			}
			else
			{
				handle = hawk_sio_open(hawk_rtx_getgem(rtx), 0, riod->name, flags);
			}
			if (!handle)
			{
				set_rio_error(rtx, HAWK_EOPEN, HAWK_T("unable to open"), riod->name);
				return -1;
			}

		#if defined(HAWK_OOCH_IS_UCH)
			{
				hawk_cmgr_t* cmgr = hawk_rtx_getiocmgrstd(rtx, riod->name);
				if (cmgr) hawk_sio_setcmgr(handle, cmgr);
			}
		#endif

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

		case HAWK_RIO_CMD_CLOSE:
			hawk_sio_close((hawk_sio_t*)riod->handle);
			riod->handle = HAWK_NULL;
			return 0;

		case HAWK_RIO_CMD_READ:
		{
			hawk_ooi_t t;
			t = hawk_sio_getoochars((hawk_sio_t*)riod->handle, data, size);
			if (t <= -1) set_rio_error(rtx, HAWK_EOPEN, HAWK_T("unable to read"), riod->name);
			return t;
		}

		case HAWK_RIO_CMD_READ_BYTES:
		{
			hawk_ooi_t t;
			t = hawk_sio_getbchars((hawk_sio_t*)riod->handle, data, size);
			if (t <= -1) set_rio_error(rtx, HAWK_EOPEN, HAWK_T("unable to read"), riod->name);
			return t;
		}

		case HAWK_RIO_CMD_WRITE:
		{
			hawk_ooi_t t;
			t = hawk_sio_putoochars((hawk_sio_t*)riod->handle, data, size);
			if (t <= -1) set_rio_error(rtx, HAWK_EOPEN, HAWK_T("unable to write"), riod->name);
			return t;
		}

		case HAWK_RIO_CMD_WRITE_BYTES:
		{
			hawk_ooi_t t;
			t = hawk_sio_putbchars((hawk_sio_t*)riod->handle, data, size);
			if (t <= -1) set_rio_error(rtx, HAWK_EOPEN, HAWK_T("unable to write"), riod->name);
			return t;
		}

		case HAWK_RIO_CMD_FLUSH:
		{
			int n;

			n = hawk_sio_flush((hawk_sio_t*)riod->handle);
			if (HAWK_UNLIKELY(n <= -1))
			{
				/* if flushing fails, discard the buffered data
				 * keeping the unflushed data causes causes subsequent write or close() to
				 * flush again and again */
				hawk_sio_drain ((hawk_sio_t*)riod->handle);
			}
			return n;
		}

		case HAWK_RIO_CMD_NEXT:
			return -1;
	}

	return -1;
}

static int open_rio_console (hawk_rtx_t* rtx, hawk_rio_arg_t* riod)
{
	rxtn_t* rxtn = GET_RXTN(rtx);
	hawk_sio_t* sio;

	if (riod->mode == HAWK_RIO_CONSOLE_READ)
	{
	#if 1
		/* this part is more complex than the code in the else part.
		 * it handles simple assignemnt as well as open files
		 * via in ARGV instead of rxtn->c.in.files */
		xtn_t* xtn = (xtn_t*)GET_XTN(hawk_rtx_gethawk(rtx));
		hawk_val_t* v_argc, * v_argv, * v_pair;
		hawk_int_t i_argc;
		const hawk_ooch_t* file;
		hawk_map_t* map;
		hawk_map_pair_t* pair;
		hawk_ooch_t ibuf[128];
		hawk_oow_t ibuflen;
		hawk_oocs_t as;
		int x;

		v_argc = hawk_rtx_getgbl(rtx, xtn->gbl_argc);
		HAWK_ASSERT(v_argc != HAWK_NULL);
		if (hawk_rtx_valtoint(rtx, v_argc, &i_argc) <= -1) return -1;

		/* 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
		 */
		v_argv = hawk_rtx_getgbl(rtx, xtn->gbl_argv);
		HAWK_ASSERT(v_argv != HAWK_NULL);
		if (HAWK_RTX_GETVALTYPE(rtx, v_argv) != HAWK_VAL_MAP)
		{
			/* with flexmap on, you can change ARGV to a scalar.
			 *   BEGIN { ARGV="xxx"; }
			 * you must not do this. */
			hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EINVAL, HAWK_T("phony value in ARGV"));
			return -1;
		}

		map = ((hawk_val_map_t*)v_argv)->map;
		HAWK_ASSERT(map != HAWK_NULL);

	nextfile:
		if (rxtn->c.in.index >= (i_argc - 1))  /* ARGV is a kind of 0-based array unlike other normal arrays or substring indexing scheme */
		{
			/* reached the last ARGV */
			if (rxtn->c.in.count <= 0) /* but no file has been ever opened */
			{
			console_open_stdin:
				/* open stdin */
				sio = open_sio_std_rtx(rtx, HAWK_SIO_STDIN, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR);
				if (HAWK_UNLIKELY(!sio)) return -1;

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

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

			return 0;
		}

		ibuflen = hawk_int_to_oocstr(rxtn->c.in.index + 1, 10, HAWK_NULL, ibuf, HAWK_COUNTOF(ibuf));

		pair = hawk_map_search(map, ibuf, ibuflen);
		if (!pair)
		{
			if (rxtn->c.in.index < i_argc)
			{
				/* not found but still didn't hit ARGC */
				rxtn->c.in.index++;
				goto nextfile;
			}

			/* the key doesn't exist any more */
			if (rxtn->c.in.count <= 0) goto console_open_stdin;
			return 0; /* end of console */
		}

		v_pair = HAWK_HTB_VPTR(pair);
		HAWK_ASSERT(v_pair != HAWK_NULL);

		as.ptr = hawk_rtx_getvaloocstr(rtx, v_pair, &as.len);
		if (HAWK_UNLIKELY(!as.ptr)) return -1;

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

		if (hawk_count_oocstr(as.ptr) < as.len)
		{
			/* the name contains one or more '\0' */
			hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EIONMNL, HAWK_T("invalid I/O name of length %zu containing '\\0'"), as.len);
			hawk_rtx_freevaloocstr(rtx, v_pair, as.ptr);
			return -1;
		}

		file = as.ptr;

		/*
		 * this is different from the -v option.
		 * if an argument has a special form of var=val, it is treated specially
		 *
		 * on the command-line
		 *   hawk -f a.hawk a=20 /etc/passwd
		 * or via ARGV
		 *    hawk 'BEGIN { ARGV[1] = "a=20"; }
		 *                { print a $1; }' dummy /etc/hosts
		 */
		if ((x = check_var_assign(rtx, file)) != 0)
		{
			hawk_rtx_freevaloocstr(rtx, v_pair, as.ptr);
			if (HAWK_UNLIKELY(x <= -1)) return -1;
			rxtn->c.in.index++;
			goto nextfile;
		}

		/* a temporary variable sio is used here not to change
		 * any fields of riod when the open operation fails */
		sio = (file[0] == HAWK_T('-') && file[1] == HAWK_T('\0'))?
			open_sio_std_rtx(rtx, HAWK_SIO_STDIN, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR):
			open_sio_rtx(rtx, file, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR | HAWK_SIO_KEEPPATH);
		if (HAWK_UNLIKELY(!sio))
		{
			hawk_rtx_freevaloocstr(rtx, v_pair, as.ptr);
			return -1;
		}

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

		if (hawk_rtx_setfilenamewithoochars(rtx, file, hawk_count_oocstr(file)) <= -1)
		{
			hawk_sio_close(sio);
			hawk_rtx_freevaloocstr(rtx, v_pair, as.ptr);
			return -1;
		}

		hawk_rtx_freevaloocstr(rtx, v_pair, as.ptr);
		riod->handle = sio;

		/* increment the counter of files successfully opened */
		rxtn->c.in.count++;
		rxtn->c.in.index++;
		return 1;
	#else
		/* simple console open implementation.
		 * no var=val handling. no ARGV handling */

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

			if (rxtn->c.in.count == 0)
			{
			console_open_stdin:
				sio = open_sio_std_rtx(rtx, HAWK_SIO_STDIN, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR);
				if (sio == HAWK_NULL) return -1;

				if (rxtn->c.cmgr) hawk_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 hawk_ooch_t* file;

		nextfile:
			file = rxtn->c.in.files[rxtn->c.in.index];
			if (!file)
			{
				/* no more input file */
				if (rxtn->c.in.count == 0) goto console_open_stdin;
				return 0;
			}

			if (file[0] == '\0')
			{
				rxtn->c.in.index++;
				goto nextfile;
			}

			sio = (file[0] == '-' && file[1] == '\0')?
				open_sio_std_rtx(rtx, HAWK_SIO_STDIN, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR):
				open_sio_rtx(rtx, file, HAWK_SIO_READ | HAWK_SIO_IGNOREECERR);
			if (HAWK_UNLIKELY(!sio)) return -1;

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

			if (hawk_rtx_setfilenamewithoochars(rtx, file, hawk_count_oocstr(file)) <= -1)
			{
				hawk_sio_close(sio);
				return -1;
			}

			riod->handle = sio;

			/* increment the counter of files successfully opened */
			rxtn->c.in.count++;
			rxtn->c.in.index++;
			return 1;
		}
	#endif
	}
	else if (riod->mode == HAWK_RIO_CONSOLE_WRITE)
	{
		if (rxtn->c.out.files == HAWK_NULL)
		{
			HAWK_ASSERT(rxtn->c.out.index == 0);

			if (rxtn->c.out.count == 0)
			{
				sio = open_sio_std_rtx(
					rtx, HAWK_SIO_STDOUT,
					HAWK_SIO_WRITE | HAWK_SIO_IGNOREECERR | HAWK_SIO_LINEBREAK
				);
				if (sio == HAWK_NULL) return -1;

				if (rxtn->c.cmgr) hawk_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 */
			hawk_sio_t* sio;
			const hawk_ooch_t* file;

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

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

			sio = (file[0] == HAWK_T('-') && file[1] == HAWK_T('\0'))?
				open_sio_std_rtx(rtx, HAWK_SIO_STDOUT, HAWK_SIO_WRITE | HAWK_SIO_IGNOREECERR | HAWK_SIO_LINEBREAK):
				open_sio_rtx(rtx, file, HAWK_SIO_WRITE | HAWK_SIO_CREATE | HAWK_SIO_TRUNCATE | HAWK_SIO_IGNOREECERR);
			if (sio == HAWK_NULL) return -1;

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

			if (hawk_rtx_setofilenamewithoochars(rtx, file, hawk_count_oocstr(file)) <= -1)
			{
				hawk_sio_close(sio);
				return -1;
			}

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

	}

	return -1;
}

static hawk_ooi_t hawk_rio_console (hawk_rtx_t* rtx, hawk_rio_cmd_t cmd, hawk_rio_arg_t* riod, void* data, hawk_oow_t size)
{
	switch (cmd)
	{
		case HAWK_RIO_CMD_OPEN:
			return open_rio_console(rtx, riod);

		case HAWK_RIO_CMD_CLOSE:
			if (riod->handle) hawk_sio_close((hawk_sio_t*)riod->handle);
			return 0;

		case HAWK_RIO_CMD_READ:
		{
			hawk_ooi_t nn;

			while ((nn = hawk_sio_getoochars((hawk_sio_t*)riod->handle, data, size)) == 0)
			{
				int n;
				hawk_sio_t* sio = (hawk_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) hawk_sio_close(sio);

				/* reset FNR to 0 here since the caller doesn't know that the file has changed. */
				hawk_rtx_setgbl(rtx, HAWK_GBL_FNR, hawk_rtx_makeintval(rtx, 0));
				riod->console_switched = 1;
			}

			if (nn <= -1) set_rio_error(rtx, HAWK_EREAD, HAWK_T("unable to read"), hawk_sio_getpath((hawk_sio_t*)riod->handle));
			return nn;
		}

		case HAWK_RIO_CMD_READ_BYTES:
		{
			hawk_ooi_t nn;

			while ((nn = hawk_sio_getbchars((hawk_sio_t*)riod->handle, data, size)) == 0)
			{
				int n;
				hawk_sio_t* sio = (hawk_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) hawk_sio_close(sio);

				hawk_rtx_setgbl(rtx, HAWK_GBL_FNR, hawk_rtx_makeintval(rtx, 0));
				riod->console_switched = 1;
			}

			if (nn <= -1) set_rio_error(rtx, HAWK_EREAD, HAWK_T("unable to read"), hawk_sio_getpath((hawk_sio_t*)riod->handle));
			return nn;
		}

		case HAWK_RIO_CMD_WRITE:
		{
			hawk_ooi_t nn;
			nn = hawk_sio_putoochars((hawk_sio_t*)riod->handle, data, size);
			if (nn <= -1) set_rio_error(rtx, HAWK_EREAD, HAWK_T("unable to write"), hawk_sio_getpath((hawk_sio_t*)riod->handle));
			return nn;
		}

		case HAWK_RIO_CMD_WRITE_BYTES:
		{
			hawk_ooi_t nn;
			nn = hawk_sio_putbchars((hawk_sio_t*)riod->handle, data, size);
			if (nn <= -1) set_rio_error(rtx, HAWK_EREAD, HAWK_T("unable to write"), hawk_sio_getpath((hawk_sio_t*)riod->handle));
			return nn;
		}

		case HAWK_RIO_CMD_FLUSH:
		{
			int n;

			n = hawk_sio_flush((hawk_sio_t*)riod->handle);
			if (HAWK_UNLIKELY(n <= -1))
			{
				/* if flushing fails, discard the buffered data
				 * keeping the unflushed data causes causes subsequent write or close() to
				 * flush again and again */
				hawk_sio_drain ((hawk_sio_t*)riod->handle);
			}
			return n;
		}

		case HAWK_RIO_CMD_NEXT:
		{
			int n;
			hawk_sio_t* sio = (hawk_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) hawk_sio_close(sio);
			return n;
		}

	}

	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
	return -1;
}

static void fini_rxtn (hawk_rtx_t* rtx, void* ctx)
{
	rxtn_t* rxtn = GET_RXTN(rtx);
	/*xtn_t* xtn = (xtn_t*)GET_XTN(hawk_rtx_gethawk(rtx));*/

	if (rxtn->c.in.files)
	{
		hawk_oow_t i;
		for (i = 0; rxtn->c.in.files[i]; i++) hawk_rtx_freemem(rtx, rxtn->c.in.files[i]);
		hawk_rtx_freemem(rtx, rxtn->c.in.files);
		rxtn->c.in.files = HAWK_NULL;
		rxtn->c.in.count = 0;
		rxtn->c.in.index = 0;
	}

	if (rxtn->c.out.files)
	{
		hawk_oow_t i;
		for (i = 0; rxtn->c.out.files[i]; i++) hawk_rtx_freemem(rtx, rxtn->c.out.files[i]);
		hawk_rtx_freemem(rtx, rxtn->c.out.files);
		rxtn->c.out.files = HAWK_NULL;
		rxtn->c.out.count = 0;
		rxtn->c.out.index = 0;
	}

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

static int build_argcv (hawk_rtx_t* rtx, int argc_id, int argv_id, const hawk_ooch_t* id, hawk_ooch_t* icf[])
{
	hawk_ooch_t** p;
	hawk_oow_t argc;
	hawk_val_t* v_argc;
	hawk_val_t* v_argv;
	hawk_val_t* v_tmp;
	hawk_ooch_t key[HAWK_SIZEOF(hawk_int_t)*8+2];
	hawk_oow_t key_len;

	v_argv = hawk_rtx_makemapval(rtx);
	if (v_argv == HAWK_NULL) return -1;

	hawk_rtx_refupval(rtx, v_argv);

	/* make ARGV[0] */
	v_tmp = hawk_rtx_makestrvalwithoocstr(rtx, id);
	if (v_tmp == HAWK_NULL)
	{
		hawk_rtx_refdownval(rtx, v_argv);
		return -1;
	}

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

	key_len = hawk_copy_oocstr(key, HAWK_COUNTOF(key), HAWK_T("0"));
	if (hawk_map_upsert(((hawk_val_map_t*)v_argv)->map, key, key_len, v_tmp, 0) == HAWK_NULL)
	{
		/* if the assignment operation fails, decrements
		 * the reference of v_tmp to free it */
		hawk_rtx_refdownval(rtx, v_tmp);

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

		return -1;
	}

	if (icf)
	{
		for (argc = 1, p = icf; *p; p++, argc++)
		{
			/* the argument must compose a numeric value if possible */
			/*v_tmp = hawk_rtx_makenstrvalwithoocstr(rtx, *p); */
			v_tmp = hawk_rtx_makenumorstrvalwithoochars(rtx, *p, hawk_count_oocstr(*p));
			if (HAWK_UNLIKELY(!v_tmp))
			{
				hawk_rtx_refdownval(rtx, v_argv);
				return -1;
			}

			key_len = hawk_int_to_oocstr(argc, 10, HAWK_NULL, key, HAWK_COUNTOF(key));
			HAWK_ASSERT(key_len != (hawk_oow_t)-1);

			hawk_rtx_refupval(rtx, v_tmp);

			if (hawk_map_upsert(((hawk_val_map_t*)v_argv)->map, key, key_len, v_tmp, 0) == HAWK_NULL)
			{
				hawk_rtx_refdownval(rtx, v_tmp);
				hawk_rtx_refdownval(rtx, v_argv);
				return -1;
			}
		}
	}
	else argc = 1;

	v_argc = hawk_rtx_makeintval(rtx, (hawk_int_t)argc);
	if (v_argc == HAWK_NULL)
	{
		hawk_rtx_refdownval(rtx, v_argv);
		return -1;
	}

	hawk_rtx_refupval(rtx, v_argc);

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

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

	hawk_rtx_refdownval(rtx, v_argc);
	hawk_rtx_refdownval(rtx, v_argv);

	return 0;
}

/* TODO: use wenviron where it's available */
typedef hawk_bch_t env_char_t;
#define ENV_CHAR_IS_BCH

#if defined(HAVE_CRT_EXTERNS_H)
#define environ (*(_NSGetEnviron()))
#else
extern char** environ;
#endif

static int build_environ (hawk_rtx_t* rtx, int gbl_id, env_char_t* envarr[])
{
	hawk_val_t* v_env;
	hawk_val_t* v_tmp;

	v_env = hawk_rtx_makemapval(rtx);
	if (v_env == HAWK_NULL) return -1;

	hawk_rtx_refupval(rtx, v_env);

	if (envarr)
	{
		env_char_t* eq;
		hawk_ooch_t* kptr, * vptr;
		hawk_oow_t klen, vlen, count;

		for (count = 0; envarr[count]; count++)
		{
		#if ((defined(ENV_CHAR_IS_BCH) && defined(HAWK_OOCH_IS_BCH)) || \
		     (defined(ENV_CHAR_IS_UCH) && defined(HAWK_OOCH_IS_UCH)))
			eq = hawk_find_oochar_in_oocstr(envarr[count], '=');
			if (HAWK_UNLIKELY(!eq || eq == envarr[count])) continue;

			kptr = envarr[count];
			klen = eq - envarr[count];
			vptr = eq + 1;
			vlen = hawk_count_oocstr(vptr);
		#elif defined(ENV_CHAR_IS_BCH)
			eq = hawk_find_bchar_in_bcstr(envarr[count], '=');
			if (HAWK_UNLIKELY(!eq || eq == envarr[count])) continue;

			*eq = '\0';

			/* dupbtoucstr() may fail for invalid encoding. as the environment
			 * variaables are not under control, call mbstowcsalldup() instead
			 * to go on despite encoding failure */

			kptr = hawk_rtx_dupbtoucstr(rtx, envarr[count], &klen, 1);
			vptr = hawk_rtx_dupbtoucstr(rtx, eq + 1, &vlen, 1);
			if (HAWK_UNLIKELY(!kptr) || HAWK_UNLIKELY(!vptr))
			{
				if (kptr) hawk_rtx_freemem(rtx, kptr);
				if (vptr) hawk_rtx_freemem(rtx, vptr);
				hawk_rtx_refdownval(rtx, v_env);
				return -1;
			}

			*eq = '=';
		#else
			eq = hawk_find_uchar_in_ucstr(envarr[count], '=');
			if (HAWK_UNLIKELY(!eq || eq == envarr[count])) continue;

			*eq = '\0';

			kptr = hawk_rtx_duputobcstr(rtx, envarr[count], &klen);
			vptr = hawk_rtx_duputobcstr(rtx, eq + 1, &vlen);
			if (HAWK_UNLIKELY(!kptr) || HAWK_UNLIKELY(!vptr))
			{
				if (kptr) hawk_rtx_freemem(rtx, kptr);
				if (vptr) hawk_rtx_freeme(rtx, vptr):
				hawk_rtx_refdownval(rtx, v_env);
				return -1;
			}

			*eq = '=';
		#endif

			/* the string in ENVIRON should be a numeric value if
			 * it can be converted to a number.
			 *v_tmp = hawk_rtx_makenstrvalwithoocstr(rtx, vptr);*/
			v_tmp = hawk_rtx_makenumorstrvalwithoochars(rtx, vptr, vlen);
			if (HAWK_UNLIKELY(!v_tmp))
			{
		#if ((defined(ENV_CHAR_IS_BCH) && defined(HAWK_OOCH_IS_BCH)) || \
		     (defined(ENV_CHAR_IS_UCH) && defined(HAWK_OOCH_IS_UCH)))
				/* nothing to do */
		#else
				if (vptr) hawk_rtx_freemem(rtx, vptr);
				if (kptr) hawk_rtx_freemem(rtx, kptr);
		#endif
				hawk_rtx_refdownval(rtx, v_env);
				return -1;
			}

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

			if (hawk_map_upsert(((hawk_val_map_t*)v_env)->map, kptr, klen, v_tmp, 0) == HAWK_NULL)
			{
				/* if the assignment operation fails, decrements
				 * the reference of v_tmp to free it */
				hawk_rtx_refdownval(rtx, v_tmp);

		#if ((defined(ENV_CHAR_IS_BCH) && defined(HAWK_OOCH_IS_BCH)) || \
		     (defined(ENV_CHAR_IS_UCH) && defined(HAWK_OOCH_IS_UCH)))
				/* nothing to do */
		#else
				if (vptr) hawk_rtx_freemem(rtx, vptr);
				if (kptr) hawk_rtx_freemem(rtx, kptr);
		#endif

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

				return -1;
			}

		#if ((defined(ENV_CHAR_IS_BCH) && defined(HAWK_OOCH_IS_BCH)) || \
		     (defined(ENV_CHAR_IS_UCH) && defined(HAWK_OOCH_IS_UCH)))
				/* nothing to do */
		#else
			if (vptr) hawk_rtx_freemem(rtx, vptr);
			if (kptr) hawk_rtx_freemem(rtx, kptr);
		#endif
		}
	}

	if (hawk_rtx_setgbl(rtx, gbl_id, v_env) <= -1)
	{
		hawk_rtx_refdownval(rtx, v_env);
		return -1;
	}

	hawk_rtx_refdownval(rtx, v_env);
	return 0;
}

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

static hawk_rtx_t* open_rtx_std (
	hawk_t*            hawk,
	hawk_oow_t         xtnsize,
	const hawk_ooch_t* id,
	hawk_ooch_t*       icf[],
	hawk_ooch_t*       ocf[],
	hawk_cmgr_t*       cmgr)
{
	hawk_rtx_t* rtx;
	hawk_rio_cbs_t rio;
	rxtn_t* rxtn;
	xtn_t* xtn;

	xtn = GET_XTN(hawk);

	rio.pipe = hawk_rio_pipe;
	rio.file = hawk_rio_file;
	rio.console = hawk_rio_console;

	rtx = hawk_rtx_open(hawk, HAWK_SIZEOF(rxtn_t) + xtnsize, &rio);
	if (HAWK_UNLIKELY(!rtx)) return HAWK_NULL;

	rtx->_instsize += HAWK_SIZEOF(rxtn_t);

	rxtn = GET_RXTN(rtx);

	if (rtx->hawk->opt.trait & HAWK_RIO)
	{
		if (hawk_htb_init(&rxtn->cmgrtab, hawk_getgem(hawk), 256, 70, HAWK_SIZEOF(hawk_ooch_t), 1) <= -1)
		{
			hawk_rtx_close(rtx);
			return HAWK_NULL;
		}
		hawk_htb_setstyle (&rxtn->cmgrtab, hawk_get_htb_style(HAWK_HTB_STYLE_INLINE_COPIERS));
		rxtn->cmgrtab_inited = 1;
	}

	rxtn->ecb.close = fini_rxtn;
	hawk_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 (hawk_rtx_setofilenamewithoochars(rtx, ocf[0], hawk_count_oocstr(ocf[0])) <= -1)
		{
			hawk_rtx_errortohawk(rtx, hawk);
			hawk_rtx_close(rtx);
			return HAWK_NULL;
		}
	}

	if (make_additional_globals(rtx, xtn, id, icf) <= -1)
	{
		hawk_rtx_errortohawk(rtx, hawk);
		hawk_rtx_close(rtx);
		return HAWK_NULL;
	}

	return rtx;
}

hawk_rtx_t* hawk_rtx_openstdwithbcstr (
	hawk_t*           hawk,
	hawk_oow_t        xtnsize,
	const hawk_bch_t* id,
	hawk_bch_t*       icf[],
	hawk_bch_t*       ocf[],
	hawk_cmgr_t*      cmgr)
{
	hawk_rtx_t* rtx = HAWK_NULL;
	hawk_oow_t wcslen, i;
	hawk_ooch_t* xid, ** xicf = HAWK_NULL, ** xocf = HAWK_NULL;

#if defined(HAWK_OOCH_IS_UCH)
	xid = hawk_dupbtoucstr(hawk, id, &wcslen, 0);
	if (HAWK_UNLIKELY(!xid)) return HAWK_NULL;
#else
	xid = (hawk_ooch_t*)id;
#endif

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

		xicf = (hawk_ooch_t**)hawk_callocmem(hawk, HAWK_SIZEOF(*xicf) * (i + 1));
		if (!xicf) goto done;

		for (i = 0; icf[i]; i++)
		{
		#if defined(HAWK_OOCH_IS_UCH)
			xicf[i] = hawk_dupbtoucstr(hawk, icf[i], &wcslen, 0);
		#else
			xicf[i] = hawk_dupbcstr(hawk, icf[i], HAWK_NULL);
		#endif
			if (!xicf[i]) goto done;
		}
		xicf[i] = HAWK_NULL;
	}

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

		xocf = (hawk_ooch_t**)hawk_callocmem(hawk, HAWK_SIZEOF(*xocf) * (i + 1));
		if (!xocf) goto done;

		for (i = 0; ocf[i]; i++)
		{
		#if defined(HAWK_OOCH_IS_UCH)
			xocf[i] = hawk_dupbtoucstr(hawk, ocf[i], &wcslen, 0);
		#else
			xocf[i] = hawk_dupbcstr(hawk, ocf[i], HAWK_NULL);
		#endif
			if (!xocf[i]) goto done;
		}
		xocf[i] = HAWK_NULL;
	}

	rtx = open_rtx_std(hawk, xtnsize, xid, xicf, xocf, cmgr);

done:
	if (!rtx)
	{
		if (xocf)
		{
			for (i = 0; xocf[i]; i++) hawk_freemem(hawk, xocf[i]);
			hawk_freemem(hawk, xocf);
		}
		if (xicf)
		{
			for (i = 0; xicf[i]; i++) hawk_freemem(hawk, xicf[i]);
			hawk_freemem(hawk, xicf);
		}
	}

#if defined(HAWK_OOCH_IS_UCH)
	hawk_freemem(hawk, xid);
#endif

	return rtx;
}

hawk_rtx_t* hawk_rtx_openstdwithucstr (
	hawk_t*           hawk,
	hawk_oow_t        xtnsize,
	const hawk_uch_t* id,
	hawk_uch_t*       icf[],
	hawk_uch_t*       ocf[],
	hawk_cmgr_t*      cmgr)
{
	hawk_rtx_t* rtx = HAWK_NULL;
	hawk_oow_t mbslen, i;
	hawk_ooch_t* xid, ** xicf = HAWK_NULL, ** xocf = HAWK_NULL;

#if defined(HAWK_OOCH_IS_BCH)
	xid = hawk_duputobcstr(hawk, id, &mbslen);
	if (!xid) return HAWK_NULL;
#else
	xid = (hawk_ooch_t*)id;
#endif

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

		xicf = (hawk_ooch_t**)hawk_callocmem(hawk, HAWK_SIZEOF(*xicf) * (i + 1));
		if (!xicf) goto done;

		for (i = 0; icf[i]; i++)
		{
		#if defined(HAWK_OOCH_IS_BCH)
			xicf[i] = hawk_duputobcstr(hawk, icf[i], &mbslen);
		#else
			xicf[i] = hawk_dupucstr(hawk, icf[i], HAWK_NULL);
		#endif
			if (HAWK_UNLIKELY(!xicf[i])) goto done;
		}
		xicf[i] = HAWK_NULL;
	}

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

		xocf = (hawk_ooch_t**)hawk_callocmem(hawk, HAWK_SIZEOF(*xocf) * (i + 1));
		if (!xocf) goto done;

		for (i = 0; ocf[i]; i++)
		{
		#if defined(HAWK_OOCH_IS_BCH)
			xocf[i] = hawk_duputobcstr(hawk, ocf[i], &mbslen);
		#else
			xocf[i] = hawk_dupucstr(hawk, ocf[i], HAWK_NULL);
		#endif
			if (!xocf[i]) goto done;
		}
		xocf[i] = HAWK_NULL;
	}

	rtx = open_rtx_std(hawk, xtnsize, xid, xicf, xocf, cmgr);

done:
	if (!rtx)
	{
		if (xocf)
		{
			for (i = 0; xocf[i]; i++) hawk_freemem(hawk, xocf[i]);
			hawk_freemem(hawk, xocf);
		}
		if (xicf)
		{
			for (i = 0; xicf[i]; i++) hawk_freemem(hawk, xicf[i]);
			hawk_freemem(hawk, xicf);
		}
	}

#if defined(HAWK_OOCH_IS_BCH)
	hawk_freemem(hawk, xid);
#endif

	return rtx;
}

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

static HAWK_INLINE void init_ioattr (ioattr_t* ioattr)
{
	int i;
	HAWK_MEMSET (ioattr, 0, HAWK_SIZEOF(*ioattr));
	for (i = 0; i < HAWK_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 (hawk_htb_t* tab, const hawk_ooch_t* ptr, hawk_oow_t len)
{
	hawk_htb_pair_t* pair;

	pair = hawk_htb_search(tab, ptr, len);
	if (pair) return HAWK_HTB_VPTR(pair);

	return HAWK_NULL;
}

static ioattr_t* find_or_make_ioattr (hawk_rtx_t* rtx, hawk_htb_t* tab, const hawk_ooch_t* ptr, hawk_oow_t len)
{
	hawk_htb_pair_t* pair;

	pair = hawk_htb_search(tab, ptr, len);
	if (pair == HAWK_NULL)
	{
		ioattr_t ioattr;

		init_ioattr (&ioattr);

		pair = hawk_htb_insert(tab, (void*)ptr, len, (void*)&ioattr, HAWK_SIZEOF(ioattr));
		if (pair == HAWK_NULL) return HAWK_NULL;
	}

	return (ioattr_t*)HAWK_HTB_VPTR(pair);
}

static int fnc_setioattr (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	/*hawk_t* hawk = hawk_rtx_gethawk(rtx);*/
	rxtn_t* rxtn;
	hawk_val_t* v[3];
	hawk_ooch_t* ptr[3];
	hawk_oow_t len[3];
	int i, ret = 0, fret = 0;
	int tmout;

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

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

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

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

		hawk_int_t l;
		hawk_flt_t r;
		int x;

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

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

		if (x == 0)
		{
			ioattr->tmout[tmout].sec = l;
			ioattr->tmout[tmout].nsec = 0;
		}
		else if (x >= 1)
		{
			hawk_flt_t nsec;
			ioattr->tmout[tmout].sec = (hawk_int_t)r;
			nsec = r - ioattr->tmout[tmout].sec;
			ioattr->tmout[tmout].nsec = HAWK_SEC_TO_NSEC(nsec);
		}
	}
#if defined(HAWK_OOCH_IS_UCH)
	else if (hawk_comp_oocstr(ptr[1], HAWK_T("codepage"), 1) == 0 ||
	         hawk_comp_oocstr(ptr[1], HAWK_T("encoding"), 1) == 0)
	{
		ioattr_t* ioattr;
		hawk_cmgr_t* cmgr;

		if (ptr[2][0] == HAWK_T('\0'))
		{
			cmgr = HAWK_NULL;
		}
		else
		{
			cmgr = hawk_get_cmgr_by_name(ptr[2]);
			if (cmgr == HAWK_NULL)
			{
				fret = -1;
				goto done;
			}
		}

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

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

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

	if (ret >= 0)
	{
		v[0] = hawk_rtx_makeintval(rtx, (hawk_int_t)fret);
		if (v[0] == HAWK_NULL) return -1;
		hawk_rtx_setretval(rtx, v[0]);
	}

	return ret;
}

static int fnc_getioattr (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	rxtn_t* rxtn;
	hawk_val_t* v[2];
	hawk_ooch_t* ptr[2];
	hawk_oow_t len[2];
	int i, ret = 0;
	int tmout;
	hawk_val_t* rv = HAWK_NULL;

	ioattr_t* ioattr;
	ioattr_t ioattr_buf;

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

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

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

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

	if ((tmout = timeout_code (ptr[1])) >= 0)
	{
		if (ioattr->tmout[tmout].nsec == 0)
			rv = hawk_rtx_makeintval(rtx, ioattr->tmout[tmout].sec);
		else
			rv = hawk_rtx_makefltval(rtx, (hawk_flt_t)ioattr->tmout[tmout].sec + HAWK_NSEC_TO_SEC((hawk_flt_t)ioattr->tmout[tmout].nsec));
		if (rv == HAWK_NULL)
		{
			ret = -1;
			goto done;
		}
	}
#if defined(HAWK_OOCH_IS_UCH)
	else if (hawk_comp_oocstr(ptr[1], HAWK_T("codepage"), 1) == 0 ||
	         hawk_comp_oocstr(ptr[1], HAWK_T("encoding"), 1) == 0)
	{
		rv = hawk_rtx_makestrvalwithoocstr(rtx, ioattr->cmgr_name);
		if (rv == HAWK_NULL)
		{
			ret = -1;
			goto done;
		}
	}
#endif
	else
	{
		/* unknown attribute name */
		goto done;
	}

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

	if (ret >= 0)
	{
		if (rv)
		{
			hawk_rtx_refupval(rtx, rv);
			ret = hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)hawk_rtx_getarg(rtx, 2), rv);
			hawk_rtx_refdownval(rtx, rv);
			if (ret >= 0) hawk_rtx_setretval(rtx, HAWK_VAL_ZERO);
		}
		else
		{
			hawk_rtx_setretval(rtx, HAWK_VAL_NEGONE);
		}

	}

	return ret;
}

hawk_cmgr_t* hawk_rtx_getiocmgrstd (hawk_rtx_t* rtx, const hawk_ooch_t* ioname)
{
#if defined(HAWK_OOCH_IS_UCH)
	rxtn_t* rxtn = GET_RXTN(rtx);
	ioattr_t* ioattr;

	HAWK_ASSERT(rxtn->cmgrtab_inited == 1);

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

static int add_globals (hawk_t* hawk)
{
	xtn_t* xtn = GET_XTN(hawk);

	xtn->gbl_argc = hawk_addgblwithoocstr(hawk, HAWK_T("ARGC"));
	xtn->gbl_argv = hawk_addgblwithoocstr(hawk, HAWK_T("ARGV"));
	xtn->gbl_environ = hawk_addgblwithoocstr(hawk, HAWK_T("ENVIRON"));

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

struct fnctab_t
{
	const hawk_ooch_t* name;
	hawk_fnc_spec_t spec;
};

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

	/* additional functions */
	{ HAWK_T("setioattr"), { {3, 3, HAWK_NULL},       fnc_setioattr, HAWK_RIO } },
	{ HAWK_T("getioattr"), { {3, 3, HAWK_T("vvr")},   fnc_getioattr, HAWK_RIO } }
};

static int add_functions (hawk_t* hawk)
{
	int i;

	for (i = 0; i < HAWK_COUNTOF(fnctab); i++)
	{
		if (hawk_addfnc(hawk, fnctab[i].name, &fnctab[i].spec) == HAWK_NULL) return -1;
	}

	return 0;
}

