aboutsummaryrefslogtreecommitdiff
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
parented36765b4239d7af89a06773ad4ff1154d5344be (diff)
Add some examples
-rw-r--r--examples/file.tm213
-rw-r--r--examples/wrap.tm92
2 files changed, 305 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}")
diff --git a/examples/wrap.tm b/examples/wrap.tm
new file mode 100644
index 00000000..59218de7
--- /dev/null
+++ b/examples/wrap.tm
@@ -0,0 +1,92 @@
+HELP := "
+ wrap: A tool for wrapping lines of text that are piped in through standard input
+
+ usage: wrap [--help] [--width=80] [--min_split=3] [--no-rewrap] [--hyphen='-']
+ --help: Print this message and exit
+ --width=N: The width to wrap the text
+ --min-split=N: The minimum amount of text on either end of a hyphenation split
+ --rewrap|--no-rewrap: Whether to rewrap text that is already wrapped or only split long lines
+ --hyphen='...': The text to use for hyphenation
+"
+
+file := use ./file.tm
+
+UNICODE_HYPHEN := Text.from_codepoint_names(["hyphen"])
+
+func unwrap(text:Text, preserve_paragraphs=yes, hyphen="-")->Text:
+ if preserve_paragraphs:
+ paragraphs := text:split($/{2+ nl}/)
+ if paragraphs.length > 1:
+ return \n\n:join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs])
+
+ return text:replace($/$(hyphen)$(\n)/, "")
+
+func wrap(text:Text, width:Int, min_split=3, hyphen="-")->Text:
+ if width <= 0:
+ fail("Width must be a positive integer, not $width")
+
+ if 2*min_split - hyphen.length > width:
+ fail("
+ Minimum word split length ($min_split) is too small for the given wrap width ($width)!
+
+ I can't fit a $(2*min_split - hyphen.length)-wide word on a line without splitting it,
+ ... and I can't split it without splitting into chunks smaller than $min_split.
+ ")
+
+ lines := [:Text]
+ line := ""
+ for word in text:split($/{whitespace}/):
+ letters := word:split()
+ skip if letters.length == 0
+
+ while not _can_fit_word(line, letters, width):
+ line_space := width - line.length
+ if line != "": line_space -= 1
+
+ if min_split > 0 and line_space >= min_split + hyphen.length and letters.length >= 2*min_split:
+ # Split word with a hyphen:
+ split := line_space - hyphen.length
+ split = split _max_ min_split
+ split = split _min_ (letters.length - min_split)
+ if line != "": line ++= " "
+ line ++= ((++) letters:to(split)) ++ hyphen
+ letters = letters:from(split + 1)
+ else if line == "":
+ # Force split word without hyphenation:
+ if line != "": line ++= " "
+ line ++= ((++) letters:to(line_space))
+ letters = letters:from(line_space + 1)
+ else:
+ pass # Move to next line
+
+ lines:insert(line)
+ line = ""
+
+ if letters.length > 0:
+ if line != "": line ++= " "
+ line ++= (++) letters
+
+ if line != "":
+ lines:insert(line)
+
+ return \n:join(lines)
+
+func _can_fit_word(line:Text, letters:[Text], width:Int; inline)->Bool:
+ if line == "":
+ return letters.length <= width
+ else:
+ return line.length + 1 + letters.length <= width
+
+func main(width=80, min_split=3, rewrap=yes, hyphen="-"):
+ when file.read("/dev/stdin") is Failure(reason):
+ fail(reason)
+ is Success(text):
+ if rewrap:
+ text = unwrap(text)
+
+ first := yes
+ for paragraph in text:split($/{2+ nl}/):
+ if not first:
+ say(\n, newline=no)
+ say(wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen))
+ first = no