aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/parser.cpp')
-rw-r--r--src/parser.cpp1436
1 files changed, 1436 insertions, 0 deletions
diff --git a/src/parser.cpp b/src/parser.cpp
new file mode 100644
index 0000000..5ba96fa
--- /dev/null
+++ b/src/parser.cpp
@@ -0,0 +1,1436 @@
+#include <stdarg.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+
+#include "sciteco.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"
+
+//#define DEBUG
+
+gint macro_pc = 0;
+
+namespace States {
+ StateStart start;
+ StateControl control;
+ StateFCommand fcommand;
+ StateCondCommand condcommand;
+ StateECommand ecommand;
+ StateScintilla_symbols scintilla_symbols;
+ StateScintilla_lParam scintilla_lparam;
+ StateInsert insert;
+
+ 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 = '\x1B';
+
+void
+Execute::step(const gchar *macro) throw (State::Error)
+{
+ while (macro[macro_pc]) {
+#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
+
+ State::input(macro[macro_pc]);
+ macro_pc++;
+ }
+}
+
+void
+Execute::macro(const gchar *macro, bool locals) throw (State::Error)
+{
+ GotoTable *parent_goto_table = Goto::table;
+ GotoTable macro_goto_table(false);
+
+ QRegisterTable *parent_locals = QRegisters::locals;
+ QRegisterTable macro_locals(false);
+
+ 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 = &macro_goto_table;
+ if (locals) {
+ macro_locals.initialize();
+ QRegisters::locals = &macro_locals;
+ }
+
+ try {
+ step(macro);
+ if (Goto::skip_label)
+ throw State::Error("Label \"%s\" not found",
+ Goto::skip_label);
+ } catch (...) {
+ g_free(Goto::skip_label);
+ Goto::skip_label = NULL;
+
+ QRegisters::locals = parent_locals;
+ Goto::table = parent_goto_table;
+
+ macro_pc = parent_pc;
+ States::current = parent_state;
+
+ throw; /* forward */
+ }
+
+ QRegisters::locals = parent_locals;
+ Goto::table = parent_goto_table;
+
+ macro_pc = parent_pc;
+ States::current = parent_state;
+}
+
+bool
+Execute::file(const gchar *filename, bool locals)
+{
+ gchar *macro_str, *p = NULL;
+
+ if (!g_file_get_contents(filename, &macro_str, NULL, NULL))
+ return false;
+ /* only when executing files, ignore Hash-Bang line */
+ if (*macro_str == '#')
+ p = MAX(strchr(macro_str, '\r'), strchr(macro_str, '\n'));
+
+ try {
+ macro(p ? p+1 : macro_str, locals);
+ } catch (...) {
+ g_free(macro_str);
+ return false;
+ }
+
+ g_free(macro_str);
+ return true;
+}
+
+State::Error::Error(const gchar *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ interface.vmsg(Interface::MSG_ERROR, fmt, ap);
+ va_end(ap);
+}
+
+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<bool>(Modifiers::colon);
+ Modifiers::colon = false;
+ return true;
+}
+
+void
+State::input(gchar chr) throw (Error)
+{
+ 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<State *>(States::current);
+ States::current = state;
+ }
+}
+
+State *
+State::get_next_state(gchar chr) throw (Error)
+{
+ 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;
+}
+
+gchar *
+StateExpectString::machine_input(gchar chr) throw (Error)
+{
+ switch (machine.mode) {
+ case Machine::MODE_UPPER:
+ chr = g_ascii_toupper(chr);
+ break;
+ case Machine::MODE_LOWER:
+ chr = g_ascii_tolower(chr);
+ break;
+ default:
+ break;
+ }
+
+ if (machine.toctl) {
+ chr = CTL_KEY(g_ascii_toupper(chr));
+ machine.toctl = false;
+ }
+
+ if (machine.state == Machine::STATE_ESCAPED) {
+ machine.state = Machine::STATE_START;
+ goto original;
+ }
+
+ if (chr == '^') {
+ machine.toctl = true;
+ return NULL;
+ }
+
+ switch (machine.state) {
+ case Machine::STATE_START:
+ switch (chr) {
+ case CTL_KEY('Q'):
+ case CTL_KEY('R'): machine.state = Machine::STATE_ESCAPED; break;
+ case CTL_KEY('V'): machine.state = Machine::STATE_LOWER; break;
+ case CTL_KEY('W'): machine.state = Machine::STATE_UPPER; break;
+ case CTL_KEY('E'): machine.state = Machine::STATE_CTL_E; break;
+ default:
+ goto original;
+ }
+ break;
+
+ case Machine::STATE_LOWER:
+ machine.state = Machine::STATE_START;
+ if (chr != CTL_KEY('V'))
+ return g_strdup((gchar []){g_ascii_tolower(chr), '\0'});
+ machine.mode = Machine::MODE_LOWER;
+ break;
+
+ case Machine::STATE_UPPER:
+ machine.state = Machine::STATE_START;
+ if (chr != CTL_KEY('W'))
+ return g_strdup((gchar []){g_ascii_toupper(chr), '\0'});
+ machine.mode = Machine::MODE_UPPER;
+ break;
+
+ case Machine::STATE_CTL_E:
+ switch (g_ascii_toupper(chr)) {
+ case 'Q': machine.state = Machine::STATE_CTL_EQ; break;
+ case 'U': machine.state = Machine::STATE_CTL_EU; break;
+ default:
+ machine.state = Machine::STATE_START;
+ return g_strdup((gchar []){CTL_KEY('E'), chr, '\0'});
+ }
+ break;
+
+ case Machine::STATE_CTL_EU:
+ if (chr == '.') {
+ machine.state = Machine::STATE_CTL_EU_LOCAL;
+ } else {
+ QRegister *reg;
+
+ machine.state = Machine::STATE_START;
+ reg = QRegisters::globals[g_ascii_toupper(chr)];
+ if (!reg)
+ throw InvalidQRegError(chr);
+ return g_strdup(CHR2STR(reg->get_integer()));
+ }
+ break;
+
+ case Machine::STATE_CTL_EU_LOCAL: {
+ QRegister *reg;
+
+ machine.state = Machine::STATE_START;
+ reg = (*QRegisters::locals)[g_ascii_toupper(chr)];
+ if (!reg)
+ throw InvalidQRegError(chr, true);
+ return g_strdup(CHR2STR(reg->get_integer()));
+ }
+
+ case Machine::STATE_CTL_EQ:
+ if (chr == '.') {
+ machine.state = Machine::STATE_CTL_EQ_LOCAL;
+ } else {
+ QRegister *reg;
+
+ machine.state = Machine::STATE_START;
+ reg = QRegisters::globals[g_ascii_toupper(chr)];
+ if (!reg)
+ throw InvalidQRegError(chr);
+ return reg->get_string();
+ }
+ break;
+
+ case Machine::STATE_CTL_EQ_LOCAL: {
+ QRegister *reg;
+
+ machine.state = Machine::STATE_START;
+ reg = (*QRegisters::locals)[g_ascii_toupper(chr)];
+ if (!reg)
+ throw InvalidQRegError(chr, true);
+ return reg->get_string();
+ }
+
+ default:
+ g_assert(TRUE);
+ }
+
+ return NULL;
+
+original:
+ return g_strdup((gchar []){chr, '\0'});
+}
+
+State *
+StateExpectString::custom(gchar chr) throw (Error)
+{
+ 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 '\x1B':
+ case '{':
+ undo.push_var(escape_char) = g_ascii_toupper(chr);
+ return this;
+ }
+ }
+
+ if (escape_char == '{') {
+ switch (chr) {
+ case '{':
+ undo.push_var(nesting);
+ nesting++;
+ break;
+ case '}':
+ undo.push_var(nesting);
+ nesting--;
+ break;
+ }
+ } else if (g_ascii_toupper(chr) == escape_char) {
+ undo.push_var(nesting);
+ nesting--;
+ }
+
+ if (!nesting) {
+ State *next;
+ gchar *string = strings[0];
+
+ undo.push_str(strings[0]) = NULL;
+ if (last)
+ undo.push_var(escape_char) = '\x1B';
+ nesting = 1;
+
+ if (string_building) {
+ undo.push_var(machine);
+ machine.state = Machine::STATE_START;
+ machine.mode = Machine::MODE_NORMAL;
+ machine.toctl = false;
+ }
+
+ next = done(string ? : "");
+ g_free(string);
+ return next;
+ }
+
+ BEGIN_EXEC(this);
+
+ /*
+ * String building characters
+ */
+ if (string_building) {
+ undo.push_var(machine);
+ insert = machine_input(chr);
+ if (!insert)
+ return this;
+ } else {
+ insert = g_strdup((gchar []){chr, '\0'});
+ }
+
+ /*
+ * String accumulation
+ */
+ undo.push_str(strings[0]);
+ String::append(strings[0], insert);
+
+ process(strings[0], strlen(insert));
+ g_free(insert);
+ return this;
+}
+
+StateExpectQReg::StateExpectQReg() : State(), got_local(false)
+{
+ transitions['\0'] = this;
+}
+
+State *
+StateExpectQReg::custom(gchar chr) throw (Error)
+{
+ QRegister *reg;
+
+ if (chr == '.') {
+ undo.push_var(got_local) = true;
+ return this;
+ }
+ chr = g_ascii_toupper(chr);
+
+ if (got_local) {
+ undo.push_var(got_local) = false;
+ reg = (*QRegisters::locals)[chr];
+ } else {
+ reg = QRegisters::globals[chr];
+ }
+ if (!reg)
+ throw InvalidQRegError(chr, got_local);
+
+ return got_register(reg);
+}
+
+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;
+ transitions['S'] = &States::search;
+ transitions['N'] = &States::searchall;
+
+ transitions['['] = &States::pushqreg;
+ transitions[']'] = &States::popqreg;
+ transitions['G'] = &States::getqregstring;
+ transitions['Q'] = &States::getqreginteger;
+ transitions['U'] = &States::setqreginteger;
+ transitions['%'] = &States::increaseqreg;
+ transitions['M'] = &States::macro;
+ transitions['X'] = &States::copytoqreg;
+}
+
+void
+StateStart::insert_integer(gint64 v)
+{
+ gchar buf[64+1]; /* maximum length if radix = 2 */
+ gchar *p = buf + sizeof(buf);
+
+ *--p = '\0';
+ interface.ssm(SCI_BEGINUNDOACTION);
+ if (v < 0) {
+ interface.ssm(SCI_ADDTEXT, 1, (sptr_t)"-");
+ v *= -1;
+ }
+ do {
+ *--p = '0' + (v % expressions.radix);
+ if (*p > '9')
+ *p += 'A' - '9';
+ } while ((v /= expressions.radix));
+ interface.ssm(SCI_ADDTEXT, buf + sizeof(buf) - p - 1,
+ (sptr_t)p);
+ interface.ssm(SCI_SCROLLCARET);
+ interface.ssm(SCI_ENDUNDOACTION);
+ ring.dirtify();
+
+ undo.push_msg(SCI_UNDO);
+}
+
+gint64
+StateStart::read_integer(void)
+{
+ uptr_t pos = interface.ssm(SCI_GETCURRENTPOS);
+ gchar c = (gchar)interface.ssm(SCI_GETCHARAT, pos);
+ gint64 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(gint64 n)
+{
+ sptr_t pos = interface.ssm(SCI_GETCURRENTPOS);
+
+ if (!Validate::pos(pos + n))
+ return FAILURE;
+
+ interface.ssm(SCI_GOTOPOS, pos + n);
+ undo.push_msg(SCI_GOTOPOS, pos);
+ return SUCCESS;
+}
+
+tecoBool
+StateStart::move_lines(gint64 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);
+ undo.push_msg(SCI_GOTOPOS, pos);
+ return SUCCESS;
+}
+
+tecoBool
+StateStart::delete_words(gint64 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;
+ }
+
+ undo.push_msg(SCI_GOTOPOS, pos);
+ undo.push_msg(SCI_UNDO);
+ ring.dirtify();
+
+ return SUCCESS;
+}
+
+State *
+StateStart::custom(gchar chr) throw (Error)
+{
+ gint64 v;
+ tecoBool rc;
+
+ /*
+ * <CTRL/x> commands implemented in StateCtrlCmd
+ */
+ if (IS_CTL(chr))
+ return States::control.get_next_state(CTL_ECHO(chr));
+
+ /*
+ * arithmetics
+ */
+ 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 '*':
+ 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;
+
+ case '.':
+ BEGIN_EXEC(this);
+ expressions.eval();
+ expressions.push(interface.ssm(SCI_GETCURRENTPOS));
+ break;
+
+ case 'Z':
+ BEGIN_EXEC(this);
+ expressions.eval();
+ expressions.push(interface.ssm(SCI_GETLENGTH));
+ break;
+
+ case 'H':
+ BEGIN_EXEC(this);
+ expressions.eval();
+ expressions.push(0);
+ expressions.push(interface.ssm(SCI_GETLENGTH));
+ break;
+
+ 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<gint>(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 = 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 = MODE_NORMAL;
+ } else {
+ undo.push_var<gint>(nest_level);
+ nest_level--;
+ }
+ } else {
+ BEGIN_EXEC(this);
+ gint64 loop_pc, loop_cnt;
+
+ expressions.discard_args();
+ g_assert(expressions.pop_op() == Expressions::OP_LOOP);
+ 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;
+
+ case ';':
+ BEGIN_EXEC(this);
+
+ v = QRegisters::globals["_"]->get_integer();
+ rc = expressions.pop_num_calc(1, v);
+ if (eval_colon())
+ rc = ~rc;
+
+ if (IS_FAILURE(rc)) {
+ expressions.discard_args();
+ g_assert(expressions.pop_op() == Expressions::OP_LOOP);
+ expressions.pop_num(); /* pc */
+ expressions.pop_num(); /* counter */
+
+ /* skip to end of loop */
+ undo.push_var<Mode>(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 = MODE_NORMAL;
+ }
+ return this;
+ }
+ BEGIN_EXEC(this);
+
+ /* skip to end of conditional; skip ELSE-part */
+ undo.push_var<Mode>(mode);
+ mode = MODE_PARSE_ONLY_COND;
+ break;
+
+ case '\'':
+ if (mode != MODE_PARSE_ONLY_COND)
+ break;
+
+ if (!nest_level) {
+ undo.push_var<Mode>(mode);
+ mode = MODE_NORMAL;
+ undo.push_var<bool>(skip_else);
+ skip_else = false;
+ } else {
+ undo.push_var<gint>(nest_level);
+ nest_level--;
+ }
+ break;
+
+ /*
+ * modifiers
+ */
+ case '@':
+ /*
+ * @ modifier has syntactic significance, so set it even
+ * in PARSE_ONLY* modes
+ */
+ undo.push_var<bool>(Modifiers::at);
+ Modifiers::at = true;
+ break;
+
+ case ':':
+ BEGIN_EXEC(this);
+ undo.push_var<bool>(Modifiers::colon);
+ Modifiers::colon = true;
+ break;
+
+ /*
+ * commands
+ */
+ case 'J':
+ BEGIN_EXEC(this);
+ v = expressions.pop_num_calc(1, 0);
+ if (Validate::pos(v)) {
+ undo.push_msg(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;
+
+ 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;
+
+ 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;
+
+ 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;
+
+ 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;
+
+ 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) {
+ undo.push_msg(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;
+ }
+
+ 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 <V>");
+ break;
+
+ 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 <Y>");
+ break;
+
+ case '=':
+ BEGIN_EXEC(this);
+ interface.msg(Interface::MSG_USER, "%" G_GINT64_FORMAT,
+ expressions.pop_num_calc());
+ break;
+
+ case 'K':
+ case 'D': {
+ gint64 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 {
+ gint64 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;
+
+ undo.push_msg(SCI_GOTOPOS, interface.ssm(SCI_GETCURRENTPOS));
+ undo.push_msg(SCI_UNDO);
+
+ interface.ssm(SCI_BEGINUNDOACTION);
+ interface.ssm(SCI_DELETERANGE, from, len);
+ interface.ssm(SCI_ENDUNDOACTION);
+ ring.dirtify();
+ break;
+ }
+
+ case 'A':
+ BEGIN_EXEC(this);
+ v = interface.ssm(SCI_GETCURRENTPOS) +
+ expressions.pop_num_calc();
+ if (!Validate::pos(v))
+ 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;
+}
+
+State *
+StateFCommand::custom(gchar chr) throw (Error)
+{
+ switch (chr) {
+ /*
+ * loop flow control
+ */
+ 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;
+
+ case '>': {
+ gint64 loop_pc, loop_cnt;
+
+ BEGIN_EXEC(&States::start);
+ /* FIXME: what if in brackets? */
+ expressions.discard_args();
+ g_assert(expressions.pop_op() == Expressions::OP_LOOP);
+ 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 = MODE_PARSE_ONLY_LOOP;
+ }
+ break;
+ }
+
+ /*
+ * conditional flow control
+ */
+ case '\'':
+ BEGIN_EXEC(&States::start);
+ /* skip to end of conditional */
+ undo.push_var<Mode>(mode);
+ mode = MODE_PARSE_ONLY_COND;
+ undo.push_var<bool>(skip_else);
+ skip_else = true;
+ break;
+
+ case '|':
+ BEGIN_EXEC(&States::start);
+ /* skip to ELSE-part or end of conditional */
+ undo.push_var<Mode>(mode);
+ mode = MODE_PARSE_ONLY_COND;
+ break;
+
+ default:
+ throw SyntaxError(chr);
+ }
+
+ return &States::start;
+}
+
+StateCondCommand::StateCondCommand() : State()
+{
+ transitions['\0'] = this;
+}
+
+State *
+StateCondCommand::custom(gchar chr) throw (Error)
+{
+ gint64 value = 0;
+ bool result;
+
+ switch (mode) {
+ case MODE_PARSE_ONLY_COND:
+ undo.push_var<gint>(nest_level);
+ nest_level++;
+ break;
+ case MODE_NORMAL:
+ value = expressions.pop_num_calc();
+ break;
+ default:
+ break;
+ }
+
+ switch (g_ascii_toupper(chr)) {
+ 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 'E':
+ case 'F':
+ case 'U':
+ case '=':
+ BEGIN_EXEC(&States::start);
+ result = value == 0;
+ break;
+ case 'G':
+ case '>':
+ BEGIN_EXEC(&States::start);
+ result = value > 0;
+ break;
+ case 'L':
+ case 'S':
+ case 'T':
+ 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);
+ mode = MODE_PARSE_ONLY_COND;
+ }
+
+ return &States::start;
+}
+
+StateControl::StateControl() : State()
+{
+ transitions['\0'] = this;
+ transitions['U'] = &States::ctlucommand;
+}
+
+State *
+StateControl::custom(gchar chr) throw (Error)
+{
+ switch (g_ascii_toupper(chr)) {
+ case 'O':
+ BEGIN_EXEC(&States::start);
+ expressions.set_radix(8);
+ break;
+
+ case 'D':
+ BEGIN_EXEC(&States::start);
+ expressions.set_radix(10);
+ break;
+
+ 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: ^i, ^I, <CTRL/I>, <TAB>
+ */
+ case 'I':
+ BEGIN_EXEC(&States::insert);
+ expressions.eval();
+ expressions.push('\t');
+ return &States::insert;
+
+ /*
+ * Alternatives: ^[, <CTRL/[>, <ESC>
+ */
+ case '[':
+ BEGIN_EXEC(&States::start);
+ expressions.discard_args();
+ break;
+
+ /*
+ * Additional numeric operations
+ */
+ 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;
+
+ default:
+ throw Error("Unsupported command <^%c>", chr);
+ }
+
+ return &States::start;
+}
+
+StateECommand::StateECommand() : State()
+{
+ transitions['\0'] = this;
+ transitions['B'] = &States::editfile;
+ transitions['S'] = &States::scintilla_symbols;
+ transitions['Q'] = &States::eqcommand;
+ transitions['W'] = &States::savefile;
+}
+
+State *
+StateECommand::custom(gchar chr) throw (Error)
+{
+ switch (g_ascii_toupper(chr)) {
+ case 'F':
+ BEGIN_EXEC(&States::start);
+ if (!ring.current)
+ throw Error("No buffer selected");
+
+ if (IS_FAILURE(expressions.pop_num_calc()) &&
+ ring.current->dirty)
+ throw Error("Buffer \"%s\" is dirty",
+ ring.current->filename ? : "(Unnamed)");
+
+ ring.close();
+ break;
+
+ case 'D':
+ BEGIN_EXEC(&States::start);
+ expressions.eval();
+ if (!expressions.args()) {
+ expressions.push(Flags::ed);
+ } else {
+ gint64 on = expressions.pop_num_calc();
+ gint64 off = expressions.pop_num_calc(1, ~(gint64)0);
+
+ undo.push_var(Flags::ed);
+ Flags::ed = (Flags::ed & ~off) | on;
+ }
+ break;
+
+ case 'X':
+ BEGIN_EXEC(&States::start);
+
+ if (IS_FAILURE(expressions.pop_num_calc()) &&
+ ring.is_any_dirty())
+ throw Error("Modified buffers exist");
+
+ undo.push_var<bool>(quit_requested);
+ 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};
+
+State *
+StateScintilla_symbols::done(const gchar *str) throw (Error)
+{
+ BEGIN_EXEC(&States::scintilla_lparam);
+
+ undo.push_var(scintilla_message);
+ if (*str) {
+ gchar **symbols = g_strsplit(str, ",", -1);
+ gint64 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("<ES> command requires at least a message code");
+
+ scintilla_message.iMessage = expressions.pop_num_calc(1, 0);
+ }
+ if (!scintilla_message.wParam)
+ scintilla_message.wParam = expressions.pop_num_calc(1, 0);
+
+ return &States::scintilla_lparam;
+}
+
+State *
+StateScintilla_lParam::done(const gchar *str) throw (Error)
+{
+ BEGIN_EXEC(&States::start);
+
+ if (!scintilla_message.lParam)
+ scintilla_message.lParam = *str ? (sptr_t)str
+ : expressions.pop_num_calc(1, 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 <n>I because
+ * beginning and end of strings must be determined
+ * syntactically
+ */
+void
+StateInsert::initial(void) throw (Error)
+{
+ int 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);
+ 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();
+
+ undo.push_msg(SCI_UNDO);
+}
+
+void
+StateInsert::process(const gchar *str, gint new_chars) throw (Error)
+{
+ 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();
+
+ undo.push_msg(SCI_UNDO);
+}
+
+State *
+StateInsert::done(const gchar *str __attribute__((unused))) throw (Error)
+{
+ /* nothing to be done when done */
+ return &States::start;
+}