diff options
Diffstat (limited to 'src/parser.cpp')
-rw-r--r-- | src/parser.cpp | 291 |
1 files changed, 192 insertions, 99 deletions
diff --git a/src/parser.cpp b/src/parser.cpp index 4cefb28..3541db1 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -21,6 +21,7 @@ #include <string.h> #include <exception> +#include <new> #include <glib.h> #include <glib/gprintf.h> @@ -82,6 +83,18 @@ static gint nest_level = 0; gchar *strings[2] = {NULL, NULL}; gchar escape_char = CTL_KEY_ESC; +LoopStack loop_stack; + +/** + * Loop frame pointer: The number of elements on + * the loop stack when a macro invocation frame is + * created. + * This is used to perform checks for flow control + * commands to avoid jumping with invalid PCs while + * not creating a new stack per macro frame. + */ +static guint loop_stack_fp = 0; + /** * Handles all expected exceptions, converting them to * SciTECO::Error and preparing them for stack frame insertion. @@ -144,6 +157,9 @@ Execute::macro(const gchar *macro, bool locals) State *parent_state = States::current; gint parent_pc = macro_pc; + guint parent_loop_fp = loop_stack_fp; + + guint parent_brace_level = expressions.brace_level; /* * need this to fixup state on rubout: state machine emits undo token @@ -152,18 +168,26 @@ Execute::macro(const gchar *macro, bool locals) */ undo.push_var(States::current) = &States::start; macro_pc = 0; + loop_stack_fp = loop_stack.items(); Goto::table = ¯o_goto_table; - /* locals are allocated so that we do not waste call stack space */ if (locals) { + /* + * Locals are only allocated when needed to save + * space on the call stack and to improve the speed + * of local macro calls. + * However since the QRegisterTable object is rather + * small we can allocate it using alloca() on the stack. + */ parent_locals = QRegisters::locals; - QRegisters::locals = new QRegisterTable(false); + QRegisters::locals = g_newa(QRegisterTable, 1); + new (QRegisters::locals) QRegisterTable(false); } try { try { step(macro, strlen(macro)); - } catch (Return) { + } catch (Return &info) { /* * Macro returned - handle like regular * end of macro, even though some checks @@ -174,6 +198,24 @@ Execute::macro(const gchar *macro, bool locals) * with a trailing ^[ in macro. */ States::current = &States::start; + + /* + * Discard all braces, except the current one. + */ + expressions.brace_return(parent_brace_level, info.args); + + /* + * Clean up the loop stack. + * We are allowed to return in loops. + * NOTE: This does not have to be undone. + */ + loop_stack.clear(loop_stack_fp); + } + + if (G_UNLIKELY(loop_stack.items() > loop_stack_fp)) { + Error error("Unterminated loop"); + error.set_coord(macro, loop_stack.peek().pc); + throw error; } /* @@ -182,7 +224,7 @@ Execute::macro(const gchar *macro, bool locals) * via Error::set_coord() */ try { - if (Goto::skip_label) + if (G_UNLIKELY(Goto::skip_label)) throw Error("Label \"%s\" not found", Goto::skip_label); @@ -195,7 +237,7 @@ Execute::macro(const gchar *macro, bool locals) * cannot be used :-(. */ expressions.discard_args(); - } else if (States::current != &States::start) { + } else if (G_UNLIKELY(States::current != &States::start)) { /* * can only happen if we returned because * of macro end @@ -222,11 +264,13 @@ Execute::macro(const gchar *macro, bool locals) Goto::skip_label = NULL; if (locals) { - delete QRegisters::locals; + /* memory is reclaimed on return */ + QRegisters::locals->~QRegisterTable(); QRegisters::locals = parent_locals; } Goto::table = parent_goto_table; + loop_stack_fp = parent_loop_fp; macro_pc = parent_pc; States::current = parent_state; @@ -234,11 +278,13 @@ Execute::macro(const gchar *macro, bool locals) } if (locals) { - delete QRegisters::locals; + /* memory is reclaimed on return */ + QRegisters::locals->~QRegisterTable(); QRegisters::locals = parent_locals; } Goto::table = parent_goto_table; + loop_stack_fp = parent_loop_fp; macro_pc = parent_pc; States::current = parent_state; } @@ -827,12 +873,12 @@ StateStart::custom(gchar chr) expressions.push(-1); expressions.push_calc(Expressions::OP_MUL); } - expressions.push(Expressions::OP_BRACE); + expressions.brace_open(); break; case ')': BEGIN_EXEC(this); - expressions.eval(true); + expressions.brace_close(); break; case ',': @@ -912,55 +958,75 @@ StateStart::custom(gchar chr) */ case '<': if (mode == MODE_PARSE_ONLY_LOOP) { - undo.push_var<gint>(nest_level); - nest_level++; - return this; - } - BEGIN_EXEC(this); + undo.push_var(nest_level)++; + } else { + LoopContext ctx; - expressions.eval(); - if (!expressions.args()) - /* infinite loop */ - expressions.push(-1); + BEGIN_EXEC(this); - if (!expressions.peek_num()) { - expressions.pop_num(); + expressions.eval(); + ctx.pass_through = eval_colon(); + ctx.counter = expressions.pop_num_calc(0, -1); + if (ctx.counter) { + /* + * Non-colon modified, we add implicit + * braces, so loop body won't see parameters. + * Colon modified, loop starts can be used + * to process stack elements which is symmetric + * to ":>". + */ + if (!ctx.pass_through) + expressions.brace_open(); - /* 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); + ctx.pc = macro_pc; + loop_stack.push(ctx); + LoopStack::undo_pop<loop_stack>(); + } else { + /* skip to end of loop */ + undo.push_var(mode) = MODE_PARSE_ONLY_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--; - } + if (!nest_level) + undo.push_var(mode) = MODE_NORMAL; + else + undo.push_var(nest_level)--; } else { BEGIN_EXEC(this); - tecoInt loop_pc, loop_cnt; - expressions.discard_args(); - if (expressions.pop_op() != Expressions::OP_LOOP) + if (loop_stack.items() <= loop_stack_fp) throw Error("Loop end without corresponding " "loop start command"); - loop_pc = expressions.pop_num(); - loop_cnt = expressions.pop_num(); + LoopContext &ctx = loop_stack.peek(); + bool colon_modified = eval_colon(); - if (loop_cnt != 1) { + /* + * Colon-modified loop ends can be used to + * aggregate values on the stack. + * A non-colon modified ">" behaves like ":>" + * for pass-through loop starts, though. + */ + if (!ctx.pass_through) { + if (colon_modified) { + expressions.eval(); + expressions.push(Expressions::OP_NEW); + } else { + expressions.discard_args(); + } + } + + if (ctx.counter == 1) { + /* this was the last loop iteration */ + if (!ctx.pass_through) + expressions.brace_close(); + LoopStack::undo_push<loop_stack>(loop_stack.pop()); + } else { /* repeat loop */ - macro_pc = loop_pc; - expressions.push(MAX(loop_cnt - 1, -1)); - expressions.push(loop_pc); - expressions.push(Expressions::OP_LOOP); + macro_pc = ctx.pc; + ctx.counter = MAX(ctx.counter - 1, -1); } } break; @@ -981,34 +1047,32 @@ StateStart::custom(gchar chr) * without colon-modifying the search command (or at a * later point). * - * Executing \(lq;\(rq outside of iterations yields an - * error. + * Executing \(lq;\(rq outside of iterations in the current + * macro invocation level yields an error. It is thus not + * possible to let a macro break a caller's loop. */ case ';': BEGIN_EXEC(this); + if (loop_stack.items() <= loop_stack_fp) + throw Error("<;> only allowed in iterations"); + v = QRegisters::globals["_"]->get_integer(); rc = expressions.pop_num_calc(0, v); if (eval_colon()) rc = ~rc; if (IS_FAILURE(rc)) { + LoopContext ctx = loop_stack.pop(); + 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 */ + if (!ctx.pass_through) + expressions.brace_close(); + + LoopStack::undo_push<loop_stack>(ctx); /* skip to end of loop */ - undo.push_var<Mode>(mode); - mode = MODE_PARSE_ONLY_LOOP; + undo.push_var(mode) = MODE_PARSE_ONLY_LOOP; } break; @@ -1556,60 +1620,79 @@ StateFCommand::custom(gchar chr) * loop flow control */ /*$ - * F< -- Go to loop start + * F< -- Go to loop start or jump to beginning of macro * * Immediately jumps to the current loop's start. * Also works from inside conditionals. + * + * Outside of loops \(em or in a macro without + * a loop \(em this jumps to the beginning of the macro. */ 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; + + macro_pc = loop_stack.items() > loop_stack_fp + ? loop_stack.peek().pc : -1; break; /*$ * F> -- Go to loop end + * :F> * * 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. + * If the loop has remaining iterations or runs indefinitely, + * the jump is performed immediately just as if \(lq>\(rq + * had been executed. + * If the loop has reached its last iteration, \*(ST will + * parse until the loop end command has been found and control + * resumes after the end of the loop. * * 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. + * When colon-modified, \fB:F>\fP behaves like \fB:>\fP + * and allows numbers to be aggregated on the stack. + * + * Calling \fBF>\fP outside of a loop at the current + * macro invocation level will throw an error. + */ + /* + * NOTE: This is almost identical to the normal + * loop end since we don't really want to or need to + * parse till the end of the loop. */ 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) + + if (loop_stack.items() <= loop_stack_fp) throw Error("Jump to loop end without corresponding " "loop start command"); - loop_pc = expressions.pop_num(); - loop_cnt = expressions.pop_num(); + LoopContext &ctx = loop_stack.peek(); + bool colon_modified = eval_colon(); - 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 { + if (!ctx.pass_through) { + if (colon_modified) { + expressions.eval(); + expressions.push(Expressions::OP_NEW); + } else { + expressions.discard_args(); + } + } + + if (ctx.counter == 1) { + /* this was the last loop iteration */ + if (!ctx.pass_through) + expressions.brace_close(); + LoopStack::undo_push<loop_stack>(loop_stack.pop()); /* skip to end of loop */ - undo.push_var<Mode>(mode); - mode = MODE_PARSE_ONLY_LOOP; + undo.push_var(mode) = MODE_PARSE_ONLY_LOOP; + } else { + /* repeat loop */ + macro_pc = ctx.pc; + ctx.counter = MAX(ctx.counter - 1, -1); } break; } @@ -1956,13 +2039,19 @@ StateEscape::custom(gchar chr) * [a1,a2,...]^[$ * * Returns from the current macro invocation. - * The numeric stack is not modified, giving the - * effect of returning the arguments or stack contents - * preceding the command to the macro caller. - * It is generally semantically equivalent to reaching - * the end of the current macro but is executed faster. - * - * Therefore returning from the top-level macro in batch mode + * This will pass control to the calling macro immediately + * and is thus faster than letting control reach the macro's end. + * Also, direct arguments to \fB$$\fP will be left on the expression + * stack when the macro returns. + * \fB$$\fP closes loops automatically and is thus safe to call + * from loop bodies. + * Furthermore, it has defined semantics when executed + * from within braced expressions: + * All braces opened in the current macro invocation will + * be closed and their values discarded. + * Only the direct arguments to \fB$$\fP will be kept. + * + * Returning from the top-level macro in batch mode * will exit the program or start up interactive mode depending * on whether program exit has been requested. * \(lqEX\fB$$\fP\(rq is thus a common idiom to exit @@ -1971,6 +2060,9 @@ StateEscape::custom(gchar chr) * In interactive mode, returning from the top-level macro * (i.e. typing \fB$$\fP at the command line) has the * effect of command line termination. + * The arguments to \fB$$\fP are currently not used + * when terminating a command line \(em the new command line + * will always start with a clean expression stack. * * Only the first \fIescape\fP of \fB$$\fP may be typed * in up-arrow mode as \fB^[$\fP \(em the second character @@ -1978,15 +2070,16 @@ StateEscape::custom(gchar chr) */ if (chr == CTL_KEY_ESC) { BEGIN_EXEC(&States::start); - throw Return(); + expressions.eval(); + throw Return(expressions.args()); } /* * Alternatives: ^[, <CTRL/[>, <ESC> */ /*$ - * ^[ -- Discard all arguments - * $ + * $ -- Discard all arguments + * ^[ * * Pops and discards all values from the stack that * might otherwise be used as arguments to following @@ -1997,9 +2090,9 @@ StateEscape::custom(gchar chr) * 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. + * The up-arrow notation however is processed like any + * ordinary command and only works at the begining of + * a command. */ if (mode == MODE_NORMAL) expressions.discard_args(); |