03c5cab96c86536545d3a73c672d0c63bfcf1cf4 — Drew DeVault 6 days ago 4951519 master
Mostly implement break and continue builtins

The main thing that's missing here is moving the number of loops into
the call stack:

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#break

To prevent you from breaking out of your caller's loop in a function.
A builtin/break.c => builtin/break.c +36 -0
@@ 0,0 1,36 @@
+ #define _POSIX_C_SOURCE 200809L
+ #include <mrsh/builtin.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include "builtin.h"
+ #include "shell/shell.h"
+ #include "shell/task.h"
+ 
+ static const char break_usage[] = "usage: %s [n]\n";
+ 
+ int builtin_break(struct mrsh_state *state, int argc, char *argv[]) {
+ 	if (argc > 2) {
+ 		fprintf(stderr, break_usage, argv[0]);
+ 		return 1;
+ 	}
+ 
+ 	int n = 1;
+ 	if (argc == 2) {
+ 		char *end;
+ 		n = strtol(argv[1], &end, 10);
+ 		if (end[0] != '\0' || argv[0][0] == '\0' || n < 0) {
+ 			fprintf(stderr, "%s: invalid loop number '%s'\n", argv[0], argv[1]);
+ 			return 1;
+ 		}
+ 	}
+ 
+ 	if (n > state->nloops) {
+ 		n = state->nloops;
+ 	}
+ 
+ 	state->nloops -= n - 1;
+ 	state->branch_control =
+ 		strcmp(argv[0], "break") == 0 ? MRSH_BRANCH_BREAK : MRSH_BRANCH_CONTINUE;
+ 	return TASK_STATUS_INTERRUPTED;
+ }

M builtin/builtin.c => builtin/builtin.c +2 -0
@@ 18,8 18,10 @@ { ":", builtin_colon, true },
  	{ "alias", builtin_alias, false },
  	{ "bg", builtin_bg, false },
+ 	{ "break", builtin_break, true },
  	{ "cd", builtin_cd, false },
  	{ "command", builtin_command, false },
+ 	{ "continue", builtin_break, true },
  	{ "eval", builtin_eval, true },
  	{ "exit", builtin_exit, true },
  	{ "export", builtin_export, true },

M include/builtin.h => include/builtin.h +1 -0
@@ 10,6 10,7 @@   int builtin_alias(struct mrsh_state *state, int argc, char *argv[]);
  int builtin_bg(struct mrsh_state *state, int argc, char *argv[]);
+ int builtin_break(struct mrsh_state *state, int argc, char *argv[]);
  int builtin_cd(struct mrsh_state *state, int argc, char *argv[]);
  int builtin_command(struct mrsh_state *state, int argc, char *argv[]);
  int builtin_colon(struct mrsh_state *state, int argc, char *argv[]);

M include/mrsh/shell.h => include/mrsh/shell.h +10 -0
@@ 83,6 83,12 @@   struct mrsh_job;
  
+ enum mrsh_branch_control {
+ 	MRSH_BRANCH_BREAK,
+ 	MRSH_BRANCH_CONTINUE,
+ 	MRSH_BRANCH_RETURN,
+ };
+ 
  struct mrsh_state {
  	int exit;
  	int fd;


@@ 100,6 106,10 @@ struct termios term_modes;
  	struct mrsh_array jobs; // struct mrsh_job *
  	struct mrsh_job *foreground_job;
+ 
+ 	// TODO: move this to context
+ 	enum mrsh_branch_control branch_control;
+ 	int nloops;
  };
  
  void mrsh_function_destroy(struct mrsh_function *fn);

M include/shell/task.h => include/shell/task.h +4 -0
@@ 16,6 16,10 @@ * The task has been stopped and the job has been put in the background.
   */
  #define TASK_STATUS_STOPPED -3
+ /**
+  * The task has been interrupted for some reason.
+  */
+ #define TASK_STATUS_INTERRUPTED -4
  
  struct task_interface;
  

M meson.build => meson.build +1 -0
@@ 74,6 74,7 @@ 'buffer.c',
  		'builtin/alias.c',
  		'builtin/bg.c',
+ 		'builtin/break.c',
  		'builtin/builtin.c',
  		'builtin/cd.c',
  		'builtin/colon.c',

