/*
 * Copyright (C) 2012-2016 Robin Haberkorn
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include 
#include 
#include 
#include 
#include 
#include "sciteco.h"
#include "string-utils.h"
#include "interface.h"
#include "undo.h"
#include "expressions.h"
#include "goto.h"
#include "qregisters.h"
#include "ring.h"
#include "parser.h"
#include "symbols.h"
#include "search.h"
#include "spawn.h"
#include "glob.h"
#include "cmdline.h"
#include "ioview.h"
#include "error.h"
namespace SciTECO {
//#define DEBUG
gint macro_pc = 0;
namespace States {
	StateStart		start;
	StateControl		control;
	StateASCII		ascii;
	StateFCommand		fcommand;
	StateChangeDir		changedir;
	StateCondCommand	condcommand;
	StateECommand		ecommand;
	StateScintilla_symbols	scintilla_symbols;
	StateScintilla_lParam	scintilla_lparam;
	StateInsert		insert_building(true);
	StateInsert		insert_nobuilding(false);
	StateInsertIndent	insert_indent;
	State *current = &start;
}
namespace Modifiers {
	static bool colon = false;
	static bool at = false;
}
enum Mode mode = MODE_NORMAL;
/* FIXME: perhaps integrate into Mode */
static bool skip_else = false;
static gint nest_level = 0;
gchar *strings[2] = {NULL, NULL};
gchar escape_char = CTL_KEY_ESC;
/**
 * Handles all expected exceptions, converting them to
 * SciTECO::Error and preparing them for stack frame insertion.
 * This method will only throw SciTECO::Error and
 * SciTECO::Cmdline *.
 */
void
Execute::step(const gchar *macro, gint stop_pos)
{
	while (macro_pc < stop_pos) {
#ifdef DEBUG
		g_printf("EXEC(%d): input='%c'/%x, state=%p, mode=%d\n",
			 macro_pc, macro[macro_pc], macro[macro_pc],
			 States::current, mode);
#endif
		try {
			/*
			 * convert bad_alloc and other C++ standard
			 * library exceptions
			 */
			try {
				if (interface.is_interrupted())
					throw Error("Interrupted");
				State::input(macro[macro_pc]);
			} catch (std::exception &error) {
				throw StdError(error);
			}
		} catch (Error &error) {
			error.set_coord(macro, macro_pc);
			throw; /* forward */
		}
		macro_pc++;
	}
}
/*
 * may throw non SciTECO::Error exceptions which are not to be
 * associated with the macro invocation stack frame
 */
