cfad47cfa3/src/osportable.cc

User picture

Commiter: Nikos Chantziaras

Author: Nikos Chantziaras

Revision: cfad47cfa3


File Size: 21.6 KB

(June 01, 2009 20:54 UTC) Almost 3 years ago

Initial commit.

 

Showing without highlighting since it looks like a big file and may slow your browser - show with highlighting

Show/hide line numbers
/* This file implements some of the functions described in
 * tads2/osifc.h.  We don't need to implement them all, as most of them
 * are provided by tads2/osnoui.c and tads2/osgen3.c.
 *
 * This file implements the "portable" subset of these functions;
 * functions that depend upon curses/ncurses are defined in oscurses.cc.
 */
#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif


#include "os.h"
#ifdef RUNTIME
// We utilize the Tads 3 Unicode character mapping facility even for
// Tads 2 (only in the interpreter).
#include "charmap.h"
// Need access to command line options: globalApp->options. Only at
// runtime - compiler uses different mechanism for command line
// options.
#include "frobtadsapp.h"
#endif // RUNTIME


/* Service routine.
 * Try to bring returned by system charset name to a name of one of
 * the encodings in cmaplib.t3r.  The proper way to do it is to
 * maintain an external database of charset aliases, which user can
 * edit.  But this adds unnecessary complexity, which we really don't
 * need.  So, the "database" is internal.
 *
 * TODO: Restore previous bahavior when we fix out curses interface.
 * For now, we always return "us-ascii".
 */
