summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/exec.c189
-rw-r--r--src/exec.h26
-rw-r--r--src/expand.c104
-rw-r--r--src/expand.h31
-rw-r--r--src/main.c23
-rw-r--r--src/preproc.c318
-rw-r--r--src/preproc.h116
-rw-r--r--src/utils.c71
-rw-r--r--src/utils.h111
9 files changed, 989 insertions, 0 deletions
diff --git a/src/exec.c b/src/exec.c
new file mode 100644
index 0000000..fccda7a
--- /dev/null
+++ b/src/exec.c
@@ -0,0 +1,189 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "utils.h"
+#include "exec.h"
+#include "preproc.h"
+#include "expand.h"
+
+
+void execute_command(Command cmd, int* pgid) {
+
+ pid_t pid = fork();
+
+ if (pid == 0) {
+ if (*pgid == -1) {
+ setpgid(0, 0);
+ } else {
+ setpgid(0, *pgid);
+ }
+
+ dup2(cmd.stdin_fd, STDIN_FILENO);
+ dup2(cmd.stdout_fd, STDOUT_FILENO);
+
+ for (int j = 0; j < cmd.redirs.count; j++) {
+ FdRedirect fd_rd = *get_from_FdRedirectVec(&cmd.redirs, j);
+ dup2(fd_rd.new_fd, fd_rd.old_fd);
+ }
+
+ for (int i = 0; i < cmd.child_close_fds.count; i++) {
+ close(*get_from_IntVec(&cmd.child_close_fds, i));
+ }
+
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTSTP, SIG_DFL);
+ signal(SIGTTIN, SIG_DFL);
+ signal(SIGTTOU, SIG_DFL);
+
+ execvp(cmd.args[0], cmd.args);
+ exit(67);
+ }
+
+ if (*pgid == -1) {
+ setpgid(pid, pid);
+ *pgid = pid;
+ } else {
+ setpgid(pid, *pgid);
+ }
+}
+
+Command make_command(ExpandedPipelineElement elem) {
+ Command cmd = {
+ .child_close_fds = make_IntVec(64),
+ .parent_close_fds = make_IntVec(64),
+ .redirs = make_FdRedirectVec(32),
+ .term = elem.conn,
+ };
+ StrVec arg_strs = make_StrVec(256);
+ for (int i = 0; i < elem.cmd_slots.count; i++) {
+ Slot curr_slot = *get_from_SlotVec(&elem.cmd_slots, i);
+ Slot next_slot;
+ int fd;
+ FdRedirect redir;
+ switch (curr_slot.kind) {
+ case SLOT_STR:
+ push_to_StrVec(&arg_strs, curr_slot.as.str);
+ break;
+ case SLOT_SPEC:
+ switch (curr_slot.as.spec) {
+ case READ_REDIR:
+ i++;
+ next_slot = *get_from_SlotVec(&elem.cmd_slots, i);
+ if (next_slot.kind != SLOT_STR) exit(6);
+ fd = open(next_slot.as.str, O_RDONLY, 0644);
+ redir = (FdRedirect){.old_fd = STDIN_FILENO, .new_fd = fd};
+ push_to_FdRedirectVec(&cmd.redirs, redir);
+ push_to_IntVec(&cmd.parent_close_fds, fd);
+ push_to_IntVec(&cmd.child_close_fds, fd);
+ break;
+ case WRITE_REDIR_TRUNC:
+ i++;
+ next_slot = *get_from_SlotVec(&elem.cmd_slots, i);
+ if (next_slot.kind != SLOT_STR) exit(6);
+ fd = open(next_slot.as.str, O_WRONLY | O_CREAT | O_TRUNC , 0644);
+ redir = (FdRedirect){.old_fd = STDOUT_FILENO, .new_fd = fd};
+ push_to_FdRedirectVec(&cmd.redirs, redir);
+ push_to_IntVec(&cmd.parent_close_fds, fd);
+ push_to_IntVec(&cmd.child_close_fds, fd);
+ break;
+ case WRITE_REDIR_APPEND:
+ i++;
+ next_slot = *get_from_SlotVec(&elem.cmd_slots, i);
+ if (next_slot.kind != SLOT_STR) exit(6);
+ fd = open(next_slot.as.str, O_WRONLY | O_CREAT | O_APPEND, 0644);
+ redir = (FdRedirect){.old_fd = STDOUT_FILENO, .new_fd = fd};
+ push_to_FdRedirectVec(&cmd.redirs, redir);
+ push_to_IntVec(&cmd.parent_close_fds, fd);
+ push_to_IntVec(&cmd.child_close_fds, fd);
+ break;
+ default:
+ exit(7);
+ }
+ break;
+ default:
+ exit(67);
+ // TODO: other slot kinds
+ }
+ }
+ cmd.args = malloc(sizeof(char*) * arg_strs.count);
+ for (int j = 0; j < arg_strs.count; j++) {
+ cmd.args[j] = *get_from_StrVec(&arg_strs, j);
+ }
+ return cmd;
+}
+
+
+void run_cmds(CommandVec cmds) {
+ signal(SIGTTIN, SIG_IGN);
+ signal(SIGTTOU, SIG_IGN);
+
+ int trunk_fd = STDIN_FILENO;
+ int pgid = -1;
+
+ for (int i = 0; i < cmds.count; i++) {
+ Command cmd = *get_from_CommandVec(&cmds, i);
+ cmd.stdin_fd = trunk_fd;
+
+ if (i > 0) {
+ push_to_IntVec(&cmd.child_close_fds, trunk_fd);
+ push_to_IntVec(&cmd.parent_close_fds, trunk_fd);
+ }
+
+ cmd.stdout_fd = STDOUT_FILENO;
+ if (cmd.term == CONN_PIPE) {
+ int pipe_fds[2];
+ pipe(pipe_fds);
+ cmd.stdout_fd = pipe_fds[1];
+ push_to_IntVec(&cmd.child_close_fds, pipe_fds[1]);
+ push_to_IntVec(&cmd.parent_close_fds, pipe_fds[1]);
+ trunk_fd = pipe_fds[0];
+ }
+
+ execute_command(cmd, &pgid);
+
+ for (int j = 0; j < cmd.parent_close_fds.count; j++) {
+ close(*get_from_IntVec(&cmd.parent_close_fds, j));
+ }
+ }
+
+ tcsetpgrp(STDIN_FILENO, pgid);
+ while (waitpid(-pgid, NULL, 0) > 0);
+ tcsetpgrp(STDIN_FILENO, getpgrp());
+}
+
+
+CommandVec make_cmd_vec_pl(ExpandedPipeline pl) {
+ CommandVec cmds = make_CommandVec(256);
+ for (int i = 0; i < pl.count; i++) {
+ ExpandedPipelineElement elem = *get_from_ExpandedPipeline(&pl, i);
+ Command cmd = make_command(elem);
+ push_to_CommandVec(&cmds, cmd);
+ }
+ return cmds;
+}
+
+
+void process_input_line(char* line, ShellState* shstate) {
+ PreprocArena arena = {
+ .tokens = make_TokenVec(256),
+ .chunks = make_ChunkVec(256),
+ .regions = make_RegionVec(256),
+ };
+ TokenPtrVec line_tok_ptrs = lex(line, &arena);
+ Token* line_toks = malloc(sizeof(Token) * line_tok_ptrs.count);
+ for (int i = 0; i < line_tok_ptrs.count; i++) {
+ line_toks[i] = **get_from_TokenPtrVec(&line_tok_ptrs, i);
+ }
+ Pipeline pl = parse_tokstream(line_toks);
+ ExpandedPipeline exp_pl = expand_pipeline(&pl, shstate);
+ CommandVec cmds = make_cmd_vec_pl(exp_pl);
+ run_cmds(cmds);
+ free(line_toks);
+}
+
diff --git a/src/exec.h b/src/exec.h
new file mode 100644
index 0000000..9ee8164
--- /dev/null
+++ b/src/exec.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "utils.h"
+#include "preproc.h"
+
+
+typedef struct {
+ int old_fd;
+ int new_fd;
+} FdRedirect;
+
+DECLARE_VEC(FdRedirect, FdRedirectVec)
+
+typedef struct {
+ char** args;
+ FdRedirectVec redirs;
+ int stdin_fd;
+ int stdout_fd;
+ IntVec child_close_fds;
+ IntVec parent_close_fds;
+ Connector term;
+} Command;
+
+DECLARE_VEC(Command, CommandVec)
+
+void process_input_line(char* line, ShellState* shstate);
diff --git a/src/expand.c b/src/expand.c
new file mode 100644
index 0000000..11764f9
--- /dev/null
+++ b/src/expand.c
@@ -0,0 +1,104 @@
+#include "expand.h"
+#include "preproc.h"
+#include "utils.h"
+#include <string.h>
+
+
+typedef struct {
+ QuotationMode qmode;
+ char* content;
+} ExpandedChunk;
+
+
+ExpandedChunk expand_chunk(Chunk chunk, ShellState* shstate) {
+ char* content;
+ if (chunk.qmode == SINGLE_Q || chunk.kind == LITERAL) {
+ if (chunk.qmode == SINGLE_Q && chunk.kind != LITERAL) exit(69);
+ content = chunk.as.literal_str;
+ } else if (chunk.kind == VAR_EXP) {
+ content = get_var(chunk.as.varname, shstate);
+ } else if (chunk.kind == REGION_EXP) {
+ exit(7); // Not supported yet; arithm
+ }
+ return (ExpandedChunk){.qmode = chunk.qmode, .content = content};
+}
+
+
+typedef struct {
+ StrVec complete_strs;
+ String active_str;
+} WordSplittingGerm;
+
+void add_expchunk(char* next_str, WordSplittingGerm* curr_res) {
+ for (int pos = 0; pos < strlen(next_str); pos++) {
+ if (next_str[pos] == ' ') {
+ push_to_StrVec(&curr_res->complete_strs, inner(&curr_res->active_str));
+ curr_res->active_str = make_String(256);
+ } else {
+ push_to_String(&curr_res->active_str, next_str[pos]);
+ }
+ }
+}
+
+
+void expand_word(Word word, SlotVec* target, ShellState* shstate) {
+ WordSplittingGerm curr_res = {
+ .complete_strs = make_StrVec(256),
+ .active_str = make_String(256)
+ };
+ for (int i = 0; i < word.count; i++) {
+ Chunk chunk = **get_from_Word(&word, i);
+ ExpandedChunk expchunk = expand_chunk(chunk, shstate);
+ switch (expchunk.qmode) {
+ case UN_Q:
+ add_expchunk(expchunk.content, &curr_res);
+ break;
+ case SINGLE_Q:
+ append_str_to_String(&curr_res.active_str, expchunk.content);
+ break;
+ case DOUBLE_Q:
+ // TODO: further expansions here
+ append_str_to_String(&curr_res.active_str, expchunk.content);
+ break;
+ }
+ }
+ if (curr_res.active_str.count > 0) {
+ push_to_StrVec(&curr_res.complete_strs, inner(&curr_res.active_str));
+ }
+ for (int j = 0; j < curr_res.complete_strs.count; j++) {
+ char* str = *get_from_StrVec(&curr_res.complete_strs, j);
+ push_to_SlotVec(target, (Slot){.kind = SLOT_STR, .as.str = str});
+ }
+}
+
+ExpandedPipelineElement expand_pipeline_element(PipelineElement elem, ShellState* shstate) {
+ ExpandedPipelineElement result = {.cmd_slots = make_SlotVec(256), .conn = elem.conn};
+ for (int i = 0; i < elem.cmd_toks.count; i++) {
+ Token curr_tok = *get_from_TokenVec(&elem.cmd_toks, i);
+ switch (curr_tok.kind) {
+ case TOK_WORD:
+ expand_word(curr_tok.as.word, &result.cmd_slots, shstate);
+ break;
+ case TOK_SPECIAL:
+ push_to_SlotVec(&result.cmd_slots, (Slot){.kind = SLOT_SPEC, .as.spec = curr_tok.as.spec});
+ break;
+ case TOK_PROCSUB_IN:
+ push_to_SlotVec(&result.cmd_slots, (Slot){.kind = SLOT_PROC_IN, .as.proc_reg= curr_tok.as.procsub_region});
+ break;
+ case TOK_PROCSUB_OUT:
+ push_to_SlotVec(&result.cmd_slots, (Slot){.kind = SLOT_PROC_OUT, .as.proc_reg= curr_tok.as.procsub_region});
+ break;
+ }
+ }
+ return result;
+}
+
+ExpandedPipeline expand_pipeline(Pipeline* pl, ShellState *shstate) {
+ ExpandedPipeline exp_pl = make_ExpandedPipeline(256);
+ for (int i = 0; i < pl->count; i++) {
+ PipelineElement elem = *get_from_Pipeline(pl, i);
+ ExpandedPipelineElement exp_elem = expand_pipeline_element(elem, shstate);
+ push_to_ExpandedPipeline(&exp_pl, exp_elem);
+ }
+ return exp_pl;
+}
diff --git a/src/expand.h b/src/expand.h
new file mode 100644
index 0000000..b353f39
--- /dev/null
+++ b/src/expand.h
@@ -0,0 +1,31 @@
+# pragma once
+
+#include "preproc.h"
+#include "utils.h"
+
+typedef enum {
+ SLOT_STR,
+ SLOT_SPEC,
+ SLOT_PROC_IN,
+ SLOT_PROC_OUT,
+} SlotKind;
+
+typedef struct {
+ SlotKind kind;
+ union {
+ char* str;
+ SpecialToken spec;
+ Region* proc_reg;
+ } as;
+} Slot;
+
+DECLARE_VEC(Slot, SlotVec)
+
+typedef struct {
+ SlotVec cmd_slots;
+ Connector conn;
+} ExpandedPipelineElement;
+
+DECLARE_VEC(ExpandedPipelineElement, ExpandedPipeline)
+
+ExpandedPipeline expand_pipeline(Pipeline* pl, ShellState* shstate);
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..35c2bff
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,23 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <string.h>
+
+#include "exec.h"
+
+
+int main() {
+ char* line;
+ ShellState shstate = {.shell_vars = make_KeyValMap(1), .aliases = make_KeyValMap(1)};
+ while (1) {
+ line = readline("$ ");
+ if (line[0] == '\0') continue;
+ if (strcmp(line, "exit") == 0) exit(0);
+ add_history(line);
+ process_input_line(line, &shstate);
+ }
+ free(line);
+}
+
+
diff --git a/src/preproc.c b/src/preproc.c
new file mode 100644
index 0000000..4cd1c5b
--- /dev/null
+++ b/src/preproc.c
@@ -0,0 +1,318 @@
+#include <ctype.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "preproc.h"
+#include "utils.h"
+
+
+// Lexing
+
+typedef struct {
+ char* str;
+ size_t pos;
+} RawLine;
+
+
+String collect_identifier(RawLine* line) {
+ String id_buf = make_String(256);
+ while (line->str[line->pos] == '_' || isalnum(line->str[line->pos])) {
+ push_to_String(&id_buf, line->str[line->pos++]);
+ }
+ return id_buf;
+}
+
+
+
+TokenPtrVec lex_level(RawLine* line, char until, PreprocArena* arena);
+
+
+void expect_char(RawLine* line, char expected) {
+ if (line->str[line->pos] != expected) {
+ exit(45); // TODO: nicer handling
+ }
+ line->pos++;
+}
+
+
+/*
+MaybeChunk lex_expansion_chunk(RawLine* line, RegionVec* reg_arena, QuotationMode qmode) {
+ line->pos++; // Consume $
+ if (line->str[line->pos] == '(') {
+ line->pos++;
+ if (line->str[line->pos] == '(') {
+ exit(1);
+ // TODO: this (and potentially the other parsers must be custom, we need to recognize arithm-internal parens from the terminating `))` )
+
+
+ TokenVec inner_toks = lex_level(line, ')', reg_arena);
+ expect_char(line, ')');
+ expect_char(line, ')');
+ Region* reg_ptr = push_to_RegionVec(
+ reg_arena,
+ (Region){.mode = ARITHM, .tokens = inner_toks}
+ );
+ return (MaybeChunk){
+ .some = true,
+ .value = (Chunk){
+ .qmode = qmode,
+ .kind = REGION_EXP,
+ .as.reg_ptr = reg_ptr
+ },
+ };
+ } else {
+ TokenVec inner_toks = lex_level(line, ')', reg_arena);
+ expect_char(line, ')');
+ Region* reg_ptr = push_to_RegionVec(
+ reg_arena,
+ (Region){.mode = CMDS, .tokens = inner_toks}
+ );
+ return (MaybeChunk) {
+ .some = true,
+ .value = (Chunk){
+ .qmode = qmode,
+ .kind = REGION_EXP,
+ .as.reg_ptr = reg_ptr,
+ }
+ };
+ }
+ } else {
+ String id_name = collect_identifier(line);
+ if (id_name.count > 0) {
+ return (MaybeChunk){
+ .some = true,
+ .value = (Chunk){
+ .qmode = qmode,
+ .kind = VAR_EXP,
+ .as.varname = inner(&id_name),
+ }
+ };
+ }
+ }
+ return (MaybeChunk){.some = false};
+}
+*/
+
+
+void lex_double_quoted_section(RawLine* line, Word* coll, PreprocArena* arena) {
+ line->pos++;
+ String accum = make_String(256);
+ while (line->str[line->pos] != '\"') {
+ if (line->str[line->pos] == '\0') exit(43);
+
+ MaybeChunk exp_chunk = {.some = false};
+
+ if (line->str[line->pos] == '$') {
+ exit(1);
+ /*
+ exp_chunk = lex_expansion_chunk(line, reg_arena, DOUBLE_Q);
+ if (!exp_chunk.some) {
+ push_to_String(&accum, '$');
+ }
+ */
+ } else {
+ push_to_String(&accum, line->str[line->pos++]);
+ }
+ // TODO: other special cases beyond $
+
+ if (exp_chunk.some) {
+ if (accum.count > 0) {
+ Chunk accum_chunk = {.qmode = DOUBLE_Q, .kind = LITERAL, .as.literal_str = inner(&accum)};
+ Chunk* accum_ptr = push_to_ChunkVec(&arena->chunks, accum_chunk);
+ push_to_Word(coll, accum_ptr);
+ accum = make_String(256);
+ }
+ Chunk* exp_ptr = push_to_ChunkVec(&arena->chunks, exp_chunk.value);
+ push_to_Word(coll, exp_ptr);
+ exp_chunk.some = false;
+ }
+ }
+ line->pos++;
+ if (accum.count > 0) {
+ Chunk accum_chunk = {.qmode = DOUBLE_Q, .kind = LITERAL, .as.literal_str = inner(&accum)};
+ Chunk* accum_ptr = push_to_ChunkVec(&arena->chunks, accum_chunk);
+ push_to_Word(coll, accum_ptr);
+ accum = make_String(256);
+ }
+}
+
+
+Chunk next_word_unquoted(RawLine* line, RegionVec* reg_arena) {
+ String buf = make_String(256);
+ for (char c; (c=line->str[line->pos], c != '$' && c != '\0' && c != ' '); line->pos++) {
+ push_to_String(&buf, c);
+ }
+ return (Chunk){.qmode = UN_Q, .kind = LITERAL, .as.literal_str = inner(&buf)};
+}
+
+
+Chunk collect_single_quoted_section(RawLine* line) {
+ line->pos++; // Consume quote mark
+ String chunk_str = make_String(256);
+ while (line->str[line->pos] != '\'') {
+ if (line->str[line->pos] == '\0') exit(44);
+ push_to_String(&chunk_str, line->str[line->pos++]);
+ }
+ line->pos++; // Consume closing quote
+ return (Chunk){
+ .qmode = SINGLE_Q,
+ .kind = LITERAL,
+ .as.literal_str = inner(&chunk_str),
+ };
+}
+
+
+typedef struct {
+ Word word_germ;
+ String unq_liter_germ;
+} LexState;
+
+
+void close_word_and_lit_germs(LexState* state, TokenPtrVec* toks, PreprocArena* arena) {
+ if (state->unq_liter_germ.count > 0) {
+ Chunk chu = {
+ .qmode = UN_Q,
+ .kind = LITERAL,
+ .as.literal_str = inner(&state->unq_liter_germ)
+ };
+ Chunk* chu_ptr = push_to_ChunkVec(&arena->chunks, chu);
+ push_to_Word(&state->word_germ, chu_ptr);
+ state->unq_liter_germ = make_String(256);
+ }
+ if (state->word_germ.count > 0) {
+ Token tok = {.kind = TOK_WORD, .as.word = state->word_germ};
+ Token* tok_ptr = push_to_TokenVec(&arena->tokens, tok);
+ push_to_TokenPtrVec(toks, tok_ptr);
+ state->word_germ = make_Word(256);
+ }
+}
+
+TokenPtrVec lex_level(RawLine* line, char until, PreprocArena* arena) {
+
+ TokenPtrVec line_toks = make_TokenPtrVec(256);
+ LexState state = {
+ .word_germ = make_Word(256),
+ .unq_liter_germ = make_String(256)
+ };
+
+ while (line->str[line->pos] != until) {
+
+ if (line->str[line->pos] == ' ') {
+ close_word_and_lit_germs(&state, &line_toks, arena);
+ while (line->str[line->pos] == ' ') line->pos++;
+ continue;
+ }
+
+ Token* tok_ptr;
+
+ switch (line->str[line->pos]) {
+ case '\'':
+ push_to_Word(&state.word_germ, push_to_ChunkVec(&arena->chunks, collect_single_quoted_section(line)));
+ break;
+ case '\"':
+ lex_double_quoted_section(line, &state.word_germ, arena);
+ break;
+ case '|':
+ line->pos++;
+ close_word_and_lit_germs(&state, &line_toks, arena);
+ tok_ptr = push_to_TokenVec(&arena->tokens, (Token){.kind = TOK_SPECIAL, .as.spec = PIPE});
+ push_to_TokenPtrVec(&line_toks, tok_ptr);
+ break;
+ case '$':
+ exit(1);
+ /*
+ line->pos++;
+ MaybeChunk mchunk = lex_expansion_chunk(line, reg_arena, UN_Q);
+ if (mchunk.some) {
+ push_to_Word(&state.word_germ, mchunk.value);
+ } else {
+ push_to_String(&state.unq_liter_germ, '$');
+ }
+ */
+ break;
+ case '>':
+ close_word_and_lit_germs(&state, &line_toks, arena);
+ line->pos++;
+
+ switch (line->str[line->pos]) {
+ case '(':
+ line->pos++;
+ TokenPtrVec sub_regtoks = lex_level(line, ')', arena);
+ line->pos++;
+ Region subreg = {.mode = CMDS, .tokens = sub_regtoks};
+ Region* subreg_ptr = push_to_RegionVec(&arena->regions, subreg);
+ Token tok = {.kind = TOK_PROCSUB_OUT, .as.procsub_region = subreg_ptr};
+ tok_ptr = push_to_TokenVec(&arena->tokens, tok);
+ push_to_TokenPtrVec(&line_toks, tok_ptr);
+ break;
+ case '>':
+ line->pos++;
+ tok_ptr = push_to_TokenVec(&arena->tokens, (Token){.kind = TOK_SPECIAL, .as.spec = WRITE_REDIR_APPEND});
+ push_to_TokenPtrVec(&line_toks, tok_ptr);
+ break;
+ default:
+ tok_ptr = push_to_TokenVec(&arena->tokens, (Token){.kind = TOK_SPECIAL, .as.spec = WRITE_REDIR_TRUNC});
+ push_to_TokenPtrVec(&line_toks, tok_ptr);
+ }
+ break;
+ case '<':
+ close_word_and_lit_germs(&state, &line_toks, arena);
+ line->pos++;
+ if (line->str[line->pos] == '(') {
+ line->pos++;
+ TokenPtrVec sub_regtoks = lex_level(line, ')', arena);
+ line->pos++;
+ Region subreg = {.mode = CMDS, .tokens = sub_regtoks};
+ Region* reg_ptr = push_to_RegionVec(&arena->regions, subreg);
+ Token reg_tok = {.kind = TOK_PROCSUB_IN, .as.procsub_region = reg_ptr};
+ Token* reg_tok_ptr = push_to_TokenVec(&arena->tokens, reg_tok);
+ push_to_TokenPtrVec(&line_toks, reg_tok_ptr);
+ } else {
+ Token* redir_ptr = push_to_TokenVec(&arena->tokens, (Token){.kind = TOK_SPECIAL, .as.spec = READ_REDIR});
+ push_to_TokenPtrVec(&line_toks, redir_ptr);
+ }
+ break;
+ default:
+ push_to_String(&state.unq_liter_germ, line->str[line->pos++]);
+ }
+ }
+ // TODO: handle until char _and_ EOL
+ close_word_and_lit_germs(&state, &line_toks, arena);
+ return line_toks;
+}
+
+
+TokenPtrVec lex(char* input, PreprocArena* arena) {
+ RawLine line = {.str = input, .pos = 0};
+ TokenPtrVec toks = lex_level(&line, '\0', arena);
+ Token* eol_ptr = push_to_TokenVec(&arena->tokens, (Token){.kind = TOK_SPECIAL, .as.spec = EOL});
+ push_to_TokenPtrVec(&toks, eol_ptr);
+ return toks;
+}
+
+
+
+
+// Parsing
+
+Pipeline parse_tokstream(Token* toks) {
+ size_t tok_pos = 0;
+ Pipeline result = make_Pipeline(256);
+ TokenVec tok_buf = make_TokenVec(256);
+ while (true) {
+ Token curr_tok = toks[tok_pos++];
+ if (curr_tok.kind == TOK_SPECIAL && curr_tok.as.spec == PIPE) {
+ push_to_Pipeline(&result, (PipelineElement){.cmd_toks = tok_buf, .conn = CONN_PIPE});
+ tok_buf = make_TokenVec(256);
+ } else if (curr_tok.kind == TOK_SPECIAL && curr_tok.as.spec == EOL) {
+ push_to_Pipeline(&result, (PipelineElement){.cmd_toks = tok_buf, .conn = CONN_EOL});
+ break;
+ } else {
+ push_to_TokenVec(&tok_buf, curr_tok);
+ }
+ }
+ return result;
+}
diff --git a/src/preproc.h b/src/preproc.h
new file mode 100644
index 0000000..7907f9d
--- /dev/null
+++ b/src/preproc.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include "utils.h"
+
+typedef struct Region Region;
+
+
+// Lexing stage
+typedef enum {
+ UN_Q,
+ SINGLE_Q,
+ DOUBLE_Q,
+} QuotationMode;
+
+typedef enum {
+ LITERAL,
+ REGION_EXP,
+ VAR_EXP, // TODO: Later include other exps
+} ChunkKind;
+
+typedef struct {
+ ChunkKind kind;
+ QuotationMode qmode;
+ union {
+ char* literal_str;
+ Region* reg_ptr;
+ char* varname;
+ } as;
+} Chunk;
+DECLARE_MAYBE(Chunk, MaybeChunk);
+DECLARE_VEC(Chunk*, Word)
+
+typedef enum {
+ TOK_WORD,
+ TOK_SPECIAL,
+ TOK_PROCSUB_IN,
+ TOK_PROCSUB_OUT,
+} TokenKind;
+
+typedef enum {
+ PIPE,
+ WRITE_REDIR_TRUNC,
+ WRITE_REDIR_APPEND,
+ READ_REDIR,
+ EOL,
+} SpecialToken;
+DECLARE_MAYBE(SpecialToken, MaybeSpecialToken)
+
+typedef struct {
+ TokenKind kind;
+
+ union {
+ Word word;
+ SpecialToken spec;
+ Region* procsub_region;
+ } as;
+} Token;
+
+
+DECLARE_VEC(Token*, TokenPtrVec)
+
+
+typedef enum {
+ CMDS,
+ ARITHM,
+} RegionMode;
+
+struct Region {
+ RegionMode mode;
+ TokenPtrVec tokens;
+};
+DECLARE_VEC(Region, RegionVec)
+
+
+
+DECLARE_VEC(Chunk, ChunkVec)
+DECLARE_VEC(Token, TokenVec)
+
+
+
+// Lexing
+
+typedef struct {
+ RegionVec regions;
+ TokenVec tokens;
+ ChunkVec chunks;
+} PreprocArena;
+
+
+TokenPtrVec lex(char* input, PreprocArena* arena);
+
+
+// Parsing
+
+typedef enum {
+ CONN_PIPE,
+ CONN_EOL,
+ // TODO: add the |& thingy
+} Connector;
+
+DECLARE_MAYBE(Connector, MaybeConnector)
+
+typedef struct {
+ TokenVec cmd_toks;
+ Connector conn;
+} PipelineElement;
+
+DECLARE_VEC(PipelineElement, Pipeline)
+
+Pipeline parse_tokstream(Token* tokstream);
+
+
+
+
+
+
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..1f08445
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,71 @@
+#include "utils.h"
+#include <string.h>
+#include <stdlib.h>
+
+void append_str_to_String(String* target, const char* src_str) {
+ size_t len = strlen(src_str);
+
+ for (size_t i = 0; i < len; i++) {
+ push_to_String(target, src_str[i]);
+ }
+}
+
+char* inner(String* str) {
+ char *out = malloc(str->count + 1);
+ if (!out) abort();
+
+ for (size_t i = 0; i < str->count; i++) {
+ out[i] = *get_from_String(str, i);
+ }
+
+ out[str->count] = '\0';
+ return out;
+}
+
+
+void set_map_pair(KeyValMap* map, char* key, char* val) {
+for (int i = 0; i < map->count; i++) {
+ char* key_i = get_from_KeyValMap(map, i)->key;
+ if (key_i == NULL) continue;
+ if (strcmp(key_i, key) == 0) {
+ // TODO: maybe copy instead
+ strcpy(
+ map->blocks[i / map->block_size][i % map->block_size].val,
+ val
+ );
+ break;
+ }
+ }
+}
+
+MaybeStr get_map_value(KeyValMap* map, char* key) {
+ for (int i = 0; i < map->count; i++) {
+ char* key_i = get_from_KeyValMap(map, i)->key;
+ if (key_i == NULL) continue;
+ if (strcmp(key_i, key) == 0) {
+ return (MaybeStr){
+ .some = true,
+ .value = get_from_KeyValMap(map, i)->val
+ };
+ }
+ }
+ return (MaybeStr){.some = false};
+}
+
+void unset_map_key(KeyValMap* map, char* key) {
+ for (int i = 0; i < map->count; i++) {
+ char* key_i = get_from_KeyValMap(map, i)->key;
+ if (key_i == NULL) continue;
+ if (strcmp(key_i, key) == 0) {
+ map->blocks[i / map->block_size][i % map->block_size].key = NULL;
+ break;
+ }
+ }
+}
+
+
+char* get_var(char* varname, ShellState* shstate) {
+ MaybeStr maybe_varval = get_map_value(&shstate->shell_vars, varname);
+ if (maybe_varval.some == true) return maybe_varval.value;
+ return getenv(varname);
+}
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..a213c71
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <ctype.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+
+#define DECLARE_VEC(T, Name) \
+ typedef struct { \
+ T **blocks; \
+ size_t count; \
+ size_t used; \
+ size_t cap; \
+ size_t block_size; \
+ } Name; \
+ \
+ static inline Name make_##Name(size_t block_size) { \
+ return (Name){ \
+ .blocks = NULL, \
+ .count = 0, \
+ .used = 0, \
+ .cap = 0, \
+ .block_size = block_size, \
+ }; \
+ } \
+ \
+ static inline T *push_to_##Name(Name *vec, T item) { \
+ size_t block_index = vec->count / vec->block_size; \
+ size_t item_index = vec->count % vec->block_size; \
+ \
+ if (block_index == vec->used) { \
+ if (vec->used == vec->cap) { \
+ size_t new_cap = vec->cap \
+ ? vec->cap * 2 \
+ : 8; \
+ \
+ T **new_blocks = realloc( \
+ vec->blocks, \
+ new_cap * sizeof(T *) \
+ ); \
+ \
+ if (!new_blocks) abort(); \
+ \
+ vec->blocks = new_blocks; \
+ vec->cap = new_cap; \
+ } \
+ \
+ vec->blocks[vec->used] = \
+ malloc(vec->block_size * sizeof(T)); \
+ \
+ if (!vec->blocks[vec->used]) abort(); \
+ \
+ vec->used++; \
+ } \
+ \
+ T *ptr = &vec->blocks[block_index][item_index]; \
+ *ptr = item; \
+ \
+ vec->count++; \
+ return ptr; \
+ } \
+ \
+ static inline T* get_from_##Name(Name *vec, size_t index) { \
+ if (index >= vec->count) return NULL; \
+ \
+ return &vec->blocks \
+ [index / vec->block_size] \
+ [index % vec->block_size]; \
+ }
+
+
+DECLARE_VEC(int, IntVec)
+
+DECLARE_VEC(char, String);
+DECLARE_VEC(char*, StrVec)
+
+
+void append_str_to_String(String* target, const char* src_str);
+char* inner(String* str);
+
+
+
+#define DECLARE_MAYBE(T, Name) \
+ typedef struct { \
+ bool some; \
+ T value; \
+ } Name;
+
+DECLARE_MAYBE(char*, MaybeStr)
+
+
+typedef struct {
+ char* key;
+ char* val;
+} StrPair;
+DECLARE_VEC(StrPair, KeyValMap)
+
+void set_map_pair(KeyValMap* map, char* key, char* val);
+MaybeStr get_map_value(KeyValMap* map, char* key);
+void unset_map_key(KeyValMap* map, char* key);
+
+
+typedef struct {
+ KeyValMap aliases;
+ KeyValMap shell_vars;
+} ShellState;
+
+char* get_var(char* varname, ShellState* shstate);