void
Execute::macro(const gchar *macro, bool locals)
{
	GotoTable *parent_goto_table = Goto::table;
	GotoTable macro_goto_table(false);
	QRegisterTable *parent_locals;
	State *parent_state = States::current;
	gint parent_pc = macro_pc;
	/*
	 * need this to fixup state on rubout: state machine emits undo token
	 * resetting state to parent's one, but the macro executed also emitted
	 * undo tokens resetting the state to StateStart
	 */
	undo.push_var(States::current) = &States::start;
	macro_pc = 0;
	Goto::table = ¯o_goto_table;
	/* locals are allocated so that we do not waste call stack space */
	if (locals) {
		parent_locals = QRegisters::locals;
		QRegisters::locals = new QRegisterTable(false);
	}
	try {
		step(macro, strlen(macro));
		/*
		 * Subsequent errors must still be
		 * attached to this macro invocation
		 * via Error::set_coord()
		 */
		try {
			if (Goto::skip_label)
				throw Error("Label \"%s\" not found",
				            Goto::skip_label);
			if (States::current != &States::start)
				/*
				 * can only happen if we returned because
				 * of macro end
				 */
				throw Error("Unterminated command");
			/*
			 * This handles the problem of Q-Registers
			 * local to the macro invocation being edited
			 * when the macro terminates.
			 * QRegisterTable::clear() throws an error
			 * if this happens and the Q-Reg editing
			 * is undone.
			 */
			if (locals)
				QRegisters::locals->clear();
		} catch (Error &error) {
			error.set_coord(macro, strlen(macro));
			throw; /* forward */
		}
	} catch (...) {
		g_free(Goto::skip_label);
		Goto::skip_label = NULL;
		if (locals) {
			delete QRegisters::locals;
			QRegisters::locals = parent_locals;
		}
		Goto::table = parent_goto_table;
		macro_pc = parent_pc;
		States::current = parent_state;
		throw; /* forward */
	}
	if (locals) {
		delete QRegisters::locals;
		QRegisters::locals = parent_locals;
	}
	Goto::table = parent_goto_table;
	macro_pc = parent_pc;
	States::current = parent_state;
}
void
Execute::file(const gchar *filename, bool locals)
{
	GError *gerror = NULL;
	gchar *macro_str, *p;
	if (!g_file_get_contents(filename, ¯o_str, NULL, &gerror))
		throw GlibError(gerror);
	/* only when executing files, ignore Hash-Bang line */
	if (*macro_str == '#')
		p = MAX(strchr(macro_str, '\r'), strchr(macro_str, '\n'))+1;
	else
		p = macro_str;
	try {
		macro(p, locals);
	} catch (Error &error) {
		error.pos += p - macro_str;
		if (*macro_str == '#')
			error.line++;
		error.add_frame(new Error::FileFrame(filename));
		g_free(macro_str);
		throw; /* forward */
	} catch (...) {
		g_free(macro_str);
		throw; /* forward */
	}
	g_free(macro_str);
}
State::State()
{
	for (guint i = 0; i < G_N_ELEMENTS(transitions); i++)
		transitions[i] = NULL;
}
bool
State::eval_colon(void)
{
	if (!Modifiers::colon)
		return false;
	undo.push_var(Modifiers::colon);
	Modifiers::colon = false;
	return true;
}
void
State::input(gchar chr)
{
	State *state = States::current;
	for (;;) {
		State *next = state->get_next_state(chr);
		if (next == state)
			break;
		state = next;
		chr = '\0';
	}
	if (state != States::current) {
		undo.push_var(States::current);
		States::current = state;
	}
}
State *
State::get_next_state(gchar chr)
{
	State *next = NULL;
	guint upper = g_ascii_toupper(chr);
	if (upper < G_N_ELEMENTS(transitions))
		next = transitions[upper];
	if (!next)
		next = custom(chr);
	if (!next)
		throw SyntaxError(chr);
	return next;
}
void
StringBuildingMachine::reset(void)
{
	MicroStateMachine::reset();
	undo.push_obj(qregspec_machine) = NULL;
	undo.push_var(mode) = MODE_NORMAL;
	undo.push_var(toctl) = false;
}
bool
StringBuildingMachine::input(gchar chr, gchar *&result)
{
	QRegister *reg;
	switch (mode) {
	case MODE_UPPER:
		chr = g_ascii_toupper(chr);
		break;
	case MODE_LOWER:
		chr = g_ascii_tolower(chr);
		break;
	default:
		break;
	}
	if (toctl) {
		if (chr != '^')
			chr = CTL_KEY(g_ascii_toupper(chr));
		undo.push_var(toctl) = false;
	} else if (chr == '^') {
		undo.push_var(toctl) = true;
		return false;
	}
MICROSTATE_START;
	switch (chr) {
	case CTL_KEY('Q'):
	case CTL_KEY('R'): set(&&StateEscaped); break;
	case CTL_KEY('V'): set(&&StateLower); break;
	case CTL_KEY('W'): set(&&StateUpper); break;
	case CTL_KEY('E'): set(&&StateCtlE); break;
	default:
		goto StateEscaped;
	}
	return false;
StateLower:
	set(StateStart);
	if (chr != CTL_KEY('V')) {
		result = String::chrdup(g_ascii_tolower(chr));
		return true;
	}
	undo.push_var(mode) = MODE_LOWER;
	return false;
StateUpper:
	set(StateStart);
	if (chr != CTL_KEY('W')) {
		result = String::chrdup(g_ascii_toupper(chr));
		return true;
	}
	undo.push_var(mode) = MODE_UPPER;
	return false;
StateCtlE:
	switch (g_ascii_toupper(chr)) {
	case '\\':
		undo.push_obj(qregspec_machine) = new QRegSpecMachine;
		set(&&StateCtlENum);
		break;
	case 'Q':
		undo.push_obj(qregspec_machine) = new QRegSpecMachine;
		set(&&StateCtlEQ);
		break;
	case 'U':
		undo.push_obj(qregspec_machine) = new QRegSpecMachine;
		set(&&StateCtlEU);
		break;
	default:
		result = (gchar *)g_malloc(3);
		set(StateStart);
		result[0] = CTL_KEY('E');
		result[1] = chr;
		result[2] = '\0';
		return true;
	}
	return false;
StateCtlENum:
	if (!qregspec_machine->input(chr, reg))
		return false;
	undo.push_obj(qregspec_machine) = NULL;
	set(StateStart);
	result = g_strdup(expressions.format(reg->get_integer()));
	return true;
StateCtlEU:
	if (!qregspec_machine->input(chr, reg))
		return false;
	undo.push_obj(qregspec_machine) = NULL;
	set(StateStart);
	result = String::chrdup((gchar)reg->get_integer());
	return true;
StateCtlEQ:
	if (!qregspec_machine->input(chr, reg))
		return false;
	undo.push_obj(qregspec_machine) = NULL;
	set(StateStart);
	result = reg->get_string();
	return true;
StateEscaped:
	set(StateStart);
	result = String::chrdup(chr);
	return true;
}
StringBuildingMachine::~StringBuildingMachine()
{
	delete qregspec_machine;
}
State *
StateExpectString::custom(gchar chr)
{
	gchar *insert;
	if (chr == '\0') {
		BEGIN_EXEC(this);
		initial();
		return this;
	}
	/*
	 * String termination handling
	 */
	if (Modifiers::at) {
		if (last)
			undo.push_var(Modifiers::at) = false;
		switch (escape_char) {
		case CTL_KEY_ESC:
		case '{':
			undo.push_var(escape_char) = g_ascii_toupper(chr);
			return this;
		}
	}
	if (escape_char == '{') {
		switch (chr) {
		case '{':
			undo.push_var(nesting)++;
			break;
		case '}':
			undo.push_var(nesting)--;
			break;
		}
	} else if (g_ascii_toupper(chr) == escape_char) {
		undo.push_var(nesting)--;
	}
	if (!nesting) {
		State *next;
		gchar *string = strings[0];
		undo.push_str(strings[0]) = NULL;
		if (last)
			undo.push_var(escape_char) = CTL_KEY_ESC;
		nesting = 1;
		if (string_building)
			machine.reset();
		try {
			next = done(string ? : "");
		} catch (...) {
			g_free(string);
			throw;
		}
		g_free(string);
		return next;
	}
	BEGIN_EXEC(this);
	/*
	 * String building characters
	 */
	if (string_building) {
		if (!machine.input(chr, insert))
			return this;
	} else {
		insert = String::chrdup(chr);
	}
	/*
	 * String accumulation
	 */
	undo.push_str(strings[0]);
	String::append(strings[0], insert);
	try {
		process(strings[0], strlen(insert));
	} catch (...) {
		g_free(insert);
		throw;
	}
	g_free(insert);
	return this;
}
State *
StateExpectFile::done(const gchar *str)
{
	gchar *filename = expand_path(str);
	State *next;
	try {
		next = got_file(filename);
	} catch (...) {
		g_free(filename);
		throw;
	}
	g_free(filename);
	return next;
}
StateStart::StateStart() : State()
{
	transitions['\0'] = this;
	init(" \f\r\n\v");
	transitions['!'] = &States::label;
	transitions['O'] = &States::gotocmd;
	transitions['^'] = &States::control;
	transitions['F'] = &States::fcommand;
	transitions['"'] = &States::condcommand;
	transitions['E'] = &States::ecommand;
	transitions['I'] = &States::insert_building;
	transitions['S'] = &States::search;
	transitions['N'] = &States::searchall;
	transitions['['] = &States::pushqreg;
	transitions[']'] = &States::popqreg;
	transitions['G'] = &States::getqregstring;
	transitions['Q'] = &States::queryqreg;
	transitions['U'] = &States::setqreginteger;
	transitions['%'] = &States::increaseqreg;
	transitions['M'] = &States::macro;
	transitions['X'] = &States::copytoqreg;
}
void
StateStart::insert_integer(tecoInt v)
{
	const gchar *str = expressions.format(v);
	interface.ssm(SCI_BEGINUNDOACTION);
	interface.ssm(SCI_ADDTEXT, strlen(str), (sptr_t)str);
	interface.ssm(SCI_SCROLLCARET);
	interface.ssm(SCI_ENDUNDOACTION);
	ring.dirtify();
	if (current_doc_must_undo())
		interface.undo_ssm(SCI_UNDO);
}
tecoInt
StateStart::read_integer(void)
{
	uptr_t pos = interface.ssm(SCI_GETCURRENTPOS);
	gchar c = (gchar)interface.ssm(SCI_GETCHARAT, pos);
	tecoInt v = 0;
	gint sign = 1;
	if (c == '-') {
		pos++;
		sign = -1;
	}
	for (;;) {
		c = g_ascii_toupper((gchar)interface.ssm(SCI_GETCHARAT, pos));
		if (c >= '0' && c <= '0' + MIN(expressions.radix, 10) - 1)
			v = (v*expressions.radix) + (c - '0');
		else if (c >= 'A' &&
			 c <= 'A' + MIN(expressions.radix - 10, 26) - 1)
			v = (v*expressions.radix) + 10 + (c - 'A');
		else
			break;
		pos++;
	}
	return sign * v;
}
tecoBool
StateStart::move_chars(tecoInt n)
{
	sptr_t pos = interface.ssm(SCI_GETCURRENTPOS);
	if (!Validate::pos(pos + n))
		return FAILURE;
	interface.ssm(SCI_GOTOPOS, pos + n);
	if (current_doc_must_undo())
		interface.undo_ssm(SCI_GOTOPOS, pos);
	return SUCCESS;
}
tecoBool
StateStart::move_lines(tecoInt n)
{
	sptr_t pos = interface.ssm(SCI_GETCURRENTPOS);
	sptr_t line = interface.ssm(SCI_LINEFROMPOSITION, pos) + n;
	if (!Validate::line(line))
		return FAILURE;
	interface.ssm(SCI_GOTOLINE, line);
	if (current_doc_must_undo())
		interface.undo_ssm(SCI_GOTOPOS, pos);
	return SUCCESS;
}
tecoBool
StateStart::delete_words(tecoInt n)
{
	sptr_t pos, size;
	if (!n)
		return SUCCESS;
	pos = interface.ssm(SCI_GETCURRENTPOS);
	size = interface.ssm(SCI_GETLENGTH);
	interface.ssm(SCI_BEGINUNDOACTION);
	/*
	 * FIXME: would be nice to do this with constant amount of
	 * editor messages. E.g. by using custom algorithm accessing
	 * the internal document buffer.
	 */
	if (n > 0) {
		while (n--) {
			sptr_t size = interface.ssm(SCI_GETLENGTH);
			interface.ssm(SCI_DELWORDRIGHTEND);
			if (size == interface.ssm(SCI_GETLENGTH))
				break;
		}
	} else {
		n *= -1;
		while (n--) {
			sptr_t pos = interface.ssm(SCI_GETCURRENTPOS);
			//interface.ssm(SCI_DELWORDLEFTEND);
			interface.ssm(SCI_WORDLEFTEND);
			if (pos == interface.ssm(SCI_GETCURRENTPOS))
				break;
			interface.ssm(SCI_DELWORDRIGHTEND);
		}
	}
	interface.ssm(SCI_ENDUNDOACTION);
	if (n >= 0) {
		if (size != interface.ssm(SCI_GETLENGTH)) {
			interface.ssm(SCI_UNDO);
			interface.ssm(SCI_GOTOPOS, pos);
		}
		return FAILURE;
	}
	interface.undo_ssm(SCI_GOTOPOS, pos);
	if (current_doc_must_undo())
		interface.undo_ssm(SCI_UNDO);
	ring.dirtify();
	return SUCCESS;
}
State *
StateStart::custom(gchar chr)
{
	tecoInt v;
	tecoBool rc;
	/*
	 *  commands implemented in StateCtrlCmd
	 */
	if (IS_CTL(chr))
		return States::control.get_next_state(CTL_ECHO(chr));
	/*
	 * arithmetics
	 */
	/*$
	 * [n]0|1|2|3|4|5|6|7|8|9 -> n*Radix+X -- Append digit
	 *
	 * Integer constants in \*(ST may be thought of and are
	 * technically sequences of single-digit commands.
	 * These commands take one argument from the stack
	 * (0 is implied), multiply it with the current radix
	 * (2, 8, 10, 16, ...), add the digit's value and
	 * return the resultant integer.
	 *
	 * The command-like semantics of digits may be abused
	 * in macros, for instance to append digits to computed
	 * integers.
	 * It is not an error to append a digit greater than the
	 * current radix - this may be changed in the future.
	 */
	if (g_ascii_isdigit(chr)) {
		BEGIN_EXEC(this);
		expressions.add_digit(chr);
		return this;
	}
	chr = g_ascii_toupper(chr);
	switch (chr) {
	case '/':
		BEGIN_EXEC(this);
		expressions.push_calc(Expressions::OP_DIV);
		break;
	case '*':
		if (cmdline.len == 1 && cmdline[0] == '*')
			/* special save last commandline command */
			return &States::save_cmdline;
		BEGIN_EXEC(this);
		expressions.push_calc(Expressions::OP_MUL);
		break;
	case '+':
		BEGIN_EXEC(this);
		expressions.push_calc(Expressions::OP_ADD);
		break;
	case '-':
		BEGIN_EXEC(this);
		if (!expressions.args())
			expressions.set_num_sign(-expressions.num_sign);
		else
			expressions.push_calc(Expressions::OP_SUB);
		break;
	case '&':
		BEGIN_EXEC(this);
		expressions.push_calc(Expressions::OP_AND);
		break;
	case '#':
		BEGIN_EXEC(this);
		expressions.push_calc(Expressions::OP_OR);
		break;
	case '(':
		BEGIN_EXEC(this);
		if (expressions.num_sign < 0) {
			expressions.set_num_sign(1);
			expressions.eval();
			expressions.push(-1);
			expressions.push_calc(Expressions::OP_MUL);
		}
		expressions.push(Expressions::OP_BRACE);
		break;
	case ')':
		BEGIN_EXEC(this);
		expressions.eval(true);
		break;
	case ',':
		BEGIN_EXEC(this);
		expressions.eval();
		expressions.push(Expressions::OP_NEW);
		break;
	/*$
	 * \&. -> dot -- Return buffer position
	 *
	 * \(lq.\(rq pushes onto the stack, the current
	 * position (also called ) of the currently
	 * selected buffer or Q-Register.
	 */
	case '.':
		BEGIN_EXEC(this);
		expressions.eval();
		expressions.push(interface.ssm(SCI_GETCURRENTPOS));
		break;
	/*$
	 * Z -> size -- Return buffer size
	 *
	 * Pushes onto the stack, the size of the currently selected
	 * buffer or Q-Register.
	 * This is value is also the buffer position of the document's
	 * end.
	 */
	case 'Z':
		BEGIN_EXEC(this);
		expressions.eval();
		expressions.push(interface.ssm(SCI_GETLENGTH));
		break;
	/*$
	 * H -> 0,Z -- Return range for entire buffer
	 *
	 * Pushes onto the stack the integer 0 (position of buffer
	 * beginning) and the current buffer's size.
	 * It is thus often equivalent to the expression
	 * \(lq0,Z\(rq, or more generally \(lq(0,Z)\(rq.
	 */
	case 'H':
		BEGIN_EXEC(this);
		expressions.eval();
		expressions.push(0);
		expressions.push(interface.ssm(SCI_GETLENGTH));
		break;
	/*$
	 * n\\ -- Insert or read ASCII numbers
	 * \\ -> n
	 *
	 * Backslash pops a value from the stack, formats it
	 * according to the current radix and inserts it in the
	 * current buffer or Q-Register at dot.
	 * If  is omitted (empty stack), it does the reverse -
	 * it reads from the current buffer position an integer
	 * in the current radix and pushes it onto the stack.
	 * Dot is not changed when reading integers.
	 *
	 * In other words, the command serializes or deserializes
	 * integers as ASCII characters.
	 */
	case '\\':
		BEGIN_EXEC(this);
		expressions.eval();
		if (expressions.args())
			insert_integer(expressions.pop_num_calc());
		else
			expressions.push(read_integer());
		break;
	/*
	 * control structures (loops)
	 */
	case '<':
		if (mode == MODE_PARSE_ONLY_LOOP) {
			undo.push_var(nest_level);
			nest_level++;
			return this;
		}
		BEGIN_EXEC(this);
		expressions.eval();
		if (!expressions.args())
			/* infinite loop */
			expressions.push(-1);
		if (!expressions.peek_num()) {
			expressions.pop_num();
			/* skip to end of loop */
			undo.push_var(mode);
			mode = MODE_PARSE_ONLY_LOOP;
		} else {
			expressions.push(macro_pc);
			expressions.push(Expressions::OP_LOOP);
		}
		break;
	case '>':
		if (mode == MODE_PARSE_ONLY_LOOP) {
			if (!nest_level) {
				undo.push_var(mode);
				mode = MODE_NORMAL;
			} else {
				undo.push_var(nest_level);
				nest_level--;
			}
		} else {
			BEGIN_EXEC(this);
			tecoInt loop_pc, loop_cnt;
			expressions.discard_args();
			if (expressions.pop_op() != Expressions::OP_LOOP)
				throw Error("Loop end without corresponding "
				            "loop start command");
			loop_pc = expressions.pop_num();
			loop_cnt = expressions.pop_num();
			if (loop_cnt != 1) {
				/* repeat loop */
				macro_pc = loop_pc;
				expressions.push(MAX(loop_cnt - 1, -1));
				expressions.push(loop_pc);
				expressions.push(Expressions::OP_LOOP);
			}
		}
		break;
	/*$
	 * [bool]; -- Conditionally break from loop
	 * [bool]:;
	 *
	 * Breaks from the current inner-most loop if 
	 * signifies failure (non-negative value).
	 * If colon-modified, breaks from the loop if 
	 * signifies success (negative value).
	 *
	 * If the condition code cannot be popped from the stack,
	 * the global search register's condition integer
	 * is implied instead.
	 * This way, you may break on search success/failures
	 * without colon-modifying the search command (or at a
	 * later point).
	 *
	 * Executing \(lq;\(rq outside of iterations yields an
	 * error.
	 */
	case ';':
		BEGIN_EXEC(this);
		v = QRegisters::globals["_"]->get_integer();
		rc = expressions.pop_num_calc(0, v);
		if (eval_colon())
			rc = ~rc;
		if (IS_FAILURE(rc)) {
			expressions.discard_args();
			/*
			 * FIXME: it would be better accroding to the
			 * TECO standard to throw an error
			 * always when we're not in a loop.
			 * But this is not easy to find out without
			 * modifying the expression stack.
			 */
			if (expressions.pop_op() != Expressions::OP_LOOP)
				throw Error("<;> only allowed in iterations");
			expressions.pop_num(); /* pc */
			expressions.pop_num(); /* counter */
			/* skip to end of loop */
			undo.push_var(mode);
			mode = MODE_PARSE_ONLY_LOOP;
		}
		break;
	/*
	 * control structures (conditionals)
	 */
	case '|':
		if (mode == MODE_PARSE_ONLY_COND) {
			if (!skip_else && !nest_level) {
				undo.push_var(mode);
				mode = MODE_NORMAL;
			}
			return this;
		}
		BEGIN_EXEC(this);
		/* skip to end of conditional; skip ELSE-part */
		undo.push_var(mode);
		mode = MODE_PARSE_ONLY_COND;
		break;
	case '\'':
		if (mode != MODE_PARSE_ONLY_COND)
			break;
		if (!nest_level) {
			undo.push_var(mode);
			mode = MODE_NORMAL;
			undo.push_var(skip_else);
			skip_else = false;
		} else {
			undo.push_var(nest_level);
			nest_level--;
		}
		break;
	/*
	 * Command-line editing
	 */
	/*$
	 * { -- Edit command line
	 * }
	 *
	 * The opening curly bracket is a powerful command
	 * to edit command lines but has very simple semantics.
	 * It copies the current commandline into the global
	 * command line editing register (called Escape, i.e.
	 * ASCII 27) and edits this register.
	 * The curly bracket itself is not copied.
	 *
	 * The command line may then be edited using any
	 * \*(ST command or construct.
	 * You may switch between the command line editing
	 * register and other registers or buffers.
	 * The user will then usually reapply (called update)
	 * the current command-line.
	 *
	 * The closing curly bracket will update the current
	 * command-line with the contents of the global command
	 * line editing register.
	 * To do so it merely rubs-out the current command-line
	 * up to the first changed character and inserts
	 * all characters following from the updated command
	 * line into the command stream.
	 * To prevent the undesired rubout of the entire
	 * command-line, the replacement command ("}") is only
	 * allowed when the replacement register currently edited
	 * since it will otherwise be usually empty.
	 *
	 * .B Note:
	 *   - Command line editing only works on command lines,
	 *     but not arbitrary macros.
	 *     It is therefore not available in batch mode and
	 *     will yield an error if used.
	 *   - Command line editing commands may be safely used
	 *     from macro invocations.
	 *     Such macros are called command line editing macros.
	 *   - A command line update from a macro invocation will
	 *     always yield to the outer-most macro level (i.e.
	 *     the command line macro).
	 *     Code following the update command in the macro
	 *     will thus never be executed.
	 *   - As a safe-guard against command line trashing due
	 *     to erroneous changes at the beginning of command
	 *     lines, a backup mechanism is implemented:
	 *     If the updated command line yields an error at
	 *     any command during the update, the original
	 *     command line will be restored with an algorithm
	 *     similar to command line updating and the update
	 *     command will fail instead.
	 *     That way it behaves like any other command that
	 *     yields an error:
	 *     The character resulting in the update is rejected
	 *     by the command line input subsystem.
	 *   - In the rare case that an aforementioned command line
	 *     backup fails, the commands following the erroneous
	 *     character will not be inserted again (will be lost).
	 */
	case '{':
		BEGIN_EXEC(this);
		if (!undo.enabled)
			throw Error("Command-line editing only possible in "
				    "interactive mode");
		current_doc_undo_edit();
		QRegisters::globals.edit(CTL_KEY_ESC_STR);
		interface.ssm(SCI_BEGINUNDOACTION);
		interface.ssm(SCI_CLEARALL);
		interface.ssm(SCI_ADDTEXT, cmdline.pc, (sptr_t)cmdline.str);
		interface.ssm(SCI_SCROLLCARET);
		interface.ssm(SCI_ENDUNDOACTION);
		/* must always support undo on global register */
		interface.undo_ssm(SCI_UNDO);
		break;
	case '}':
		BEGIN_EXEC(this);
		if (!undo.enabled)
			throw Error("Command-line editing only possible in "
				    "interactive mode");
		if (QRegisters::current != QRegisters::globals[CTL_KEY_ESC_STR])
			throw Error("Command-line replacement only allowed when "
				    "editing the replacement register");
		/* replace cmdline in the outer macro environment */
		cmdline.replace();
		/* never reached */
	/*
	 * modifiers
	 */
	case '@':
		/*
		 * @ modifier has syntactic significance, so set it even
		 * in PARSE_ONLY* modes
		 */
		undo.push_var(Modifiers::at);
		Modifiers::at = true;
		break;
	case ':':
		BEGIN_EXEC(this);
		undo.push_var(Modifiers::colon);
		Modifiers::colon = true;
		break;
	/*
	 * commands
	 */
	/*$
	 * [position]J -- Go to position in buffer
	 * [position]:J -> Success|Failure
	 *
	 * Sets dot to .
	 * If  is omitted, 0 is implied and \(lqJ\(rq will
	 * go to the beginning of the buffer.
	 *
	 * If  is outside the range of the buffer, the
	 * command yields an error.
	 * If colon-modified, the command will instead return a
	 * condition boolean signalling whether the position could
	 * be changed or not.
	 */
	case 'J':
		BEGIN_EXEC(this);
		v = expressions.pop_num_calc(0, 0);
		if (Validate::pos(v)) {
			if (current_doc_must_undo())
				interface.undo_ssm(SCI_GOTOPOS,
				                   interface.ssm(SCI_GETCURRENTPOS));
			interface.ssm(SCI_GOTOPOS, v);
			if (eval_colon())
				expressions.push(SUCCESS);
		} else if (eval_colon()) {
			expressions.push(FAILURE);
		} else {
			throw MoveError("J");
		}
		break;
	/*$
	 * [n]C -- Move dot  characters
	 * -C
	 * [n]:C -> Success|Failure
	 *
	 * Adds  to dot. 1 or -1 is implied if  is omitted.
	 * Fails if  would move dot off-page.
	 * The colon modifier results in a success-boolean being
	 * returned instead.
	 */
	case 'C':
		BEGIN_EXEC(this);
		rc = move_chars(expressions.pop_num_calc());
		if (eval_colon())
			expressions.push(rc);
		else if (IS_FAILURE(rc))
			throw MoveError("C");
		break;
	/*$
	 * [n]R -- Move dot  characters backwards
	 * -R
	 * [n]:R -> Success|Failure
	 *
	 * Subtracts  from dot.
	 * It is equivalent to \(lq-nC\(rq.
	 */
	case 'R':
		BEGIN_EXEC(this);
		rc = move_chars(-expressions.pop_num_calc());
		if (eval_colon())
			expressions.push(rc);
		else if (IS_FAILURE(rc))
			throw MoveError("R");
		break;
	/*$
	 * [n]L -- Move dot  lines forwards
	 * -L
	 * [n]:L -> Success|Failure
	 *
	 * Move dot to the beginning of the line specified
	 * relatively to the current line.
	 * Therefore a value of 0 for  goes to the
	 * beginning of the current line, 1 will go to the
	 * next line, -1 to the previous line etc.
	 * If  is omitted, 1 or -1 is implied depending on
	 * the sign prefix.
	 *
	 * If  would move dot off-page, the command yields
	 * an error.
	 * The colon-modifer results in a condition boolean
	 * being returned instead.
	 */
	case 'L':
		BEGIN_EXEC(this);
		rc = move_lines(expressions.pop_num_calc());
		if (eval_colon())
			expressions.push(rc);
		else if (IS_FAILURE(rc))
			throw MoveError("L");
		break;
	/*$
	 * [n]B -- Move dot  lines backwards
	 * -B
	 * [n]:B -> Success|Failure
	 *
	 * Move dot to the beginning of the line 
	 * lines before the current one.
	 * It is equivalent to \(lq-nL\(rq.
	 */
	case 'B':
		BEGIN_EXEC(this);
		rc = move_lines(-expressions.pop_num_calc());
		if (eval_colon())
			expressions.push(rc);
		else if (IS_FAILURE(rc))
			throw MoveError("B");
		break;
	/*$
	 * [n]W -- Move dot by words
	 * -W
	 * [n]:W -> Success|Failure
	 *
	 * Move dot  words forward.
	 *   - If  is positive, dot is positioned at the beginning
	 *     of the word  words after the current one.
	 *   - If  is negative, dot is positioned at the end
	 *     of the word  words before the current one.
	 *   - If  is zero, dot is not moved.
	 *
	 * \(lqW\(rq uses Scintilla's definition of a word as
	 * configurable using the
	 * .B SCI_SETWORDCHARS
	 * message.
	 *
	 * Otherwise, the command's behaviour is analogous to
	 * the \(lqC\(rq command.
	 */
	case 'W': {
		sptr_t pos;
		unsigned int msg = SCI_WORDRIGHTEND;
		BEGIN_EXEC(this);
		v = expressions.pop_num_calc();
		pos = interface.ssm(SCI_GETCURRENTPOS);
		/*
		 * FIXME: would be nice to do this with constant amount of
		 * editor messages. E.g. by using custom algorithm accessing
		 * the internal document buffer.
		 */
		if (v < 0) {
			v *= -1;
			msg = SCI_WORDLEFTEND;
		}
		while (v--) {
			sptr_t pos = interface.ssm(SCI_GETCURRENTPOS);
			interface.ssm(msg);
			if (pos == interface.ssm(SCI_GETCURRENTPOS))
				break;
		}
		if (v < 0) {
			if (current_doc_must_undo())
				interface.undo_ssm(SCI_GOTOPOS, pos);
			if (eval_colon())
				expressions.push(SUCCESS);
		} else {
			interface.ssm(SCI_GOTOPOS, pos);
			if (eval_colon())
				expressions.push(FAILURE);
			else
				throw MoveError("W");
		}
		break;
	}
	/*$
	 * [n]V -- Delete words forward
	 * -V
	 * [n]:V -> Success|Failure
	 *
	 * Deletes the next  words until the end of the
	 * n'th word after the current one.
	 * If  is negative, deletes up to end of the
	 * n'th word before the current one.
	 * If  is omitted, 1 or -1 is implied depending on the
	 * sign prefix.
	 *
	 * It uses Scintilla's definition of a word as configurable
	 * using the
	 * .B SCI_SETWORDCHARS
	 * message.
	 *
	 * If the words to delete extend beyond the range of the
	 * buffer, the command yields an error.
	 * If colon-modified it instead returns a condition code.
	 */
	case 'V':
		BEGIN_EXEC(this);
		rc = delete_words(expressions.pop_num_calc());
		if (eval_colon())
			expressions.push(rc);
		else if (IS_FAILURE(rc))
			throw Error("Not enough words to delete with ");
		break;
	/*$
	 * [n]Y -- Delete word backwards
	 * -Y
	 * [n]:Y -> Success|Failure
	 *
	 * Delete  words backward.
	 * Y is equivalent to \(lq-nV\(rq.
	 */
	case 'Y':
		BEGIN_EXEC(this);
		rc = delete_words(-expressions.pop_num_calc());
		if (eval_colon())
			expressions.push(rc);
		else if (IS_FAILURE(rc))
			throw Error("Not enough words to delete with ");
		break;
	/*$
	 * = -- Show value as message
	 *
	 * Shows integer  as a message in the message line and/or
	 * on the console.
	 * It is currently always formatted as a decimal integer and
	 * shown with the user-message severity.
	 * The command fails if  is not given.
	 */
	/**
	 * @todo perhaps care about current radix
	 * @todo colon-modifier to suppress line-break on console?
	 */
	case '=':
		BEGIN_EXEC(this);
		expressions.eval();
		if (!expressions.args())
			throw ArgExpectedError('=');
		interface.msg(InterfaceCurrent::MSG_USER,
		              "%" TECO_INTEGER_FORMAT,
			      expressions.pop_num_calc());
		break;
	/*$
	 * [n]K -- Kill lines
	 * -K
	 * from,to K
	 * [n]:K -> Success|Failure
	 * from,to:K -> Success|Failure
	 *
	 * Deletes characters up to the beginning of the
	 * line  lines after or before the current one.
	 * If  is 0, \(lqK\(rq will delete up to the beginning
	 * of the current line.
	 * If  is omitted, the sign prefix will be implied.
	 * So to delete the entire line regardless of the position
	 * in it, one can use \(lq0KK\(rq.
	 *
	 * If the deletion is beyond the buffer's range, the command
	 * will yield an error unless it has been colon-modified
	 * so it returns a condition code.
	 *
	 * If two arguments  and  are available, the
	 * command is synonymous to ,D.
	 */
	case 'K':
	/*$
	 * [n]D -- Delete characters
	 * -D
	 * from,to D
	 * [n]:D -> Success|Failure
	 * from,to:D -> Success|Failure
	 *
	 * If  is positive, the next  characters (up to and
	 * character .+) are deleted.
	 * If  is negative, the previous  characters are
	 * deleted.
	 * If  is omitted, the sign prefix will be implied.
	 *
	 * If two arguments can be popped from the stack, the
	 * command will delete the characters with absolute
	 * position  up to  from the current buffer.
	 *
	 * If the character range to delete is beyond the buffer's
	 * range, the command will yield an error unless it has
	 * been colon-modified so it returns a condition code
	 * instead.
	 */
	case 'D': {
		tecoInt from, len;
		BEGIN_EXEC(this);
		expressions.eval();
		if (expressions.args() <= 1) {
			from = interface.ssm(SCI_GETCURRENTPOS);
			if (chr == 'D') {
				len = expressions.pop_num_calc();
				rc = TECO_BOOL(Validate::pos(from + len));
			} else /* chr == 'K' */ {
				sptr_t line;
				line = interface.ssm(SCI_LINEFROMPOSITION, from) +
				       expressions.pop_num_calc();
				len = interface.ssm(SCI_POSITIONFROMLINE, line)
				    - from;
				rc = TECO_BOOL(Validate::line(line));
			}
			if (len < 0) {
				len *= -1;
				from -= len;
			}
		} else {
			tecoInt to = expressions.pop_num();
			from = expressions.pop_num();
			len = to - from;
			rc = TECO_BOOL(len >= 0 && Validate::pos(from) &&
				       Validate::pos(to));
		}
		if (eval_colon())
			expressions.push(rc);
		else if (IS_FAILURE(rc))
			throw RangeError(chr);
		if (len == 0 || IS_FAILURE(rc))
			break;
		if (current_doc_must_undo()) {
			interface.undo_ssm(SCI_GOTOPOS, interface.ssm(SCI_GETCURRENTPOS));
			interface.undo_ssm(SCI_UNDO);
		}
		interface.ssm(SCI_BEGINUNDOACTION);
		interface.ssm(SCI_DELETERANGE, from, len);
		interface.ssm(SCI_ENDUNDOACTION);
		ring.dirtify();
		break;
	}
	/*$
	 * [n]A -> code -- Get character code from buffer
	 * -A -> code
	 *
	 * Returns the character  of the character
	 *  relative to dot from the buffer.
	 * This can be an ASCII  or Unicode codepoint
	 * depending on Scintilla's encoding of the current
	 * buffer.
	 *   - If  is 0, return the  of the character
	 *     pointed to by dot.
	 *   - If  is 1, return the  of the character
	 *     immediately after dot.
	 *   - If  is -1, return the  of the character
	 *     immediately preceding dot, ecetera.
	 *   - If  is omitted, the sign prefix is implied.
	 *
	 * If the position of the queried character is off-page,
	 * the command will yield an error.
	 */
	/** @todo does Scintilla really return code points??? */
	case 'A':
		BEGIN_EXEC(this);
		v = interface.ssm(SCI_GETCURRENTPOS) +
		    expressions.pop_num_calc();
		/*
		 * NOTE: We cannot use Validate::pos() here since
		 * the end of the buffer is not a valid position for .
		 */
		if (v < 0 || v >= interface.ssm(SCI_GETLENGTH))
			throw RangeError("A");
		expressions.push(interface.ssm(SCI_GETCHARAT, v));
		break;
	default:
		throw SyntaxError(chr);
	}
	return this;
}
StateFCommand::StateFCommand() : State()
{
	transitions['\0'] = this;
	transitions['K'] = &States::searchkill;
	transitions['D'] = &States::searchdelete;
	transitions['S'] = &States::replace;
	transitions['R'] = &States::replacedefault;
	transitions['G'] = &States::changedir;
}
State *
StateFCommand::custom(gchar chr)
{
	switch (chr) {
	/*
	 * loop flow control
	 */
	/*$
	 * F< -- Go to loop start
	 *
	 * Immediately jumps to the current loop's start.
	 * Also works from inside conditionals.
	 */
	case '<':
		BEGIN_EXEC(&States::start);
		/* FIXME: what if in brackets? */
		expressions.discard_args();
		if (expressions.peek_op() == Expressions::OP_LOOP)
			/* repeat loop */
			macro_pc = expressions.peek_num();
		else
			macro_pc = -1;
		break;
	/*$
	 * F> -- Go to loop end
	 *
	 * Jumps to the current loop's end.
	 * If the loop has a counter or runs idefinitely, the jump
	 * is performed immediately.
	 * If the loop has reached its last iteration, parsing
	 * until the loop end command has been found is performed.
	 *
	 * In interactive mode, if the loop is incomplete and must
	 * be exited, you can type in the loop's remaining commands
	 * without them being executed (but they are parsed).
	 *
	 * Calling \fBF\>\fP outside of a loop will throw an
	 * error.
	 */
	case '>': {
		tecoInt loop_pc, loop_cnt;
		BEGIN_EXEC(&States::start);
		/* FIXME: what if in brackets? */
		expressions.discard_args();
		if (expressions.pop_op() != Expressions::OP_LOOP)
			throw Error("Jump to loop end without corresponding "
			            "loop start command");
		loop_pc = expressions.pop_num();
		loop_cnt = expressions.pop_num();
		if (loop_cnt != 1) {
			/* repeat loop */
			macro_pc = loop_pc;
			expressions.push(MAX(loop_cnt - 1, -1));
			expressions.push(loop_pc);
			expressions.push(Expressions::OP_LOOP);
		} else {
			/* skip to end of loop */
			undo.push_var(mode);
			mode = MODE_PARSE_ONLY_LOOP;
		}
		break;
	}
	/*
	 * conditional flow control
	 */
	/*$
	 * F\' -- Jump to end of conditional
	 */
	case '\'':
		BEGIN_EXEC(&States::start);
		/* skip to end of conditional */
		undo.push_var(mode);
		mode = MODE_PARSE_ONLY_COND;
		undo.push_var(skip_else);
		skip_else = true;
		break;
	/*$
	 * F| -- Jump to else-part of conditional
	 *
	 * Jump to else-part of conditional or end of
	 * conditional (only if invoked from inside the
	 * condition's else-part).
	 */
	case '|':
		BEGIN_EXEC(&States::start);
		/* skip to ELSE-part or end of conditional */
		undo.push_var(mode);
		mode = MODE_PARSE_ONLY_COND;
		break;
	default:
		throw SyntaxError(chr);
	}
	return &States::start;
}
void
UndoTokenChangeDir::run(void)
{
	/*
	 * Changing the directory on rub-out may fail.
	 * This is handled silently.
	 */
	g_chdir(dir);
}
/*$
 * FG[directory]$ -- Change working directory
 *
 * Changes the process' current working directory
 * to  which affects all subsequent
 * operations on relative file names like
 * tab-completions.
 * It is also inherited by external processes spawned
 * via \fBEC\fP and \fBEG\fP.
 *
 * If  is omitted, the working directory
 * is changed to the current user's home directory
 * as set by the \fBHOME\fP environment variable
 * (i.e. its corresponding \(lq$HOME\(rq environment
 * register).
 * This variable is always initialized by \*(ST
 * (see \fBsciteco\fP(1)).
 * Therefore the expression \(lqFG\fB$\fP\(rq is
 * exactly equivalent to both \(lqFG~\fB$\fP\(rq and
 * \(lqFG^EQ[$HOME]\fB$\fP\(rq.
 *
 * The current working directory is also mapped to
 * the special global Q-Register \(lq$\(rq (dollar sign)
 * which may be used retrieve the current working directory.
 *
 * String-building characters are enabled on this
 * command and directories can be tab-completed.
 */
