stk/Src/port.c

834 lines
21 KiB
C

/*
*
* p o r t . c -- ports implementation
*
* Copyright © 1993-1996 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
*
*
* Permission to use, copy, and/or distribute this software and its
* documentation for any purpose and without fee is hereby granted, provided
* that both the above copyright notice and this permission notice appear in
* all copies and derived works. Fees for distribution or use of this
* software or derived works may only be charged with express written
* permission of the copyright holder.
* This software is provided ``as is'' without express or implied warranty.
*
* This software is a derivative work of other copyrighted softwares; the
* copyright notices of these softwares are placed in the file COPYRIGHTS
*
*
* Author: Erick Gallesio [eg@unice.fr]
* Creation date: 17-Feb-1993 12:27
* Last file update: 13-Sep-1996 21:59
*
*/
#ifndef WIN32
# include <sys/ioctl.h>
# include <sys/time.h>
# include <ctype.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h> /* This seems to be useful only for AIX */
#endif
#ifndef NO_FD_SET
# define SELECT_MASK fd_set
#else
# ifndef _AIX
typedef long fd_mask;
# endif
# if defined(_IBMR2)
# define SELECT_MASK void
# else
# define SELECT_MASK int
# endif
#endif
#include "stk.h"
#ifdef WIN32
/* Provide substitute functions dor WIN32 */
FILE *popen(char *cmd, char *mode)
{
/* Returning NULL will yield an error */
return NULL;
}
void pclose(FILE *f)
{}
#endif
/* external vars */
SCM STk_curr_iport, STk_curr_oport, STk_curr_eport, STk_eof_object;
SCM STk_Cfile2port(char *name, FILE *f, int type, int flags)
{
SCM z;
NEWCELL(z, type);
z->storage_as.port.p = (struct port_descr *)
must_malloc(sizeof(struct port_descr));
PORT_FILE(z) = f;
PORT_FLAGS(z) = flags;
PORT_REVENT(z) = Ntruth;
PORT_WEVENT(z) = Ntruth;
PORT_NAME(z) = (char *) must_malloc(strlen(name)+1);
strcpy(PORT_NAME(z), name);
return z;
}
static SCM makeport(char *name, int type, char *mode, int error)
{
SCM z = Ntruth;
int flags = 0;
FILE *f;
char *full_name;
STk_disallow_sigint();
if (strncmp(name, "| ", 2)) {
full_name = CHARS(STk_internal_expand_file_name(name));
if ((f = fopen(full_name, mode)) == NULL) {
if (error) Err("could not open file", STk_makestring(name));
else goto Out;
}
}
else {
full_name = name;
if ((f = popen(name+1, mode)) == NULL) {
flags = PIPE_PORT;
if (error) Err("could not create pipe", STk_makestring(name));
else goto Out;
}
}
z = STk_Cfile2port(full_name, f, type, flags);
Out:
STk_allow_sigint();
return(z);
}
static SCM verify_port(char *who, SCM port, int mode)
{
char buff[100];
if (port == UNBOUND) /* test write 'cause of flush */
port = (mode&F_WRITE) ? STk_curr_oport: STk_curr_iport;
if (!(INP(port) || OUTP(port))) {
sprintf(buff, "%s: bad port", who);
Err(buff, port);
}
if (PORT_FLAGS(port) & PORT_CLOSED) {
sprintf(buff, "%s: port is closed", who);
Err(buff, port);
}
if ((mode & F_READ) && INP(port)) return port; /* not else. It can be both */
if ((mode & F_WRITE) && OUTP(port)) return port;
Error:
sprintf(buff, "%s: bad port", who);
Err(buff, port);
}
static void closeport(SCM port)
{
if (PORT_FLAGS(port) & PORT_CLOSED) return;
STk_disallow_sigint();
if (IPORTP(port) || OPORTP(port)) { /* Not a string port */
#ifdef USE_TK
/* For pipe and file ports, delete the fileevent associated to it (if any) */
Tcl_DeleteFileHandler(Tcl_GetFile((ClientData) fileno(PORT_FILE(port)),
TCL_UNIX_FD));
#endif
if (PORT_FLAGS(port) & PIPE_PORT) /* Pipe port */
pclose(PORT_FILE(port));
else /* File port */
fclose(PORT_FILE(port));
}
PORT_FLAGS(port) |= PORT_CLOSED;
STk_allow_sigint();
}
void STk_freeport(SCM port)
{
STk_disallow_sigint();
closeport(port);
free(PORT_NAME(port));
free(port->storage_as.port.p);
STk_allow_sigint();
}
void STk_init_standard_ports(void)
{
STk_curr_iport = STk_Cfile2port("*stdin*", STk_stdin, tc_iport, 0);
STk_gc_protect(&STk_curr_iport);
STk_curr_oport = STk_Cfile2port("*stdout*", STk_stdout, tc_oport, 0);
STk_gc_protect(&STk_curr_oport);
STk_curr_eport = STk_Cfile2port("*stderr*", STk_stderr, tc_oport, 0);
STk_gc_protect(&STk_curr_eport);
NEWCELL(STk_eof_object, tc_eof);
STk_gc_protect(&STk_eof_object);
STk_line_counter = 1;
STk_current_filename = UNBOUND; /* Ubound <=> stdin */
STk_gc_protect(&STk_current_filename);
}
/******************************************************************************
*
* L O A D stuff
*
******************************************************************************/
static int do_load(char *full_name)
{
FILE *f;
int c;
if (!STk_dirp(full_name)) {
f = fopen(full_name, "r");
if (f == NULL) return 0;
if (VCELL(Intern(LOAD_VERBOSE)) != Ntruth)
fprintf(STk_stderr, ";; Loading file \"%s\"\n", full_name);
/* Just read one character. Assume that file is an object if this
* character is a control one. Here, I don't try to see if the file magic
* number has a particular value, since I'm not nure that all platforms
* use identical conventions
*/
c = Getc(f); Ungetc(c, f);
if (c != EOF && ((iscntrl(c)&& c!= '\n') || !isascii(c))) {
fclose(f);
STk_load_object_file(full_name);
}
else {
/* file seems not to be an object file. Try to load it as a Scheme file */
jmp_buf jb, *prev_jb = Top_jmp_buf;
long prev_context = Error_context;
SCM previous_file, form;
int k, previous_line;
/* Save info about current line and file */
previous_file = STk_current_filename;
previous_line = STk_line_counter;
STk_line_counter = 1;
STk_current_filename = STk_makestring(full_name);
/* save normal error jmpbuf so that eval error don't lead to toplevel */
/* This permits to close the opened file in case of error */
/* If in a "catch", keep the ERR_IGNORED bit set */
if ((k = setjmp(jb)) == 0) {
Top_jmp_buf = &jb;
for( ; ; ) {
form = STk_readf(f, FALSE);
if EQ(form, STk_eof_object) break;
STk_eval(form, NIL);
}
}
fclose(f);
Top_jmp_buf = prev_jb;
Error_context = prev_context;
if (k) /*propagate error */ longjmp(*Top_jmp_buf, k);
/* No error: restore info about current line and file */
STk_current_filename = previous_file;
STk_line_counter = previous_line;
}
if (VCELL(Intern(LOAD_VERBOSE)) != Ntruth)
fprintf(STk_stderr, ";; File \"%s\" loaded\n", full_name);
return 1;
}
/* No file found */
return 0;
}
static int try_loadfile(char *prefix, char *fname, SCM suffixes)
{
char full_name[MAX_PATH_LENGTH], *s;
/* First try to load without suffix */
if (strlen(prefix) + strlen(fname) + 2 >= MAX_PATH_LENGTH) goto TooLong;
sprintf(full_name, "%s%s%s", prefix, (*prefix ? "/": ""), fname);
if (do_load(full_name)) return 1;
/* Now try to load file with suffix */
for ( ; NNULLP(suffixes); suffixes = CDR(suffixes)) {
/* We are sure that suffixes is a well formed list (ensured by loadfile) */
if (NSTRINGP(CAR(suffixes))) Err("load: bad suffix component", CAR(suffixes));
s = CHARS(CAR(suffixes));
if (strlen(prefix)+strlen(fname)+strlen(s)+3 >= MAX_PATH_LENGTH) goto TooLong;
sprintf(full_name, "%s%s%s.%s", prefix, (*prefix ? "/": ""), fname, s);
if (do_load(full_name)) return 1;
}
/* No file loaded */
return 0;
TooLong:
Err("load: Filename too long", NIL);
}
SCM STk_loadfile(char *fname, int err_if_absent)
{
int len;
SCM load_path, load_suffixes;
len = strlen(fname);
load_path = VCELL(Intern(LOAD_PATH));
load_suffixes = VCELL(Intern(LOAD_SUFFIXES));
if (STk_llength(load_path)<0) Err("load: bad loading path", load_path);
if (STk_llength(load_suffixes)<0) Err("load: bad set of suffixes", load_suffixes);
#ifdef WIN32
if ((len > 0 && (fname[0] == '/' || fname[0] == '\\' || fname[0] == '~')) ||
(len > 1 && fname[0] == '.' && (fname[1] == '/' || fname[1] == '\\')) ||
(len > 2 && fname[0] == '.' && fname[1] == '.' && (fname[2] == '/' ||
fname[2]=='\\')) ||
(len > 1 && isalpha(fname[0]) && fname[1]==':')) {
#else
if ((len > 0 && (fname[0] == '/' || fname[0] == '~')) ||
(len > 1 && fname[0] == '.' && fname[1] == '/') ||
(len > 2 && fname[0] == '.' && fname[1] == '.' && fname[2] == '/')) {
#endif
if (fname[0] == '~')
fname = CHARS(STk_internal_expand_file_name(fname));
if (try_loadfile("", fname, load_suffixes))
return(err_if_absent? UNDEFINED: Truth);
}
else {
/* Use *load-path* for loading file */
for ( ; NNULLP(load_path); load_path = CDR(load_path)) {
if (NSTRINGP(CAR(load_path)))
Err("load: bad loading path component", CAR(load_path));
if (try_loadfile(CHARS(CAR(load_path)), fname, load_suffixes))
return(err_if_absent? UNDEFINED: Truth);
}
}
/* If we are here, we have been unable to load a file. Report err if needed */
if (err_if_absent)
Err("load: cannot open file", STk_makestring(fname));
return Ntruth;
}
PRIMITIVE STk_input_portp(SCM port)
{
return IPORTP(port)? Truth: Ntruth;
}
PRIMITIVE STk_output_portp(SCM port)
{
return OPORTP(port)? Truth: Ntruth;
}
PRIMITIVE STk_current_input_port(void)
{
return STk_curr_iport;
}
PRIMITIVE STk_current_output_port(void)
{
return STk_curr_oport;
}
PRIMITIVE STk_current_error_port(void)
{
return STk_curr_eport;
}
PRIMITIVE STk_with_input_from_file(SCM string, SCM thunk)
{
jmp_buf env, *prev_env = Top_jmp_buf;
SCM result, prev_iport = STk_curr_iport;
int prev_context = Error_context;
int k;
if (NSTRINGP(string)) Err("with-input-from-file: bad string", string);
if (!STk_is_thunk(thunk)) Err("with-input-from-file: bad thunk", thunk);
STk_curr_iport = UNBOUND; /* will not be changed if opening fails */
if ((k = setjmp(env)) == 0) {
Top_jmp_buf = &env;
STk_curr_iport = makeport(CHARS(string), tc_iport, "r", TRUE);
result = Apply(thunk, NIL);
}
/* restore normal error jmpbuf and current input port*/
if (STk_curr_iport != UNBOUND) closeport(STk_curr_iport);
STk_curr_iport = prev_iport;
Top_jmp_buf = prev_env;
Error_context = prev_context;
if (k) /*propagate error */ longjmp(*Top_jmp_buf, k);
return result;
}
PRIMITIVE STk_with_output_to_file(SCM string, SCM thunk)
{
jmp_buf env, *prev_env = Top_jmp_buf;
SCM result, prev_oport = STk_curr_oport;
int prev_context = Error_context;
int k;
if (NSTRINGP(string)) Err("with-output-to-file: bad string", string);
if (!STk_is_thunk(thunk)) Err("with-output-to-file: bad thunk", thunk);
STk_curr_oport = UNBOUND; /* will not be changed if opening fails */
if ((k = setjmp(env)) == 0) {
Top_jmp_buf = &env;
STk_curr_oport = makeport(CHARS(string), tc_oport, "w", TRUE);
result = Apply(thunk, NIL);
}
/* restore normal error jmpbuf and current output port*/
if (STk_curr_oport != UNBOUND) closeport(STk_curr_oport);
STk_curr_oport = prev_oport;
Top_jmp_buf = prev_env;
Error_context = prev_context;
if (k) /*propagate error */ longjmp(*Top_jmp_buf, k);
return result;
}
PRIMITIVE STk_open_input_file(SCM filename)
{
if (NSTRINGP(filename)) Err("open-input-file: bad file name", filename);
return makeport(CHARS(filename), tc_iport, "r", TRUE);
}
PRIMITIVE STk_open_output_file(SCM filename)
{
if (NSTRINGP(filename)) Err("open-output-file: bad file name", filename);
return makeport(CHARS(filename), tc_oport, "w", TRUE);
}
PRIMITIVE STk_close_input_port(SCM port)
{
if (!INP(port)) Err("close-input-port: not an input port", port);
closeport(port);
return UNDEFINED;
}
PRIMITIVE STk_close_output_port(SCM port)
{
if (!OUTP(port)) Err("close-output-port: not an output port", port);
closeport(port);
return UNDEFINED;
}
PRIMITIVE STk_read(SCM port)
{
port = verify_port("read", port, F_READ);
return(STk_readf(PORT_FILE(port), FALSE));
}
PRIMITIVE STk_read_char(SCM port)
{
int c;
port = verify_port("read-char", port, F_READ);
c = Getc(PORT_FILE(port));
return (c == EOF) ? STk_eof_object : STk_makechar(c);
}
PRIMITIVE STk_peek_char(SCM port)
{
int c;
port = verify_port("peek-char", port, F_READ);
c = Getc(PORT_FILE(port));
Ungetc(c, PORT_FILE(port));
return (c == EOF) ? STk_eof_object : STk_makechar(c);
}
PRIMITIVE STk_eof_objectp(SCM obj)
{
return (obj == STk_eof_object)? Truth : Ntruth;
}
#ifdef WIN32
PRIMITIVE STk_char_readyp(SCM port)
{
STk_panic("Not yet implemented!");
}
#else
PRIMITIVE STk_char_readyp(SCM port)
{
port = verify_port("char-ready?", port, F_READ);
if (Eof(PORT_FILE(port))) return Truth;
if (ISPORTP(port)) /* !eof -> */ return Truth;
else {
/* First, see if characters are available in the buffer */
if (STk_file_data_available(PORT_FILE(port)))
return Truth;
#ifdef HAVE_SELECT
{
SELECT_MASK readfds;
struct timeval timeout;
int f = fileno(PORT_FILE(port));
FD_ZERO(&readfds);
FD_SET(f, &readfds);
timeout.tv_sec = timeout.tv_usec = 0;
return (select(f+1, &readfds, NULL, NULL, &timeout)) ? Truth : Ntruth;
}
#else
# ifdef FIONREAD
{
int result;
ioctl(fileno(PORT_FILE(port)), FIONREAD, &result);
return result ? Truth : Ntruth;
}
# else
return Truth;
# endif
#endif
}
}
#endif
PRIMITIVE STk_write(SCM expr, SCM port)
{
port = verify_port("write", port, F_WRITE);
STk_print(expr, port, WRT_MODE);
return UNDEFINED;
}
PRIMITIVE STk_display(SCM expr, SCM port)
{
port = verify_port("display", port, F_WRITE);
STk_print(expr, port, DSP_MODE);
return UNDEFINED;
}
PRIMITIVE STk_newline(SCM port)
{
port = verify_port("newline", port, F_WRITE);
Putc('\n', PORT_FILE(port));
return UNDEFINED;
}
PRIMITIVE STk_write_char(SCM c, SCM port)
{
if (NCHARP(c)) Err("write-char: not a character", c);
port = verify_port("write-char", port, F_WRITE);
Putc(CHAR(c), PORT_FILE(port));
return UNDEFINED;
}
/*
* The name `scheme_load' is needed because of a symbol table conflict
* in libc. This is bogus, but what do you do.
*/
PRIMITIVE STk_scheme_load(SCM filename)
{
if (NSTRINGP(filename)) Err("load: bad file name", filename);
return STk_loadfile(CHARS(filename), 1);
}
/*
*
* STk bonus
*
*/
static SCM internal_format(SCM l,int len,int error)/* a very simple and poor one */
{
SCM port, fmt;
int format_in_string = 0;
char *p;
FILE *f;
if (error) {
if (len < 1) Err("error: Bad list of parameters", l);
format_in_string = 1;
port = STk_open_output_string();
len -= 1;
}
else {
if (len < 2) Err("format: Bad list of parameters", l);
port = CAR(l); l = CDR(l);
len -= 2;
}
fmt = CAR(l); l = CDR(l);
if (BOOLEANP(port)){
if (port == Truth) port = STk_curr_oport;
else {
format_in_string = 1;
port= STk_open_output_string();
}
}
verify_port(error? "error": "format", port, F_WRITE);
if (NSTRINGP(fmt)) Err("format: bad format string", fmt);
f = PORT_FILE(port);
for(p=CHARS(fmt); *p; p++) {
if (*p == '~') {
switch(*(++p)) {
case 'S':
case 's':
case 'A':
case 'a': if (len-- > 0) {
STk_print(CAR(l),
port,
(tolower(*p) == 's')? WRT_MODE: DSP_MODE);
l = CDR(l);
}
else Err("format: too much ~ in format string", l);
continue;
case '%': Putc('\n', f);
continue;
case '~': Putc('~', f);
continue;
default: Putc('~', f);
/* NO BREAK */
}
}
Putc(*p, f);
}
if (NNULLP(l)) Err("format: too few ~ in format string", l);
return format_in_string ? STk_get_output_string(port) : UNDEFINED;
}
PRIMITIVE STk_format(SCM l, int len)
{
return internal_format(l, len, FALSE);
}
PRIMITIVE STk_error(SCM l, int len)
{
/* Set context to ERR_OK but keep the bit indicating if error must be caught */
Error_context = ERR_OK | (Error_context & ERR_IGNORED);
Err(CHARS(internal_format(l, len, TRUE)), NIL);
return UNDEFINED; /* for compiler */
}
PRIMITIVE STk_try_load(SCM filename)
{
if (NSTRINGP(filename)) Err("try-load: bad file name", filename);
return STk_loadfile(CHARS(filename), FALSE);
}
PRIMITIVE STk_open_file(SCM filename, SCM mode)
{
int type;
if (NSTRINGP(filename)) Err("open-file: bad file name", filename);
if (NSTRINGP(mode) || CHARS(mode)[1] != '\0') goto Error;
switch (CHARS(mode)[0]) {
case 'a':
case 'w': type = tc_oport; break;
case 'r': type = tc_iport; break;
default: ;
Error: Err("open-file: bad mode", mode);
}
return(makeport(CHARS(filename), type, CHARS(mode), FALSE));
}
PRIMITIVE STk_close_port(SCM port)
{
if (INP(port) || OUTP(port)) closeport(port);
else Err("close-port: bad port", port);
return UNDEFINED;
}
PRIMITIVE STk_read_line(SCM port)
{
FILE *f;
int c, i, size = 128;
char *buff = (char *) must_malloc(size);
SCM res;
port = verify_port("read-line", port, F_READ);
f = PORT_FILE(port);
for (i = 0; ; i++) {
switch (c = Getc(f)) {
case EOF: if (i == 0) { free(buff); return STk_eof_object; }
case '\n': res = STk_makestrg(i, buff); free(buff); return res;
default: if (i == size) {
size += size / 2;
buff = must_realloc(buff, size);
}
buff[i] = c;
}
}
}
PRIMITIVE STk_flush(SCM port)
{
int code;
port = verify_port("flush", port, F_WRITE|F_READ);
code = fflush(PORT_FILE(port));
if (code == EOF) Err("flush: cannot flush buffer", port);
return UNDEFINED;
}
/******************************************************************************
*
* Autoload stuff
*
******************************************************************************/
static SCM list_of_files = NULL;
static SCM make_autoload(SCM file)
{
SCM z;
NEWCELL(z, tc_autoload);
CAR(z) = file;
return z;
}
void STk_do_autoload(SCM var)
{
SCM file, autoload;
autoload = VCELL(var); file = CAR(autoload);
/* Retain in a list, files which are currently autoloaded to avoid mult. load */
if (!list_of_files) {
list_of_files = NIL;
STk_gc_protect(&list_of_files);
}
if (STk_member(file, list_of_files) != Ntruth) return;
list_of_files = Cons(file, list_of_files);
STk_loadfile(CHARS(file), TRUE);
list_of_files = CDR(list_of_files);
if (TYPEP(VCELL(var), tc_autoload)) {
Err("autoload: symbol was not defined", var);
}
}
PRIMITIVE STk_autoload(SCM l, SCM env, int len)
{
SCM file;
if (len < 2) Err("autoload: bad parameter list", l);
file = CAR(l);
if (NSTRINGP(file)) Err("autoload: bad file name", file);
for (l = CDR(l); NNULLP(l); l = CDR(l)) {
if (NSYMBOLP(CAR(l))) Err("autoload: bad symbol", CAR(l));
VCELL(CAR(l)) = make_autoload(file);
}
return UNDEFINED;
}
PRIMITIVE STk_autoloadp(SCM l, SCM env, int len)
{
if (len != 1 || NSYMBOLP(CAR(l)))
Err("autoload?: bad symbol", l);
return TYPEP(CAR(l), tc_autoload) ? Truth: Ntruth;
}
#ifdef USE_TK
/******************************************************************************
*
* Port event management
*
******************************************************************************/
static void apply_file_closure(SCM closure)
{
Apply(closure, NIL);
}
static SCM when_port_ready(SCM port, SCM closure, char *name, int mode)
{
char str[50];
Tcl_File f;
if (NIPORTP(port) && NOPORTP(port)) {
sprintf(str, "%s: bad port", name);
STk_err(str, port);
}
if (closure == UNBOUND) {
/* Return the current handler closure */
return ((mode == TCL_READABLE)? PORT_REVENT(port): PORT_WEVENT(port));
}
f = Tcl_GetFile((ClientData) fileno(PORT_FILE(port)), TCL_UNIX_FD);
if (closure == Ntruth) {
Tcl_DeleteFileHandler(f);
if (mode == TCL_READABLE)
PORT_REVENT(port) = Ntruth;
else
PORT_WEVENT(port) = Ntruth;
}
else {
if (STk_procedurep(closure) == Ntruth) {
sprintf(str, "%s: bad closure", name);
STk_err(str, closure);
}
Tcl_CreateFileHandler(f, mode, (Tcl_FileProc *) apply_file_closure,
(ClientData) closure);
if (mode == TCL_READABLE)
PORT_REVENT(port) = closure;
else
PORT_WEVENT(port) = closure;
}
return UNDEFINED;
}
PRIMITIVE STk_when_port_readable(SCM port, SCM closure)
{
return when_port_ready(port, closure, "when-port-readable", TCL_READABLE);
}
PRIMITIVE STk_when_port_writable(SCM port, SCM closure)
{
return when_port_ready(port, closure, "when-port-writable", TCL_WRITABLE);
}
#endif