From 9240c25c999dc9d69912ce3c1d5d875ff6449520 Mon Sep 17 00:00:00 2001
From: Bill Hails <billhails2014@gmail.com>
Date: Sat, 9 Nov 2024 15:18:43 +0000
Subject: [PATCH] new memstream filehandle type

---
 docs/generated/builtins.md |  3 ++
 fn/ioutils.fn              | 24 +++++++++++
 src/builtin_io.c           | 86 ++++++++++++++++++++++++++++++++++----
 src/builtin_io.h           |  1 +
 src/builtins.yaml          |  8 ++++
 src/memory.c               |  2 +
 src/primitives.yaml        |  4 ++
 tests/fn/test_hygiene.fn   | 14 -------
 tests/fn/test_tostring.fn  |  4 ++
 9 files changed, 123 insertions(+), 23 deletions(-)
 delete mode 100644 tests/fn/test_hygiene.fn
 create mode 100644 tests/fn/test_tostring.fn

diff --git a/docs/generated/builtins.md b/docs/generated/builtins.md
index a30ae33..d638e26 100644
--- a/docs/generated/builtins.md
+++ b/docs/generated/builtins.md
@@ -4,12 +4,15 @@ Support for declaring builtins
 
 ```mermaid
 flowchart TD
+BuiltInMemBufHash --entries--> entries
 BuiltIn --name--> HashSymbol
 BuiltIn --result--> TcType
 BuiltIn --args--> BuiltInArgs
 BuiltIn --implementation--> void_ptr
 BuiltInImplementation --implementation--> void_ptr
 BuiltInImplementation --nargs--> int
+BuiltInMemBuf --buffer--> string
+BuiltInMemBuf --size--> size
 BuiltInArgs["BuiltInArgs[]"] --entries--> TcType
 BuiltIns["BuiltIns[]"] --entries--> BuiltIn
 ```
diff --git a/fn/ioutils.fn b/fn/ioutils.fn
index ebb2abe..797aece 100644
--- a/fn/ioutils.fn
+++ b/fn/ioutils.fn
@@ -29,3 +29,27 @@ fn with_input_from(filename, handler) {
         }
     }
 }
+
+fn with_buffer(handler) {
+    switch (openmem()) {
+        (success(filehandle)) {
+            let data = some(handler(filehandle));
+            in
+                close(filehandle);
+                data;
+        }
+        (failure(errmsg)) {
+            print(errmsg);
+            nothing;
+        }
+    }
+}
+
+fn to_string(data) {
+    unsafe switch (with_buffer(fn (buf) {
+        fputv(buf, data);
+        fgets(buf);
+    })) {
+        (some(result)) { result }
+    }
+}
diff --git a/src/builtin_io.c b/src/builtin_io.c
index a4b9877..29b5cde 100644
--- a/src/builtin_io.c
+++ b/src/builtin_io.c
@@ -54,6 +54,7 @@ static void registerGets(BuiltIns *registry);
 static void registerFGets(BuiltIns *registry);
 
 static void registerOpen(BuiltIns *registry);
+static void registerOpenMemstream(BuiltIns *registry);
 static void registerClose(BuiltIns *registry);
 
 static void registerOpenDir(BuiltIns *registry);
@@ -62,6 +63,21 @@ static void registerCloseDir(BuiltIns *registry);
 
 static void registerFType(BuiltIns *registry);
 