State *
StateChangeDir::got_file(const gchar *filename)
{
	gchar *dir;
	BEGIN_EXEC(&States::start);
	/* passes ownership of string to undo token object */
	undo.push(new UndoTokenChangeDir(g_get_current_dir()));
	dir = *filename ? g_strdup(filename)
	                : QRegisters::globals["$HOME"]->get_string();
	if (g_chdir(dir)) {
		/* FIXME: Is errno usable on Windows here? */
		Error err("Cannot change working directory "
		          "to \"%s\"", dir);
		g_free(dir);
		throw err;
	}
	g_free(dir);
	return &States::start;
}
StateCondCommand::StateCondCommand() : State()
{
	transitions['\0'] = this;
}
State *
StateCondCommand::custom(gchar chr)
{
	tecoInt value = 0;
	bool result;
	switch (mode) {
	case MODE_PARSE_ONLY_COND:
		undo.push_var(nest_level)++;
		break;
	case MODE_NORMAL:
		expressions.eval();
		if (chr == '~')
			/* don't pop value for ~ conditionals */
			break;
		if (!expressions.args())
			throw ArgExpectedError('"');
		value = expressions.pop_num_calc();
		break;
	default:
		break;
	}
	switch (g_ascii_toupper(chr)) {
	case '~':
		BEGIN_EXEC(&States::start);
		result = !expressions.args();
		break;
	case 'A':
		BEGIN_EXEC(&States::start);
		result = g_ascii_isalpha((gchar)value);
		break;
	case 'C':
		BEGIN_EXEC(&States::start);
		result = g_ascii_isalnum((gchar)value) ||
			 value == '.' || value == '$' || value == '_';
		break;
	case 'D':
		BEGIN_EXEC(&States::start);
		result = g_ascii_isdigit((gchar)value);
		break;
	case 'I':
		BEGIN_EXEC(&States::start);
		result = G_IS_DIR_SEPARATOR((gchar)value);
		break;
	case 'S':
	case 'T':
		BEGIN_EXEC(&States::start);
		result = IS_SUCCESS(value);
		break;
	case 'F':
	case 'U':
		BEGIN_EXEC(&States::start);
		result = IS_FAILURE(value);
		break;
	case 'E':
	case '=':
		BEGIN_EXEC(&States::start);
		result = value == 0;
		break;
	case 'G':
	case '>':
		BEGIN_EXEC(&States::start);
		result = value > 0;
		break;
	case 'L':
	case '<':
		BEGIN_EXEC(&States::start);
		result = value < 0;
		break;
	case 'N':
		BEGIN_EXEC(&States::start);
		result = value != 0;
		break;
	case 'R':
		BEGIN_EXEC(&States::start);
		result = g_ascii_isalnum((gchar)value);
		break;
	case 'V':
		BEGIN_EXEC(&States::start);
		result = g_ascii_islower((gchar)value);
		break;
	case 'W':
		BEGIN_EXEC(&States::start);
		result = g_ascii_isupper((gchar)value);
		break;
	default:
		throw Error("Invalid conditional type \"%c\"", chr);
	}
	if (!result)
		/* skip to ELSE-part or end of conditional */
		undo.push_var(mode) = MODE_PARSE_ONLY_COND;
	return &States::start;
}
StateControl::StateControl() : State()
{
	transitions['\0'] = this;
	transitions['I'] = &States::insert_indent;
	transitions['U'] = &States::ctlucommand;
	transitions['^'] = &States::ascii;
}
State *
StateControl::custom(gchar chr)
{
	switch (g_ascii_toupper(chr)) {
	/*$
	 * ^O -- Set radix to 8 (octal)
	 */
	case 'O':
		BEGIN_EXEC(&States::start);
		expressions.set_radix(8);
		break;
	/*$
	 * ^D -- Set radix to 10 (decimal)
	 */
	case 'D':
		BEGIN_EXEC(&States::start);
		expressions.set_radix(10);
		break;
	/*$
	 * radix^R -- Set and get radix
	 * ^R -> radix
	 *
	 * Set current radix to arbitrary value .
	 * If  is omitted, the command instead
	 * returns the current radix.
	 */
	case 'R':
		BEGIN_EXEC(&States::start);
		expressions.eval();
		if (!expressions.args())
			expressions.push(expressions.radix);
		else
			expressions.set_radix(expressions.pop_num_calc());
		break;
	/*
	 * Alternatives: ^[, , 
	 */
	/*$
	 * ^[ -- Discard all arguments
	 * $
	 *
	 * Pops and discards all values from the stack that
	 * might otherwise be used as arguments to following
	 * commands.
	 * Therefore it stops popping on stack boundaries like
	 * they are introduced by arithmetic brackets or loops.
	 *
	 * Note that ^[ is usually typed using the Escape key.
	 * CTRL+[ however is possible as well and equivalent to
	 * Escape in every manner.
	 * The Caret-[ notation however is processed like any
	 * ordinary command and only works as the discard-arguments
	 * command.
	 */
	case '[':
		BEGIN_EXEC(&States::start);
		expressions.discard_args();
		break;
	/*
	 * Additional numeric operations
	 */
	/*$
	 * n^_ -> ~n -- Binary negation
	 *
	 * Binary negates (complements)  and returns
	 * the result.
	 * Binary complements are often used to negate
	 * \*(ST booleans.
	 */
	case '_':
		BEGIN_EXEC(&States::start);
		expressions.push(~expressions.pop_num_calc());
		break;
	case '*':
		BEGIN_EXEC(&States::start);
		expressions.push_calc(Expressions::OP_POW);
		break;
	case '/':
		BEGIN_EXEC(&States::start);
		expressions.push_calc(Expressions::OP_MOD);
		break;
	case '#':
		BEGIN_EXEC(&States::start);
		expressions.push_calc(Expressions::OP_XOR);
		break;
	default:
		throw Error("Unsupported command <^%c>", chr);
	}
	return &States::start;
}
/*$
 * ^^c -> n -- Get ASCII code of character
 *
 * Returns the ASCII code of the character 
 * that is part of the command.
 * Can be used in place of integer constants for improved
 * readability.
 * For instance ^^A will return 65.
 *
 * Note that this command can be typed CTRL+Caret or
 * Caret-Caret.
 */