static const char*
get_charset_alias( const char* charset )
{
	const char* p;
	const char* c;

	// Special case.
	if (!stricmp(charset, "ANSI_X3.4-1968")) return "us-ascii";

	// If it's in ru_RU.KOI8-R form, try to extract charset part
	// (everything after last point).

	// Find the last point in the charset name.
	for (p = charset, c = charset ; *p != '\0' ; ++p) {
		if (*p == '.') c = p + 1;
	}

	// '.' was the last symbol in charset name?
	if (*c == '\0') c = charset;

	if (!stricmp(c, "KOI8-R")) return "koi8-r";

	if (!stricmp(c, "ISO-8859-1"))  return "iso1";
	if (!stricmp(c, "ISO-8859-2"))  return "iso2";
	if (!stricmp(c, "ISO-8859-3"))  return "iso3";
	if (!stricmp(c, "ISO-8859-4"))  return "iso4";
	if (!stricmp(c, "ISO-8859-5"))  return "iso5";
	if (!stricmp(c, "ISO-8859-6"))  return "iso6";
	if (!stricmp(c, "ISO-8859-7"))  return "iso7";
	if (!stricmp(c, "ISO-8859-8"))  return "iso8";
	if (!stricmp(c, "ISO-8859-9"))  return "iso9";
	if (!stricmp(c, "ISO-8859-10")) return "iso10";
	if (!stricmp(c, "8859-1"))      return "iso1";
	if (!stricmp(c, "8859-2"))      return "iso2";
	if (!stricmp(c, "8859-3"))      return "iso3";
	if (!stricmp(c, "8859-4"))      return "iso4";
	if (!stricmp(c, "8859-5"))      return "iso5";
	if (!stricmp(c, "8859-6"))      return "iso6";
	if (!stricmp(c, "8859-7"))      return "iso7";
	if (!stricmp(c, "8859-8"))      return "iso8";
	if (!stricmp(c, "8859-9"))      return "iso9";
	if (!stricmp(c, "8859-10"))     return "iso10";

	if (!stricmp(c, "CP1250"))  return "cp1250";
	if (!stricmp(c, "CP-1250")) return "cp1250";
	if (!stricmp(c, "CP_1250")) return "cp1250";
	if (!stricmp(c, "CP1251"))  return "cp1251";
	if (!stricmp(c, "CP-1251")) return "cp1251";
	if (!stricmp(c, "CP_1251")) return "cp1251";
	if (!stricmp(c, "CP1252"))  return "cp1252";
	if (!stricmp(c, "CP-1252")) return "cp1252";
	if (!stricmp(c, "CP_1252")) return "cp1252";
	if (!stricmp(c, "CP1253"))  return "cp1253";
	if (!stricmp(c, "CP-1253")) return "cp1253";
	if (!stricmp(c, "CP_1253")) return "cp1253";
	if (!stricmp(c, "CP1254"))  return "cp1254";
	if (!stricmp(c, "CP-1254")) return "cp1254";
	if (!stricmp(c, "CP_1254")) return "cp1254";
	if (!stricmp(c, "CP1255"))  return "cp1255";
	if (!stricmp(c, "CP-1255")) return "cp1255";
	if (!stricmp(c, "CP_1255")) return "cp1255";
	if (!stricmp(c, "CP1256"))  return "cp1256";
	if (!stricmp(c, "CP-1256")) return "cp1256";
	if (!stricmp(c, "CP_1256")) return "cp1256";
	if (!stricmp(c, "CP1257"))  return "cp1257";
	if (!stricmp(c, "CP-1257")) return "cp1257";
	if (!stricmp(c, "CP_1257")) return "cp1257";
	if (!stricmp(c, "CP1258"))  return "cp1258";
	if (!stricmp(c, "CP-1258")) return "cp1258";
	if (!stricmp(c, "CP_1258")) return "cp1258";

	if (!stricmp(c, "CP437"))  return "cp437";
	if (!stricmp(c, "CP-437")) return "cp437";
	if (!stricmp(c, "CP_437")) return "cp437";
	if (!stricmp(c, "PC437"))  return "cp437";
	if (!stricmp(c, "PC-437")) return "cp437";
	if (!stricmp(c, "PC_437")) return "cp437";
	if (!stricmp(c, "CP737"))  return "cp737";
	if (!stricmp(c, "CP-737")) return "cp737";
	if (!stricmp(c, "CP_737")) return "cp737";
	if (!stricmp(c, "PC737"))  return "cp737";
	if (!stricmp(c, "PC-737")) return "cp737";
	if (!stricmp(c, "PC_737")) return "cp737";
	if (!stricmp(c, "CP775"))  return "cp775";
	if (!stricmp(c, "CP-775")) return "cp775";
	if (!stricmp(c, "CP_775")) return "cp775";
	if (!stricmp(c, "PC775"))  return "cp775";
	if (!stricmp(c, "PC-775")) return "cp775";
	if (!stricmp(c, "PC_775")) return "cp775";
	if (!stricmp(c, "CP850"))  return "cp850";
	if (!stricmp(c, "CP-850")) return "cp850";
	if (!stricmp(c, "CP_850")) return "cp850";
	if (!stricmp(c, "PC850"))  return "cp850";
	if (!stricmp(c, "PC-850")) return "cp850";
	if (!stricmp(c, "PC_850")) return "cp850";
	if (!stricmp(c, "CP852"))  return "cp852";
	if (!stricmp(c, "CP-852")) return "cp852";
	if (!stricmp(c, "CP_852")) return "cp852";
	if (!stricmp(c, "PC852"))  return "cp852";
	if (!stricmp(c, "PC-852")) return "cp852";
	if (!stricmp(c, "PC_852")) return "cp852";
	if (!stricmp(c, "CP855"))  return "cp855";
	if (!stricmp(c, "CP-855")) return "cp855";
	if (!stricmp(c, "CP_855")) return "cp855";
	if (!stricmp(c, "PC855"))  return "cp855";
	if (!stricmp(c, "PC-855")) return "cp855";
	if (!stricmp(c, "PC_855")) return "cp855";
	if (!stricmp(c, "CP857"))  return "cp857";
	if (!stricmp(c, "CP-857")) return "cp857";
	if (!stricmp(c, "CP_857")) return "cp857";
	if (!stricmp(c, "PC857"))  return "cp857";
	if (!stricmp(c, "PC-857")) return "cp857";
	if (!stricmp(c, "PC_857")) return "cp857";
	if (!stricmp(c, "CP860"))  return "cp860";
	if (!stricmp(c, "CP-860")) return "cp860";
	if (!stricmp(c, "CP_860")) return "cp860";
	if (!stricmp(c, "PC860"))  return "cp860";
	if (!stricmp(c, "PC-860")) return "cp860";
	if (!stricmp(c, "PC_860")) return "cp860";
	if (!stricmp(c, "CP861"))  return "cp861";
	if (!stricmp(c, "CP-861")) return "cp861";
	if (!stricmp(c, "CP_861")) return "cp861";
	if (!stricmp(c, "PC861"))  return "cp861";
	if (!stricmp(c, "PC-861")) return "cp861";
	if (!stricmp(c, "PC_861")) return "cp861";
	if (!stricmp(c, "CP862"))  return "cp862";
	if (!stricmp(c, "CP-862")) return "cp862";
	if (!stricmp(c, "CP_862")) return "cp862";
	if (!stricmp(c, "PC862"))  return "cp862";
	if (!stricmp(c, "PC-862")) return "cp862";
	if (!stricmp(c, "PC_862")) return "cp862";
	if (!stricmp(c, "CP863"))  return "cp863";
	if (!stricmp(c, "CP-863")) return "cp863";
	if (!stricmp(c, "CP_863")) return "cp863";
	if (!stricmp(c, "PC863"))  return "cp863";
	if (!stricmp(c, "PC-863")) return "cp863";
	if (!stricmp(c, "PC_863")) return "cp863";
	if (!stricmp(c, "CP864"))  return "cp864";
	if (!stricmp(c, "CP-864")) return "cp864";
	if (!stricmp(c, "CP_864")) return "cp864";
	if (!stricmp(c, "PC864"))  return "cp864";
	if (!stricmp(c, "PC-864")) return "cp864";
	if (!stricmp(c, "PC_864")) return "cp864";
	if (!stricmp(c, "CP865"))  return "cp865";
	if (!stricmp(c, "CP-865")) return "cp865";
	if (!stricmp(c, "CP_865")) return "cp865";
	if (!stricmp(c, "PC865"))  return "cp865";
	if (!stricmp(c, "PC-865")) return "cp865";
	if (!stricmp(c, "PC_865")) return "cp865";
	if (!stricmp(c, "CP866"))  return "cp866";
	if (!stricmp(c, "CP-866")) return "cp866";
	if (!stricmp(c, "CP_866")) return "cp866";
	if (!stricmp(c, "PC866"))  return "cp866";
	if (!stricmp(c, "PC-866")) return "cp866";
	if (!stricmp(c, "PC_866")) return "cp866";
	if (!stricmp(c, "CP869"))  return "cp869";
	if (!stricmp(c, "CP-869")) return "cp869";
	if (!stricmp(c, "CP_869")) return "cp869";
	if (!stricmp(c, "PC869"))  return "cp869";
	if (!stricmp(c, "PC-869")) return "cp869";
	if (!stricmp(c, "PC_869")) return "cp869";
	if (!stricmp(c, "CP874"))  return "cp874";
	if (!stricmp(c, "CP-874")) return "cp874";
	if (!stricmp(c, "CP_874")) return "cp874";
	if (!stricmp(c, "PC874"))  return "cp874";
	if (!stricmp(c, "PC-874")) return "cp874";
	if (!stricmp(c, "PC_874")) return "cp874";

	// There are Mac OS Roman, Central European, Cyrillic, Greek,
	// Icelandic and Turkish charmaps in cmaplib.t3r. But what codes
	// system supposed to return?

	// Unrecognized.
	return charset;
}


