9 2

[PATCH v2 1/3] Implement case clause

Details
Message ID
<20181228181347.6395-1-sir@cmpwn.com>
Sender timestamp
1546020825
DKIM signature
permerror
Download raw message
Patch: +195 -2
---
 include/shell/task.h     |   2 +
 meson.build              |   1 +
 shell/task/ast.c         |  10 +++-
 shell/task/case_clause.c | 109 +++++++++++++++++++++++++++++++++++++++
 test/case.sh             |  74 ++++++++++++++++++++++++++
 test/meson.build         |   1 +
 6 files changed, 195 insertions(+), 2 deletions(-)
 create mode 100644 shell/task/case_clause.c
 create mode 100644 test/case.sh

diff --git a/include/shell/task.h b/include/shell/task.h
index 85c55a4..6a05069 100644
--- a/include/shell/task.h
+++ b/include/shell/task.h
@@ -66,6 +66,8 @@ struct task *task_loop_clause_create(const struct mrsh_array *condition,
 	const struct mrsh_array *body, bool until);
 struct task *task_for_clause_create(const char *name,
 	const struct mrsh_array *word_list, const struct mrsh_array *body);
+struct task *task_case_clause_create(
+		const struct mrsh_word *word, const struct mrsh_array *cases);
 struct task *task_function_definition_create(const char *name,
 	const struct mrsh_command *body);
 struct task *task_binop_create(enum mrsh_binop_type type,
diff --git a/meson.build b/meson.build
index a51e623..5b00c3b 100644
--- a/meson.build
+++ b/meson.build
@@ -83,6 +83,7 @@ lib_mrsh = library(
 		'shell/task/ast.c',
 		'shell/task/async.c',
 		'shell/task/binop.c',
+		'shell/task/case_clause.c',
 		'shell/task/command_builtin.c',
 		'shell/task/command_function.c',
 		'shell/task/command_process.c',
diff --git a/shell/task/ast.c b/shell/task/ast.c
index a1e9f36..41e4a63 100644
--- a/shell/task/ast.c
+++ b/shell/task/ast.c
@@ -107,6 +107,10 @@ static struct task *task_for_for_clause(const struct mrsh_for_clause *fc) {
 	return task_for_clause_create(fc->name, &fc->word_list, &fc->body);
 }
 
+static struct task *task_for_case_clause(const struct mrsh_case_clause *cc) {
+	return task_case_clause_create(cc->word, &cc->items);
+}
+
 static struct task *task_for_function_definition(
 		const struct mrsh_function_definition *fn) {
 	return task_function_definition_create(fn->name, fn->body);
@@ -132,12 +136,14 @@ struct task *task_for_command(const struct mrsh_command *cmd) {
 	case MRSH_FOR_CLAUSE:;
 		struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);
 		return task_for_for_clause(fc);
+	case MRSH_CASE_CLAUSE:;
+		struct mrsh_case_clause *cc =
+			mrsh_command_get_case_clause(cmd);
+		return task_for_case_clause(cc);
 	case MRSH_FUNCTION_DEFINITION:;
 		struct mrsh_function_definition *fn =
 			mrsh_command_get_function_definition(cmd);
 		return task_for_function_definition(fn);
-	case MRSH_CASE_CLAUSE:
-		assert(false); // TODO: implement this
 	}
 	assert(false);
 }
diff --git a/shell/task/case_clause.c b/shell/task/case_clause.c
new file mode 100644
index 0000000..780d392
--- /dev/null
+++ b/shell/task/case_clause.c
@@ -0,0 +1,109 @@
+#include "shell/task.h"
+#include <fnmatch.h>
+#include <stdlib.h>
+
+struct task_case_item {
+	struct task *body;
+	struct mrsh_array *patterns; // struct mrsh_word *
+	size_t index;
+	struct task *word;
+};
+
+struct task_case_clause {
+	struct task task;
+
+	struct {
+		struct mrsh_word *word;
+	} ast;
+	struct {
+		struct task *word;
+	} tasks;
+
+	char *word;
+	struct task_case_item *selected;
+	struct mrsh_array cases; // struct task_case_item *
+	size_t index;
+};
+
+static void task_case_clause_destroy(struct task *task) {
+	struct task_case_clause *tcc = (struct task_case_clause *)task;
+	for (size_t i = 0; i < tcc->cases.len; ++i) {
+		struct task_case_item *tci = tcc->cases.data[i];
+		task_destroy(tci->body);
+		free(tci);
+	}
+	mrsh_array_finish(&tcc->cases);
+	task_destroy(tcc->tasks.word);
+	mrsh_word_destroy(tcc->ast.word);
+	free(tcc->word);
+	free(tcc);
+}
+
+static int task_case_clause_poll(struct task *task, struct context *ctx) {
+	struct task_case_clause *tcc = (struct task_case_clause *)task;
+	if (tcc->tasks.word) {
+		int word_status = task_poll(tcc->tasks.word, ctx);
+		if (word_status < 0) {
+			return word_status;
+		}
+		tcc->word = mrsh_word_str(tcc->ast.word);
+	}
+
+	while (!tcc->selected && tcc->index < tcc->cases.len) {
+		struct task_case_item *tci =
+			(struct task_case_item *)tcc->cases.data[tcc->index];
+		if (tci->word) {
+			int word_status = task_poll(tci->word, ctx);
+			if (word_status < 0) {
+				return word_status;
+			}
+			struct mrsh_word_string *word = (struct mrsh_word_string *)
+				tci->patterns->data[tci->index - 1];
+			task_destroy(tci->word);
+			tci->word = NULL;
+			fprintf(stderr, "pattern: %s, test: %s\n", word->str, tcc->word);
+			if (fnmatch(word->str, tcc->word, 0) == 0) {
+				tcc->selected = tci;
+				break;
+			}
+			if (tci->index == tci->patterns->len) {
+				++tcc->index;
+			}
+		} else {
+			struct mrsh_word **word_ptr =
+				(struct mrsh_word **)&tci->patterns->data[tci->index++];
+			tci->word = task_word_create(word_ptr, TILDE_EXPANSION_NAME);
+		}
+	}
+
+	if (tcc->selected) {
+		return task_poll(tcc->selected->body, ctx);
+	}
+
+	return 0;
+}
+
+static const struct task_interface task_case_clause_impl = {
+	.destroy = task_case_clause_destroy,
+	.poll = task_case_clause_poll,
+};
+
+struct task *task_case_clause_create(
+		const struct mrsh_word *word, const struct mrsh_array *cases) {
+	struct task_case_clause *task = calloc(1, sizeof(struct task_case_clause));
+	task_init(&task->task, &task_case_clause_impl);
+	if (!mrsh_array_reserve(&task->cases, cases->len)) {
+		free(task);
+		return NULL;
+	}
+	for (size_t i = 0; i < cases->len; ++i) {
+		struct mrsh_case_item *mci = cases->data[i];
+		struct task_case_item *tci = calloc(1, sizeof(struct task_case_item));
+		mrsh_array_add(&task->cases, tci);
+		tci->patterns = &mci->patterns;
+		tci->body = task_for_command_list_array(&mci->body);
+	}
+	task->ast.word = mrsh_word_copy(word);
+	task->tasks.word = task_word_create(&task->ast.word, TILDE_EXPANSION_NAME);
+	return (struct task *)task;
+}
diff --git a/test/case.sh b/test/case.sh
new file mode 100644
index 0000000..8a7d3c8
--- /dev/null
+++ b/test/case.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+x=hello
+
+case "$x" in
+	hello)
+		echo pass
+		;;
+	world)
+		echo fail
+		;;
+esac
+
+case "$x" in
+	he*)
+		echo pass
+		;;
+	*)
+		echo fail
+		;;
+esac
+
+case "$x" in
+	foo)
+		echo fail
+		;;
+	he??o)
+		echo pass
+		;;
+esac
+
+case "$x" in
+	foo)
+		echo fail
+		;;
+	*)
+		echo pass
+		;;
+esac
+
+case "$x" in
+	world|hello)
+		echo pass
+		;;
+	*)
+		echo fail
+		;;
+esac
+
+case "$x" in
+	hell[a-z])
+		echo pass
+		;;
+	*)
+		echo fail
+		;;
+esac
+
+y=hello
+
+# Expanding patterns
+case "$x" in
+	$y)
+		echo pass
+		;;
+	*)
+		echo fail
+		;;
+esac
+
+# ;; optional for last item
+case hello in
+	*)
+		echo pass
+esac
diff --git a/test/meson.build b/test/meson.build
index 29516f7..b429be4 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -4,6 +4,7 @@ ref_sh = find_program('sh', required: false)
 test_files = [
 	'conformance/if.sh',
 
+	'case.sh',
 	'loop.sh',
 	'subshell.sh',
 	'word.sh',
-- 
2.20.1

[PATCH v2 2/3] Add shell/entry.c

Details
Message ID
<20181228181347.6395-2-sir@cmpwn.com>
In-Reply-To
<20181228181347.6395-1-sir@cmpwn.com> (view parent)
Sender timestamp
1546020826
DKIM signature
permerror
Download raw message
Patch: +145 -93
---
 include/mrsh/entry.h |  34 ++++++++++++++
 main.c               | 100 +++--------------------------------------
 meson.build          |   1 +
 shell/entry.c        | 103 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 145 insertions(+), 93 deletions(-)
 create mode 100644 include/mrsh/entry.h
 create mode 100644 shell/entry.c

diff --git a/include/mrsh/entry.h b/include/mrsh/entry.h
new file mode 100644
index 0000000..3ffb1b8
--- /dev/null
+++ b/include/mrsh/entry.h
@@ -0,0 +1,34 @@
+#ifndef MRSH_ENTRY_H
+#define MRSH_ENTRY_H
+
+#include <mrsh/shell.h>
+
+/**
+ * Expands $PS1 or returns the POSIX-specified default of "$" or "#". The caller
+ * must free the return value.
+ */
+char *mrsh_get_ps1(struct mrsh_state *state, int next_history_id);
+
+/**
+ * Expands $PS2 or returns the POSIX-specified default of ">". The caller must
+ * free the return value.
+ */
+char *mrsh_get_ps2(struct mrsh_state *state);
+
+/**
+ * Copies variables from the environment and sets up internal variables like
+ * IFS, PPID, PWD, etc.
+ */
+void mrsh_populate_env(struct mrsh_state *state);
+
+/**
+ * Sources /etc/profile and $HOME/.profile. Note that this behavior is not
+ * specified by POSIX. It is recommended to call this file in login shells
+ * (for which argv[0][0] == '-' by convention).
+ */
+void mrsh_source_profile(struct mrsh_state *state);
+
+/** Sources $ENV. It is recommended to source this in interactive shells. */
+void mrsh_source_env(struct mrsh_state *state);
+
+#endif
diff --git a/main.c b/main.c
index f4e2b6a..06bc0ab 100644
--- a/main.c
+++ b/main.c
@@ -5,82 +5,19 @@
 #include <mrsh/ast.h>
 #include <mrsh/buffer.h>
 #include <mrsh/builtin.h>
+#include <mrsh/entry.h>
 #include <mrsh/parser.h>
 #include <mrsh/shell.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include "builtin.h"
 #include "frontend.h"
 
-static char *expand_ps(struct mrsh_state *state, const char *ps1) {
-	struct mrsh_parser *parser = mrsh_parser_with_data(ps1, strlen(ps1));
-	struct mrsh_word *word = mrsh_parse_word(parser);
-	mrsh_parser_destroy(parser);
-	if (word == NULL) {
-		return NULL;
-	}
-
-	mrsh_run_word(state, &word);
-
-	return mrsh_word_str(word);
-}
-
-static char *get_ps1(struct mrsh_state *state) {
-	const char *ps1 = mrsh_env_get(state, "PS1", NULL);
-	if (ps1 != NULL) {
-		// TODO: Replace ! with next history ID
-		return expand_ps(state, ps1);
-	}
-	char *p = malloc(3);
-	sprintf(p, "%s", getuid() ? "$ " : "# ");
-	return p;
-}
-
-static char *get_ps2(struct mrsh_state *state) {
-	const char *ps2 = mrsh_env_get(state, "PS2", NULL);
-	if (ps2 != NULL) {
-		return expand_ps(state, ps2);
-	}
-	return strdup("> ");
-}
-
-static void source_file(struct mrsh_state *state, char *path) {
-	if (access(path, F_OK) == -1) {
-		return;
-	}
-	char *env_argv[] = { ".", path };
-	mrsh_run_builtin(state, sizeof(env_argv) / sizeof(env_argv[0]), env_argv);
-}
-
-static void source_profile(struct mrsh_state *state) {
-	source_file(state, "/etc/profile");
-
-	char path[PATH_MAX];
-	int n = snprintf(path, sizeof(path), "%s/.profile", getenv("HOME"));
-	if (n == sizeof(path)) {
-		fprintf(stderr, "Warning: $HOME/.profile is longer than PATH_MAX\n");
-		return;
-	}
-	source_file(state, path);
-}
-
-static void source_env(struct mrsh_state *state) {
-	char *path = getenv("ENV");
-	if (path == NULL) {
-		return;
-	}
-	// TODO: parameter expansion
-	source_file(state, path);
-}
-
 static const char *get_alias(const char *name, void *data) {
 	struct mrsh_state *state = data;
 	return mrsh_hashtable_get(&state->aliases, name);
 }
 
-extern char **environ;
-
 int main(int argc, char *argv[]) {
 	struct mrsh_state state = {0};
 	mrsh_state_init(&state);
@@ -91,39 +28,15 @@ int main(int argc, char *argv[]) {
 		return EXIT_FAILURE;
 	}
 
-	for (size_t i = 0; environ[i] != NULL; ++i) {
-		char *eql = strchr(environ[i], '=');
-		size_t klen = eql - environ[i];
-		char *key = strndup(environ[i], klen);
-		char *val = &eql[1];
-		mrsh_env_set(&state, key, val, MRSH_VAR_ATTRIB_EXPORT);
-		free(key);
-	}
-
-	mrsh_env_set(&state, "IFS", " \t\n", MRSH_VAR_ATTRIB_NONE);
-
-	pid_t ppid = getppid();
-	char ppid_str[24];
-	snprintf(ppid_str, sizeof(ppid_str), "%d", ppid);
-	mrsh_env_set(&state, "PPID", ppid_str, MRSH_VAR_ATTRIB_NONE);
-
-	// TODO check if path is well-formed, has . or .., and handle symbolic links
-	const char *pwd = mrsh_env_get(&state, "PWD", NULL);
-	if (pwd == NULL || strlen(pwd) >= PATH_MAX) {
-		char cwd[PATH_MAX];
-		getcwd(cwd, PATH_MAX);
-		mrsh_env_set(&state, "PWD", cwd, MRSH_VAR_ATTRIB_EXPORT);
-	}
-
-	mrsh_env_set(&state, "OPTIND", "1", MRSH_VAR_ATTRIB_NONE);
+	mrsh_populate_env(&state);
 
 	if (!(state.options & MRSH_OPT_NOEXEC)) {
 		// If argv[0] begins with `-`, it's a login shell
 		if (state.args->argv[0][0] == '-') {
-			source_profile(&state);
+			mrsh_source_profile(&state);
 		}
 		if (state.interactive) {
-			source_env(&state);
+			mrsh_source_env(&state);
 		}
 	}
 
@@ -159,9 +72,10 @@ int main(int argc, char *argv[]) {
 		if (state.interactive) {
 			char *prompt;
 			if (read_buffer.len > 0) {
-				prompt = get_ps2(&state);
+				prompt = mrsh_get_ps2(&state);
 			} else {
-				prompt = get_ps1(&state);
+				// TODO: next_history_id
+				prompt = mrsh_get_ps1(&state, 0);
 			}
 			char *line = NULL;
 			size_t n = interactive_next(&state, &line, prompt);
diff --git a/meson.build b/meson.build
index 5b00c3b..16da304 100644
--- a/meson.build
+++ b/meson.build
@@ -75,6 +75,7 @@ lib_mrsh = library(
 		'parser/program.c',
 		'parser/word.c',
 		'shell/arithm.c',
+		'shell/entry.c',
 		'shell/path.c',
 		'shell/process.c',
 		'shell/redir.c',
diff --git a/shell/entry.c b/shell/entry.c
new file mode 100644
index 0000000..a43bbb0
--- /dev/null
+++ b/shell/entry.c
@@ -0,0 +1,103 @@
+#define _POSIX_C_SOURCE 200809L
+#include <mrsh/builtin.h>
+#include <mrsh/entry.h>
+#include <mrsh/shell.h>
+#include <mrsh/parser.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "builtin.h"
+
+extern char **environ;
+
+static char *expand_ps(struct mrsh_state *state, const char *ps1) {
+	struct mrsh_parser *parser = mrsh_parser_with_data(ps1, strlen(ps1));
+	struct mrsh_word *word = mrsh_parse_word(parser);
+	mrsh_parser_destroy(parser);
+	if (word == NULL) {
+		return NULL;
+	}
+	mrsh_run_word(state, &word);
+	return mrsh_word_str(word);
+}
+
+char *mrsh_get_ps1(struct mrsh_state *state, int next_history_id) {
+	// TODO: Replace ! with next history ID
+	const char *ps1 = mrsh_env_get(state, "PS1", NULL);
+	if (ps1 != NULL) {
+		return expand_ps(state, ps1);
+	}
+	char *p = malloc(3);
+	sprintf(p, "%s", getuid() ? "$ " : "# ");
+	return p;
+}
+
+char *mrsh_get_ps2(struct mrsh_state *state) {
+	// TODO: Replace ! with next history ID
+	const char *ps2 = mrsh_env_get(state, "PS2", NULL);
+	if (ps2 != NULL) {
+		return expand_ps(state, ps2);
+	}
+	return strdup("> ");
+}
+
+void mrsh_populate_env(struct mrsh_state *state) {
+	for (size_t i = 0; environ[i] != NULL; ++i) {
+		char *eql = strchr(environ[i], '=');
+		size_t klen = eql - environ[i];
+		char *key = strndup(environ[i], klen);
+		char *val = &eql[1];
+		mrsh_env_set(state, key, val, MRSH_VAR_ATTRIB_EXPORT);
+		free(key);
+	}
+
+	mrsh_env_set(state, "IFS", " \t\n", MRSH_VAR_ATTRIB_NONE);
+
+	pid_t ppid = getppid();
+	char ppid_str[24];
+	snprintf(ppid_str, sizeof(ppid_str), "%d", ppid);
+	mrsh_env_set(state, "PPID", ppid_str, MRSH_VAR_ATTRIB_NONE);
+
+	// TODO check if path is well-formed, has . or .., and handle symbolic links
+	const char *pwd = mrsh_env_get(state, "PWD", NULL);
+	if (pwd == NULL || strlen(pwd) >= PATH_MAX) {
+		char cwd[PATH_MAX];
+		getcwd(cwd, PATH_MAX);
+		mrsh_env_set(state, "PWD", cwd, MRSH_VAR_ATTRIB_EXPORT);
+	}
+
+	mrsh_env_set(state, "OPTIND", "1", MRSH_VAR_ATTRIB_NONE);
+}
+
+static void source_file(struct mrsh_state *state, char *path) {
+	if (access(path, F_OK) == -1) {
+		return;
+	}
+	char *env_argv[] = { ".", path };
+	mrsh_run_builtin(state, sizeof(env_argv) / sizeof(env_argv[0]), env_argv);
+}
+
+void mrsh_source_profile(struct mrsh_state *state) {
+	source_file(state, "/etc/profile");
+
+	char path[PATH_MAX];
+	int n = snprintf(path, sizeof(path), "%s/.profile", getenv("HOME"));
+	if (n == sizeof(path)) {
+		fprintf(stderr, "Warning: $HOME/.profile is longer than PATH_MAX\n");
+		return;
+	}
+	source_file(state, path);
+}
+
+void mrsh_source_env(struct mrsh_state *state) {
+	char *path = getenv("ENV");
+	if (path == NULL) {
+		return;
+	}
+	if (getuid() != geteuid() || getgid() != getegid()) {
+		return;
+	}
+	// TODO: parameter expansion
+	source_file(state, path);
+}
-- 
2.20.1

[PATCH v2 3/3] Add mrsh_get_alias

Details
Message ID
<20181228181347.6395-3-sir@cmpwn.com>
In-Reply-To
<20181228181347.6395-1-sir@cmpwn.com> (view parent)
Sender timestamp
1546020827
DKIM signature
permerror
Download raw message
Patch: +18 -8
---
 include/mrsh/parser.h |  2 +-
 include/mrsh/shell.h  |  4 ++++
 main.c                |  7 +------
 parser/parser.c       |  2 +-
 shell/shell.c         | 11 +++++++++++
 5 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/include/mrsh/parser.h b/include/mrsh/parser.h
index c67f59d..29f1992 100644
--- a/include/mrsh/parser.h
+++ b/include/mrsh/parser.h
@@ -52,7 +52,7 @@ bool mrsh_parser_eof(struct mrsh_parser *state);
 /**
  * Set the alias callback.
  */
-void mrsh_parser_set_alias(struct mrsh_parser *state,
+void mrsh_parser_set_alias_fn(struct mrsh_parser *state,
 	mrsh_parser_alias_func_t alias, void *user_data);
 /**
  * Check if the parser ended with a syntax error. The error message is returned.
diff --git a/include/mrsh/shell.h b/include/mrsh/shell.h
index e0fae04..14e6094 100644
--- a/include/mrsh/shell.h
+++ b/include/mrsh/shell.h
@@ -92,7 +92,11 @@ struct mrsh_state {
 
 void mrsh_function_destroy(struct mrsh_function *fn);
 
+struct mrsh_parser;
+
 void mrsh_state_init(struct mrsh_state *state);
+void mrsh_state_set_parser_alias_fn(
+		struct mrsh_state *state, struct mrsh_parser *parser);
 void mrsh_state_finish(struct mrsh_state *state);
 void mrsh_env_set(struct mrsh_state *state,
 	const char *key, const char *value, uint32_t attribs);
diff --git a/main.c b/main.c
index 06bc0ab..2a6532b 100644
--- a/main.c
+++ b/main.c
@@ -13,11 +13,6 @@
 #include <unistd.h>
 #include "frontend.h"
 
-static const char *get_alias(const char *name, void *data) {
-	struct mrsh_state *state = data;
-	return mrsh_hashtable_get(&state->aliases, name);
-}
-
 int main(int argc, char *argv[]) {
 	struct mrsh_state state = {0};
 	mrsh_state_init(&state);
@@ -65,7 +60,7 @@ int main(int argc, char *argv[]) {
 			parser = mrsh_parser_with_fd(fd);
 		}
 	}
-	mrsh_parser_set_alias(parser, get_alias, &state);
+	mrsh_state_set_parser_alias_fn(&state, parser);
 
 	struct mrsh_buffer read_buffer = {0};
 	while (state.exit == -1) {
diff --git a/parser/parser.c b/parser/parser.c
index d320313..184349b 100644
--- a/parser/parser.c
+++ b/parser/parser.c
@@ -352,7 +352,7 @@ bool mrsh_parser_eof(struct mrsh_parser *state) {
 	return state->has_sym && state->sym == EOF_TOKEN;
 }
 
-void mrsh_parser_set_alias(struct mrsh_parser *state,
+void mrsh_parser_set_alias_fn(struct mrsh_parser *state,
 		mrsh_parser_alias_func_t alias, void *user_data) {
 	state->alias = alias;
 	state->alias_user_data = user_data;
diff --git a/shell/shell.c b/shell/shell.c
index fb010ba..fa84fe6 100644
--- a/shell/shell.c
+++ b/shell/shell.c
@@ -1,6 +1,7 @@
 #define _POSIX_C_SOURCE 200809L
 #include <assert.h>
 #include <mrsh/hashtable.h>
+#include <mrsh/parser.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
@@ -22,6 +23,16 @@ void mrsh_state_init(struct mrsh_state *state) {
 	state->args = calloc(1, sizeof(struct mrsh_call_frame));
 }
 
+static const char *get_alias(const char *name, void *data) {
+	struct mrsh_state *state = data;
+	return mrsh_hashtable_get(&state->aliases, name);
+}
+
+void mrsh_state_set_parser_alias_fn(
+		struct mrsh_state *state, struct mrsh_parser *parser) {
+	mrsh_parser_set_alias_fn(parser, get_alias, state);
+}
+
 static void state_string_finish_iterator(const char *key, void *value,
 		void *user_data) {
 	free(value);
-- 
2.20.1
Details
Message ID
<qwMQv6wDvtEBP5uRyLL4SsXyHK6-gI-pjND4uQNsHvh75lAQBpbM_EEPwGTjTrcBwhTXoSC5fElyDoeH-WJ0ClY0yBAXyGqz8T_c7sXoax4=@emersion.fr>
In-Reply-To
<20181228181347.6395-1-sir@cmpwn.com> (view parent)
Sender timestamp
1546133924
DKIM signature
pass
Download raw message
On Friday, December 28, 2018 7:13 PM, Drew DeVault <sir@cmpwn.com> wrote:
> +static int task_case_clause_poll(struct task *task, struct context *ctx) {
> +	struct task_case_clause *tcc = (struct task_case_clause *)task;
> +	if (tcc->tasks.word) {
> +		int word_status = task_poll(tcc->tasks.word, ctx);
> +		if (word_status < 0) {
> +			return word_status;
> +		}
> +		tcc->word = mrsh_word_str(tcc->ast.word);

I get some memory leaks when executing the test suite with ASan:
https://sr.ht/km5x.txt

It seems like this branch is taken multiple times, and the old value is
overwritten without being free'd.

> +	}
> +
> +	while (!tcc->selected && tcc->index < tcc->cases.len) {
> +		struct task_case_item *tci =
> +			(struct task_case_item *)tcc->cases.data[tcc->index];
> +		if (tci->word) {
> +			int word_status = task_poll(tci->word, ctx);
> +			if (word_status < 0) {
> +				return word_status;
> +			}
> +			struct mrsh_word_string *word = (struct mrsh_word_string *)
> +				tci->patterns->data[tci->index - 1];
> +			task_destroy(tci->word);
> +			tci->word = NULL;
> +			fprintf(stderr, "pattern: %s, test: %s\n", word->str, tcc->word);

Seems like there's a debug log here.

Re: [PATCH v2 3/3] Add mrsh_get_alias

Details
Message ID
<m9YU2s77-GXdta0pAj1BBj9Yg8xNnBQbcM_Kate0FngEZtkkfvAntOuYiI46DJu7J_0QadZbKAKLNquz7Pcwk28ZQdwSWSbYrEYQKak5TJc=@emersion.fr>
In-Reply-To
<20181228181347.6395-3-sir@cmpwn.com> (view parent)
Sender timestamp
1546134085
DKIM signature
pass
Download raw message
On Friday, December 28, 2018 7:13 PM, Drew DeVault <sir@cmpwn.com> wrote:
>+void mrsh_parser_set_alias_fn(struct mrsh_parser *state,
> 	mrsh_parser_alias_func_t alias, void *user_data);

Minor note: when applying this patch I'll rename this to
mrsh_parser_set_alias_func, for consistency with the type name.

[PATCH v3] Implement case clause

Details
Message ID
<20190101005242.25170-1-sir@cmpwn.com>
In-Reply-To
<qwMQv6wDvtEBP5uRyLL4SsXyHK6-gI-pjND4uQNsHvh75lAQBpbM_EEPwGTjTrcBwhTXoSC5fElyDoeH-WJ0ClY0yBAXyGqz8T_c7sXoax4=@emersion.fr> (view parent)
Sender timestamp
1546303962
DKIM signature
permerror
Download raw message
Patch: +196 -2
---
 include/shell/task.h     |   2 +
 meson.build              |   1 +
 shell/task/ast.c         |  10 +++-
 shell/task/case_clause.c | 110 +++++++++++++++++++++++++++++++++++++++
 test/case.sh             |  74 ++++++++++++++++++++++++++
 test/meson.build         |   1 +
 6 files changed, 196 insertions(+), 2 deletions(-)
 create mode 100644 shell/task/case_clause.c
 create mode 100644 test/case.sh

diff --git a/include/shell/task.h b/include/shell/task.h
index 85c55a4..6a05069 100644
--- a/include/shell/task.h
+++ b/include/shell/task.h
@@ -66,6 +66,8 @@ struct task *task_loop_clause_create(const struct mrsh_array *condition,
 	const struct mrsh_array *body, bool until);
 struct task *task_for_clause_create(const char *name,
 	const struct mrsh_array *word_list, const struct mrsh_array *body);
+struct task *task_case_clause_create(
+		const struct mrsh_word *word, const struct mrsh_array *cases);
 struct task *task_function_definition_create(const char *name,
 	const struct mrsh_command *body);
 struct task *task_binop_create(enum mrsh_binop_type type,
diff --git a/meson.build b/meson.build
index a51e623..5b00c3b 100644
--- a/meson.build
+++ b/meson.build
@@ -83,6 +83,7 @@ lib_mrsh = library(
 		'shell/task/ast.c',
 		'shell/task/async.c',
 		'shell/task/binop.c',
+		'shell/task/case_clause.c',
 		'shell/task/command_builtin.c',
 		'shell/task/command_function.c',
 		'shell/task/command_process.c',
diff --git a/shell/task/ast.c b/shell/task/ast.c
index a1e9f36..41e4a63 100644
--- a/shell/task/ast.c
+++ b/shell/task/ast.c
@@ -107,6 +107,10 @@ static struct task *task_for_for_clause(const struct mrsh_for_clause *fc) {
 	return task_for_clause_create(fc->name, &fc->word_list, &fc->body);
 }
 
+static struct task *task_for_case_clause(const struct mrsh_case_clause *cc) {
+	return task_case_clause_create(cc->word, &cc->items);
+}
+
 static struct task *task_for_function_definition(
 		const struct mrsh_function_definition *fn) {
 	return task_function_definition_create(fn->name, fn->body);
@@ -132,12 +136,14 @@ struct task *task_for_command(const struct mrsh_command *cmd) {
 	case MRSH_FOR_CLAUSE:;
 		struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);
 		return task_for_for_clause(fc);
+	case MRSH_CASE_CLAUSE:;
+		struct mrsh_case_clause *cc =
+			mrsh_command_get_case_clause(cmd);
+		return task_for_case_clause(cc);
 	case MRSH_FUNCTION_DEFINITION:;
 		struct mrsh_function_definition *fn =
 			mrsh_command_get_function_definition(cmd);
 		return task_for_function_definition(fn);
-	case MRSH_CASE_CLAUSE:
-		assert(false); // TODO: implement this
 	}
 	assert(false);
 }
diff --git a/shell/task/case_clause.c b/shell/task/case_clause.c
new file mode 100644
index 0000000..a92aa40
--- /dev/null
+++ b/shell/task/case_clause.c
@@ -0,0 +1,110 @@
+#include "shell/task.h"
+#include <fnmatch.h>
+#include <stdlib.h>
+
+struct task_case_item {
+	struct task *body;
+	struct mrsh_array *patterns; // struct mrsh_word *
+	size_t index;
+	struct task *word;
+};
+
+struct task_case_clause {
+	struct task task;
+
+	struct {
+		struct mrsh_word *word;
+	} ast;
+	struct {
+		struct task *word;
+	} tasks;
+
+	char *word;
+	struct task_case_item *selected;
+	struct mrsh_array cases; // struct task_case_item *
+	size_t index;
+};
+
+static void task_case_clause_destroy(struct task *task) {
+	struct task_case_clause *tcc = (struct task_case_clause *)task;
+	for (size_t i = 0; i < tcc->cases.len; ++i) {
+		struct task_case_item *tci = tcc->cases.data[i];
+		task_destroy(tci->body);
+		free(tci);
+	}
+	mrsh_array_finish(&tcc->cases);
+	task_destroy(tcc->tasks.word);
+	mrsh_word_destroy(tcc->ast.word);
+	free(tcc->word);
+	free(tcc);
+}
+
+static int task_case_clause_poll(struct task *task, struct context *ctx) {
+	struct task_case_clause *tcc = (struct task_case_clause *)task;
+	if (tcc->tasks.word) {
+		int word_status = task_poll(tcc->tasks.word, ctx);
+		if (word_status < 0) {
+			return word_status;
+		}
+		tcc->word = mrsh_word_str(tcc->ast.word);
+		task_destroy(tcc->tasks.word);
+		tcc->tasks.word = NULL;
+	}
+
+	while (!tcc->selected && tcc->index < tcc->cases.len) {
+		struct task_case_item *tci =
+			(struct task_case_item *)tcc->cases.data[tcc->index];
+		if (tci->word) {
+			int word_status = task_poll(tci->word, ctx);
+			if (word_status < 0) {
+				return word_status;
+			}
+			struct mrsh_word_string *word = (struct mrsh_word_string *)
+				tci->patterns->data[tci->index - 1];
+			task_destroy(tci->word);
+			tci->word = NULL;
+			if (fnmatch(word->str, tcc->word, 0) == 0) {
+				tcc->selected = tci;
+				break;
+			}
+			if (tci->index == tci->patterns->len) {
+				++tcc->index;
+			}
+		} else {
+			struct mrsh_word **word_ptr =
+				(struct mrsh_word **)&tci->patterns->data[tci->index++];
+			tci->word = task_word_create(word_ptr, TILDE_EXPANSION_NAME);
+		}
+	}
+
+	if (tcc->selected) {
+		return task_poll(tcc->selected->body, ctx);
+	}
+
+	return 0;
+}
+
+static const struct task_interface task_case_clause_impl = {
+	.destroy = task_case_clause_destroy,
+	.poll = task_case_clause_poll,
+};
+
+struct task *task_case_clause_create(
+		const struct mrsh_word *word, const struct mrsh_array *cases) {
+	struct task_case_clause *task = calloc(1, sizeof(struct task_case_clause));
+	task_init(&task->task, &task_case_clause_impl);
+	if (!mrsh_array_reserve(&task->cases, cases->len)) {
+		free(task);
+		return NULL;
+	}
+	for (size_t i = 0; i < cases->len; ++i) {
+		struct mrsh_case_item *mci = cases->data[i];
+		struct task_case_item *tci = calloc(1, sizeof(struct task_case_item));
+		mrsh_array_add(&task->cases, tci);
+		tci->patterns = &mci->patterns;
+		tci->body = task_for_command_list_array(&mci->body);
+	}
+	task->ast.word = mrsh_word_copy(word);
+	task->tasks.word = task_word_create(&task->ast.word, TILDE_EXPANSION_NAME);
+	return (struct task *)task;
+}
diff --git a/test/case.sh b/test/case.sh
new file mode 100644
index 0000000..8a7d3c8
--- /dev/null
+++ b/test/case.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+x=hello
+
+case "$x" in
+	hello)
+		echo pass
+		;;
+	world)
+		echo fail
+		;;
+esac
+
+case "$x" in
+	he*)
+		echo pass
+		;;
+	*)
+		echo fail
+		;;
+esac
+
+case "$x" in
+	foo)
+		echo fail
+		;;
+	he??o)
+		echo pass
+		;;
+esac
+
+case "$x" in
+	foo)
+		echo fail
+		;;
+	*)
+		echo pass
+		;;
+esac
+
+case "$x" in
+	world|hello)
+		echo pass
+		;;
+	*)
+		echo fail
+		;;
+esac
+
+case "$x" in
+	hell[a-z])
+		echo pass
+		;;
+	*)
+		echo fail
+		;;
+esac
+
+y=hello
+
+# Expanding patterns
+case "$x" in
+	$y)
+		echo pass
+		;;
+	*)
+		echo fail
+		;;
+esac
+
+# ;; optional for last item
+case hello in
+	*)
+		echo pass
+esac
diff --git a/test/meson.build b/test/meson.build
index 29516f7..b429be4 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -4,6 +4,7 @@ ref_sh = find_program('sh', required: false)
 test_files = [
 	'conformance/if.sh',
 
+	'case.sh',
 	'loop.sh',
 	'subshell.sh',
 	'word.sh',
-- 
2.20.1

Re: [PATCH v3] Implement case clause

Details
Message ID
<R3lyXPxo3CuaeySfuPGQPGdaYfNC4O5heoqYuc2oBByFB4pb3eaEKEKSqklKczEsMrAdNi1Y8_sRbCwzvTvnSkNHBpJYAvmlM-JyE_9KJTc=@emersion.fr>
In-Reply-To
<20190101005242.25170-1-sir@cmpwn.com> (view parent)
Sender timestamp
1546463415
DKIM signature
pass
Download raw message
Pushed:

To git.sr.ht:~emersion/mrsh
   7fc12bb..aa7d536  master -> master

Thanks!

Re: [PATCH v2 2/3] Add shell/entry.c

Details
Message ID
<1mD8RpRxjPUuWQEWiPy7dbE4Xq6KF4Dx2RAUPJ0eQT5PCmeSN1zYod1yelW-HEEMDUJStFU5xC1DFUYydOpRwIFzyTFFIHsiJHJ0O4hVZxs=@emersion.fr>
In-Reply-To
<20181228181347.6395-2-sir@cmpwn.com> (view parent)
Sender timestamp
1547742783
DKIM signature
pass
Download raw message
I kind of forgot about these patches, sorry. Would you mind rebasing
them?

Maybe it makes more sense to name this util.h instead of entry.h?

Re: [PATCH v2 2/3] Add shell/entry.c

Details
Message ID
<20190117215142.GA28501@miku>
In-Reply-To
<1mD8RpRxjPUuWQEWiPy7dbE4Xq6KF4Dx2RAUPJ0eQT5PCmeSN1zYod1yelW-HEEMDUJStFU5xC1DFUYydOpRwIFzyTFFIHsiJHJ0O4hVZxs=@emersion.fr> (view parent)
Sender timestamp
1547761903
DKIM signature
permerror
Download raw message
On 2019-01-17  4:33 PM, Simon Ser wrote:
> Maybe it makes more sense to name this util.h instead of entry.h?

-1. these are stuff that the entry point of the shell would use.

Re: [PATCH v2 2/3] Add shell/entry.c

Details
Message ID
<Bug8g1yOCXhuMc3Sx0IdYj4i87TcGi97s1obt0ueceMqc0mgE9WkH0kgqYVRs-DuJwbXiJ3WPAUaDvQsjYsQIpLM__EBm2s5QQRdwD8qwW0=@emersion.fr>
In-Reply-To
<20190117215142.GA28501@miku> (view parent)
Sender timestamp
1547762290
DKIM signature
pass
Download raw message
On Thursday, January 17, 2019 10:51 PM, Drew DeVault <sir@cmpwn.com> wrote:
> > Maybe it makes more sense to name this util.h instead of entry.h?
>
> -1. these are stuff that the entry point of the shell would use.

Fair enough.