StateASCII::StateASCII() : State()
{
	transitions['\0'] = this;
}
State *
StateASCII::custom(gchar chr)
{
	BEGIN_EXEC(&States::start);
	expressions.push(chr);
	return &States::start;
}
StateECommand::StateECommand() : State()
{
	transitions['\0'] = this;
	transitions['%'] = &States::epctcommand;
	transitions['B'] = &States::editfile;
	transitions['C'] = &States::executecommand;
	transitions['G'] = &States::egcommand;
	transitions['I'] = &States::insert_nobuilding;
	transitions['M'] = &States::macro_file;
	transitions['N'] = &States::glob_pattern;
	transitions['S'] = &States::scintilla_symbols;
	transitions['Q'] = &States::eqcommand;
	transitions['U'] = &States::eucommand;
	transitions['W'] = &States::savefile;
}
State *
StateECommand::custom(gchar chr)
{
	switch (g_ascii_toupper(chr)) {
	/*$
	 * [bool]EF -- Remove buffer from ring
	 * -EF
	 *
	 * Removes buffer from buffer ring, effectively
	 * closing it.
	 * If the buffer is dirty (modified), EF will yield
	 * an error.
	 *  may be a specified to enforce closing dirty
	 * buffers.
	 * If it is a Failure condition boolean (negative),
	 * the buffer will be closed unconditionally.
	 * If  is absent, the sign prefix (1 or -1) will
	 * be implied, so \(lq-EF\(rq will always close the buffer.
	 *
	 * It is noteworthy that EF will be executed immediately in
	 * interactive mode but can be rubbed out at a later time
	 * to reopen the file.
	 * Closed files are kept in memory until the command line
	 * is terminated.
	 */
	case 'F':
		BEGIN_EXEC(&States::start);
		if (QRegisters::current)
			throw Error("Q-Register currently edited");
		if (IS_FAILURE(expressions.pop_num_calc()) &&
		    ring.current->dirty)
			throw Error("Buffer \"%s\" is dirty",
				    ring.current->filename ? : "(Unnamed)");
		ring.close();
		break;
	/*$
	 * flags ED -- Set and get ED-flags
	 * [off,]on ED
	 * ED -> flags
	 *
	 * With arguments, the command will set the \fBED\fP flags.
	 *  is a bitmap of flags to set.
	 * Specifying one argument to set the flags is a special
	 * case of specifying two arguments that allow to control
	 * which flags to enable/disable.
	 *  is a bitmap of flags to disable (set to 0 in ED
	 * flags) and  is a bitmap of flags that is ORed into
	 * the flags variable.
	 * If  is omitted, the value 0^_ is implied.
	 * In otherwords, all flags are turned off before turning
	 * on the  flags.
	 * Without any argument ED returns the current flags.
	 *
	 * Currently, the following flags are used by \*(ST:
	 *   - 16: Enable/disable automatic translation of end of
	 *     line sequences to and from line feed.
	 *   - 32: Enable/Disable buffer editing hooks
	 *     (via execution of macro in global Q-Register \(lqED\(rq)
	 *   - 64: Enable/Disable function key macros
	 *   - 128: Enable/Disable enforcement of UNIX98
	 *     \(lq/bin/sh\(rq emulation for operating system command
	 *     executions
	 *
	 * The features controlled thus are discribed in other sections
	 * of this manual.
	 *
	 * The default value of the \fBED\fP flags is 16
	 * (only automatic EOL translation enabled).
	 */
	case 'D':
		BEGIN_EXEC(&States::start);
		expressions.eval();
		if (!expressions.args()) {
			expressions.push(Flags::ed);
		} else {
			tecoInt on = expressions.pop_num_calc();
			tecoInt off = expressions.pop_num_calc(0, ~(tecoInt)0);
			undo.push_var(Flags::ed);
			Flags::ed = (Flags::ed & ~off) | on;
		}
		break;
	/*$
	 * [key]EJ -> value -- Get and set system properties
	 * -EJ -> value
	 * value,keyEJ
	 * rgb,color,3EJ
	 *
	 * This command may be used to get and set system
	 * properties.
	 * With one argument, it retrieves a numeric property
	 * identified by \fIkey\fP.
	 * If \fIkey\fP is omitted, the prefix sign is implied
	 * (1 or -1).
	 * With two arguments, it sets property \fIkey\fP to
	 * \fIvalue\fP and returns nothing. Some property \fIkeys\fP
	 * may require more than one value. Properties may be
	 * write-only or read-only.
	 *
	 * The following property keys are defined:
	 * .IP 0 4
	 * The current user interface: 1 for Curses, 2 for GTK
	 * (\fBread-only\fP)
	 * .IP 1
	 * The current numbfer of buffers: Also the numeric id
	 * of the last buffer in the ring. This is implied if
	 * no argument is given, so \(lqEJ\(rq returns the number
	 * of buffers in the ring.
	 * (\fBread-only\fP)
	 * .IP 2
	 * The current undo stack memory limit in bytes.
	 * This limit helps to prevent dangerous out-of-memory
	 * conditions (e.g. resulting from infinite loops) by
	 * approximating the memory used by \*(ST's undo stack and is only
	 * effective in interactive mode.
	 * Commands which would exceed that limit fail instead.
	 * When getting, a zero value indicates that memory limiting is
	 * disabled.
	 * Setting a value less than or equal to 0 as in
	 * \(lq0,2EJ\(rq disables the limit.
	 * \fBWarning:\fP Disabling memory limiting may provoke
	 * uncontrollable out-of-memory errors in long running
	 * or infinite loops.
	 * Setting a new limit may fail if the current undo stack
	 * is too large for the new limit \(em if this happens
	 * you may have to clear your command-line first.
	 * Undo stack memory limiting is enabled by default.
	 * .IP 3
	 * This \fBwrite-only\fP property allows redefining the
	 * first 16 entries of the terminal color palette \(em a
	 * feature required by some
	 * color schemes when using the Curses user interface.
	 * When setting this property, you are making a request
	 * to define the terminal \fIcolor\fP as the Scintilla-compatible
	 * RGB color value given in the \fIrgb\fP parameter.
	 * \fIcolor\fP must be a value between 0 and 15
	 * corresponding to black, red, green, yellow, blue, magenta,
	 * cyan, white, bright black, bright red, etc. in that order.
	 * The \fIrgb\fP value has the format 0xBBGGRR, i.e. the red
	 * component is the least-significant byte and all other bytes
	 * are ignored.
	 * Note that on curses, RGB color values sent to Scintilla
	 * are actually mapped to these 16 colors by the Scinterm port
	 * and may represent colors with no resemblance to the \(lqRGB\(rq
	 * value used (depending on the current palette) \(em they should
	 * instead be viewed as placeholders for 16 standard terminal
	 * color codes.
	 * Please refer to the Scinterm manual for details on the allowed
	 * \(lqRGB\(rq values and how they map to terminal colors.
	 * This command provides a crude way to request exact RGB colors
	 * for the first 16 terminal colors.
	 * The color definition may be queued or be completely ignored
	 * on other user interfaces and no feedback is given
	 * if it fails. In fact feedback cannot be given reliably anyway.
	 * Note that on 8 color terminals, only the first 8 colors
	 * can be redefined (if you are lucky).
	 * Note that due to restrictions of most terminal emulators
	 * and some curses implementations, this command simply will not
	 * restore the original palette entry or request
	 * when rubbed out and should generally only be used in
	 * \fIbatch-mode\fP \(em typically when loading a color scheme.
	 * For the same reasons \(em even though \*(ST tries hard to
	 * restore the original palette on exit \(em palette changes may
	 * persist after \*(ST terminates on most terminal emulators on Unix.
	 * The only emulator which will restore their default palette
	 * on exit the author is aware of is \fBxterm\fP(1) and
	 * the Linux console driver.
	 * You have been warned. Good luck.
	 */
	case 'J': {
		BEGIN_EXEC(&States::start);
		enum {
			EJ_USER_INTERFACE = 0,
			EJ_BUFFERS,
			EJ_UNDO_MEMORY_LIMIT,
			EJ_INIT_COLOR
		};
		tecoInt property;
		expressions.eval();
		property = expressions.pop_num_calc();
		if (expressions.args() > 0) {
			/* set property */
			tecoInt value = expressions.pop_num_calc();
			switch (property) {
			case EJ_UNDO_MEMORY_LIMIT:
				undo.set_memory_limit(MAX(0, value));
				break;
			case EJ_INIT_COLOR:
				if (value < 0 || value >= 16)
					throw Error("Invalid color code %" TECO_INTEGER_FORMAT
					            " specified for ", value);
				if (!expressions.args())
					throw ArgExpectedError("EJ");
				interface.init_color((guint)value,
				                     (guint32)expressions.pop_num_calc());
				break;
			default:
				throw Error("Cannot set property %" TECO_INTEGER_FORMAT
				            " for ", property);
			}
			break;
		}
		switch (property) {
		case EJ_USER_INTERFACE:
#ifdef INTERFACE_CURSES
			expressions.push(1);
#elif defined(INTERFACE_GTK)
			expressions.push(2);
#else
#error Missing value for current interface!
#endif
			break;
		case EJ_BUFFERS:
			expressions.push(ring.get_id(ring.last()));
			break;
		case EJ_UNDO_MEMORY_LIMIT:
			expressions.push(undo.memory_limit);
			break;
		default:
			throw Error("Invalid property %" TECO_INTEGER_FORMAT
			            " for ", property);
		}
		break;
	}
	/*$
	 * 0EL -- Set or get End of Line mode
	 * 13,10:EL
	 * 1EL
	 * 13:EL
	 * 2EL
	 * 10:EL
	 * EL -> 0 | 1 | 2
	 * :EL -> 13,10 | 13 | 10
	 *
	 * Sets or gets the current document's End Of Line (EOL) mode.
	 * This is a thin wrapper around Scintilla's
	 * \fBSCI_SETEOLMODE\fP and \fBSCI_GETEOLMODE\fP messages but is
	 * shorter to type and supports restoring the EOL mode upon rubout.
	 * Like the Scintilla message,  does \fBnot\fP change the
	 * characters in the current document.
	 * If automatic EOL translation is activated (which is the default),
	 * \*(ST will however use this information when saving files or
	 * writing to external processes.
	 *
	 * With one argument, the EOL mode is set according to these
	 * constants:
	 * .IP 0 4
	 * Carriage return (ASCII 13), followed by line feed (ASCII 10).
	 * This is the default EOL mode on DOS/Windows.
	 * .IP 1
	 * Carriage return (ASCII 13).
	 * The default EOL mode on old Mac OS systems.
	 * .IP 2
	 * Line feed (ASCII 10).
	 * The default EOL mode on POSIX/UNIX systems.
	 *
	 * In its colon-modified form, the EOL mode is set according
	 * to the EOL characters on the expression stack.
	 * \*(ST will only pop as many values as are necessary to
	 * determine the EOL mode.
	 *
	 * Without arguments, the current EOL mode is returned.
	 * When colon-modified, the current EOL mode's character sequence
	 * is pushed onto the expression stack.
	 */
	case 'L':
		BEGIN_EXEC(&States::start);
		expressions.eval();
		if (expressions.args() > 0) {
			gint eol_mode;
			if (eval_colon()) {
				switch (expressions.pop_num_calc()) {
				case '\r':
					eol_mode = SC_EOL_CR;
					break;
				case '\n':
					if (!expressions.args()) {
						eol_mode = SC_EOL_LF;
						break;
					}
					if (expressions.pop_num_calc() == '\r') {
						eol_mode = SC_EOL_CRLF;
						break;
					}
					/* fall through */
				default:
					throw Error("Invalid EOL sequence for ");
				}
			} else {
				eol_mode = expressions.pop_num_calc();
				switch (eol_mode) {
				case SC_EOL_CRLF:
				case SC_EOL_CR:
				case SC_EOL_LF:
					break;
				default:
					throw Error("Invalid EOL mode %d for ",
					            eol_mode);
				}
			}
			interface.undo_ssm(SCI_SETEOLMODE,
			                   interface.ssm(SCI_GETEOLMODE));
			interface.ssm(SCI_SETEOLMODE, eol_mode);
		} else if (eval_colon()) {
			expressions.push_str(get_eol_seq(interface.ssm(SCI_GETEOLMODE)));
		} else {
			expressions.push(interface.ssm(SCI_GETEOLMODE));
		}
		break;
	/*$
	 * [bool]EX -- Exit program
	 * -EX
	 * :EX
	 *
	 * Exits \*(ST, or rather requests program termination
	 * at the end of the top-level macro.
	 * Therefore instead of exiting immediately which
	 * could be annoying in interactive mode, EX will
	 * result in program termination only when the command line
	 * is terminated.
	 * This allows EX to be rubbed out and used in macros.
	 * The usual command to exit \*(ST in interactive mode
	 * is thus \(lqEX\fB$$\fP\(rq.
	 * In batch mode EX will exit the program if control
	 * reaches the end of the munged file \(em instead of
	 * starting up interactive mode.
	 *
	 * If any buffer is dirty (modified), EX will yield
	 * an error.
	 * When specifying  as a success/truth condition
	 * boolean, EX will not check whether there are modified
	 * buffers and will always succeed.
	 * If  is omitted, the sign prefix is implied
	 * (1 or -1).
	 * In other words \(lq-EX\fB$$\fP\(rq is the usual
	 * interactive command sequence to discard all unsaved
	 * changes and exit.
	 *
	 * When colon-modified,  is ignored and EX
	 * will instead immediately try to save all modified buffers \(em
	 * this can of course be reversed using rubout.
	 * Saving all buffers can fail, e.g. if the unnamed file
	 * is modified or if there is an IO error.
	 * \(lq:EX\fB$$\fP\(rq is nevertheless the usual interactive
	 * command sequence to exit while saving all modified
	 * buffers.
	 */
	/** @bug what if changing file after EX? will currently still exit */
	case 'X':
		BEGIN_EXEC(&States::start);
		if (eval_colon())
			ring.save_all_dirty_buffers();
		else if (IS_FAILURE(expressions.pop_num_calc()) &&
		         ring.is_any_dirty())
			throw Error("Modified buffers exist");
		undo.push_var(quit_requested) = true;
		break;
	default:
		throw SyntaxError(chr);
	}
	return &States::start;
}
static struct ScintillaMessage {
	unsigned int	iMessage;
	uptr_t		wParam;
	sptr_t		lParam;
} scintilla_message = {0, 0, 0};
/*$
 * -- Send Scintilla message
 * [lParam[,wParam]]ESmessage[,wParam]$[lParam]$ -> result
 *
 * Send Scintilla message with code specified by symbolic
 * name ,  and .
 *  may be symbolic when specified as part of the
 * first string argument.
 * If not it is popped from the stack.
 *  may be specified as a constant string whose
 * pointer is passed to Scintilla if specified as the second
 * string argument.
 * If the second string argument is empty,  is popped
 * from the stack instead.
 * Parameters popped from the stack may be omitted, in which
 * case 0 is implied.
 * The message's return value is pushed onto the stack.
 *
 * All messages defined by Scintilla (as C macros) can be
 * used by passing their name as a string to ES
 * (e.g. ESSCI_LINESONSCREEN...).
 * The \(lqSCI_\(rq prefix may be omitted and message symbols
 * are case-insensitive.
 * Only the Scintilla lexer symbols (SCLEX_..., SCE_...)
 * may be used symbolically with the ES command as ,
 * other values must be passed as integers on the stack.
 * In interactive mode, symbols may be auto-completed by
 * pressing Tab.
 * String-building characters are by default interpreted
 * in the string arguments.
 *
 * .BR Warning :
 * Almost all Scintilla messages may be dispatched using
 * this command.
 * \*(ST does not keep track of the editor state changes
 * performed by these commands and cannot undo them.
 * You should never use it to change the editor state
 * (position changes, deletions, etc.) or otherwise
 * rub out will result in an inconsistent editor state.
 * There are however exceptions:
 *   - In the editor profile and batch mode in general,
 *     the ES command may be used freely.
 *   - In the ED hook macro (register \(lqED\(rq),
 *     when a file is added to the ring, most destructive
 *     operations can be performed since rubbing out the
 *     EB command responsible for the hook execution also
 *     removes the buffer from the ring again.
 */