/* Open text file for reading and writing.
 */
osfildef*
osfoprwt( const char* fname, os_filetype_t )
{
//	assert(fname != 0);
	// Try opening the file in read/write mode.
	osfildef* fp = fopen(fname, "r+");
	// If opening the file failed, it probably means that it doesn't
	// exist.  In that case, create a new file in read/write mode.
	if (fp == 0) fp = fopen(fname, "w+");
	return fp;
}


/* Open binary file for reading/writing.
 */
osfildef*
osfoprwb( const char* fname, os_filetype_t )
{
//	assert(fname != 0);
	osfildef* fp = fopen(fname, "r+b");
	if (fp == 0) fp = fopen(fname, "w+b");
	return fp;
}


/* Convert string to all-lowercase.
 */
char*
os_strlwr( char* s )
{
//	assert(s != 0);
	for (int i = 0; s[i] != '\0'; ++i) {
		s[i] = tolower(s[i]);
	}
	return s;
}


/* Seek to the resource file embedded in the current executable file.
 *
 * We don't support this (and probably never will).
 */
osfildef*
os_exeseek( const char*, const char* )
{
	return static_cast(osfildef*)(0);
}


/* Create and open a temporary file.
 */
osfildef*
os_create_tempfile( const char* fname, char* buf )
{
	if (fname != 0 and fname[0] != '\0') {
		// A filename has been specified; use it.
		return fopen(fname, "w+b");
	}

	//ASSERT(buf != 0);

	// No filename needed; create a nameless tempfile.
	buf[0] = '\0';
	return tmpfile();
}


/* Delete a temporary file created with os_create_tempfile().
 */
