aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--builtins/shell.c67
-rw-r--r--builtins/shell.h22
-rw-r--r--builtins/tomo.h1
-rw-r--r--compile.c2
-rw-r--r--environment.c4
-rw-r--r--parse.c4
7 files changed, 101 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 5f0f2be8..876ac3be 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,7 @@ CFLAGS=$(CCONFIG) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS)
LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl
BUILTIN_OBJS=builtins/siphash.o builtins/array.o builtins/bool.o builtins/channel.o builtins/nums.o builtins/functions.o builtins/integers.o \
builtins/pointer.o builtins/memory.o builtins/text.o builtins/thread.o builtins/c_string.o builtins/table.o \
- builtins/types.o builtins/util.o builtins/files.o builtins/range.o
+ builtins/types.o builtins/util.o builtins/files.o builtins/range.o builtins/shell.o
TESTS=$(patsubst %.tm,%.tm.testresult,$(wildcard test/*.tm))
all: libtomo.so tomo
diff --git a/builtins/shell.c b/builtins/shell.c
new file mode 100644
index 00000000..da6cbe5b
--- /dev/null
+++ b/builtins/shell.c
@@ -0,0 +1,67 @@
+// Boolean methods/type info
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "array.h"
+#include "functions.h"
+#include "integers.h"
+#include "shell.h"
+#include "text.h"
+#include "types.h"
+#include "util.h"
+
+public Pattern_t Shell$escape_text(Text_t text)
+{
+ // TODO: optimize for ASCII and short strings
+ Array_t shell_graphemes = {.atomic=1};
+#define add_char(c) Array$insert(&shell_graphemes, (uint32_t[1]){c}, I_small(0), sizeof(uint32_t))
+ add_char('\'');
+ const char *text_utf8 = Text$as_c_string(text);
+ for (const char *p = text_utf8; *p; p++) {
+ if (*p == '\'') {
+ add_char('\'');
+ add_char('"');
+ add_char('\'');
+ add_char('"');
+ add_char('\'');
+ } else
+ add_char(*p);
+ }
+ add_char('\'');
+#undef add_char
+ return (Text_t){.length=shell_graphemes.length, .tag=TEXT_GRAPHEMES, .graphemes=shell_graphemes.data};
+}
+
+public Text_t Shell$run(Shell_t command, int32_t *status)
+{
+ const char *cmd_str = Text$as_c_string(command);
+ FILE *prog = popen(cmd_str, "r");
+
+ const int chunk_size = 256;
+ char *buf = GC_MALLOC_ATOMIC(chunk_size);
+ Text_t output = Text("");
+ size_t just_read;
+ do {
+ just_read = fread(buf, sizeof(char), chunk_size, prog);
+ if (just_read > 0) {
+ output = Texts(output, Text$from_strn(buf, just_read));
+ buf = GC_MALLOC_ATOMIC(chunk_size);
+ }
+ } while (just_read > 0);
+
+ if (status)
+ *status = WEXITSTATUS(pclose(prog));
+ else
+ pclose(prog);
+
+ return Text$trim(output, Pattern("{1 nl}"), false, true);
+}
+
+public const TypeInfo Shell$info = {
+ .size=sizeof(Shell_t),
+ .align=__alignof__(Shell_t),
+ .tag=TextInfo,
+ .TextInfo={.lang="Shell"},
+};
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/builtins/shell.h b/builtins/shell.h
new file mode 100644
index 00000000..0429d496
--- /dev/null
+++ b/builtins/shell.h
@@ -0,0 +1,22 @@
+#pragma once
+
+// A lang for Shell Command Language
+
+#include <gc/cord.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "types.h"
+#include "datatypes.h"
+
+#define Shell_t Text_t
+#define Shell(text) ((Shell_t)Text(text))
+#define Shells(...) ((Shell_t)Texts(__VA_ARGS__))
+
+Text_t Shell$run(Shell_t command, int32_t *status);
+Shell_t Shell$escape_text(Text_t text);
+
+extern const TypeInfo Shell$info;
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
+
diff --git a/builtins/tomo.h b/builtins/tomo.h
index 8d6661ca..68e115bb 100644
--- a/builtins/tomo.h
+++ b/builtins/tomo.h
@@ -22,6 +22,7 @@
#include "nums.h"
#include "pointer.h"
#include "range.h"
+#include "shell.h"
#include "siphash.h"
#include "table.h"
#include "text.h"
diff --git a/compile.c b/compile.c
index e53b43bb..0be3aa89 100644
--- a/compile.c
+++ b/compile.c
@@ -2994,6 +2994,8 @@ CORD compile_type_info(env_t *env, 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->libname, text->env->namespace->parent), text->lang, ")");
}
case StructType: {
diff --git a/environment.c b/environment.c
index 666149cc..39a3e24a 100644
--- a/environment.c
+++ b/environment.c
@@ -243,6 +243,10 @@ env_t *new_compilation_unit(CORD *libname)
{"Pattern", Type(TextType, .lang="Pattern", .env=namespace_env(env, "Pattern")), "Pattern_t", "Pattern$info", TypedArray(ns_entry_t,
{"escape_text", "Pattern$escape_text", "func(text:Text)->Pattern"},
)},
+ {"Shell", Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell")), "Shell_t", "Shell$info", TypedArray(ns_entry_t,
+ {"escape_text", "Shell$escape_text", "func(text:Text)->Shell"},
+ {"run", "Shell$run", "func(command:Shell, status=!&Int32?)->Text"},
+ )},
{"Text", TEXT_TYPE, "Text_t", "Text$info", TypedArray(ns_entry_t,
{"find", "Text$find", "func(text:Text, pattern:Pattern, start=1, length=!&Int64)->Int"},
{"find_all", "Text$find_all", "func(text:Text, pattern:Pattern)->[Text]"},
diff --git a/parse.c b/parse.c
index 2f3a8c3d..1e421085 100644
--- a/parse.c
+++ b/parse.c
@@ -1185,6 +1185,8 @@ PARSER(parse_text) {
} else if (strchr(interp_chars, *pos)) {
open_interp = *pos;
++pos;
+ } else if (*pos == '(') {
+ open_interp = '@'; // For shell commands
}
static const char *quote_chars = "\"'`|/;([{<";
if (!strchr(quote_chars, *pos))
@@ -1195,6 +1197,8 @@ PARSER(parse_text) {
if (!lang && open_quote == '/')
lang = "Pattern";
+ else if (!lang && open_quote == '(')
+ lang = "Shell";
} else {
return NULL;
}