aboutsummaryrefslogtreecommitdiff
path: root/examples/file.tm
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-09-05 15:37:33 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-09-05 15:37:33 -0400
commit52717c14e6910a067995ae5dfd49506b8b044431 (patch)
treea7d658fdc0a6e5deac4cc50d45cabe5b01573ff5 /examples/file.tm
parented36765b4239d7af89a06773ad4ff1154d5344be (diff)
Add some examples
Diffstat (limited to 'examples/file.tm')
-rw-r--r--examples/file.tm213
1 files changed, 213 insertions, 0 deletions
diff --git a/examples/file.tm b/examples/file.tm
new file mode 100644
index 00000000..6b8807eb
--- /dev/null
+++ b/examples/file.tm
@@ -0,0 +1,213 @@
+# A module for interacting with files
+extern builtin_last_err:func()->Text
+
+use <fcntl.h>
+use <stdio.h>
+use <sys/mman.h>
+use <sys/stat.h>
+use <unistd.h>
+
+enum FileReadResult(Success(text:Text), Failure(reason:Text))
+
+func _wrap_with_finalizer(obj:@Memory, finalizer:func(obj:@Memory))->@Memory:
+ return inline C (
+ ({
+ FILE **wrapper = GC_MALLOC(sizeof(FILE*));
+ *wrapper = $obj;
+ GC_register_finalizer(wrapper, (void*)$finalizer.fn, wrapper, NULL, NULL);
+ wrapper;
+ })
+ ):@Memory
+
+func _close_file(fp:@Memory):
+ inline C {
+ if (*(FILE**)$fp)
+ fclose(*(FILE**)$fp);
+ *(FILE**)$fp = NULL;
+ }
+
+func read(path:Text)->FileReadResult:
+ inline C {
+ int fd = open(Text$as_c_string($path), O_RDONLY);
+ if (fd != -1) {
+ struct stat sb;
+ if (fstat(fd, &sb) == -1) {
+ const char *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ char *gc_mem = GC_MALLOC_ATOMIC(sb.st_size+1);
+ memcpy(gc_mem, mem, sb.st_size);
+ gc_mem[sb.st_size] = '\0';
+ return file$FileReadResult$tagged$Success(Text$from_strn(gc_mem, sb.st_size));
+ } else {
+ const int chunk_size = 256;
+ char *buf = GC_MALLOC_ATOMIC(chunk_size);
+ Text_t contents = Text("");
+ size_t just_read;
+ do {
+ just_read = read(fd, buf, chunk_size);
+ if (just_read > 0) {
+ contents = Texts(contents, Text$from_strn(buf, just_read));
+ buf = GC_MALLOC_ATOMIC(chunk_size);
+ }
+ } while (just_read > 0);
+ close(fd);
+ return file$FileReadResult$tagged$Success(contents);
+ }
+ }
+ }
+ return Failure(builtin_last_err())
+
+struct WriteHandle(_file:@Memory):
+ func write(h:WriteHandle, text:Text, flush=yes):
+ inline C {
+ fputs(Text$as_c_string($text), *(FILE**)$h.$_file);
+ if ($flush)
+ fflush(*(FILE**)$h.$_file);
+ }
+
+ func close(h:WriteHandle):
+ _close_file(h._file)
+
+enum FileWriteResult(Open(h:WriteHandle), Failure(reason:Text))
+func writing(path:Text)->FileWriteResult:
+ maybe_f := inline C (
+ fopen(Text$as_c_string($path), "w")
+ ):@Memory?
+ when maybe_f is @f:
+ obj := _wrap_with_finalizer(f, _close_file)
+ return Open(WriteHandle(obj))
+ else:
+ return Failure(builtin_last_err())
+
+func appending(path:Text)->FileWriteResult:
+ maybe_f := inline C (
+ fopen(Text$as_c_string($path), "a")
+ ):@Memory?
+ when maybe_f is @f:
+ return Open(WriteHandle(f))
+ else:
+ return Failure(builtin_last_err())
+
+struct LineReader(_file:@Memory):
+ func stdin()->LineReader:
+ f := inline C (
+ stdin
+ ):@Memory
+ return LineReader(_wrap_with_finalizer(f, _close_file))
+
+ func is_finished(r:LineReader)->Bool:
+ return inline C (
+ feof(*(FILE**)$r.$_file) != 0;
+ ):Bool
+
+ func next_line(r:LineReader)->FileReadResult:
+ line := inline C (
+ ({
+ if (*(FILE**)$r.$_file == NULL) fail("File has already been closed!");
+ char *buf = NULL;
+ size_t space = 0;
+ ssize_t len = getline(&buf, &space, *(FILE**)$r.$_file);
+ if (len < 0) return (file$FileReadResult_t){1, .$Failure={Text("End of file")}};
+ if (len > 0 && buf[len-1] == '\n') --len;
+ char *line = GC_MALLOC_ATOMIC(len + 1);
+ memcpy(line, buf, len);
+ line[len] = '\0';
+ if (buf) free(buf);
+ Text$from_strn(line, len);
+ })
+ ):Text
+ return Success(line)
+
+ func from_file(path:Text)->FileLineReaderResult:
+ maybe_f := inline C (
+ fopen(Text$as_c_string($path), "r")
+ ):@Memory?
+ when maybe_f is @f:
+ obj := _wrap_with_finalizer(f, _close_file)
+ return Open(LineReader(obj))
+ else:
+ return Failure(builtin_last_err())
+
+ func from_command(cmd:Text)->FileLineReaderResult:
+ maybe_f := inline C (
+ popen(Text$as_c_string($cmd), "r")
+ ):@Memory?
+ when maybe_f is @f:
+ obj := _wrap_with_finalizer(f, _close_file)
+ return Open(LineReader(obj))
+ else:
+ return Failure(builtin_last_err())
+
+ func close(r:LineReader):
+ _close_file(r._file)
+
+enum FileLineReaderResult(Open(reader:LineReader), Failure(reason:Text))
+
+func command(cmd:Text)->FileReadResult:
+ maybe_f := inline C (
+ popen(Text$as_c_string($cmd), "r")
+ ):@Memory?
+
+ when maybe_f is @f:
+ text := inline C (
+ ({
+ const int chunk_size = 256;
+ char *buf = GC_MALLOC_ATOMIC(chunk_size);
+ Text_t contents = Text("");
+ size_t just_read;
+ do {
+ just_read = fread(buf, sizeof(char), chunk_size, $f);
+ if (just_read > 0) {
+ contents = Texts(contents, Text$from_strn(buf, just_read));
+ buf = GC_MALLOC_ATOMIC(chunk_size);
+ }
+ } while (just_read > 0);
+ contents;
+ })
+ ):Text
+ text = text:replace($/$(\n){end}/, "")
+ return Success(text)
+ else:
+ return Failure(builtin_last_err())
+
+func main():
+ word := ""
+ when command("shuf -n 1 /usr/share/dict/words") is Success(w):
+ >> word = w
+ is Failure(msg):
+ fail(msg)
+
+ # when writing("test.txt") is Open(f):
+ # say("Writing {word} to test.txt")
+ # f:write("Hello {word}!{\n}")
+ # is Failure(msg):
+ # fail(msg)
+
+ # when read("test.txt") is Success(text):
+ # say("Roundtrip: {text}")
+ # is Failure(msg):
+ # fail(msg)
+
+ # say("Reading stdin:")
+ # reader := LineReader.stdin()
+ # while yes:
+ # when reader:next_line() is Success(line):
+ # >> line
+ # else: stop
+
+ # say("Reading cmd:")
+ # when LineReader.from_command("ping google.com") is Open(reader):
+ # while yes:
+ # when reader:next_line() is Success(line):
+ # >> line
+ # else: stop
+ # is Failure(msg):
+ # fail("{msg}")
+
+ # say("Reading /dev/stdin:")
+ # when LineReader.from_file("/dev/stdin") is Open(reader):
+ # while yes:
+ # when reader:next_line() is Success(line):
+ # >> line
+ # else: stop
+ # is Failure(msg):
+ # fail("{msg}")