int
osfdel_temp( const char* fname )
{
	//ASSERT(fname != 0);

	if (fname[0] == '\0' or remove(fname) == 0) {
		// Either it was a nameless temp-file and has been
		// already deleted by the system, or deleting it
		// succeeded.  In either case, the operation was
		// successful.
		return 0;
	}
	// It was not a nameless tempfile and remove() failed.
	return 1;
}


/* Get the temporary file path.
 *
 * tads2/osnoui.c defines a DOS version of this routine when MSDOS is
 * defined.
 */
#ifndef MSDOS
void
os_get_tmp_path( char* buf )
{
	// Try the usual env. variable first.
	const char* tmpDir = getenv("TMPDIR");

	// If no such variable exists, try P_tmpdir (if defined in
	// <stdio.h>).
#ifdef P_tmpdir
	if (tmpDir == 0 or tmpDir[0] == '\0') {
		tmpDir = P_tmpdir;
	}
#endif

	// If we still lack a path, use "/tmp".
	if (tmpDir == 0 or tmpDir[0] == '\0') {
		tmpDir = "/tmp";
	}

	// If the directory doesn't exist or is not a directory, don't
	// provide anything at all (which means that the current
	// directory will be used).
	struct stat inf;
	int statRet = stat(tmpDir, &inf);
	if (statRet != 0 or (inf.st_mode & S_IFMT) != S_IFDIR) {
		buf[0] = '\0';
		return;
	}

	strcpy(buf, tmpDir);

	// Append a slash if necessary.
	size_t len = strlen(buf);
	if (buf[len - 1] != '/') {
		buf[len] = '/';
		buf[len + 1] = '\0';
	}
}
#endif // not MSDOS


/* Get a suitable seed for a random number generator.
 *
 * We don't just use the system-clock, but build a number as random as
 * the mechanisms of the standard C-library allow.  This is somewhat of
 * an overkill, but it's simple enough so here goes.  Someone has to
 * write a nuclear war simulator in TADS to test this thoroughly.
 */
void
os_rand( long* val )
{
	//assert(val != 0);
	// Use the current time as the first seed.
	srand(static_cast(unsigned int)(time(0)));

	// Create the final seed by generating a random number using
	// high-order bits, because on some systems the low-order bits
	// aren't very random.
	*val = 1 + static_cast(long)(static_cast(long double)(65535) * rand() / (RAND_MAX + 1.0));
}


/* Get the current system high-precision timer.
 *
 * We provide four (4) implementations of this function:
 *
 *   1. clock_gettime() is available
 *   2. gettimeofday() is available
 *   3. ftime() is available
 *   4. Neither is available - fall back to time()
 *
 * Note that HAVE_CLOCK_GETTIME, HAVE_GETTIMEOFDAY and HAVE_FTIME are
 * mutually exclusive; if one of them is defined, the others aren't.  No
 * need for #else here.
 *
 * Although not required by the TADS VM, these implementations will
 * always return 0 on the first call.
 */
#ifdef HAVE_CLOCK_GETTIME
// The system has the clock_gettime() function.
long
os_get_sys_clock_ms( void )
{
	// We need to remember the exact time this function has been
	// called for the first time, and use that time as our
	// zero-point.  On each call, we simply return the difference
	// in milliseconds between the current time and our zero point.
	static struct timespec zeroPoint;

	// Did we get the zero point yet?
	static bool initialized = false;

	// Not all systems provide a monotonic clock; check if it's
	// available before falling back to the global system-clock.  A
	// monotonic clock is guaranteed not to change while the system
	// is running, so we prefer it over the global clock.
	static const clockid_t clockType =
#ifdef HAVE_CLOCK_MONOTONIC
		CLOCK_MONOTONIC;
#else
		CLOCK_REALTIME;
#endif
	// We must get the current time in each call.
	struct timespec currTime;

	// Initialize our zero-point, if not already done so.
	if (not initialized) {
		clock_gettime(clockType, &zeroPoint);
		initialized = true;
	}

	// Get the current time.
	clock_gettime(clockType, &currTime);

	// Note that tv_nsec contains *nano*seconds, not milliseconds,
	// so we need to convert it; a millisec is 1.000.000 nanosecs.
	return (currTime.tv_sec - zeroPoint.tv_sec) * 1000
	     + (currTime.tv_nsec - zeroPoint.tv_nsec) / 1000000;
}
#endif // HAVE_CLOCK_GETTIME

