From 0bba31912665a82f848642e6b4247071a3ee177a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 17 Mar 2025 19:29:28 -0400 Subject: [PATCH] Big overhaul: - Clean up environment code using type strings instead of manually defining types - Add Commands module - Move Shell lang into an example module that uses Commands module - Fix some bugs with chained library dependencies --- .gitignore | 7 +- Makefile | 4 +- compile.c | 6 +- environment.c | 108 ++++++++-------- examples/commands/README.md | 5 + examples/commands/commands.c | 177 ++++++++++++++++++++++++++ examples/commands/commands.tm | 63 +++++++++ examples/shell/shell.tm | 35 +++++ examples/tomo-install/tomo-install.tm | 9 +- parse.c | 19 ++- parse.h | 1 + stdlib/README.md | 1 - stdlib/shell.c | 157 ----------------------- stdlib/shell.h | 35 ----- stdlib/tomo.h | 1 - tomo.c | 7 +- 16 files changed, 367 insertions(+), 268 deletions(-) create mode 100644 examples/commands/README.md create mode 100644 examples/commands/commands.c create mode 100644 examples/commands/commands.tm create mode 100644 examples/shell/shell.tm delete mode 100644 stdlib/shell.c delete mode 100644 stdlib/shell.h diff --git a/.gitignore b/.gitignore index 140d7bf..1a2f8c8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ /tags /test/* !/test/*.tm -/examples/* +/examples !/examples/*.tm -!/examples/*.ini +!/examples/*/*.tm +!/examples/*/*.ini +!/examples/*/*.md +/junk diff --git a/Makefile b/Makefile index e0774c8..f7305c6 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m\033[m')" LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl BUILTIN_OBJS=stdlib/siphash.o stdlib/arrays.o stdlib/bools.o stdlib/bytes.o stdlib/nums.o stdlib/integers.o \ stdlib/pointers.o stdlib/memory.o stdlib/text.o stdlib/threads.o stdlib/c_strings.o stdlib/tables.o \ - stdlib/types.o stdlib/util.o stdlib/files.o stdlib/shell.o stdlib/paths.o stdlib/rng.o \ + stdlib/types.o stdlib/util.o stdlib/files.o stdlib/paths.o stdlib/rng.o \ stdlib/optionals.o stdlib/patterns.o stdlib/metamethods.o stdlib/functiontype.o stdlib/stdlib.o \ stdlib/structs.o stdlib/enums.o stdlib/moments.o stdlib/mutexeddata.o TESTS=$(patsubst %.tm,%.tm.testresult,$(wildcard test/*.tm)) @@ -72,7 +72,7 @@ clean: pandoc --lua-filter=.pandoc/bold-code.lua -s $< -t man -o $@ examples: - ./tomo -IL examples/vectors examples/base64 examples/log examples/ini examples/game examples/http examples/threads examples/tomodeps examples/tomo-install examples/wrap + ./tomo -IL examples/vectors examples/commands examples/shell examples/base64 examples/log examples/ini examples/game examples/http examples/threads examples/tomodeps examples/tomo-install examples/wrap ./tomo examples/learnxiny.tm install: tomo libtomo.so tomo.1 diff --git a/compile.c b/compile.c index b476e80..b2bb9aa 100644 --- a/compile.c +++ b/compile.c @@ -524,8 +524,6 @@ CORD compile_type(type_t *t) return "Text_t"; else if (streq(text->lang, "Pattern")) return "Pattern_t"; - else if (streq(text->lang, "Shell")) - return "Shell_t"; else return CORD_all(namespace_prefix(text->env, text->env->namespace->parent), text->lang, "$$type"); } @@ -2660,7 +2658,7 @@ CORD compile(env_t *env, ast_t *ast) CORD lang_constructor; if (!lang || streq(lang, "Text")) lang_constructor = "Text"; - else if (streq(lang, "Pattern") || streq(lang, "Shell")) + else if (streq(lang, "Pattern")) lang_constructor = lang; else lang_constructor = CORD_all(namespace_prefix(Match(text_t, TextType)->env, Match(text_t, TextType)->env->namespace->parent), lang); @@ -3849,8 +3847,6 @@ CORD compile_type_info(type_t *t) return "&Text$info"; else if (streq(text->lang, "Pattern")) return "&Pattern$info"; - else if (streq(text->lang, "Shell")) - return "&Shell$info"; return CORD_all("(&", namespace_prefix(text->env, text->env->namespace->parent), text->lang, "$$info)"); } case StructType: { diff --git a/environment.c b/environment.c index 4ffa1dd..afa9524 100644 --- a/environment.c +++ b/environment.c @@ -5,6 +5,7 @@ #include "cordhelpers.h" #include "environment.h" +#include "parse.h" #include "stdlib/datatypes.h" #include "stdlib/tables.h" #include "stdlib/text.h" @@ -17,51 +18,60 @@ type_t *RNG_TYPE = NULL; public type_t *PATH_TYPE = NULL; public type_t *THREAD_TYPE = NULL; +static type_t *declare_type(env_t *env, const char *def_str) +{ + ast_t *ast = parse(def_str); + if (!ast) errx(1, "Couldn't not parse struct def: %s", def_str); + if (ast->tag != Block) errx(1, "Couldn't not parse struct def: %s", def_str); + ast_list_t *statements = Match(ast, Block)->statements; + if (statements == NULL || statements->next) errx(1, "Couldn't not parse struct def: %s", def_str); + switch (statements->ast->tag) { + case StructDef: { + auto def = Match(statements->ast, StructDef); + prebind_statement(env, statements->ast); + bind_statement(env, statements->ast); + return Table$str_get(*env->types, def->name); + } + case EnumDef: { + auto def = Match(statements->ast, EnumDef); + prebind_statement(env, statements->ast); + bind_statement(env, statements->ast); + return Table$str_get(*env->types, def->name); + } + default: errx(1, "Not a type definition: %s", def_str); + } +} + +static type_t *bind_type(env_t *env, const char *name, type_t *type) +{ + if (Table$str_get(*env->types, name)) + errx(1, "Duplicate binding for type: %s", name); + Table$str_set(env->types, name, type); + return type; +} + env_t *new_compilation_unit(CORD libname) { env_t *env = new(env_t); env->code = new(compilation_unit_t); env->types = new(Table_t); env->globals = new(Table_t); - env->locals = new(Table_t, .fallback=env->globals); + env->locals = env->globals; env->imports = new(Table_t); - if (!TEXT_TYPE) - TEXT_TYPE = Type(TextType, .lang="Text", .env=namespace_env(env, "Text")); + TEXT_TYPE = bind_type(env, "Text", Type(TextType, .lang="Text", .env=namespace_env(env, "Text"))); + (void)bind_type(env, "Int", Type(BigIntType)); + (void)bind_type(env, "Int32", Type(IntType, .bits=TYPE_IBITS32)); + (void)bind_type(env, "Memory", Type(MemoryType)); + MATCH_TYPE = declare_type(env, "struct Match(text:Text, index:Int, captures:[Text])"); + PATH_TYPE = declare_type(env, "struct Path(type:Int32, components:[Text])"); + THREAD_TYPE = declare_type(env, "struct Thread(; opaque)"); + RNG_TYPE = declare_type(env, "struct RNG(state:@Memory)"); typedef struct { const char *name, *code, *type_str; } ns_entry_t; - { - env_t *match_env = namespace_env(env, "Match"); - MATCH_TYPE = Type( - StructType, .name="Match", .env=match_env, - .fields=new(arg_t, .name="text", .type=TEXT_TYPE, - .next=new(arg_t, .name="index", .type=INT_TYPE, - .next=new(arg_t, .name="captures", .type=Type(ArrayType, .item_type=TEXT_TYPE))))); - } - - { - env_t *path_env = namespace_env(env, "Path"); - PATH_TYPE = Type( - StructType, .name="Path", .env=path_env, - .fields=new(arg_t, .name="type", .type=Type(IntType, .bits=TYPE_IBITS32), - .next=new(arg_t, .name="components", .type=Type(ArrayType, .item_type=TEXT_TYPE)))); - } - - { - env_t *thread_env = namespace_env(env, "Thread"); - THREAD_TYPE = Type(StructType, .name="Thread", .env=thread_env, .opaque=true); - } - - { - env_t *rng_env = namespace_env(env, "RNG"); - RNG_TYPE = Type( - StructType, .name="RNG", .env=rng_env, - .fields=new(arg_t, .name="state", .type=Type(PointerType, .pointed=Type(MemoryType)))); - } - struct { const char *name; type_t *type; @@ -353,15 +363,6 @@ env_t *new_compilation_unit(CORD libname) {"num32", "RNG$num32", "func(rng:RNG, min=Num32(0.0), max=Num32(1.0) -> Num32)"}, {"set_seed", "RNG$set_seed", "func(rng:RNG, seed:[Byte])"}, )}, - {"Shell", Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell")), "Shell_t", "Shell$info", TypedArray(ns_entry_t, - {"by_line", "Shell$by_line", "func(command:Shell -> func(->Text?)?)"}, - {"escape_int", "Int$value_as_text", "func(i:Int -> Shell)"}, - {"escape_text", "Shell$escape_text", "func(text:Text -> Shell)"}, - {"escape_text_array", "Shell$escape_text_array", "func(texts:[Text] -> Shell)"}, - {"execute", "Shell$execute", "func(command:Shell -> Int32?)"}, - {"run_bytes", "Shell$run_bytes", "func(command:Shell -> [Byte]?)"}, - {"run", "Shell$run", "func(command:Shell -> Text?)"}, - )}, {"Text", TEXT_TYPE, "Text_t", "Text$info", TypedArray(ns_entry_t, {"as_c_string", "Text$as_c_string", "func(text:Text -> CString)"}, {"at", "Text$cluster", "func(text:Text, index:Int -> Text)"}, @@ -543,12 +544,6 @@ env_t *new_compilation_unit(CORD libname) {"Path$escape_text", "func(text:Text -> Path)"}, {"Path$escape_path", "func(path:Path -> Path)"}, {"Int$value_as_text", "func(i:Int -> Path)"}); - ADD_CONSTRUCTORS("Shell", - {"Shell$escape_text", "func(text:Text -> Shell)"}, - {"Shell$escape_path", "func(path:Path -> Shell)"}, - {"Shell$escape_text_array", "func(texts:[Text] -> Shell)"}, - {"Shell$escape_path_array", "func(paths:[Path] -> Shell)"}, - {"Int$value_as_text", "func(i:Int -> Shell)"}); ADD_CONSTRUCTORS("CString", {"Text$as_c_string", "func(text:Text -> CString)"}); ADD_CONSTRUCTORS("Moment", {"Moment$now", "func(-> Moment)"}, @@ -558,11 +553,6 @@ env_t *new_compilation_unit(CORD libname) ADD_CONSTRUCTORS("Thread", {"Thread$new", "func(fn:func() -> Thread)"}); #undef ADD_CONSTRUCTORS - set_binding(namespace_env(env, "Shell"), "from_text", - Type(FunctionType, .args=new(arg_t, .name="text", .type=TEXT_TYPE), - .ret=Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell"))), - "(Shell_t)"); - set_binding(namespace_env(env, "Path"), "from_text", Type(FunctionType, .args=new(arg_t, .name="text", .type=TEXT_TYPE), .ret=PATH_TYPE), @@ -576,6 +566,8 @@ env_t *new_compilation_unit(CORD libname) struct { const char *name, *code, *type_str; } global_vars[] = { + {"USE_COLOR", "USE_COLOR", "Bool"}, + {"random", "default_rng", "RNG"}, {"say", "say", "func(text:Text, newline=yes)"}, {"print", "say", "func(text:Text, newline=yes)"}, {"ask", "ask", "func(prompt:Text, bold=yes, force_tty=yes -> Text)"}, @@ -583,8 +575,6 @@ env_t *new_compilation_unit(CORD libname) {"fail", "fail_text", "func(message:Text -> Abort)"}, {"sleep", "sleep_num", "func(seconds:Num)"}, {"now", "Moment$now", "func(->Moment)"}, - {"random", "default_rng", "RNG"}, - {"USE_COLOR", "USE_COLOR", "Bool"}, }; for (size_t i = 0; i < sizeof(global_vars)/sizeof(global_vars[0]); i++) { @@ -604,10 +594,12 @@ CORD namespace_prefix(env_t *env, namespace_t *ns) CORD prefix = CORD_EMPTY; for (; ns; ns = ns->parent) prefix = CORD_all(ns->name, "$", prefix); - if (env->libname) - prefix = CORD_all("_$", env->libname, "$", prefix); - else - prefix = CORD_all("_$", prefix); + if (env->locals != env->globals) { + if (env->libname) + prefix = CORD_all("_$", env->libname, "$", prefix); + else + prefix = CORD_all("_$", prefix); + } return prefix; } diff --git a/examples/commands/README.md b/examples/commands/README.md new file mode 100644 index 0000000..040f4bd --- /dev/null +++ b/examples/commands/README.md @@ -0,0 +1,5 @@ +# Commands + +This module provides a way to run executable programs and get their output. You +can also feed in text to the programs' `stdin`. Think of it as `popen()` on +steroids. diff --git a/examples/commands/commands.c b/examples/commands/commands.c new file mode 100644 index 0000000..f07b5c3 --- /dev/null +++ b/examples/commands/commands.c @@ -0,0 +1,177 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define READ_END 0 +#define WRITE_END 1 +public int run_command(Text_t exe, Array_t arg_array, Table_t env_table, + Array_t input_bytes, Array_t *output_bytes, Array_t *error_bytes) +{ + pthread_testcancel(); + + struct sigaction sa = { .sa_handler = SIG_IGN }, oldint, oldquit; + sigaction(SIGINT, &sa, &oldint); + sigaction(SIGQUIT, &sa, &oldquit); + sigaddset(&sa.sa_mask, SIGCHLD); + sigset_t old, reset; + sigprocmask(SIG_BLOCK, &sa.sa_mask, &old); + sigemptyset(&reset); + if (oldint.sa_handler != SIG_IGN) sigaddset(&reset, SIGINT); + if (oldquit.sa_handler != SIG_IGN) sigaddset(&reset, SIGQUIT); + posix_spawnattr_t attr; + posix_spawnattr_init(&attr); + posix_spawnattr_setsigmask(&attr, &old); + posix_spawnattr_setsigdefault(&attr, &reset); + posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_SETSIGMASK); + + int child_inpipe[2], child_outpipe[2], child_errpipe[2]; + pipe(child_inpipe); + pipe(child_outpipe); + pipe(child_errpipe); + + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_adddup2(&actions, child_inpipe[READ_END], STDIN_FILENO); + posix_spawn_file_actions_addclose(&actions, child_inpipe[WRITE_END]); + posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO); + posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]); + posix_spawn_file_actions_adddup2(&actions, child_errpipe[WRITE_END], STDERR_FILENO); + posix_spawn_file_actions_addclose(&actions, child_errpipe[READ_END]); + + const char *exe_str = Text$as_c_string(exe); + + Array_t arg_strs = {}; + Array$insert_value(&arg_strs, exe_str, I(0), sizeof(char*)); + for (int64_t i = 0; i < arg_array.length; i++) + Array$insert_value(&arg_strs, Text$as_c_string(*(Text_t*)(arg_array.data + i*arg_array.stride)), I(0), sizeof(char*)); + Array$insert_value(&arg_strs, NULL, I(0), sizeof(char*)); + char **args = arg_strs.data; + + extern char **environ; + char **env = environ; + if (env_table.entries.length > 0) { + Array_t env_array = {}; // Array of const char* + for (char **e = environ; *e; e++) + Array$insert(&env_array, e, I(0), sizeof(char*)); + + for (int64_t i = 0; i < env_table.entries.length; i++) { + struct { Text_t key, value; } *entry = env_table.entries.data + env_table.entries.stride*i; + const char *env_entry = heap_strf("%k=%k", &entry->key, &entry->value); + Array$insert(&env_array, &env_entry, I(0), sizeof(char*)); + } + Array$insert_value(&env_array, NULL, I(0), sizeof(char*)); + assert(env_array.stride == sizeof(char*)); + env = env_array.data; + } + + pid_t pid; + int ret = exe_str[0] == '/' ? + posix_spawn(&pid, exe_str, &actions, &attr, args, env) + : posix_spawnp(&pid, exe_str, &actions, &attr, args, env); + if (ret != 0) + return -1; + + posix_spawnattr_destroy(&attr); + posix_spawn_file_actions_destroy(&actions); + + close(child_inpipe[READ_END]); + close(child_outpipe[WRITE_END]); + close(child_errpipe[WRITE_END]); + + struct pollfd pollfds[3] = { + {.fd=child_inpipe[WRITE_END], .events=POLLOUT}, + {.fd=child_outpipe[WRITE_END], .events=POLLIN}, + {.fd=child_errpipe[WRITE_END], .events=POLLIN}, + }; + + if (input_bytes.length > 0 && input_bytes.stride != 1) + Array$compact(&input_bytes, sizeof(char)); + if (output_bytes) + *output_bytes = (Array_t){.atomic=1, .stride=1, .length=0}; + if (error_bytes) + *error_bytes = (Array_t){.atomic=1, .stride=1, .length=0}; + + for (;;) { + (void)poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), -1); // Wait for data or readiness + bool did_something = false; + if (pollfds[0].revents) { + if (input_bytes.length > 0) { + ssize_t written = write(child_inpipe[WRITE_END], input_bytes.data, (size_t)input_bytes.length); + if (written > 0) { + input_bytes.data += written; + input_bytes.length -= (int64_t)written; + did_something = true; + } else if (written < 0) { + close(child_inpipe[WRITE_END]); + pollfds[0].events = 0; + } + } + if (input_bytes.length <= 0) { + close(child_inpipe[WRITE_END]); + pollfds[0].events = 0; + } + } + char buf[256]; + if (pollfds[1].revents) { + ssize_t n = read(child_outpipe[READ_END], buf, sizeof(buf)); + did_something = did_something || (n > 0); + if (n <= 0) { + close(child_outpipe[READ_END]); + pollfds[1].events = 0; + } else if (n > 0 && output_bytes) { + if (output_bytes->free < n) { + output_bytes->data = GC_REALLOC(output_bytes->data, (size_t)(output_bytes->length + n)); + output_bytes->free = 0; + } + memcpy(output_bytes->data + output_bytes->length, buf, (size_t)n); + output_bytes->length += n; + } + } + if (pollfds[2].revents) { + ssize_t n = read(child_errpipe[READ_END], buf, sizeof(buf)); + did_something = did_something || (n > 0); + if (n <= 0) { + close(child_errpipe[READ_END]); + pollfds[2].events = 0; + } else if (n > 0 && error_bytes) { + if (error_bytes->free < n) { + error_bytes->data = GC_REALLOC(error_bytes->data, (size_t)(error_bytes->length + n)); + error_bytes->free = 0; + } + memcpy(error_bytes->data + error_bytes->length, buf, (size_t)n); + error_bytes->length += n; + } + } + if (!did_something) break; + } + + int status = 0; + if (ret == 0) { + while (waitpid(pid, &status, 0) < 0 && errno == EINTR) { + if (WIFEXITED(status) || WIFSIGNALED(status)) + break; + else if (WIFSTOPPED(status)) + kill(pid, SIGCONT); + } + } + + sigaction(SIGINT, &oldint, NULL); + sigaction(SIGQUIT, &oldquit, NULL); + sigprocmask(SIG_SETMASK, &old, NULL); + + if (ret) errno = ret; + return status; +} + +#undef READ_END +#undef WRITE_END diff --git a/examples/commands/commands.tm b/examples/commands/commands.tm new file mode 100644 index 0000000..940dc3d --- /dev/null +++ b/examples/commands/commands.tm @@ -0,0 +1,63 @@ +# Functions for running system commands + +use ./commands.c + +extern run_command:func(exe:Text, args:[Text], env:{Text,Text}, input:[Byte], output:&[Byte], error:&[Byte] -> Int32) + +enum ExitType(Exited(status:Int32), Signaled(signal:Int32), Failed) + +struct ProgramResult(stdout:[Byte], stderr:[Byte], exit_type:ExitType): + func or_fail(r:ProgramResult -> ProgramResult): + when r.exit_type is Exited(status): + if status == 0: + return r + else: fail("Program failed: $r") + fail("Program failed: $r") + + func output_text(r:ProgramResult, trim_newline=yes -> Text?): + when r.exit_type is Exited(status): + if status == 0: + if text := Text.from_bytes(r.stdout): + if trim_newline: + text = text:trim($/{1 nl}/, trim_left=no, trim_right=yes) + return text + else: return none + return none + + func error_text(r:ProgramResult -> Text?): + when r.exit_type is Exited(status): + if status == 0: + return Text.from_bytes(r.stderr) + else: return none + return none + +struct Command(command:Text, args=[:Text], env={:Text,Text}): + func run(command:Command, input="", input_bytes=[:Byte] -> ProgramResult): + if input.length > 0: + (&input_bytes):insert_all(input:bytes()) + + stdout := [:Byte] + stderr := [:Byte] + status := run_command(command.command, command.args, command.env, input_bytes, &stdout, &stderr) + + if inline C : Bool { WIFEXITED(_$status) }: + return ProgramResult(stdout, stderr, ExitType.Exited(inline C : Int32 { WEXITSTATUS(_$status) })) + + if inline C : Bool { WIFSIGNALED(_$status) }: + return ProgramResult(stdout, stderr, ExitType.Signaled(inline C : Int32 { WTERMSIG(_$status) })) + + return ProgramResult(stdout, stderr, ExitType.Failed) + + func get_output(command:Command, input="", trim_newline=yes -> Text?): + return command:run(input=input):output_text(trim_newline=trim_newline) + + func get_output_bytes(command:Command, input="", input_bytes=[:Byte] -> [Byte]?): + result := command:run(input=input, input_bytes=input_bytes) + when result.exit_type is Exited(status): + if status == 0: return result.stdout + return none + else: return none + +func main(command:Text, args:[Text], input=""): + cmd := Command(command, args) + say(cmd:get_output(input=input)!) diff --git a/examples/shell/shell.tm b/examples/shell/shell.tm new file mode 100644 index 0000000..cf5fcd2 --- /dev/null +++ b/examples/shell/shell.tm @@ -0,0 +1,35 @@ +use commands + +lang Shell: + convert(text:Text -> Shell): + return Shell.from_text("'" ++ text:replace($/'/, `'"'"'`) ++ "'") + + convert(texts:[Text] -> Shell): + return Shell.from_text(" ":join([Shell(t).text for t in texts])) + + convert(path:Path -> Shell): + return Shell(Text(path:expand_home())) + + convert(paths:[Path] -> Shell): + return Shell.from_text(" ":join([Shell(Text(p)).text for p in paths])) + + convert(n:Int -> Shell): return Shell.from_text(Text(n)) + convert(n:Int64 -> Shell): return Shell.from_text(Text(n)) + convert(n:Int32 -> Shell): return Shell.from_text(Text(n)) + convert(n:Int16 -> Shell): return Shell.from_text(Text(n)) + convert(n:Int8 -> Shell): return Shell.from_text(Text(n)) + convert(n:Num -> Shell): return Shell.from_text(Text(n)) + convert(n:Num32 -> Shell): return Shell.from_text(Text(n)) + + func command(shell:Shell -> Command): + return Command("sh", ["-c", shell.text]) + + func run(shell:Shell, input="", input_bytes=[:Byte] -> ProgramResult): + return shell:command():run(input=input, input_bytes=input_bytes) + + func get_output(shell:Shell, input="", trim_newline=yes -> Text?): + return shell:command():get_output(input=input, trim_newline=trim_newline) + + func get_output_bytes(shell:Shell, input="", input_bytes=[:Byte] -> [Byte]?): + return shell:command():get_output_bytes(input=input, input_bytes=input_bytes) + diff --git a/examples/tomo-install/tomo-install.tm b/examples/tomo-install/tomo-install.tm index 43e2302..0205c38 100644 --- a/examples/tomo-install/tomo-install.tm +++ b/examples/tomo-install/tomo-install.tm @@ -1,3 +1,4 @@ +use shell _USAGE := " tomo-install file.tm... @@ -33,7 +34,7 @@ func main(paths:[Path]): for url in urls: original_url := url url_without_protocol := url:trim($|http{0-1 s}://|, trim_right=no) - hash := $(echo -n @url_without_protocol | sha256sum):run()!:slice(to=32) + hash := $Shell@(echo -n @url_without_protocol | sha256sum):get_output()!:slice(to=32) if (~/.local/share/tomo/installed/$hash):is_directory(): say("Already installed: $url") skip @@ -61,14 +62,14 @@ func main(paths:[Path]): echo @original_url > ~/.local/share/tomo/installed/@hash/source.url tomo -L ~/.local/share/tomo/installed/@hash ln -f -s ../installed/@hash/lib@hash.so ~/.local/share/tomo/lib/lib@hash.so - `:run()!) + `:get_output()!) if alias: - say($( + say($Shell( set -exuo pipefail ln -f -s @hash ~/.local/share/tomo/installed/@alias ln -f -s lib@hash.so ~/.local/share/tomo/lib/lib@alias.so - ):run()!) + ):get_output()!) say("$\[1]Installed $url!$\[]") diff --git a/parse.c b/parse.c index 757c8cb..5110e98 100644 --- a/parse.c +++ b/parse.c @@ -1377,8 +1377,6 @@ PARSER(parse_text) { if (!lang && (open_quote == '/' || open_quote == '|')) lang = "Pattern"; - else if (!lang && open_quote == '(') - lang = "Shell"; } else { return NULL; } @@ -2604,4 +2602,21 @@ type_ast_t *parse_type_str(const char *str) { return ast; } +ast_t *parse(const char *str) { + file_t *file = spoof_file("", str); + parse_ctx_t ctx = { + .file=file, + .on_err=NULL, + }; + + const char *pos = file->text; + whitespace(&pos); + ast_t *ast = parse_file_body(&ctx, pos); + pos = ast->end; + whitespace(&pos); + if (pos < file->text + file->len && *pos != '\0') + parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the file"); + return ast; +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/parse.h b/parse.h index 8fe7125..95b2ff9 100644 --- a/parse.h +++ b/parse.h @@ -8,5 +8,6 @@ type_ast_t *parse_type_str(const char *str); ast_t *parse_file(const char *path, jmp_buf *on_err); +ast_t *parse(const char *str); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/README.md b/stdlib/README.md index c381411..ba47dd0 100644 --- a/stdlib/README.md +++ b/stdlib/README.md @@ -29,7 +29,6 @@ some common functionality. - Paths: [header](stdlib/paths.h), [implementation](stdlib/paths.c) - Patterns: [header](stdlib/patterns.h), [implementation](stdlib/patterns.c) - Pointers: [header](stdlib/pointers.h), [implementation](stdlib/pointers.c) -- Shell: [header](stdlib/shell.h), [implementation](stdlib/shell.c) - Tables: [header](stdlib/tables.h), [implementation](stdlib/tables.c) - Text: [header](stdlib/text.h), [implementation](stdlib/text.c) - Threads: [header](stdlib/threads.h), [implementation](stdlib/threads.c) diff --git a/stdlib/shell.c b/stdlib/shell.c deleted file mode 100644 index 30dc7c6..0000000 --- a/stdlib/shell.c +++ /dev/null @@ -1,157 +0,0 @@ -// A lang for Shell Command Language -#include -#include -#include -#include - -#include "arrays.h" -#include "integers.h" -#include "paths.h" -#include "patterns.h" -#include "shell.h" -#include "text.h" -#include "types.h" -#include "util.h" - -public Shell_t Shell$escape_text(Text_t text) -{ - return Texts(Text("'"), Text$replace(text, Text("'"), Text("'\"'\"'"), Text(""), false), Text("'")); -} - -public Shell_t Shell$escape_path(Path_t path) -{ - return Shell$escape_text(Path$as_text(&path, false, &Path$info)); -} - -public Shell_t Shell$escape_text_array(Array_t texts) -{ - Array_t all_escaped = {}; - for (int64_t i = 0; i < texts.length; i++) { - Text_t raw = *(Text_t*)(texts.data + i*texts.stride); - Text_t escaped = Shell$escape_text(raw); - Array$insert(&all_escaped, &escaped, I(0), sizeof(Text_t)); - } - return Text$join(Text(" "), all_escaped); -} - -public Shell_t Shell$escape_path_array(Array_t paths) -{ - Array_t all_escaped = {}; - for (int64_t i = 0; i < paths.length; i++) { - Path_t path = *(Path_t*)(paths.data + i*paths.stride); - Text_t escaped = Shell$escape_path(path); - Array$insert(&all_escaped, &escaped, I(0), sizeof(Text_t)); - } - return Text$join(Text(" "), all_escaped); -} - -public OptionalArray_t Shell$run_bytes(Shell_t command) -{ - const char *cmd_str = Text$as_c_string(command); - FILE *prog = popen(cmd_str, "r"); - if (!prog) - return NONE_ARRAY; - - size_t capacity = 256, len = 0; - char *content = GC_MALLOC_ATOMIC(capacity); - char chunk[256]; - size_t just_read; - do { - just_read = fread(chunk, 1, sizeof(chunk), prog); - if (just_read == 0) { - if (errno == EAGAIN || errno == EINTR) - continue; - break; - } - - if (len + (size_t)just_read >= capacity) - content = GC_REALLOC(content, (capacity *= 2)); - - memcpy(content + len, chunk, (size_t)just_read); - len += (size_t)just_read; - } while (just_read == sizeof(chunk)); - - int status = pclose(prog); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - return NONE_ARRAY; - - return (Array_t){.data=content, .atomic=1, .stride=1, .length=len}; -} - -public OptionalText_t Shell$run(Shell_t command) -{ - Array_t bytes = Shell$run_bytes(command); - if (bytes.length < 0) - return NONE_TEXT; - - if (bytes.length > 0 && *(char*)(bytes.data + (bytes.length-1)*bytes.stride) == '\n') { - --bytes.length; - if (bytes.length > 0 && *(char*)(bytes.data + (bytes.length-1)*bytes.stride) == '\r') - --bytes.length; - } - return Text$from_bytes(bytes); -} - -public OptionalInt32_t Shell$execute(Shell_t command) -{ - const char *cmd_str = Text$as_c_string(command); - int status = system(cmd_str); - if (WIFEXITED(status)) - return (OptionalInt32_t){.i=WEXITSTATUS(status)}; - else - return (OptionalInt32_t){.is_none=true}; -} - -static void _line_reader_cleanup(FILE **f) -{ - if (f && *f) { - pclose(*f); - *f = NULL; - } -} - -static Text_t _next_line(FILE **f) -{ - if (!f || !*f) return NONE_TEXT; - - char *line = NULL; - size_t size = 0; - ssize_t len = getline(&line, &size, *f); - if (len <= 0) { - _line_reader_cleanup(f); - return NONE_TEXT; - } - - while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n')) - --len; - - if (u8_check((uint8_t*)line, (size_t)len) != NULL) - fail("Invalid UTF8!"); - - Text_t line_text = Text$format("%.*s", len, line); - free(line); - return line_text; -} - -public OptionalClosure_t Shell$by_line(Shell_t command) -{ - const char *cmd_str = Text$as_c_string(command); - FILE *prog = popen(cmd_str, "r"); - if (!prog) - return NONE_CLOSURE; - - FILE **wrapper = GC_MALLOC(sizeof(FILE*)); - *wrapper = prog; - GC_register_finalizer(wrapper, (void*)_line_reader_cleanup, NULL, NULL, NULL); - return (Closure_t){.fn=(void*)_next_line, .userdata=wrapper}; -} - -public const TypeInfo_t Shell$info = { - .size=sizeof(Shell_t), - .align=__alignof__(Shell_t), - .tag=TextInfo, - .TextInfo={.lang="Shell"}, - .metamethods=Text$metamethods, -}; - -// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/shell.h b/stdlib/shell.h deleted file mode 100644 index 517a360..0000000 --- a/stdlib/shell.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -// A lang for Shell Command Language - -#include -#include - -#include "arrays.h" -#include "datatypes.h" -#include "optionals.h" -#include "text.h" -#include "types.h" - -#define Shell_t Text_t -#define OptionalShell_t Text_t -#define Shell(text) ((Shell_t)Text(text)) -#define Shells(...) ((Shell_t)Texts(__VA_ARGS__)) - -OptionalClosure_t Shell$by_line(Shell_t command); -Shell_t Shell$escape_text(Text_t text); -Shell_t Shell$escape_path(Path_t path); -Shell_t Shell$escape_text_array(Array_t texts); -Shell_t Shell$escape_path_array(Array_t paths); -OptionalArray_t Shell$run_bytes(Shell_t command); -OptionalText_t Shell$run(Shell_t command); -OptionalInt32_t Shell$execute(Shell_t command); - -#define Shell$hash Text$hash -#define Shell$compare Text$compare -#define Shell$equal Text$equal - -extern const TypeInfo_t Shell$info; - -// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 - diff --git a/stdlib/tomo.h b/stdlib/tomo.h index d3887af..61dba40 100644 --- a/stdlib/tomo.h +++ b/stdlib/tomo.h @@ -25,7 +25,6 @@ #include "patterns.h" #include "pointers.h" #include "rng.h" -#include "shell.h" #include "siphash.h" #include "structs.h" #include "tables.h" diff --git a/tomo.c b/tomo.c index 279a7c7..07c431a 100644 --- a/tomo.c +++ b/tomo.c @@ -21,7 +21,6 @@ #include "stdlib/optionals.h" #include "stdlib/patterns.h" #include "stdlib/paths.h" -#include "stdlib/shell.h" #include "stdlib/text.h" #include "typecheck.h" #include "types.h" @@ -490,6 +489,12 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l case USE_MODULE: { Text_t lib = Text$format("'%s/.local/share/tomo/installed/%s/lib%s.so'", getenv("HOME"), use->path, use->path); Table$set(to_link, &lib, ((Bool_t[1]){1}), Table$info(&Text$info, &Bool$info)); + + Array_t children = Path$glob(Path$from_str(heap_strf("%s/.local/share/tomo/installed/%s/*.tm", getenv("HOME"), use->path))); + for (int64_t i = 0; i < children.length; i++) { + Path_t *child = (Path_t*)(children.data + i*children.stride); + build_file_dependency_graph(*child, to_compile, to_link); + } break; } case USE_SHARED_OBJECT: {