~emersion/mrsh

c5f12815eaabf62b673c0e7a8300e462ee3cb244 — Simon Ser 5 months ago 5cae98d
Perform complete word expansion on for clause words

Also add a few new tests to make sure we don't break this.

Closes: https://github.com/emersion/mrsh/issues/125
2 files changed, 35 insertions(+), 19 deletions(-)

M shell/task/task.c
M test/for.sh
M shell/task/task.c => shell/task/task.c +8 -18
@@ 129,33 129,23 @@ static int run_for_clause(struct mrsh_context *ctx, struct mrsh_for_clause *fc) 
		call_frame_get_priv(ctx->state->frame);
	int loop_num = ++frame_priv->nloops;

	struct mrsh_array word_fields = {0};
	struct mrsh_array fields = {0};
	for (size_t i = 0; i < fc->word_list.len; i++) {
		// TODO: this mutates the AST
		struct mrsh_word **word_ptr =
			(struct mrsh_word **)&fc->word_list.data[i];
		expand_tilde(ctx->state, word_ptr, false);
		int ret = run_word(ctx, word_ptr);
		struct mrsh_word *word = fc->word_list.data[i];
		int ret = expand_word(ctx, word, &fields);
		if (ret < 0) {
			return ret;
		}
		mrsh_array_add(&word_fields, *word_ptr);
	}

	struct mrsh_array expanded_fields = {0};
	if (!expand_pathnames(&expanded_fields, &word_fields)) {
		return TASK_STATUS_ERROR;
	}
	mrsh_array_finish(&word_fields);

	int loop_ret = 0;
	size_t word_index = 0;
	while (ctx->state->exit == -1) {
		if (word_index == expanded_fields.len) {
		if (word_index == fields.len) {
			break;
		}

		mrsh_env_set(ctx->state, fc->name, expanded_fields.data[word_index],
		mrsh_env_set(ctx->state, fc->name, fields.data[word_index],
			MRSH_VAR_ATTRIB_NONE);
		word_index++;



@@ 189,10 179,10 @@ interrupt:
		}
	}

	for (size_t i = 0; i < expanded_fields.len; i++) {
		free(expanded_fields.data[i]);
	for (size_t i = 0; i < fields.len; i++) {
		free(fields.data[i]);
	}
	mrsh_array_finish(&expanded_fields);
	mrsh_array_finish(&fields);

	--frame_priv->nloops;
	return loop_ret;

M test/for.sh => test/for.sh +27 -1
@@ 1,13 1,39 @@
#!/bin/sh

two=2
echo "Simple for loop"
for i in 1 2 3; do
	echo $i
done

echo "Word expansion in for loop"
two=2
for i in 1 $two $(echo 3); do
	echo $i
done
echo $i

echo "No-op for loop"
for i; do
	echo
done
echo $i

echo "Field splitting in for loop, expanded from parameter"
asdf='a s d f'
for c in $asdf; do
	echo $c
done

echo "Field splitting in for loop, expanded from command substitution"
for c in $(echo a s d f); do
	echo $c
done

echo "Field splitting in for loop, with IFS set"
(
	IFS=':'
	asdf='a:s:d:f'
	for c in $asdf; do
		echo $c
	done
)