State *
StateScintilla_symbols::done(const gchar *str)
{
	BEGIN_EXEC(&States::scintilla_lparam);
	undo.push_var(scintilla_message);
	if (*str) {
		gchar **symbols = g_strsplit(str, ",", -1);
		tecoInt v;
		if (!symbols[0])
			goto cleanup;
		if (*symbols[0]) {
			v = Symbols::scintilla.lookup(symbols[0], "SCI_");
			if (v < 0)
				throw Error("Unknown Scintilla message symbol \"%s\"",
					    symbols[0]);
			scintilla_message.iMessage = v;
		}
		if (!symbols[1])
			goto cleanup;
		if (*symbols[1]) {
			v = Symbols::scilexer.lookup(symbols[1]);
			if (v < 0)
				throw Error("Unknown Scintilla Lexer symbol \"%s\"",
					    symbols[1]);
			scintilla_message.wParam = v;
		}
		if (!symbols[2])
			goto cleanup;
		if (*symbols[2]) {
			v = Symbols::scilexer.lookup(symbols[2]);
			if (v < 0)
				throw Error("Unknown Scintilla Lexer symbol \"%s\"",
					    symbols[2]);
			scintilla_message.lParam = v;
		}
cleanup:
		g_strfreev(symbols);
	}
	expressions.eval();
	if (!scintilla_message.iMessage) {
		if (!expressions.args())
			throw Error(" command requires at least a message code");
		scintilla_message.iMessage = expressions.pop_num_calc(0, 0);
	}
	if (!scintilla_message.wParam)
		scintilla_message.wParam = expressions.pop_num_calc(0, 0);
	return &States::scintilla_lparam;
}
State *
StateScintilla_lParam::done(const gchar *str)
{
	BEGIN_EXEC(&States::start);
	if (!scintilla_message.lParam)
		scintilla_message.lParam = *str ? (sptr_t)str
						: expressions.pop_num_calc(0, 0);
	expressions.push(interface.ssm(scintilla_message.iMessage,
				       scintilla_message.wParam,
				       scintilla_message.lParam));
	undo.push_var(scintilla_message);
	memset(&scintilla_message, 0, sizeof(scintilla_message));
	return &States::start;
}
/*
 * NOTE: cannot support VideoTECO's I because
 * beginning and end of strings must be determined
 * syntactically
 */