#ifdef HAVE_GETTIMEOFDAY
// The system has the gettimeofday() function.
long
os_get_sys_clock_ms( void )
{
	static struct timeval zeroPoint;
	static bool initialized = false;
	// gettimeofday() needs the timezone as a second argument.  This
	// is obsolete today, but we don't care anyway; we just pass
	// something.
	struct timezone bogus = {0, 0};
	struct timeval currTime;

	if (not initialized) {
		gettimeofday(&zeroPoint, &bogus);
		initialized = true;
	}

	gettimeofday(&currTime, &bogus);

	// 'tv_usec' contains *micro*seconds, not milliseconds.  A
	// millisec is 1.000 microsecs.
	return (currTime.tv_sec - zeroPoint.tv_sec) * 1000 + (currTime.tv_usec - zeroPoint.tv_usec) / 1000;
}
#endif // HAVE_GETTIMEOFDAY


#ifdef HAVE_FTIME
// The system has the ftime() function.  ftime() is in <sys/timeb.h>.
#include <sys/timeb.h>
long
os_get_sys_clock_ms( void )
{
	static timeb zeroPoint;
	static bool initialized = false;
	struct timeb currTime;

	if (not initialized) {
		ftime(&zeroPoint);
		initialized = true;
	}

	ftime(&currTime);
	return (currTime.time - zeroPoint.time) * 1000 + (currTime.millitm - zeroPoint.millitm);
}
#endif // HAVE_FTIME


#ifndef HAVE_CLOCK_GETTIME
#ifndef HAVE_GETTIMEOFDAY
#ifndef HAVE_FTIME
// Use the standard time() function from the C library.  We lose
// millisecond-precision, but that's the best we can do with time().
long
os_get_sys_clock_ms( void )
{
	static time_t zeroPoint = time(0);
	return (time(0) - zeroPoint) * 1000;
}
#endif
#endif
#endif


/* Get filename from startup parameter, if possible.
 *
 * TODO: Find out what this is supposed to do.
 */
int
os_paramfile( char* )
{
	return 0;
}


/* Get a special directory path.
 *
 * If env. variables exist, they always override the compile-time
 * defaults.
 *
 * We ignore the argv parameter, since on Unix binaries aren't stored
 * together with data on disk.
 */
void
os_get_special_path( char* buf, size_t buflen, const char*, int id )
{
	const char* res;

	switch (id) {
	  case OS_GSP_T3_RES:
		res = getenv("T3_RESDIR");
		if (res == 0 or res[0] == '\0') {
			res = T3_RES_DIR;
		}
		break;
	  case OS_GSP_T3_INC:
		res = getenv("T3_INCDIR");
		if (res == 0 or res[0] == '\0') {
			res = T3_INC_DIR;
		}
		break;
	  case OS_GSP_T3_LIB:
		res = getenv("T3_LIBDIR");
		if (res == 0 or res[0] == '\0') {
			res = T3_LIB_DIR;
		}
		break;
	  case OS_GSP_T3_USER_LIBS:
		// There's no compile-time default for user libs.
		res = getenv("T3_USERLIBDIR");
		break;
	  default:
		// TODO: We could print a warning here to inform the
		// user that we're outdated.
		res = 0;
	}

	if (res != 0) {
		// Only use the detected path if it exists and is a
		// directory.
		struct stat inf;
		int statRet = stat(res, &inf);
		if (statRet == 0 and (inf.st_mode & S_IFMT) == S_IFDIR) {
			strncpy(buf, res, buflen - 1);
			return;
		}
	}
	// Indicate failure.
	buf[0] = '\0';
}


/* Generate a filename for a character-set mapping file.
 *
 * Follow DOS convention: start with the current local charset
 * identifier, then the internal ID, and the suffix ".tcp".  No path
 * prefix, which means look in current directory.  This is what we
 * want, because mapping files are supposed to be distributed with a
 * game, not with the interpreter.
 */
void
os_gen_charmap_filename( char* filename, char* internal_id, char* /*argv0*/ )
{
	char mapname[32];

	os_get_charmap(mapname, OS_CHARMAP_DISPLAY);

	// Theoretically, we can get mapname so long that with 4-letter
	// internal id and 4-letter extension '.tcp' it will be longer
	// than OSFNMAX. Highly unlikely, but...
	size_t len = strlen(mapname);
	if (len > OSFNMAX - 9) len = OSFNMAX - 9;

	memcpy(filename, mapname, len);
	strcat(filename, internal_id);
	strcat(filename, ".tcp");
}


/* Receive notification that a character mapping file has been loaded.
 */
