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
This commit is contained in:
Bruce Hill 2025-03-17 19:29:28 -04:00
parent 94993c5f11
commit 0bba319126
16 changed files with 367 additions and 268 deletions

7
.gitignore vendored
View File

@ -7,6 +7,9 @@
/tags
/test/*
!/test/*.tm
/examples/*
/examples
!/examples/*.tm
!/examples/*.ini
!/examples/*/*.tm
!/examples/*/*.ini
!/examples/*/*.md
/junk

View File

@ -32,7 +32,7 @@ CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m<flags...>\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

View File

@ -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: {

View File

@ -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;
}

View File

@ -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.

View File

@ -0,0 +1,177 @@
#pragma once
#include <errno.h>
#include <gc.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <spawn.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <unistd.h>
#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

View File

@ -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)!)

35
examples/shell/shell.tm Normal file
View File

@ -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)

View File

@ -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!$\[]")

19
parse.c
View File

@ -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("<string>", 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

View File

@ -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

View File

@ -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)

View File

@ -1,157 +0,0 @@
// A lang for Shell Command Language
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistr.h>
#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

View File

@ -1,35 +0,0 @@
#pragma once
// A lang for Shell Command Language
#include <stdbool.h>
#include <stdint.h>
#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

View File

@ -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"

7
tomo.c
View File

@ -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: {