/*$
 * [c1,c2,...]I[text]$ -- Insert text with string building characters
 *
 * First inserts characters for all the values
 * on the argument stack (interpreted as codepoints).
 * It does so in the order of the arguments, i.e.
 *  is inserted before , ecetera.
 * Secondly, the command inserts .
 * In interactive mode,  is inserted interactively.
 *
 * String building characters are \fBenabled\fP for the
 * I command.
 * When editing \*(ST macros, using the \fBEI\fP command
 * may be better, since it has string building characters
 * disabled.
 */
/*$
 * [c1,c2,...]EI[text]$ -- Insert text without string building characters
 *
 * Inserts text at the current position in the current
 * document.
 * This command is identical to the \fBI\fP command,
 * except that string building characters are \fBdisabled\fP.
 * Therefore it may be beneficial when editing \*(ST
 * macros.
 */
void
StateInsert::initial(void)
{
	guint args;
	expressions.eval();
	args = expressions.args();
	if (!args)
		return;
	interface.ssm(SCI_BEGINUNDOACTION);
	for (int i = args; i > 0; i--) {
		gchar chr = (gchar)expressions.peek_num(i-1);
		interface.ssm(SCI_ADDTEXT, 1, (sptr_t)&chr);
	}
	for (int i = args; i > 0; i--)
		expressions.pop_num_calc();
	interface.ssm(SCI_SCROLLCARET);
	interface.ssm(SCI_ENDUNDOACTION);
	ring.dirtify();
	if (current_doc_must_undo())
		interface.undo_ssm(SCI_UNDO);
}
void
StateInsert::process(const gchar *str, gint new_chars)
{
	interface.ssm(SCI_BEGINUNDOACTION);
	interface.ssm(SCI_ADDTEXT, new_chars,
		      (sptr_t)(str + strlen(str) - new_chars));
	interface.ssm(SCI_SCROLLCARET);
	interface.ssm(SCI_ENDUNDOACTION);
	ring.dirtify();
	if (current_doc_must_undo())
		interface.undo_ssm(SCI_UNDO);
}
State *
StateInsert::done(const gchar *str)
{
	/* nothing to be done when done */
	return &States::start;
}
/*
 * Alternatives: ^i, ^I, , 
 */
