interpreter.c
bool step_interpreter(interpreter_state *state) {
	/* per the DevPerc "spec" at http://esolangs.org/wiki/Deviating_Percolator
	 * we must read and substitute character-by-character until hitting \n
	 */
	char line_buf[BUF_SIZE + 1];
	size_t pos = state->start_pos;
	size_t line_pos = 0;
	while (true) {
		char in_char = state->text[pos];
		char out_char = substitute(state, in_char);

		/* we're done! */
		if (out_char == '\n') {
			line_buf[line_pos] = '\0';
			break;
		}
		
		line_buf[line_pos] = out_char;

		pos++;
		line_pos++;

		if (!(pos < state->text_len)) {
			line_buf[line_pos - 1] = '\0';
			break;
		}

		if (!(pos < BUF_SIZE)) {
			error_line_ex(state, pos, "line exceeds %zu characters. Increase the BUF_SIZE constant when compiling", (size_t)BUF_SIZE);
		}
	}

	size_t line_len = line_pos;
	/* reset the position now that we need to parse this line */
	pos = state->start_pos;
	line_pos = 0;

	if (!line_len) {
		error_line_ex(state, pos, "line cannot be empty");
	}

	/* seek out comment, if any, so we can ignore line beyond that point */
	const char *slash_pos = strchr(line_buf, '/');
	size_t statement_len = line_len;
	if (slash_pos) {
		statement_len = slash_pos - (const char *)&line_buf;
	}

	/* seek out space after first word of statement */
	const char *space_pos = strchr(line_buf, ' ');
	if (!space_pos) {
		error_line_ex(state, pos, "invalid statement \"%.*s\"", (int)statement_len, line_buf);
	}
	const size_t name_len = space_pos - (const char *)&line_buf;

	/* IF PROCEEDTO statement */
	if (name_len == 2 && !strncmp("IF", line_buf, 2)) {
		/* line after space should be "<expr> PROCEEDTO <expr>" */
		const char *to_pos = strstr(space_pos + 1, " PROCEEDTO ");

		/* strpos will look beyond end of statement as it's not bound by length
		 * so we should check in case there was a comment like "/ TO ", say
		 */
		if (!to_pos || to_pos > line_buf + statement_len) {
			error_line_ex(state, pos, "invalid IF PROCEEDTO statement"); 
		}

		/* now to just interpret our two expressions! */
		unsigned char condition = (char)interpret_expression(
			state,
			/* name is between "IF " and " PROCEEDTO " */
			space_pos + 1,
			to_pos - (space_pos + 1)
		);

		/* condition is "true" if non-zero, like C
		 * since expressions lack side-effects, we can legally short-circuit
		 * evaluation of the line number expression
		 */
		if (condition != 0) {
			unsigned char line_no = interpret_expression(
				state,
				/* new value is between " PROCEEDTO " and end of statement */
				to_pos + 11,
				(statement_len - (to_pos + 11 - (const char*)&line_buf))
			);

			state->start_pos = line_no_to_pos(state, line_no);
			/* yes, I know goto is supposedly evil */
			goto jump;
		}
	/* PUT statement */
	} else if (name_len == 3 && !strncmp("PUT", line_buf, 3)) {
		/* line after space must be an expression */
		unsigned char result = interpret_expression(state, space_pos + 1, statement_len - 4);
		putchar(result);
	/* GET statement */
	} else if (name_len == 3 && !strncmp("GET", line_buf, 3)) {
		/* line after space must be an expression (register name) */
		char register_name = (char)interpret_expression(state, space_pos + 1, statement_len - 4);

		int result = getc(stdin);
		/* DevPerc "spec" requires 255 to be produced on EOF for some reason */
		if (result == EOF) {
			result = 255;
		}
		*lookup_register(state, register_name) = (unsigned char)result;
	/* DEFINE TO statement */
	} else if (name_len == 6 && !strncmp("DEFINE", line_buf, 6)) {
		/* line after space should be "<expr> TO <expr>" */
		const char *to_pos = strstr(space_pos + 1, " TO ");

		/* strpos will look beyond end of statement as it's not bound by length
		 * so we should check in case there was a comment like "/ TO ", say
		 */
		if (!to_pos || to_pos > line_buf + statement_len) {
			error_line_ex(state, pos, "invalid DEFINE TO statement"); 
		}

		/* now to just interpret our two expressions! */
		char register_name = (char)interpret_expression(
			state,
			/* name is between "DEFINE " and " TO " */
			space_pos + 1,
			to_pos - (space_pos + 1)
		);
		unsigned char register_value = interpret_expression(
			state,
			/* new value is between " TO " and end of statement */
			to_pos + 4,
			(statement_len - (to_pos + 4 - (const char*)&line_buf))
		);

		*lookup_register(state, register_name) = register_value;
	} else {
		error_line_ex(state, pos, "unrecognised statement name: \"%.*s\"", (int)name_len, line_buf); 
	}

	/* all's good: proceed to next line */
	state->start_pos = pos + line_len + 1;

jump:

	/* EOF */
	if (!(state->start_pos < state->text_len)) {
		/* done */
		return 1;
	} else {
		/* not done */
		return 0;
	}
}
/* execute one line
 * this assumes the interpreter is in a valid state and may segfault if not */
