~emersion/mrsh

5f018e1bbac8c98e20a4e4692f5f8c25026ee464 — Simon Ser 10 months ago b51626f
shell/task/word: implement positional parameter expansion

My life is a perfect graveyard of buried hopes.
― L.M. Montgomery
2 files changed, 116 insertions(+), 5 deletions(-)

M shell/task/word.c
M test/word.sh
M shell/task/word.c => shell/task/word.c +98 -5
@@ 140,6 140,33 @@ static const char *parameter_get_value(struct mrsh_state *state,
	return mrsh_env_get(state, name, NULL);
}

static struct mrsh_word *expand_positional_params(struct mrsh_state *state,
		bool quote_args) {
	const char *ifs = mrsh_env_get(state, "IFS", NULL);
	char sep[2] = {0};
	if (ifs == NULL) {
		sep[0] = ' ';
	} else {
		sep[0] = ifs[0];
	}

	struct mrsh_array words = {0};
	for (int i = 1; i < state->frame->argc; i++) {
		const char *arg = state->frame->argv[i];
		if (i > 1 && sep[0] != '\0') {
			struct mrsh_word_string *ws =
				mrsh_word_string_create(strdup(sep), false);
			mrsh_array_add(&words, &ws->word);
		}
		struct mrsh_word_string *ws =
			mrsh_word_string_create(strdup(arg), quote_args);
		mrsh_array_add(&words, &ws->word);
	}

	struct mrsh_word_list *wl = mrsh_word_list_create(&words, false);
	return &wl->word;
}

static struct mrsh_word *create_word_string(const char *str) {
	struct mrsh_word_string *ws = mrsh_word_string_create(strdup(str), false);
	return &ws->word;


@@ 357,7 384,8 @@ static int apply_parameter_str_op(struct context *ctx,
	}
}

int run_word(struct context *ctx, struct mrsh_word **word_ptr) {
static int _run_word(struct context *ctx, struct mrsh_word **word_ptr,
		bool double_quoted) {
	struct mrsh_word *word = *word_ptr;

	int ret;


@@ 386,8 414,13 @@ int run_word(struct context *ctx, struct mrsh_word **word_ptr) {
		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
			if (strcmp(wp->name, "@") == 0) {
				// $@ expands to quoted fields only if it's inside double quotes
				// TODO: error out if expansion is unspecified
				value_word = expand_positional_params(ctx->state,
					double_quoted);
			} else if (strcmp(wp->name, "*") == 0) {
				value_word = expand_positional_params(ctx->state, false);
			} else if (value != NULL) {
				value_word = create_word_string(value);
			} else {


@@ 482,17 515,77 @@ int run_word(struct context *ctx, struct mrsh_word **word_ptr) {
		mrsh_parser_destroy(parser);
		return ret;
	case MRSH_WORD_LIST:;
		// For word lists, we just need to expand each word
		struct mrsh_word_list *wl = mrsh_word_get_list(word);

		struct mrsh_array at_sign_words = {0};
		for (size_t i = 0; i < wl->children.len; ++i) {
			struct mrsh_word **child_ptr =
				(struct mrsh_word **)&wl->children.data[i];
			ret = run_word(ctx, child_ptr);

			bool is_at_sign = false;
			if ((*child_ptr)->type == MRSH_WORD_PARAMETER) {
				struct mrsh_word_parameter *wp =
					mrsh_word_get_parameter(*child_ptr);
				is_at_sign = strcmp(wp->name, "@") == 0;
			}

			ret = _run_word(ctx, child_ptr,
				double_quoted || wl->double_quoted);
			if (ret < 0) {
				return ret;
			}

			if (wl->double_quoted && is_at_sign) {
				// Fucking $@ needs special handling: we need to extract the
				// fields it expands to outside of the double quotes
				mrsh_array_add(&at_sign_words, *child_ptr);
			}
		}

		if (at_sign_words.len > 0) {
			// We need to put $@ expansions outside of the double quotes.
			// Disclaimer: this is a PITA.
			struct mrsh_array quoted = {0};
			struct mrsh_array unquoted = {0};
			size_t at_sign_idx = 0;
			for (size_t i = 0; i < wl->children.len; i++) {
				struct mrsh_word *child = wl->children.data[i];
				wl->children.data[i] = NULL; // steal the child
				if (at_sign_idx >= at_sign_words.len ||
						child != at_sign_words.data[at_sign_idx]) {
					mrsh_array_add(&quoted, child);
					continue;
				}

				if (quoted.len > 0) {
					struct mrsh_word_list *quoted_wl =
						mrsh_word_list_create(&quoted, true);
					mrsh_array_add(&unquoted, &quoted_wl->word);
					// `unquoted` has been stolen by mrsh_word_list_create
					quoted = (struct mrsh_array){0};
				}

				mrsh_array_add(&unquoted, at_sign_words.data[at_sign_idx]);

				at_sign_idx++;
			}
			if (quoted.len > 0) {
				struct mrsh_word_list *quoted_wl =
					mrsh_word_list_create(&quoted, true);
				mrsh_array_add(&unquoted, &quoted_wl->word);
			}

			struct mrsh_word_list *unquoted_wl =
				mrsh_word_list_create(&unquoted, false);
			swap_words(word_ptr, &unquoted_wl->word);
		}
		mrsh_array_finish(&at_sign_words);

		return 0;
	}
	assert(false);
}

int run_word(struct context *ctx, struct mrsh_word **word_ptr) {
	return _run_word(ctx, word_ptr, false);
}

M test/word.sh => test/word.sh +18 -0
@@ 26,6 26,24 @@ echo ${a:+GOOD} ${idontexist:+BAD} ${null:+BAD} ${idontexist:+}
echo ${a+GOOD} ${idontexist+BAD} ${null+GOOD} ${null+}
echo ${#hello} ${#null} ${#idontexist}

nargs() {
	echo "$#"
}

set a 'b   c' d '	e'
echo $*
nargs $*
echo "$*"
nargs "$*"
echo $@
nargs $@
echo "$@"
nargs "$@"
echo "1  $@  2"
nargs "1  $@  2"
echo ${null:-"$@"}
nargs ${null:-"$@"}

# Examples from the spec
# ${parameter}: dash and busybox choke on this
#a=1