// A lang for Shell Command Language #include #include #include #include #include "arrays.h" #include "integers.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) { // 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((uint8_t)*p); } add_char('\''); #undef add_char return (Text_t){.length=shell_graphemes.length, .tag=TEXT_GRAPHEMES, .graphemes=shell_graphemes.data}; } 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 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 NULL_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 NULL_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 NULL_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); } 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 NULL_TEXT; char *line = NULL; size_t size = 0; ssize_t len = getline(&line, &size, *f); if (len <= 0) { _line_reader_cleanup(f); return NULL_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 NULL_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"}, }; // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0