aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2021-07-05 01:07:35 +0200
committerMattias Andrée <maandree@kth.se>2021-07-05 01:21:28 +0200
commite4535303d2062b6d959d7ecd88ce97dd75684784 (patch)
tree3a5b30b99079fef3b42c2169af4d1dc5447ea1af
downloadapsh-e4535303d2062b6d959d7ecd88ce97dd75684784.tar.gz
apsh-e4535303d2062b6d959d7ecd88ce97dd75684784.tar.bz2
apsh-e4535303d2062b6d959d7ecd88ce97dd75684784.tar.xz
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--.gitignore5
-rw-r--r--LICENSE15
-rw-r--r--Makefile27
-rw-r--r--README2
-rw-r--r--apsh.c751
-rw-r--r--config.h13
-rw-r--r--config.mk8
7 files changed, 821 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e4204e7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*\#*
+*~
+*.o
+*.su
+/apsh
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c44b2d9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2021 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..16394bb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OBJ =\
+ apsh.o
+
+HDR =\
+ config.h
+
+all: apsh
+$(OBJ): $(@:.o=.c) $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CPPFLAGS) $(CFLAGS)
+
+apsh: $(OBJ)
+ $(CC) -o $@ $(OBJ) $(LDFLAGS)
+
+clean:
+ -rm -f -- *.o *.su apsh
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+.PHONY: all clean
diff --git a/README b/README
new file mode 100644
index 0000000..aa00934
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+NAME
+ apsh — advanced piping shell
diff --git a/apsh.c b/apsh.c
new file mode 100644
index 0000000..f732b20
--- /dev/null
+++ b/apsh.c
@@ -0,0 +1,751 @@
+/* See LICENSE file for copyright and license details. */
+#include <libsimple.h>
+#include <libsimple-arg.h>
+#include "config.h"
+
+USAGE("");
+
+enum argument_type {
+ VERBATIM,
+ ESCAPED,
+ SPECIAL,
+ FUNCTION_MARK,
+ SUBSHELL_INPUT, /* >(...) */
+ SUBSHELL_OUTPUT, /* <(...) */
+ SUBSHELL_INPUT_OUTPUT, /* <>(...) ## create socket for both input and output of subshell */
+ SUBSHELL_SUBSTITUTION,
+ SUBSHELL, /* (...) or ((...)) ## if non-first argument: format shell code into a string (can be used for a clean subshell) */
+ REDIRECTION /* at beginning of argument, use next redirection and use reminder of argument as right-hand side */
+};
+
+enum redirection_type {
+ REDIRECT_INPUT,
+ REDIRECT_INPUT_TO_FD,
+ REDIRECT_OUTPUT,
+ REDIRECT_OUTPUT_APPEND,
+ REDIRECT_OUTPUT_CLOBBERING,
+ REDIRECT_OUTPUT_TO_FD,
+ REDIRECT_INPUT_OUTPUT,
+ REDIRECT_INPUT_OUTPUT_TO_FD,
+ HERE_STRING,
+ HERE_DOCUMENT,
+ HERE_DOCUMENT_INDENTED
+};
+
+enum command_terminal {
+ DOUBLE_SEMICOLON,
+ SEMICOLON,
+ AMPERSAND,
+ SOCKET_PIPE,
+ PIPE,
+ PIPE_AMPERSAND,
+ AND,
+ OR
+};
+
+enum shell_terminator {
+ END_OF_FILE,
+ ROUND,
+ ROUND_ROUND,
+ SQUARE,
+ BACKTICK,
+};
+
+struct parser_state;
+
+struct argument {
+ enum argument_type type;
+ union {
+ struct { /* VERBATIM, ESCAPED */
+ char *text;
+ size_t length;
+ };
+ char symbol; /* SPECIAL */
+ struct parser_state *root; /* SUBSHELL, SUBSHELL_* */
+ }; /* none for FUNCTION_MARK, REDIRECTION */
+ struct argument *next_part;
+};
+
+struct redirection {
+ enum redirection_type type;
+ struct argument *left_hand_side;
+};
+
+struct command {
+ enum command_terminal terminal;
+ struct argument **arguments;
+ size_t narguments;
+ struct redirection **redirections;
+ size_t nredirections;
+};
+
+struct parser_state {
+ struct parser_state *parent;
+ struct command **commands;
+ size_t ncommands;
+ struct argument **arguments;
+ size_t narguments;
+ struct redirection **redirections;
+ size_t nredirections;
+ struct argument *current_argument;
+ struct argument *current_argument_end;
+ enum shell_terminator exit_on;
+ char at_dollar;
+ char is_expr_shell;
+ char need_right_hand_side;
+};
+
+struct here_document {
+ struct redirection *redirection;
+ struct argument *argument;
+ struct here_document *next;
+};
+
+static size_t line_number = 1;
+static int tty_input = 0;
+
+static struct parser_state *state;
+static struct here_document *here_documents_first = NULL;
+static struct here_document **here_documents_next = &here_documents_first;
+
+static void flush_dollar(void);
+static void verbatim(const char *text, size_t text_length, int from_quote);
+
+static void
+whitespace(int strict)
+{
+ flush_dollar();
+
+ if (state->need_right_hand_side) {
+ if (strict)
+ eprintf("premature end of command\n");
+ return;
+ }
+
+ if (state->current_argument) {
+ state->arguments = erealloc(state->arguments, (state->narguments + 1) * sizeof(*state->arguments));
+ state->arguments[state->narguments++] = state->current_argument;
+ state->current_argument = NULL;
+ state->current_argument_end = NULL;
+ }
+}
+
+static void
+terminate_command(enum command_terminal terminal)
+{
+ whitespace(1);
+
+ state->commands = erealloc(state->commands, (state->ncommands + 1) * sizeof(*state->commands));
+ state->commands[state->ncommands] = ecalloc(1, sizeof(**state->commands));
+ state->commands[state->ncommands]->terminal = terminal;
+ state->commands[state->ncommands]->arguments = state->arguments;
+ state->commands[state->ncommands]->narguments = state->narguments;
+ state->commands[state->ncommands]->redirections = state->redirections;
+ state->commands[state->ncommands]->nredirections = state->nredirections;
+ state->ncommands += 1;
+ state->arguments = NULL;
+ state->narguments = 0;
+ state->redirections = NULL;
+ state->nredirections = 0;
+
+ if (!state->parent) {
+ if (terminal == DOUBLE_SEMICOLON || terminal == SEMICOLON || terminal == AMPERSAND) {
+ /* TODO unless in a special construct such as while, case, for, if, or {, run and clear
+ * also require that any here-document is specified (count them and run when given)
+ */
+ }
+ }
+}
+
+static void
+semicolon(int maybe)
+{
+ if (!maybe || state->narguments)
+ terminate_command(SEMICOLON);
+}
+
+static void
+end_subshell(void)
+{
+ semicolon(1);
+ /* TODO validate subshell content */
+ state = state->parent;
+}
+
+static void
+add_redirection(enum redirection_type type)
+{
+ state->redirections = erealloc(state->redirections, (state->nredirections + 1) * sizeof(*state->redirections));
+ state->redirections[state->nredirections] = ecalloc(1, sizeof(**state->redirections));
+ state->redirections[state->nredirections]->type = type;
+ if (state->current_argument) {
+ if (state->current_argument->type == REDIRECTION) {
+ whitespace(1);
+ } else {
+ state->redirections[state->nredirections]->left_hand_side = state->current_argument;
+ state->current_argument = NULL;
+ state->current_argument_end = NULL;
+ }
+ }
+ state->current_argument_end = state->current_argument = calloc(1, sizeof(*state->current_argument));
+ state->current_argument_end->type = REDIRECTION;
+ if (type == HERE_DOCUMENT || type == HERE_DOCUMENT_INDENTED) {
+ *here_documents_next = emalloc(sizeof(**here_documents_next));
+ (*here_documents_next)->redirection = state->redirections[state->nredirections];
+ (*here_documents_next)->argument = state->current_argument;
+ (*here_documents_next)->next = NULL;
+ here_documents_next = &(*here_documents_next)->next;
+ }
+ state->nredirections += 1;
+ state->need_right_hand_side = 1;
+}
+
+static void
+add_shell_io(enum argument_type type, enum shell_terminator exit_on)
+{
+ struct parser_state *new_state;
+
+ state->need_right_hand_side = 0;
+
+ if (!state->current_argument_end)
+ state->current_argument_end = state->current_argument = ecalloc(1, sizeof(struct argument));
+ else
+ state->current_argument_end = state->current_argument_end->next_part = ecalloc(1, sizeof(struct argument));
+
+ new_state = ecalloc(1, sizeof(*new_state));
+ new_state->parent = state;
+ new_state->exit_on = exit_on;
+
+ state->current_argument_end->type = type;
+ state->current_argument_end->root = state;
+
+ state = new_state;
+}
+
+static void
+add_function_mark(void)
+{
+ whitespace(1);
+ if (!state->current_argument_end)
+ state->current_argument_end = state->current_argument = ecalloc(1, sizeof(struct argument));
+ else
+ state->current_argument_end = state->current_argument_end->next_part = ecalloc(1, sizeof(struct argument));
+ state->current_argument_end->type = FUNCTION_MARK;
+ whitespace(1);
+}
+
+static void
+parse_symbol(const char *token, size_t token_length)
+{
+ struct parser_state *old_state = state;
+
+ while (token_length) {
+ if (state->at_dollar) {
+ state->at_dollar = 0;
+ if (token_length >= 2 && token[0] == '(' && token[1] == '(') {
+ add_shell_io(SUBSHELL_SUBSTITUTION, ROUND_ROUND);
+ state->is_expr_shell = 1;
+ token = &token[2];
+ token_length -= 2;
+ } else if (token_length >= 1 && token[0] == '(') {
+ add_shell_io(SUBSHELL_SUBSTITUTION, ROUND);
+ token = &token[1];
+ token_length -= 1;
+ } else if (token_length >= 1 && token[0] == '[') {
+ add_shell_io(SUBSHELL_SUBSTITUTION, SQUARE);
+ state->is_expr_shell = 1;
+ token = &token[1];
+ token_length -= 1;
+ } else if (token_length >= 1 && token[0] == '{') { /* TODO */
+ token = &token[1];
+ token_length -= 1;
+ } else {
+ state->at_dollar = 1;
+ flush_dollar();
+ continue;
+ }
+ }
+
+ if (token_length >= 3 && token[0] == '<' && token[1] == '<' && token[2] == '<') {
+ add_redirection(HERE_STRING);
+ token = &token[3];
+ token_length -= 3;
+
+ } else if (token_length >= 3 && token[0] == '<' && token[1] == '<' && token[2] == '-') {
+ add_redirection(HERE_DOCUMENT_INDENTED);
+ token = &token[3];
+ token_length -= 3;
+
+ } else if (token_length >= 3 && token[0] == '<' && token[1] == '>' && token[2] == '(') {
+ add_shell_io(SUBSHELL_INPUT_OUTPUT, ROUND);
+ token = &token[3];
+ token_length -= 3;
+
+ } else if (token_length >= 3 && token[0] == '<' && token[1] == '>' && token[2] == '|') {
+ terminate_command(SOCKET_PIPE);
+ token = &token[3];
+ token_length -= 3;
+
+ } else if (token_length >= 3 && token[0] == '<' && token[1] == '>' && token[2] == '&') {
+ add_redirection(REDIRECT_INPUT_OUTPUT_TO_FD);
+ token = &token[3];
+ token_length -= 3;
+
+ } else if (token_length >= 2 && token[0] == ')' && token[1] == ')') {
+ if (state->exit_on == ROUND_ROUND)
+ end_subshell();
+ else
+ eprintf("stray )) at line %zu\n", line_number);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '(' && token[1] == ')') {
+ add_function_mark();
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '(' && token[1] == '(') {
+ add_shell_io(SUBSHELL, ROUND_ROUND);
+ state->is_expr_shell = 1;
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == ';' && token[1] == ';') {
+ terminate_command(DOUBLE_SEMICOLON);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '<' && token[1] == '(') {
+ add_shell_io(SUBSHELL_OUTPUT, ROUND);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '<' && token[1] == '<') {
+ add_redirection(HERE_DOCUMENT);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '<' && token[1] == '>') {
+ add_redirection(REDIRECT_INPUT_OUTPUT);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '<' && token[1] == '&') {
+ add_redirection(REDIRECT_INPUT_TO_FD);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '>' && token[1] == '(') {
+ add_shell_io(SUBSHELL_INPUT, ROUND);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '>' && token[1] == '>') {
+ add_redirection(REDIRECT_OUTPUT_APPEND);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '>' && token[1] == '&') {
+ add_redirection(REDIRECT_OUTPUT_TO_FD);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '>' && token[1] == '|') {
+ add_redirection(REDIRECT_OUTPUT_CLOBBERING);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '|' && token[1] == '&') {
+ terminate_command(PIPE_AMPERSAND);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '|' && token[1] == '|') {
+ terminate_command(OR);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 2 && token[0] == '&' && token[1] == '&') {
+ terminate_command(AND);
+ token = &token[2];
+ token_length -= 2;
+
+ } else if (token_length >= 1 && token[0] == ')') {
+ if (state->exit_on == ROUND)
+ end_subshell();
+ else
+ eprintf("stray ) at line %zu\n", line_number);
+ token = &token[1];
+ token_length -= 1;
+
+ } else if (token_length >= 1 && token[0] == ']') {
+ if (state->exit_on == SQUARE)
+ end_subshell();
+ else
+ verbatim(token, 1, 0);
+ token = &token[1];
+ token_length -= 1;
+
+ } else if (token_length >= 1 && token[0] == '(') {
+ add_shell_io(SUBSHELL, ROUND);
+ state->is_expr_shell = old_state->is_expr_shell;
+ token = &token[1];
+ token_length -= 1;
+
+ } else if (token_length >= 1 && token[0] == ';') {
+ semicolon(0);
+ token = &token[1];
+ token_length -= 1;
+
+ } else if (token_length >= 1 && token[0] == '<') {
+ add_redirection(REDIRECT_INPUT);
+ token = &token[1];
+ token_length -= 1;
+
+ } else if (token_length >= 1 && token[0] == '>') {
+ add_redirection(REDIRECT_OUTPUT);
+ token = &token[1];
+ token_length -= 1;
+
+ } else if (token_length >= 1 && token[0] == '|') {
+ terminate_command(PIPE);
+ token = &token[1];
+ token_length -= 1;
+
+ } else if (token_length >= 1 && token[0] == '&') {
+ terminate_command(AMPERSAND);
+ token = &token[1];
+ token_length -= 1;
+
+ } else {
+ verbatim(token, 1, 0);
+ token = &token[1];
+ token_length -= 1;
+ }
+ }
+}
+
+static void
+symbol(char *token, size_t token_length, size_t escaped_newlines)
+{
+ size_t new_length, r, w;
+ if (escaped_newlines) {
+ r = w = 0;
+ new_length = token_length - 2 * escaped_newlines;
+ while (escaped_newlines--) {
+ if (token[r] == '\\')
+ r += 2;
+ else
+ token[w++] = token[r++];
+ }
+ memcpy(&token[w], &token[r], token_length - r);
+ token_length = new_length;
+ }
+ parse_symbol(token, token_length);
+}
+
+static void
+backtick(void)
+{
+ flush_dollar();
+ if (state->exit_on == BACKTICK)
+ end_subshell();
+ else
+ add_shell_io(SUBSHELL_SUBSTITUTION, BACKTICK);
+}
+
+static void
+double_quote(void)
+{
+ flush_dollar();
+ /* TODO */
+}
+
+static void
+verbatim(const char *text, size_t text_length, int from_quote)
+{
+ struct argument *argend;
+
+ state->need_right_hand_side = 0;
+
+ if (from_quote && state->at_dollar) {
+ state->at_dollar = 0;
+ if (!state->current_argument_end)
+ state->current_argument_end = state->current_argument = ecalloc(1, sizeof(struct argument));
+ else
+ state->current_argument_end = state->current_argument_end->next_part = ecalloc(1, sizeof(struct argument));
+ state->current_argument_end->type = ESCAPED;
+ } else {
+ flush_dollar();
+ if (!state->current_argument_end) {
+ state->current_argument_end = state->current_argument = ecalloc(1, sizeof(struct argument));
+ state->current_argument_end->type = VERBATIM;
+ } else if (state->current_argument_end->type != VERBATIM) {
+ state->current_argument_end = state->current_argument_end->next_part = ecalloc(1, sizeof(struct argument));
+ state->current_argument_end->type = VERBATIM;
+ }
+ }
+
+ argend = state->current_argument_end;
+
+ argend->text = erealloc(argend->text, argend->length + text_length + 1);
+ memcpy(&argend->text[argend->length], text, text_length);
+ argend->length += text_length;
+ argend->text[argend->length] = '\0';
+}
+
+static void
+flush_dollar(void)
+{
+ if (state->at_dollar) {
+ state->at_dollar = 0;
+ verbatim("$", 1, 0);
+ }
+}
+
+static void
+append_special(char symbol)
+{
+ state->need_right_hand_side = 0;
+
+ if (!state->current_argument_end)
+ state->current_argument_end = state->current_argument = ecalloc(1, sizeof(struct argument));
+ else
+ state->current_argument_end = state->current_argument_end->next_part = ecalloc(1, sizeof(struct argument));
+
+ state->current_argument_end->type = SPECIAL;
+ state->current_argument_end->symbol = symbol;
+}
+
+static void
+unverbatim(const char *text, size_t text_length)
+{
+ size_t verbatim_length;
+
+ /* TODO handle state->dollar */
+
+ while (text_length) {
+ for (verbatim_length = 0; verbatim_length < text_length; verbatim_length++)
+ if (*text == '*' || *text == '?' || *text == '[' || *text == ']' ||
+ *text == ',' || *text == '.' || *text == '{' || *text == '}' ||
+ *text == '~' || *text == '!' || *text == '=')
+ break;
+ if (verbatim_length) {
+ verbatim(text, verbatim_length, 0);
+ text = &text[verbatim_length];
+ text_length -= verbatim_length;
+ } else {
+ append_special(*text);
+ text = &text[1];
+ text_length -= 1;
+ }
+ }
+}
+
+static void
+dollar(void)
+{
+ /* TODO forbid $ if giving argument to here-document */
+ if (state->at_dollar)
+ unverbatim("$", 1);
+ else
+ state->at_dollar = 1;
+}
+
+static int
+end_of_file(void)
+{
+ semicolon(1);
+ return !(state->parent || state->ncommands);
+}
+
+static size_t
+parse(char *code, size_t code_len, int end_of_file_reached)
+{
+#define IS_SYMBOL(C)\
+ ((!state->is_expr_shell && (\
+ (C) == '|' || (C) == '&' || (C) == ';' || \
+ (C) == '<' || (C) == '>' || (C) == '-')) || \
+ (C) == '{' || (C) == '}' || \
+ (C) == '(' || (C) == ')' || \
+ (C) == '[' || (C) == ']')
+
+ static int she_is_comment = 1;
+ static int in_comment = 0;
+ static int at_line_beginning = 1;
+
+ size_t read_bytes = 0;
+ size_t token_len;
+ size_t new_lines;
+
+ for (; read_bytes < code_len; read_bytes += token_len, code = &code[token_len]) {
+ if (at_line_beginning) {
+ if (here_documents_first) {
+ /* TODO read until terminator, remove indentation if <<- and then parse in "-mode but accept " */
+ }
+ at_line_beginning = 0;
+ }
+
+ if (in_comment) {
+ if (*code == '\n') {
+ in_comment = 0;
+ } else {
+ token_len = 1;
+ continue;
+ }
+ }
+
+ if (*code == '\0') {
+ if (!tty_input)
+ weprintf("ignoring NUL byte at line %zu\n", line_number);
+
+ } else if (*code == '\n') {
+ line_number += 1;
+ she_is_comment = 1;
+ whitespace(0);
+ semicolon(1);
+ token_len = 1;
+ at_line_beginning = 1;
+
+ } else if (isspace(*code)) {
+ she_is_comment = 1;
+ whitespace(0);
+ for (token_len = 1; token_len < code_len - read_bytes; token_len++)
+ if (!isspace(code[token_len]) || code[token_len] == '\n')
+ break;
+
+ } else if (*code == '#' && she_is_comment) {
+ in_comment = 1;
+ token_len = 1;
+
+ } else if (IS_SYMBOL(*code)) {
+ she_is_comment = 1;
+ new_lines = 0;
+ for (token_len = 1; token_len < code_len - read_bytes; token_len++) {
+ if (code[token_len] == '\\' && token_len + 1 < code_len - read_bytes && code[token_len] == '\n') {
+ new_lines += 1;
+ } else if (!IS_SYMBOL(code[token_len])) {
+ symbol(code, token_len, new_lines);
+ line_number += new_lines;
+ goto next;
+ }
+ }
+ if (end_of_file_reached) {
+ symbol(code, token_len, new_lines);
+ line_number += new_lines;
+ } else {
+ break;
+ }
+
+ } else if (*code == '\\') {
+ she_is_comment = 0;
+ if (code_len - read_bytes < 2)
+ break;
+ token_len = 2;
+ if (code[1] == '\n')
+ line_number += 1;
+ else
+ verbatim(&code[1], 1, 0);
+
+ } else if (*code == '$') {
+ she_is_comment = 0;
+ dollar();
+ token_len = 1;
+
+ } else if (*code == '`') {
+ she_is_comment = 1;
+ backtick();
+ token_len = 1;
+
+ } else if (*code == '"') {
+ she_is_comment = 0;
+ double_quote();
+
+ } else if (*code == '\'') {
+ she_is_comment = 0;
+ new_lines = 0;
+ for (token_len = 1; token_len < code_len - read_bytes; token_len++) {
+ if (code[token_len] == '\'') {
+ token_len += 1;
+ if (!state->at_dollar || code[token_len - 2] != '\\') {
+ verbatim(&code[1], token_len - 2, 1);
+ line_number += new_lines;
+ goto next;
+ }
+ } else if (code[token_len] == '\n') {
+ new_lines += 1;
+ }
+ }
+ break;
+
+ } else {
+ she_is_comment = 0;
+ for (token_len = 1; token_len < code_len - read_bytes; token_len++)
+ if (isspace(*code) || IS_SYMBOL(*code) || *code == '\\' ||
+ *code == '$' || *code == '`' || *code == '"' || *code == '\'')
+ break;
+ unverbatim(code, token_len);
+ }
+
+ next:;
+ }
+
+ return read_bytes;
+
+#undef IS_SYMBOL
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *buffer = NULL;
+ size_t buffer_size = 0;
+ size_t buffer_head = 0;
+ size_t buffer_tail = 0;
+ ssize_t r;
+ size_t n;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc)
+ usage();
+
+ tty_input = isatty(STDIN_FILENO);
+ if (tty_input)
+ weprintf("apsh is currently not implemented to be interactive\n");
+
+ state = ecalloc(1, sizeof(*state));
+
+ for (;;) {
+ if (buffer_size - buffer_head < PARSE_RINGBUFFER_MIN_AVAILABLE) {
+ if (buffer_tail && buffer_head - buffer_tail <= buffer_tail) {
+ memcpy(&buffer[0], &buffer[buffer_tail], buffer_head - buffer_tail);
+ buffer_head -= buffer_tail;
+ buffer_tail = 0;
+ }
+ if (buffer_size - buffer_head < PARSE_RINGBUFFER_MIN_AVAILABLE)
+ buffer = erealloc(buffer, buffer_size += PARSE_RINGBUFFER_INCREASE_SIZE);
+ }
+
+ r = read(STDIN_FILENO, &buffer[buffer_head], buffer_size - buffer_head);
+ if (r <= 0) {
+ if (!r)
+ break;
+ eprintf("read <stdin>:");
+ }
+ n = (size_t)r;
+
+ buffer_head += n;
+ buffer_tail += parse(&buffer[buffer_tail], buffer_head - buffer_tail, 0);
+ }
+
+ buffer_tail += parse(&buffer[buffer_tail], buffer_head - buffer_tail, 1);
+ if (buffer_tail != buffer_head || !end_of_file())
+ eprintf("premature end of file reached\n");
+
+ free(buffer);
+ return 0;
+}
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..70d9731
--- /dev/null
+++ b/config.h
@@ -0,0 +1,13 @@
+/* See LICENSE file for copyright and license details. */
+
+#ifndef PARSE_RINGBUFFER_MIN_AVAILABLE
+# define PARSE_RINGBUFFER_MIN_AVAILABLE 512
+#endif
+
+#ifndef PARSE_RINGBUFFER_INCREASE_SIZE
+# define PARSE_RINGBUFFER_INCREASE_SIZE 4096
+#endif
+
+#if PARSE_RINGBUFFER_INCREASE_SIZE < PARSE_RINGBUFFER_MIN_AVAILABLE
+# error PARSE_RINGBUFFER_INCREASE_SIZE may noy be less than PARSE_RINGBUFFER_MIN_AVAILABLE
+#endif
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..15b1181
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = /usr/share/man
+
+CC = cc
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS = -std=c99 -Wall -g
+LDFLAGS = -lsimple