mirror of
https://github.com/janet-lang/janet
synced 2025-10-29 22:53:03 +00:00
Update C API to use friendlier functions rather than macros.
Error handling is implemented with setjmp/longjmp so code can be more concise. This required a very large but straight forward refactor for all of the libraries.
This commit is contained in:
283
src/core/io.c
283
src/core/io.c
@@ -53,13 +53,16 @@ JanetAbstractType janet_io_filetype = {
|
||||
};
|
||||
|
||||
/* Check argupments to fopen */
|
||||
static int checkflags(const uint8_t *str, int32_t len) {
|
||||
static int checkflags(const uint8_t *str) {
|
||||
int flags = 0;
|
||||
int32_t i;
|
||||
if (!len || len > 3) return -1;
|
||||
int32_t len = janet_string_length(str);
|
||||
if (!len || len > 3)
|
||||
janet_panic("file mode must have a length between 1 and 3");
|
||||
switch (*str) {
|
||||
default:
|
||||
return -1;
|
||||
janet_panicf("invalid flag %c, expected w, a, or r", *str);
|
||||
break;
|
||||
case 'w':
|
||||
flags |= IO_WRITE;
|
||||
break;
|
||||
@@ -73,7 +76,8 @@ static int checkflags(const uint8_t *str, int32_t len) {
|
||||
for (i = 1; i < len; i++) {
|
||||
switch (str[i]) {
|
||||
default:
|
||||
return -1;
|
||||
janet_panicf("invalid flag %c, expected + or b", str[i]);
|
||||
break;
|
||||
case '+':
|
||||
if (flags & IO_UPDATE) return -1;
|
||||
flags |= IO_UPDATE;
|
||||
@@ -87,41 +91,6 @@ static int checkflags(const uint8_t *str, int32_t len) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
/* Check file argument */
|
||||
static IOFile *checkfile(JanetArgs args, int32_t n) {
|
||||
IOFile *iof;
|
||||
if (n >= args.n) {
|
||||
*args.ret = janet_cstringv("expected core.file");
|
||||
return NULL;
|
||||
}
|
||||
if (!janet_checktype(args.v[n], JANET_ABSTRACT)) {
|
||||
*args.ret = janet_cstringv("expected core.file");
|
||||
return NULL;
|
||||
}
|
||||
iof = (IOFile *) janet_unwrap_abstract(args.v[n]);
|
||||
if (janet_abstract_type(iof) != &janet_io_filetype) {
|
||||
*args.ret = janet_cstringv("expected core.file");
|
||||
return NULL;
|
||||
}
|
||||
return iof;
|
||||
}
|
||||
|
||||
/* Check buffer argument */
|
||||
static JanetBuffer *checkbuffer(JanetArgs args, int32_t n, int optional) {
|
||||
if (optional && n == args.n) {
|
||||
return janet_buffer(0);
|
||||
}
|
||||
if (n >= args.n) {
|
||||
*args.ret = janet_cstringv("expected buffer");
|
||||
return NULL;
|
||||
}
|
||||
if (!janet_checktype(args.v[n], JANET_BUFFER)) {
|
||||
*args.ret = janet_cstringv("expected buffer");
|
||||
return NULL;
|
||||
}
|
||||
return janet_unwrap_abstract(args.v[n]);
|
||||
}
|
||||
|
||||
static Janet makef(FILE *f, int flags) {
|
||||
IOFile *iof = (IOFile *) janet_abstract(&janet_io_filetype, sizeof(IOFile));
|
||||
iof->file = f;
|
||||
@@ -130,97 +99,75 @@ static Janet makef(FILE *f, int flags) {
|
||||
}
|
||||
|
||||
/* Open a process */
|
||||
static int janet_io_popen(JanetArgs args) {
|
||||
const uint8_t *fname, *fmode;
|
||||
int32_t modelen;
|
||||
FILE *f;
|
||||
int flags;
|
||||
JANET_MINARITY(args, 1);
|
||||
JANET_MAXARITY(args, 2);
|
||||
JANET_ARG_STRING(fname, args, 0);
|
||||
if (args.n == 2) {
|
||||
if (!janet_checktype(args.v[1], JANET_STRING) &&
|
||||
!janet_checktype(args.v[1], JANET_SYMBOL))
|
||||
JANET_THROW(args, "expected string mode");
|
||||
fmode = janet_unwrap_string(args.v[1]);
|
||||
modelen = janet_string_length(fmode);
|
||||
} else {
|
||||
fmode = (const uint8_t *)"r";
|
||||
modelen = 1;
|
||||
static Janet janet_io_popen(int32_t argc, Janet *argv) {
|
||||
janet_arity(argc, 1, 2);
|
||||
const uint8_t *fname = janet_getstring(argv, 0);
|
||||
const uint8_t *fmode = NULL;
|
||||
if (argc == 2) {
|
||||
fmode = janet_getkeyword(argv, 1);
|
||||
if (janet_string_length(fmode) != 1 ||
|
||||
!(fmode[0] == 'r' || fmode[0] == 'w')) {
|
||||
janet_panicf("invalid file mode :%S, expected :r or :w", fmode);
|
||||
}
|
||||
}
|
||||
if (modelen != 1 || !(fmode[0] == 'r' || fmode[0] == 'w')) {
|
||||
JANET_THROW(args, "invalid file mode");
|
||||
}
|
||||
flags = (fmode[0] == 'r') ? IO_PIPED | IO_READ : IO_PIPED | IO_WRITE;
|
||||
int flags = (fmode && fmode[0] == '2')
|
||||
? IO_PIPED | IO_WRITE
|
||||
: IO_PIPED | IO_READ;
|
||||
#ifdef JANET_WINDOWS
|
||||
#define popen _popen
|
||||
#endif
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#define popen(A, B) (errno = 0, NULL)
|
||||
#endif
|
||||
f = popen((const char *)fname, (const char *)fmode);
|
||||
FILE *f = popen((const char *)fname, (const char *)fmode);
|
||||
if (!f) {
|
||||
if (errno == EMFILE) {
|
||||
JANET_THROW(args, "too many streams are open");
|
||||
}
|
||||
JANET_THROW(args, "could not open file");
|
||||
return janet_wrap_nil();
|
||||
}
|
||||
JANET_RETURN(args, makef(f, flags));
|
||||
return makef(f, flags);
|
||||
}
|
||||
|
||||
/* Open a a file and return a userdata wrapper around the C file API. */
|
||||
static int janet_io_fopen(JanetArgs args) {
|
||||
const uint8_t *fname, *fmode;
|
||||
int32_t modelen;
|
||||
FILE *f;
|
||||
static Janet janet_io_fopen(int32_t argc, Janet *argv) {
|
||||
janet_arity(argc, 1, 2);
|
||||
const uint8_t *fname = janet_getstring(argv, 0);
|
||||
const uint8_t *fmode;
|
||||
int flags;
|
||||
JANET_MINARITY(args, 1);
|
||||
JANET_MAXARITY(args, 2);
|
||||
JANET_ARG_STRING(fname, args, 0);
|
||||
if (args.n == 2) {
|
||||
if (!janet_checktype(args.v[1], JANET_STRING) &&
|
||||
!janet_checktype(args.v[1], JANET_SYMBOL))
|
||||
JANET_THROW(args, "expected string mode");
|
||||
fmode = janet_unwrap_string(args.v[1]);
|
||||
modelen = janet_string_length(fmode);
|
||||
if (argc == 2) {
|
||||
fmode = janet_getkeyword(argv, 1);
|
||||
flags = checkflags(fmode);
|
||||
} else {
|
||||
fmode = (const uint8_t *)"r";
|
||||
modelen = 1;
|
||||
flags = IO_READ;
|
||||
}
|
||||
if ((flags = checkflags(fmode, modelen)) < 0) {
|
||||
JANET_THROW(args, "invalid file mode");
|
||||
}
|
||||
f = fopen((const char *)fname, (const char *)fmode);
|
||||
JANET_RETURN(args, f ? makef(f, flags) : janet_wrap_nil());
|
||||
FILE *f = fopen((const char *)fname, (const char *)fmode);
|
||||
return f ? makef(f, flags) : janet_wrap_nil();
|
||||
}
|
||||
|
||||
/* Read up to n bytes into buffer. Return error string if error. */
|
||||
static const char *read_chunk(IOFile *iof, JanetBuffer *buffer, int32_t nBytesMax) {
|
||||
static void read_chunk(IOFile *iof, JanetBuffer *buffer, int32_t nBytesMax) {
|
||||
if (!(iof->flags & (IO_READ | IO_UPDATE)))
|
||||
return "file is not readable";
|
||||
/* Ensure buffer size */
|
||||
if (janet_buffer_extra(buffer, nBytesMax))
|
||||
return "buffer overflow";
|
||||
janet_panic("file is not readable");
|
||||
janet_buffer_extra(buffer, nBytesMax);
|
||||
size_t ntoread = nBytesMax;
|
||||
size_t nread = fread((char *)(buffer->data + buffer->count), 1, ntoread, iof->file);
|
||||
if (nread != ntoread && ferror(iof->file))
|
||||
return "could not read file";
|
||||
janet_panic("could not read file");
|
||||
buffer->count += (int32_t) nread;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read a certain number of bytes into memory */
|
||||
static int janet_io_fread(JanetArgs args) {
|
||||
JanetBuffer *b;
|
||||
IOFile *iof = checkfile(args, 0);
|
||||
if (!iof) return 1;
|
||||
if (iof->flags & IO_CLOSED)
|
||||
JANET_THROW(args, "file is closed");
|
||||
b = checkbuffer(args, 2, 1);
|
||||
if (!b) return 1;
|
||||
JANET_MINARITY(args, 2);
|
||||
if (janet_checktype(args.v[1], JANET_KEYWORD)) {
|
||||
const uint8_t *sym = janet_unwrap_symbol(args.v[1]);
|
||||
static Janet janet_io_fread(int32_t argc, Janet *argv) {
|
||||
janet_arity(argc, 2, 3);
|
||||
IOFile *iof = janet_getabstract(argv, 0, &janet_io_filetype);
|
||||
if (iof->flags & IO_CLOSED) janet_panic("file is closed");
|
||||
JanetBuffer *buffer;
|
||||
if (argc == 2) {
|
||||
buffer = janet_buffer(0);
|
||||
} else {
|
||||
buffer = janet_getbuffer(argv, 2);
|
||||
}
|
||||
if (janet_checktype(argv[1], JANET_KEYWORD)) {
|
||||
const uint8_t *sym = janet_unwrap_keyword(argv[1]);
|
||||
if (!janet_cstrcmp(sym, "all")) {
|
||||
/* Read whole file */
|
||||
int status = fseek(iof->file, 0, SEEK_SET);
|
||||
@@ -228,71 +175,66 @@ static int janet_io_fread(JanetArgs args) {
|
||||
/* backwards fseek did not work (stream like popen) */
|
||||
int32_t sizeBefore;
|
||||
do {
|
||||
sizeBefore = b->count;
|
||||
const char *maybeErr = read_chunk(iof, b, 1024);
|
||||
if (maybeErr) JANET_THROW(args, maybeErr);
|
||||
} while (sizeBefore < b->count);
|
||||
sizeBefore = buffer->count;
|
||||
read_chunk(iof, buffer, 1024);
|
||||
} while (sizeBefore < buffer->count);
|
||||
} else {
|
||||
fseek(iof->file, 0, SEEK_END);
|
||||
long fsize = ftell(iof->file);
|
||||
fseek(iof->file, 0, SEEK_SET);
|
||||
if (fsize > INT32_MAX) JANET_THROW(args, "buffer overflow");
|
||||
const char *maybeErr = read_chunk(iof, b, (int32_t) fsize);;
|
||||
if (maybeErr) JANET_THROW(args, maybeErr);
|
||||
read_chunk(iof, buffer, (int32_t) fsize);
|
||||
}
|
||||
} else if (!janet_cstrcmp(sym, "line")) {
|
||||
for (;;) {
|
||||
int x = fgetc(iof->file);
|
||||
if (x != EOF && janet_buffer_push_u8(b, (uint8_t)x))
|
||||
JANET_THROW(args, "buffer overflow");
|
||||
if (x != EOF) janet_buffer_push_u8(buffer, (uint8_t)x);
|
||||
if (x == EOF || x == '\n') break;
|
||||
}
|
||||
} else {
|
||||
JANET_THROW(args, "expected one of :all, :line");
|
||||
janet_panicf("expected one of :all, :line, got %v", argv[1]);
|
||||
}
|
||||
} else if (!janet_checkint(args.v[1])) {
|
||||
JANET_THROW(args, "expected positive integer");
|
||||
} else {
|
||||
int32_t len = janet_unwrap_integer(args.v[1]);
|
||||
if (len < 0) JANET_THROW(args, "expected positive integer");
|
||||
const char *maybeErr = read_chunk(iof, b, len);
|
||||
if (maybeErr) JANET_THROW(args, maybeErr);
|
||||
int32_t len = janet_getinteger(argv, 1);
|
||||
if (len < 0) janet_panic("expected positive integer");
|
||||
read_chunk(iof, buffer, len);
|
||||
}
|
||||
JANET_RETURN(args, janet_wrap_buffer(b));
|
||||
return janet_wrap_buffer(buffer);
|
||||
}
|
||||
|
||||
/* Write bytes to a file */
|
||||
static int janet_io_fwrite(JanetArgs args) {
|
||||
int32_t len, i;
|
||||
const uint8_t *str;
|
||||
IOFile *iof = checkfile(args, 0);
|
||||
if (!iof) return 1;
|
||||
static Janet janet_io_fwrite(int32_t argc, Janet *argv) {
|
||||
janet_arity(argc, 1, -1);
|
||||
IOFile *iof = janet_getabstract(argv, 0, &janet_io_filetype);
|
||||
if (iof->flags & IO_CLOSED)
|
||||
JANET_THROW(args, "file is closed");
|
||||
janet_panic("file is closed");
|
||||
if (!(iof->flags & (IO_WRITE | IO_APPEND | IO_UPDATE)))
|
||||
JANET_THROW(args, "file is not writeable");
|
||||
for (i = 1; i < args.n; i++) {
|
||||
JANET_CHECKMANY(args, i, JANET_TFLAG_BYTES);
|
||||
}
|
||||
for (i = 1; i < args.n; i++) {
|
||||
JANET_ARG_BYTES(str, len, args, i);
|
||||
if (len) {
|
||||
if (!fwrite(str, len, 1, iof->file)) JANET_THROW(args, "error writing to file");
|
||||
janet_panic("file is not writeable");
|
||||
int32_t i;
|
||||
/* Verify all arguments before writing to file */
|
||||
for (i = 1; i < argc; i++)
|
||||
janet_getbytes(argv, i);
|
||||
for (i = 1; i < argc; i++) {
|
||||
JanetByteView view = janet_getbytes(argv, i);
|
||||
if (view.len) {
|
||||
if (!fwrite(view.bytes, view.len, 1, iof->file)) {
|
||||
janet_panic("error writing to file");
|
||||
}
|
||||
}
|
||||
}
|
||||
JANET_RETURN(args, janet_wrap_abstract(iof));
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
/* Flush the bytes in the file */
|
||||
static int janet_io_fflush(JanetArgs args) {
|
||||
IOFile *iof = checkfile(args, 0);
|
||||
if (!iof) return 1;
|
||||
static Janet janet_io_fflush(int32_t argc, Janet *argv) {
|
||||
janet_arity(argc, 1, 1);
|
||||
IOFile *iof = janet_getabstract(argv, 0, &janet_io_filetype);
|
||||
if (iof->flags & IO_CLOSED)
|
||||
JANET_THROW(args, "file is closed");
|
||||
janet_panic("file is closed");
|
||||
if (!(iof->flags & (IO_WRITE | IO_APPEND | IO_UPDATE)))
|
||||
JANET_THROW(args, "file is not flushable");
|
||||
if (fflush(iof->file)) JANET_THROW(args, "could not flush file");
|
||||
JANET_RETURN(args, janet_wrap_abstract(iof));
|
||||
janet_panic("file is not writeable");
|
||||
if (fflush(iof->file))
|
||||
janet_panic("could not flush file");
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
/* Cleanup a file */
|
||||
@@ -306,38 +248,35 @@ static int janet_io_gc(void *p, size_t len) {
|
||||
}
|
||||
|
||||
/* Close a file */
|
||||
static int janet_io_fclose(JanetArgs args) {
|
||||
IOFile *iof = checkfile(args, 0);
|
||||
if (!iof) return 1;
|
||||
if (iof->flags & (IO_CLOSED))
|
||||
JANET_THROW(args, "file already closed");
|
||||
static Janet janet_io_fclose(int32_t argc, Janet *argv) {
|
||||
janet_arity(argc, 1, 1);
|
||||
IOFile *iof = janet_getabstract(argv, 0, &janet_io_filetype);
|
||||
if (iof->flags & IO_CLOSED)
|
||||
janet_panic("file is closed");
|
||||
if (iof->flags & (IO_NOT_CLOSEABLE))
|
||||
JANET_THROW(args, "file not closable");
|
||||
janet_panic("file not closable");
|
||||
if (iof->flags & IO_PIPED) {
|
||||
#ifdef JANET_WINDOWS
|
||||
#define pclose _pclose
|
||||
#endif
|
||||
if (pclose(iof->file)) JANET_THROW(args, "could not close file");
|
||||
if (pclose(iof->file)) janet_panic("could not close file");
|
||||
} else {
|
||||
if (fclose(iof->file)) JANET_THROW(args, "could not close file");
|
||||
if (fclose(iof->file)) janet_panic("could not close file");
|
||||
}
|
||||
iof->flags |= IO_CLOSED;
|
||||
JANET_RETURN(args, janet_wrap_abstract(iof));
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
/* Seek a file */
|
||||
static int janet_io_fseek(JanetArgs args) {
|
||||
static Janet janet_io_fseek(int32_t argc, Janet *argv) {
|
||||
janet_arity(argc, 2, 3);
|
||||
IOFile *iof = janet_getabstract(argv, 0, &janet_io_filetype);
|
||||
if (iof->flags & IO_CLOSED)
|
||||
janet_panic("file is closed");
|
||||
long int offset = 0;
|
||||
int whence = SEEK_CUR;
|
||||
IOFile *iof = checkfile(args, 0);
|
||||
if (!iof) return 1;
|
||||
if (iof->flags & IO_CLOSED)
|
||||
JANET_THROW(args, "file is closed");
|
||||
if (args.n >= 2) {
|
||||
const uint8_t *whence_sym;
|
||||
if (!janet_checktype(args.v[1], JANET_KEYWORD))
|
||||
JANET_THROW(args, "expected keyword");
|
||||
whence_sym = janet_unwrap_symbol(args.v[1]);
|
||||
if (argc >= 2) {
|
||||
const uint8_t *whence_sym = janet_getkeyword(argv, 1);
|
||||
if (!janet_cstrcmp(whence_sym, "cur")) {
|
||||
whence = SEEK_CUR;
|
||||
} else if (!janet_cstrcmp(whence_sym, "set")) {
|
||||
@@ -345,17 +284,14 @@ static int janet_io_fseek(JanetArgs args) {
|
||||
} else if (!janet_cstrcmp(whence_sym, "end")) {
|
||||
whence = SEEK_END;
|
||||
} else {
|
||||
JANET_THROW(args, "expected one of :cur, :set, :end");
|
||||
janet_panicf("expected one of :cur, :set, :end, got %v", argv[1]);
|
||||
}
|
||||
if (args.n >= 3) {
|
||||
double doffset;
|
||||
JANET_ARG_NUMBER(doffset, args, 2);
|
||||
offset = (long int)doffset;
|
||||
if (argc == 3) {
|
||||
offset = janet_getinteger64(argv, 2);
|
||||
}
|
||||
}
|
||||
if (fseek(iof->file, offset, whence))
|
||||
JANET_THROW(args, "error seeking file");
|
||||
JANET_RETURN(args, args.v[0]);
|
||||
if (fseek(iof->file, offset, whence)) janet_panic("error seeking file");
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
static const JanetReg cfuns[] = {
|
||||
@@ -420,25 +356,18 @@ static const JanetReg cfuns[] = {
|
||||
};
|
||||
|
||||
/* Module entry point */
|
||||
int janet_lib_io(JanetArgs args) {
|
||||
JanetTable *env = janet_env(args);
|
||||
void janet_lib_io(JanetTable *env) {
|
||||
janet_cfuns(env, NULL, cfuns);
|
||||
|
||||
/* stdout */
|
||||
janet_def(env, "stdout",
|
||||
makef(stdout, IO_APPEND | IO_NOT_CLOSEABLE | IO_SERIALIZABLE),
|
||||
"The standard output file.");
|
||||
|
||||
|
||||
/* stderr */
|
||||
janet_def(env, "stderr",
|
||||
makef(stderr, IO_APPEND | IO_NOT_CLOSEABLE | IO_SERIALIZABLE),
|
||||
"The standard error file.");
|
||||
|
||||
/* stdin */
|
||||
janet_def(env, "stdin",
|
||||
makef(stdin, IO_READ | IO_NOT_CLOSEABLE | IO_SERIALIZABLE),
|
||||
"The standard input file.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user