M shell/shell.c => shell/shell.c +1 -0
@@ 22,6 22,7 @@ void mrsh_state_init(struct mrsh_state *state) {
  	state->exit = -1;
  	state->fd = -1;
+ 	state->nloops = 0;
  	state->interactive = isatty(STDIN_FILENO);
  	state->options = state->interactive ? MRSH_OPT_INTERACTIVE : 0;
  	state->args = calloc(1, sizeof(struct mrsh_call_frame));

M shell/task/for_clause.c => shell/task/for_clause.c +41 -8
@@ 15,7 15,8 @@ struct task *word, *body;
  	} tasks;
  	size_t index;
- 	int last_body_status;
+ 	int exit_status;
+ 	int loop_num;
  };
  
  static void task_for_clause_destroy(struct task *task) {


@@ 27,20 28,30 @@   static int task_for_clause_poll(struct task *task, struct context *ctx) {
  	struct task_for_clause *tfc = (struct task_for_clause *)task;
+ 	if (tfc->loop_num == -1) {
+ 		tfc->loop_num = ++ctx->state->nloops;
+ 	}
+ 
  	while (true) {
+ 		int status;
+ 
  		if (tfc->tasks.body) {
  			/* wait for body */
- 			int body_status = task_poll(tfc->tasks.body, ctx);
- 			if (body_status < 0) {
- 				return body_status;
+ 			status = task_poll(tfc->tasks.body, ctx);
+ 			if (status == TASK_STATUS_INTERRUPTED) {
+ 				goto interrupt;
+ 			} else if (status < 0) {
+ 				return status;
  			}
  			task_destroy(tfc->tasks.body);
  			tfc->tasks.body = NULL;
  		} else if (tfc->tasks.word) {
  			/* wait for word */
- 			int word_status = task_poll(tfc->tasks.word, ctx);
- 			if (word_status < 0) {
- 				return word_status;
+ 			status = task_poll(tfc->tasks.word, ctx);
+ 			if (status == TASK_STATUS_INTERRUPTED) {
+ 				goto interrupt;
+ 			} else if (status < 0) {
+ 				return status;
  			}
  			struct mrsh_word_string *word = (struct mrsh_word_string *)
  				tfc->ast.word_list->data[tfc->index - 1];


@@ 53,14 64,34 @@ } else {
  			/* create a new word */
  			if (tfc->index == tfc->ast.word_list->len) {
- 				return 0;
+ 				goto exit;
  			}
  			struct mrsh_word **word_ptr =
  				(struct mrsh_word **)&tfc->ast.word_list->data[tfc->index++];
  			tfc->tasks.word = task_word_create(
  				word_ptr, TILDE_EXPANSION_NAME);
  		}
+ 		continue;
+ 
+ interrupt:
+ 		if (ctx->state->nloops < tfc->loop_num) {
+ 			/* break to parent loop */
+ 			return status;
+ 		}
+ 		if (ctx->state->branch_control == MRSH_BRANCH_BREAK) {
+ 			tfc->exit_status = 0;
+ 			goto exit;
+ 		} else if (ctx->state->branch_control == MRSH_BRANCH_CONTINUE) {
+ 			task_destroy(tfc->tasks.body);
+ 			tfc->tasks.body = NULL;
+ 		} else {
+ 			assert(0 && "Unknown task interruption cause");
+ 		}
  	}
+ 
+ exit:
+ 	--ctx->state->nloops;
+ 	return tfc->exit_status;
  }
  
  static const struct task_interface task_for_clause_impl = {


@@ 76,5 107,7 @@ tfc->ast.word_list = word_list;
  	tfc->ast.body = body;
  	tfc->index = 0;
+ 	tfc->exit_status = 0;
+ 	tfc->loop_num = -1;
  	return &tfc->task;
  }

M shell/task/loop_clause.c => shell/task/loop_clause.c +49 -16
@@ 1,3 1,4 @@+#include <assert.h>
  #include <stdbool.h>
  #include <stdlib.h>
  #include "shell/task.h"


@@ 11,7 12,8 @@ struct task *condition, *body;
  	} tasks;
  	bool until;
- 	int last_body_status;
+ 	int exit_status;
+ 	int loop_num;
  };
  
  static void task_loop_clause_destroy(struct task *task) {


@@ 23,34 25,64 @@   static int task_loop_clause_poll(struct task *task, struct context *ctx) {
  	struct task_loop_clause *tlc = (struct task_loop_clause *)task;
+ 	if (tlc->loop_num == -1) {
+ 		tlc->loop_num = ++ctx->state->nloops;
+ 	}
  
  	while (ctx->state->exit == -1) {
+ 		int status;
+ 
  		if (tlc->tasks.condition) {
- 			int condition_status = task_poll(tlc->tasks.condition, ctx);
- 			if (condition_status < 0) {
- 				return condition_status;
- 			} else if (condition_status > 0 && !tlc->until) {
- 				return tlc->last_body_status;
- 			} else if (condition_status == 0 && tlc->until) {
- 				return tlc->last_body_status;
+ 			status = task_poll(tlc->tasks.condition, ctx);
+ 			if (status == TASK_STATUS_INTERRUPTED) {
+ 				goto interrupt;
+ 			} else if (status < 0) {
+ 				return status;
+ 			} else if (status > 0 && !tlc->until) {
+ 				goto exit;
+ 			} else if (status == 0 && tlc->until) {
+ 				goto exit;
  			}
  			task_destroy(tlc->tasks.condition);
  			tlc->tasks.condition = NULL;
  		}
  
- 		int body_status = task_poll(tlc->tasks.body, ctx);
- 		if (body_status < 0) {
- 			return body_status;
+ 		status = task_poll(tlc->tasks.body, ctx);
+ 		if (status == TASK_STATUS_INTERRUPTED) {
+ 			goto interrupt;
+ 		} else if (status < 0) {
+ 			return status;
+ 		}
+ 
+ resume:
+ 		tlc->exit_status = status;
+ 		task_destroy(tlc->tasks.body);
+ 		tlc->tasks.condition =
+ 			task_for_command_list_array(tlc->ast.condition);
+ 		tlc->tasks.body = task_for_command_list_array(tlc->ast.body);
+ 		continue;
+ 
+ interrupt:
+ 		if (ctx->state->nloops < tlc->loop_num) {
+ 			/* break to parent loop */
+ 			return status;
+ 		}
+ 		if (ctx->state->branch_control == MRSH_BRANCH_BREAK) {
+ 			tlc->exit_status = 0;
+ 			goto exit;
+ 		} else if (ctx->state->branch_control == MRSH_BRANCH_CONTINUE) {
+ 			goto resume;
  		} else {
- 			tlc->last_body_status = body_status;
- 			task_destroy(tlc->tasks.body);
- 			tlc->tasks.condition =
- 				task_for_command_list_array(tlc->ast.condition);
- 			tlc->tasks.body = task_for_command_list_array(tlc->ast.body);
+ 			assert(0 && "Unknown task interruption cause");
  		}
  	}
  
+ 	--ctx->state->nloops;
  	return ctx->state->exit;
+ 
+ exit:
+ 	--ctx->state->nloops;
+ 	return tlc->exit_status;
  }
  
  static const struct task_interface task_loop_clause_impl = {


@@ 67,5 99,6 @@ tlc->tasks.condition = task_for_command_list_array(condition);
  	tlc->tasks.body = task_for_command_list_array(body);
  	tlc->until = until;
+ 	tlc->loop_num = -1;
  	return &tlc->task;
  }

M test/loop.sh => test/loop.sh +22 -0
@@ 1,5 1,6 @@ #!/bin/sh
  
+ echo "basic while loop"
  n=asdf
  echo start
  while [ "$n" != "fdsa" ]; do


@@ 9,6 10,27 @@ done
  echo stop
  
+ echo "continue in while loop should skip the iteration"
+ n=asdf
+ echo start
+ while [ "$n" != fdsa ]; do
+ 	n=fdsa
+ 	continue
+ 	echo "this shouldn't be printed"
+ done
+ echo stop
+ 
+ echo "break in while loop should stop the loop"
+ n=asdf
+ echo start
+ while true; do
+ 	if [ "$n" = fdsa ]; then
+ 		break
+ 	fi
+ 	n=fdsa
+ done
+ echo stop
+ 
  # Should exit immediately
  while true
  do