execute one line
this assumes the interpreter is in a valid state and may segfault if not
execute one line this assumes the interpreter is in a valid state and may segfault if not
bool step_interpreter(interpreter_state *state) {
	char line_buf[BUF_SIZE + 1];
	size_t pos = state->start_pos;
	size_t line_pos = 0;
	while (true) {
		char in_char = state->text[pos];
		char out_char = substitute(state, in_char);
		if (out_char == '\n') {
			line_buf[line_pos] = '\0';
			break;
		}
		line_buf[line_pos] = out_char;
		pos++;
		line_pos++;
		if (!(pos < state->text_len)) {
			line_buf[line_pos - 1] = '\0';
			break;
		}
		if (!(pos < BUF_SIZE)) {
			error_line_ex(state, pos, "line exceeds %zu characters. Increase the BUF_SIZE constant when compiling", (size_t)BUF_SIZE);
		}
	}
	size_t line_len = line_pos;
	pos = state->start_pos;
	line_pos = 0;
	if (!line_len) {
		error_line_ex(state, pos, "line cannot be empty");
	}
	const char *slash_pos = strchr(line_buf, '/');
	size_t statement_len = line_len;
	if (slash_pos) {
		statement_len = slash_pos - (const char *)&line_buf;
	}
	const char *space_pos = strchr(line_buf, ' ');
	if (!space_pos) {
		error_line_ex(state, pos, "invalid statement \"%.*s\"", (int)statement_len, line_buf);
	}
	const size_t name_len = space_pos - (const char *)&line_buf;
	if (name_len == 2 && !strncmp("IF", line_buf, 2)) {
		const char *to_pos = strstr(space_pos + 1, " PROCEEDTO ");
		if (!to_pos || to_pos > line_buf + statement_len) {
			error_line_ex(state, pos, "invalid IF PROCEEDTO statement"); 
		}
		unsigned char condition = (char)interpret_expression(
			state,
			space_pos + 1,
			to_pos - (space_pos + 1)
		);
		if (condition != 0) {
			unsigned char line_no = interpret_expression(
				state,
				to_pos + 11,
				(statement_len - (to_pos + 11 - (const char*)&line_buf))
			);
			state->start_pos = line_no_to_pos(state, line_no);
			goto jump;
		}
	} else if (name_len == 3 && !strncmp("PUT", line_buf, 3)) {
		unsigned char result = interpret_expression(state, space_pos + 1, statement_len - 4);
		putchar(result);
	} else if (name_len == 3 && !strncmp("GET", line_buf, 3)) {
		char register_name = (char)interpret_expression(state, space_pos + 1, statement_len - 4);
		int result = getc(stdin);
		if (result == EOF) {
			result = 255;
		}
		*lookup_register(state, register_name) = (unsigned char)result;
	} else if (name_len == 6 && !strncmp("DEFINE", line_buf, 6)) {
		const char *to_pos = strstr(space_pos + 1, " TO ");
		if (!to_pos || to_pos > line_buf + statement_len) {
			error_line_ex(state, pos, "invalid DEFINE TO statement"); 
		}
		char register_name = (char)interpret_expression(
			state,
			space_pos + 1,
			to_pos - (space_pos + 1)
		);
		unsigned char register_value = interpret_expression(
			state,
			to_pos + 4,
			(statement_len - (to_pos + 4 - (const char*)&line_buf))
		);
		*lookup_register(state, register_name) = register_value;
	} else {
		error_line_ex(state, pos, "unrecognised statement name: \"%.*s\"", (int)name_len, line_buf); 
	}
	state->start_pos = pos + line_len + 1;
jump:
	if (!(state->start_pos < state->text_len)) {
		return 1;
	} else {
		return 0;
	}
}
bool step_interpreter ( interpreter_state * state ) { char line_buf [ BUF_SIZE + 1 ] ; size_t pos = state -> start_pos ; size_t line_pos = 0 ; while ( true ) { char in_char = state -> text [ pos ] ; char out_char = substitute ( state , in_char ) ; if ( out_char == ' \n ' ) { line_buf [ line_pos ] = ' \0 ' ; break ; } line_buf [ line_pos ] = out_char ; pos ++ ; line_pos ++ ; if ( ! ( pos < state -> text_len ) ) { line_buf [ line_pos - 1 ] = ' \0 ' ; break ; } if ( ! ( pos < BUF_SIZE ) ) { error_line_ex ( state , pos , " " , ( size_t ) BUF_SIZE ) ; } } size_t line_len = line_pos ; pos = state -> start_pos ; line_pos = 0 ; if ( ! line_len ) { error_line_ex ( state , pos , " " ) ; } const char * slash_pos = strchr ( line_buf , ' ' ) ; size_t statement_len = line_len ; if ( slash_pos ) { statement_len = slash_pos - ( const char * ) & line_buf ; } const char * space_pos = strchr ( line_buf , ' ' ) ; if ( ! space_pos ) { error_line_ex ( state , pos , " \" \" " , ( int ) statement_len , line_buf ) ; } const size_t name_len = space_pos - ( const char * ) & line_buf ; if ( name_len == 2 && ! strncmp ( " " , line_buf , 2 ) ) { const char * to_pos = strstr ( space_pos + 1 , " " ) ; if ( ! to_pos || to_pos > line_buf + statement_len ) { error_line_ex ( state , pos , " " ) ; } unsigned char condition = ( char ) interpret_expression ( state , space_pos + 1 , to_pos - ( space_pos + 1 ) ) ; if ( condition != 0 ) { unsigned char line_no = interpret_expression ( state , to_pos + 11 , ( statement_len - ( to_pos + 11 - ( const char * ) & line_buf ) ) ) ; state -> start_pos = line_no_to_pos ( state , line_no ) ; goto jump ; } } else if ( name_len == 3 && ! strncmp ( " " , line_buf , 3 ) ) { unsigned char result = interpret_expression ( state , space_pos + 1 , statement_len - 4 ) ; putchar ( result ) ; } else if ( name_len == 3 && ! strncmp ( " " , line_buf , 3 ) ) { char register_name = ( char ) interpret_expression ( state , space_pos + 1 , statement_len - 4 ) ; int result = getc ( stdin ) ; if ( result == EOF ) { result = 255 ; } * lookup_register ( state , register_name ) = ( unsigned char ) result ; } else if ( name_len == 6 && ! strncmp ( " " , line_buf , 6 ) ) { const char * to_pos = strstr ( space_pos + 1 , " " ) ; if ( ! to_pos || to_pos > line_buf + statement_len ) { error_line_ex ( state , pos , " " ) ; } char register_name = ( char ) interpret_expression ( state , space_pos + 1 , to_pos - ( space_pos + 1 ) ) ; unsigned char register_value = interpret_expression ( state , to_pos + 4 , ( statement_len - ( to_pos + 4 - ( const char * ) & line_buf ) ) ) ; * lookup_register ( state , register_name ) = register_value ; } else { error_line_ex ( state , pos , " \" \" " , ( int ) name_len , line_buf ) ; } state -> start_pos = pos + line_len + 1 ; jump : if ( ! ( state -> start_pos < state -> text_len ) ) { return 1 ; } else { return 0 ; } }
execute one line
 this assumes the interpreter is in a valid state and may segfault if not
