aa7d536d91b2e4bc6685ee979d40fcbf6d4dcbd2 — Drew DeVault 11 months ago 783dd27
Implement case clause
6 files changed, 196 insertions(+), 2 deletions(-)

M include/shell/task.h
M meson.build
M shell/task/ast.c
A shell/task/case_clause.c
A test/case.sh
M test/meson.build
M include/shell/task.h => include/shell/task.h +2 -0
@@ 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,

M meson.build => meson.build +1 -0
@@ 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',

M shell/task/ast.c => shell/task/ast.c +8 -2
@@ 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);
 }

A shell/task/case_clause.c => shell/task/case_clause.c +110 -0
@@ 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;
+}

A test/case.sh => test/case.sh +74 -0
@@ 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

M test/meson.build => test/meson.build +1 -0
@@ 4,6 4,7 @@ ref_sh = find_program('sh', required: false)
 test_files = [
 	'conformance/if.sh',
 
+	'case.sh',
 	'loop.sh',
 	'subshell.sh',
 	'word.sh',