void
os_advise_load_charmap( char* /*id*/, char* /*ldesc*/, char* /*sysinfo*/ )
{
}


/* Get the full filename (including directory path) to the executable
 * file, given the argv[0] parameter passed into the main program.
 *
 * On Unix, you can't tell what the executable's name is by just looking
 * at argv, so we always indicate failure.  No big deal though; this
 * information is only used when the interpreter's executable is bundled
 * with a game, and we don't support this at all.
 */
int
os_get_exe_filename( char*, size_t, const char* )
{
	return false;
}


/* Generate the name of the character set mapping table for Unicode
 * characters to and from the given local character set.
 */
void
os_get_charmap( char* mapname, int charmap_id )
{
	const char* charset = 0;  // Character set name.

#ifdef RUNTIME
	// If there was a command line option, it takes precedence.
	// User knows better, so do not try to modify his setting.
	if (globalApp->options.characterSet[0] != '\0') {
		// One charset for all.
		strncpy(mapname, globalApp->options.characterSet, 32);
		return;
	}
#endif

	// There is absolutely no robust way to determine the local
	// character set.  Roughly speaking, we have three options:
	//
	// Use nl_langinfo() function.  Not always available.
	//
	// Use setlocale(LC_CTYPE, NULL).  This only works if user set
	// locale which is actually installed on the machine, or else
	// it will return NULL.  But we don't need locale, we just need
	// to know what the user wants.
	//
	// Manually look up environment variables LC_ALL, LC_CTYPE and
	// LANG.
	//
	// However, not a single one will provide us with a reliable
	// name of local character set.  There is no standard way to
	// name charsets.  For a single set we can get almost anything:
	// Windows-1251, Win-1251, CP1251, CP-1251, ru_RU.CP1251,
	// ru_RU.CP-1251, ru_RU.CP_1251...  And the only way is to
	// maintain a database of aliases.

#if HAVE_LANGINFO_CODESET
	charset = nl_langinfo(CODESET);
#else
	charset = getenv("LC_CTYPE");
	if (charset == 0 or charset[0] == '\0') {
		charset = getenv("LC_ALL");
		if (charset == 0 or charset[0] == '\0') {
			charset = getenv("LANG");
		}
	}
#endif

	if (charset == 0) {
		strcpy(mapname, "us-ascii");
	} else {
		strcpy(mapname, get_charset_alias(charset));
	}
	return;
}


/* Translate a character from the HTML 4 Unicode character set to the
 * current character set used for display.
 *
 * Note that this function is used only by Tads 2.  Tads 3 does mappings
 * automatically.
 *
 * We omit this implementation when not compiling the interpreter (in
 * which case os_xlat_html4 will have been defined as an empty macro in
 * osfrobtads.h).  We do this because we don't want to introduce any
 * TADS 3 dependencies upon the TADS 2 compiler, which should compile
 * regardless of whether the TADS 3 sources are available or not.
 */
#ifndef os_xlat_html4
void
os_xlat_html4( unsigned int html4_char, char* result, size_t result_buf_len )
{
	// HTML 4 characters are Unicode.  Tads 3 provides just the
	// right mapper: Unicode to ASCII.  We make it static in order
	// not to create a mapper on each call and save CPU cycles.
	static CCharmapToLocalASCII mapper;
	result[mapper.map_char(html4_char, result, result_buf_len)] = '\0';
}
#endif


/* =====================================================================
 *
 * The functions defined below are not needed by the interpreter, or
 * have a curses-specific implementation and are therefore only used
 * when building the compiler (the compiler doesn't use curses, just
 * plain stdio).
 */
#ifndef RUNTIME

/* Wait for a character to become available from the keyboard.
 */
void
os_waitc( void )
{
	getchar();
}


/* Read a character from the keyboard.
 */
int
os_getc( void )
{
	return getchar();
}


/* Read a character from the keyboard and return the low-level,
 * untranslated key code whenever possible.
 */
int
os_getc_raw( void )
{
	return getchar();
}


/* Check for user break.
 *
 * We only provide a dummy here; we might do something more useful in
 * the future though.
 */
int
os_break( void )
{
	return false;
}


/* Yield CPU.
 *
 * We don't need this.  It's only useful for Windows 3.x and maybe pre-X Mac OS
 * and other systems with brain damaged multitasking.
 */
int
os_yield( void )
{
	return false;
}


/* Sleep for a while.
 *
 * The compiler doesn't need this.
 */
void
os_sleep_ms( long )
{
}

#endif /* ! RUNTIME */