execute one line this assumes the interpreter is in a valid state and may segfault if not
/* per the DevPerc "spec" at http://esolangs.org/wiki/Deviating_Percolator
	 * we must read and substitute character-by-character until hitting \n
	 */ /* we're done! */ /* reset the position now that we need to parse this line */ /* seek out comment, if any, so we can ignore line beyond that point */ /* seek out space after first word of statement */ /* IF PROCEEDTO statement */ /* line after space should be "<expr> PROCEEDTO <expr>" */ /* strpos will look beyond end of statement as it's not bound by length
		 * so we should check in case there was a comment like "/ TO ", say
		 */ /* now to just interpret our two expressions! */ /* name is between "IF " and " PROCEEDTO " */ /* condition is "true" if non-zero, like C
		 * since expressions lack side-effects, we can legally short-circuit
		 * evaluation of the line number expression
		 */ /* new value is between " PROCEEDTO " and end of statement */ /* yes, I know goto is supposedly evil */ /* PUT statement */ /* line after space must be an expression */ /* GET statement */ /* line after space must be an expression (register name) */ /* DevPerc "spec" requires 255 to be produced on EOF for some reason */ /* DEFINE TO statement */ /* line after space should be "<expr> TO <expr>" */ /* strpos will look beyond end of statement as it's not bound by length
		 * so we should check in case there was a comment like "/ TO ", say
		 */ /* now to just interpret our two expressions! */ /* name is between "DEFINE " and " TO " */ /* new value is between " TO " and end of statement */ /* all's good: proceed to next line */ /* EOF */ /* done */ /* not done */