~emersion/mrsh

aa7d536d91b2e4bc6685ee979d40fcbf6d4dcbd2 — Drew DeVault 1 year, 3 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',