dd5a12cb8eef8f2a34bf0ff738a85264054e29d6 — Simon Ser 2 months ago fdce27c
shell/task/word: make conditional parameter ops process words

This is a required step prior to adding $@ and $* support. Conditional
parameter operations can operate on those, which will need to return word
values to be able to indicate which parts can be subject to field splitting.
1 files changed, 88 insertions(+), 25 deletions(-)

M shell/task/word.c
M shell/task/word.c => shell/task/word.c +88 -25
@@ 101,10 101,10 @@ static const char *parameter_get_value(struct mrsh_state *state,
 	char *end;
 	long lvalue = strtol(name, &end, 10);
 	// Special cases
-	if (strcmp(name, "@") == 0) {
-		// TODO
-	} else if (strcmp(name, "*") == 0) {
-		// TODO
+	if (strcmp(name, "@") == 0 || strcmp(name, "*") == 0) {
+		// These are handled separately, because they evaluate to a word, not
+		// a raw string.
+		return NULL;
 	} else if (strcmp(name, "#") == 0) {
 		sprintf(value, "%d", state->frame->argc - 1);
 		return value;


@@ 152,23 152,43 @@ static struct mrsh_word *copy_word_or_null(struct mrsh_word *word) {
 	}
 }
 
-static int apply_parameter_op(struct context *ctx,
-		struct mrsh_word_parameter *wp, const char *str,
+static bool is_null_word(const struct mrsh_word *word) {
+	switch (word->type) {
+	case MRSH_WORD_STRING:;
+		const struct mrsh_word_string *ws = mrsh_word_get_string(word);
+		return ws->str[0] == '\0';
+	case MRSH_WORD_LIST:;
+		const struct mrsh_word_list *wl = mrsh_word_get_list(word);
+		for (size_t i = 0; i < wl->children.len; i++) {
+			const struct mrsh_word *child = wl->children.data[i];
+			if (!is_null_word(child)) {
+				return false;
+			}
+		}
+		return true;
+	default:
+		assert(false);
+	}
+}
+
+static int apply_parameter_cond_op(struct context *ctx,
+		struct mrsh_word_parameter *wp, struct mrsh_word *value,
 		struct mrsh_word **result) {
 	switch (wp->op) {
 	case MRSH_PARAM_NONE:
-		*result = str != NULL ? create_word_string(str) : NULL;
+		*result = value;
 		return 0;
 	case MRSH_PARAM_MINUS: // Use Default Values
-		if (str == NULL || (str[0] == '\0' && wp->colon)) {
+		if (value == NULL || (wp->colon && is_null_word(value))) {
+			mrsh_word_destroy(value);
 			*result = copy_word_or_null(wp->arg);
 		} else {
-			*result = create_word_string(str);
+			*result = value;
 		}
 		return 0;
 	case MRSH_PARAM_EQUAL: // Assign Default Values
-		// TODO: error out if positional or special parameter
-		if (str == NULL || (str[0] == '\0' && wp->colon)) {
+		if (value == NULL || (wp->colon && is_null_word(value))) {
+			mrsh_word_destroy(value);
 			*result = copy_word_or_null(wp->arg);
 			int ret = run_word(ctx, result);
 			if (ret < 0) {


@@ 178,11 198,12 @@ static int apply_parameter_op(struct context *ctx,
 			mrsh_env_set(ctx->state, wp->name, str, 0);
 			free(str);
 		} else {
-			*result = create_word_string(str);
+			*result = value;
 		}
 		return 0;
 	case MRSH_PARAM_QMARK: // Indicate Error if Null or Unset
-		if (str == NULL || (str[0] == '\0' && wp->colon)) {
+		if (value == NULL || (wp->colon && is_null_word(value))) {
+			mrsh_word_destroy(value);
 			char *err_msg;
 			if (wp->arg != NULL) {
 				struct mrsh_word *err_msg_word = mrsh_word_copy(wp->arg);


@@ 201,27 222,31 @@ static int apply_parameter_op(struct context *ctx,
 			// TODO: make the shell exit if non-interactive
 			return TASK_STATUS_ERROR;
 		} else {
-			*result = create_word_string(str);
+			*result = value;
 		}
 		return 0;
 	case MRSH_PARAM_PLUS: // Use Alternative Value
-		if (str == NULL || (str[0] == '\0' && wp->colon)) {
+		if (value == NULL || (wp->colon && is_null_word(value))) {
 			*result = create_word_string("");
 		} else {
 			*result = copy_word_or_null(wp->arg);
 		}
+		mrsh_word_destroy(value);
 		return 0;
+	default:
+		assert(false); // unreachable
+	}
+}
+
+static int apply_parameter_str_op(struct context *ctx,
+		struct mrsh_word_parameter *wp, const char *str,
+		struct mrsh_word **result) {
+	switch (wp->op) {
 	case MRSH_PARAM_LEADING_HASH: // String Length
 		if (str == NULL && (ctx->state->options & MRSH_OPT_NOUNSET)) {
 			*result = NULL;
 			return 0;
 		}
-		if (strcmp(wp->name, "*") == 0 || strcmp(wp->name, "@") == 0) {
-			fprintf(stderr, "%s: using the string length operator on $%s "
-				"is undefined behaviour\n",
-				ctx->state->frame->argv[0], wp->name);
-			return TASK_STATUS_ERROR;
-		}
 
 		int len = 0;
 		if (str != NULL) {


@@ 236,8 261,9 @@ static int apply_parameter_op(struct context *ctx,
 	case MRSH_PARAM_HASH: // Remove Smallest Prefix Pattern
 	case MRSH_PARAM_DHASH: // Remove Largest Prefix Pattern
 		assert(false); // TODO
+	default:
+		assert(false);
 	}
-	assert(false);
 }
 
 int run_word(struct context *ctx, struct mrsh_word **word_ptr) {


@@ 249,6 275,7 @@ int run_word(struct context *ctx, struct mrsh_word **word_ptr) {
 		return 0;
 	case MRSH_WORD_PARAMETER:;
 		struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);
+
 		const char *value = parameter_get_value(ctx->state, wp->name);
 		char lineno[16];
 		if (value == NULL && strcmp(wp->name, "LINENO") == 0) {


@@ 259,11 286,47 @@ int run_word(struct context *ctx, struct mrsh_word **word_ptr) {
 
 			value = lineno;
 		}
+
 		struct mrsh_word *result = NULL;
-		ret = apply_parameter_op(ctx, wp, value, &result);
-		if (ret < 0) {
-			return ret;
+		switch (wp->op) {
+		case MRSH_PARAM_NONE:
+		case MRSH_PARAM_MINUS:
+		case MRSH_PARAM_EQUAL:
+		case MRSH_PARAM_QMARK:
+		case MRSH_PARAM_PLUS:;
+			struct mrsh_word *value_word;
+			if (strcmp(wp->name, "@") == 0 || strcmp(wp->name, "*") == 0) {
+				assert(false); // TODO
+			} else if (value != NULL) {
+				value_word = create_word_string(value);
+			} else {
+				value_word = NULL;
+			}
+
+			ret = apply_parameter_cond_op(ctx, wp, value_word, &result);
+			if (ret < 0) {
+				return ret;
+			}
+			break;
+		case MRSH_PARAM_LEADING_HASH:
+		case MRSH_PARAM_PERCENT:
+		case MRSH_PARAM_DPERCENT:
+		case MRSH_PARAM_HASH:
+		case MRSH_PARAM_DHASH:
+			if (strcmp(wp->name, "@") == 0 || strcmp(wp->name, "*") == 0) {
+				fprintf(stderr, "%s: using this parameter operator on $%s "
+					"is undefined behaviour\n",
+					ctx->state->frame->argv[0], wp->name);
+				return TASK_STATUS_ERROR;
+			}
+
+			ret = apply_parameter_str_op(ctx, wp, value, &result);
+			if (ret < 0) {
+				return ret;
+			}
+			break;
 		}
+
 		if (result == NULL) {
 			if ((ctx->state->options & MRSH_OPT_NOUNSET)) {
 				fprintf(stderr, "%s: %s: unbound variable\n",