/*$
 * [char,...]^I[text]$ -- Insert with leading indention
 *
 * ^I (usually typed using the Tab key), first inserts
 * all the chars on the stack into the buffer, then indention
 * characters (one tab or multiple spaces) and eventually
 * the optional  is inserted interactively.
 * It is thus a derivate of the \fBI\fP (insertion) command.
 *
 * \*(ST uses Scintilla settings to determine the indention
 * characters.
 * If tab use is enabled with the \fBSCI_SETUSETABS\fP message,
 * a single tab character is inserted.
 * Tab use is enabled by default.
 * Otherwise, a number of spaces is inserted up to the
 * next tab stop so that the command's  argument
 * is inserted at the beginning of the next tab stop.
 * The size of the tab stops is configured by the
 * \fBSCI_SETTABWIDTH\fP Scintilla message (8 by default).
 * In combination with \*(ST's use of the tab key as an
 * immediate editing command for all insertions, this
 * implements support for different insertion styles.
 * The Scintilla settings apply to the current Scintilla
 * document and are thus local to the currently edited
 * buffer or Q-Register.
 *
 * However for the same reason, the ^I command is not
 * fully compatible with classic TECO which \fIalways\fP
 * inserts a single tab character and should not be used
 * for the purpose of inserting single tabs in generic
 * macros.
 * To insert a single tab character reliably, the idioms
 * \(lq9I$\(rq or \(lqI^I$\(rq may be used.
 *
 * Like the I command, ^I has string building characters
 * \fBenabled\fP.
 */
void
StateInsertIndent::initial(void)
{
	StateInsert::initial();
	interface.ssm(SCI_BEGINUNDOACTION);
	if (interface.ssm(SCI_GETUSETABS)) {
		interface.ssm(SCI_ADDTEXT, 1, (sptr_t)"\t");
	} else {
		gint len = interface.ssm(SCI_GETTABWIDTH);
		len -= interface.ssm(SCI_GETCOLUMN,
		                     interface.ssm(SCI_GETCURRENTPOS)) % len;
		gchar spaces[len];
		memset(spaces, ' ', sizeof(spaces));
		interface.ssm(SCI_ADDTEXT, sizeof(spaces), (sptr_t)spaces);
	}
	interface.ssm(SCI_SCROLLCARET);
	interface.ssm(SCI_ENDUNDOACTION);
	ring.dirtify();
	if (current_doc_must_undo())
		interface.undo_ssm(SCI_UNDO);
}
} /* namespace SciTECO */