+static BuiltInMemBufHash *memBufs = NULL;
+
+void markMemBufs() {
+    if (memBufs != NULL) {
+        markHashTable((HashTable *) memBufs);
+    }
+}
+
+static BuiltInMemBufHash *getMemBufs(void) {
+    if (memBufs == NULL) {
+        memBufs = newBuiltInMemBufHash();
+    }
+    return memBufs;
+}
+
 void registerIO(BuiltIns *registry) {
     registerPutc(registry);
     registerFPutc(registry);
@@ -76,6 +92,7 @@ void registerIO(BuiltIns *registry) {
     registerGets(registry);
     registerFGets(registry);
     registerOpen(registry);
+    registerOpenMemstream(registry);
     registerClose(registry);
     registerOpenDir(registry);
     registerReadDir(registry);
@@ -157,12 +174,25 @@ static Value builtin_puts(Vec *args) {
 #define IO_MODE_WRITE 1
 #define IO_MODE_APPEND 2
 
+static HashSymbol *fileHandleToKey(FILE *file) {
+    static char buf[128];
+    sprintf(buf, "%p", file);
+    return newSymbol(buf);
+}
 
 static void opaque_io_close(Opaque *data) {
     if (data == NULL) return;
     if (data->data == NULL) return;
     DEBUG("closing io %p", data->data);
     fclose(data->data);
+    HashSymbol *key = fileHandleToKey(data->data);
+    BuiltInMemBuf *memBuf = NULL;
+    if (getBuiltInMemBufHash(getMemBufs(), key, &memBuf)) {
+        if (memBuf->buffer != NULL) {
+            free(memBuf->buffer);
+            memBuf->buffer = NULL;
+        }
+    }
     data->data = NULL;
 }
 
@@ -204,6 +234,21 @@ static Value builtin_open(Vec *args) {
     return result;
 }
 
+static Value builtin_open_memstream(Vec *args __attribute__((unused))) {
+    BuiltInMemBuf *memBuf = newBuiltInMemBuf();
+    int save = PROTECT(memBuf);
+    FILE *file = open_memstream(&memBuf->buffer, &memBuf->size);
+    BuiltInMemBufHash *memBufs = getMemBufs();
+    HashSymbol *key = fileHandleToKey(file);
+    setBuiltInMemBufHash(memBufs, key, memBuf);
+    Opaque *wrapper = newOpaque(file, opaque_io_close, NULL);
+    Value opaque = value_Opaque(wrapper);
+    protectValue(opaque);
+    Value result = makeTryResult(1, opaque);
+    UNPROTECT(save);
+    return result;
+}
+
 static Value builtin_opendir(Vec *args) {
     char *dirname = listToUtf8(args->entries[0]);
     DIR *dir = opendir(dirname);
@@ -364,7 +409,7 @@ void fputValue(FILE *fh, Value x) {
             fputVec(fh, x.val.vec);
             break;
         default:
-            cant_happen("unrecognised value type in fputValue");
+            cant_happen("unrecognised value type %s", valueTypeName(x.type));
     }
 }
 
@@ -390,13 +435,24 @@ void putVec(Vec *x) {
 static Value private_fgets(FILE *fh) {
     ByteArray *bytes = newByteArray();
     int save = PROTECT(bytes);
-    int c;
-    while ((c = fgetc(fh)) != EOF) {
-        if (c == '\n') break;
-        if (c == 0) break;
-        pushByteArray(bytes, (Byte) c);
+    HashSymbol *key = fileHandleToKey(fh);
+    BuiltInMemBuf *buf = NULL;
+    if (getBuiltInMemBufHash(getMemBufs(), key, &buf)) {
+        fflush(fh);
+        if (buf->buffer == NULL) {
+            cant_happen("fgets on null memstream");
+        }
+        char *b = buf->buffer;
+        do { pushByteArray(bytes, (Byte) *b); } while (*(b++));
+    } else {
+        int c;
+        while ((c = fgetc(fh)) != EOF) {
+            if (c == '\n') break;
+            if (c == 0) break;
+            pushByteArray(bytes, (Byte) c);
+        }
+        pushByteArray(bytes, 0);
     }
-    pushByteArray(bytes, 0);
     Value string = utf8ToList((char *) bytes->entries);
     UNPROTECT(save);
     return string;
@@ -441,8 +497,8 @@ static Value builtin_fputv(Vec *args) {
     if (data == NULL || data->data == NULL) {
         cant_happen("fput on closed file handle");
     }
-    fputValue((FILE *) data->data, args->entries[0]);
-    return args->entries[0];
+    fputValue((FILE *) data->data, args->entries[1]);
+    return args->entries[1];
 }
 
 static TcType *pushFileArg(BuiltInArgs *args) {
@@ -572,6 +628,18 @@ static void registerOpen(BuiltIns *registry) {
     UNPROTECT(save);
 }
 
+// try(string, opaque(file))
+static void registerOpenMemstream(BuiltIns *registry) {
+    BuiltInArgs *args = newBuiltInArgs();
+    int save = PROTECT(args);
+    TcType *stringType = makeStringType();
+    PROTECT(stringType);
+    TcType *tryFileType = makeTryFileType(stringType);
+    PROTECT(tryFileType);
+    pushNewBuiltIn(registry, "openmem", tryFileType, args, (void *)builtin_open_memstream);
+    UNPROTECT(save);
+}
+
 // string -> try(string, opaque(dir))
 static void registerOpenDir(BuiltIns *registry) {
     BuiltInArgs *args = newBuiltInArgs();
diff --git a/src/builtin_io.h b/src/builtin_io.h
index 6e38b55..5ee9cc8 100644
--- a/src/builtin_io.h
+++ b/src/builtin_io.h
@@ -21,6 +21,7 @@
 #  include <stdio.h>
 #  include "builtins.h"
 
+void markMemBufs(void);
 void registerIO(BuiltIns *registry);
 void putValue(Value x);
 void fputValue(FILE *fh, Value x);
diff --git a/src/builtins.yaml b/src/builtins.yaml
index cfdf033..dfb021b 100644
--- a/src/builtins.yaml
+++ b/src/builtins.yaml
@@ -36,6 +36,14 @@ structs:
         implementation: void_ptr
         nargs: int
 
+    BuiltInMemBuf:
+        buffer: string=NULL
+        size: size=0
+
+hashes:
+    BuiltInMemBufHash:
+        entries: BuiltInMemBuf
+
 arrays:
     BuiltInArgs:
         dimension: 1
diff --git a/src/memory.c b/src/memory.c
index 29264b0..383f80d 100644
--- a/src/memory.c
+++ b/src/memory.c
@@ -32,6 +32,7 @@
 #include "symbol.h"
 #include "arithmetic.h"
 #include "opaque.h"
+#include "builtin_io.h"
 
 static int bytesAllocated = 0;
 static int nextGC = 0;
@@ -381,6 +382,7 @@ static void mark() {
     markProtected();
     markArithmetic();
     markNamespaces();
+    markMemBufs();
 #ifdef DEBUG_LOG_GC
     eprintf("starting markVarTable\n");
 #endif
diff --git a/src/primitives.yaml b/src/primitives.yaml
index 075f52f..13c3a80 100644
--- a/src/primitives.yaml
+++ b/src/primitives.yaml
@@ -120,3 +120,7 @@ file:
     printf: "%p"
     valued: true
 
+size:
+    cname: size_t
+    printf: "%zu"
+    valued: true
diff --git a/tests/fn/test_hygiene.fn b/tests/fn/test_hygiene.fn
deleted file mode 100644
index deb1ed6..0000000
--- a/tests/fn/test_hygiene.fn
+++ /dev/null
@@ -1,14 +0,0 @@
-let
-    fn now() { 1 }
-
-    macro time(expression) {
-        let start = now();
-        in {
-            let result = expression;
-            in
-                print(now() - start);
-                result
-        }
-    }
-in
-    time(time(true))
diff --git a/tests/fn/test_tostring.fn b/tests/fn/test_tostring.fn
new file mode 100644
index 0000000..8750ecf
--- /dev/null
+++ b/tests/fn/test_tostring.fn
@@ -0,0 +1,4 @@
+let
+    link "ioutils.fn" as io;
+in
+    assert(io.to_string(1234 + 5i) == "(1234+5i)")