diff options
-rw-r--r-- | src/cmdline.cpp | 3 | ||||
-rw-r--r-- | src/error.h | 7 | ||||
-rw-r--r-- | src/expressions.cpp | 46 | ||||
-rw-r--r-- | src/expressions.h | 88 | ||||
-rw-r--r-- | src/parser.cpp | 291 | ||||
-rw-r--r-- | src/parser.h | 20 | ||||
-rw-r--r-- | src/qregisters.cpp | 4 | ||||
-rw-r--r-- | src/search.cpp | 2 |
8 files changed, 311 insertions, 150 deletions
diff --git a/src/cmdline.cpp b/src/cmdline.cpp index 34a5df3..271e2be 100644 --- a/src/cmdline.cpp +++ b/src/cmdline.cpp @@ -235,6 +235,8 @@ Cmdline::keypress(gchar key) /* * Return from top-level macro, results * in command line termination. + * The return "arguments" are currently + * ignored. */ interface.popup_clear(); @@ -248,6 +250,7 @@ Cmdline::keypress(gchar key) QRegisters::view.set_scintilla_undo(true); Goto::table->clear(); expressions.clear(); + loop_stack.clear(); last_cmdline = *this; str = NULL; diff --git a/src/error.h b/src/error.h index a4f4660..f4dbfbf 100644 --- a/src/error.h +++ b/src/error.h @@ -39,7 +39,12 @@ class Quit {}; * Thrown as exception to cause a macro to * return or a command-line termination. */ -class Return {}; +class Return { +public: + guint args; + + Return(guint _args = 0) : args(_args) {} +}; class Error { gchar *description; diff --git a/src/expressions.cpp b/src/expressions.cpp index a82914e..922c212 100644 --- a/src/expressions.cpp +++ b/src/expressions.cpp @@ -27,7 +27,9 @@ namespace SciTECO { -Expressions expressions; +Expressions expressions; +Expressions::NumberStack Expressions::numbers; +Expressions::OperatorStack Expressions::operators; tecoInt Expressions::push(tecoInt number) @@ -42,7 +44,7 @@ Expressions::push(tecoInt number) number *= -1; } - numbers.undo_pop(); + NumberStack::undo_pop<numbers>(); return numbers.push(number); } @@ -56,7 +58,7 @@ Expressions::pop_num(guint index) if (numbers.items()) { n = numbers.pop(index); - numbers.undo_push(n, index); + NumberStack::undo_push<numbers>(n, index); } return n; @@ -83,7 +85,7 @@ Expressions::add_digit(gchar digit) Expressions::Operator Expressions::push(Expressions::Operator op) { - operators.undo_pop(); + OperatorStack::undo_pop<operators>(); return operators.push(op); } @@ -107,7 +109,7 @@ Expressions::pop_op(guint index) if (operators.items()) { op = operators.pop(index); - operators.undo_push(op, index); + OperatorStack::undo_push<operators>(op, index); } return op; @@ -181,8 +183,6 @@ Expressions::eval(bool pop_brace) break; op = operators.peek(n); - if (op == OP_LOOP) - break; if (op == OP_BRACE) { if (pop_brace) pop_op(n); @@ -208,18 +208,6 @@ Expressions::args(void) } gint -Expressions::find_op(Operator op) -{ - guint items = operators.items(); - - for (guint i = 0; i < items; i++) - if (operators.peek(i) == op) - return i; - - return -1; /* not found */ -} - -gint Expressions::first_op(void) { guint items = operators.items(); @@ -245,6 +233,26 @@ Expressions::discard_args(void) pop_num_calc(); } +void +Expressions::brace_return(guint keep_braces, guint args) +{ + tecoInt return_numbers[args]; + + for (guint i = args; i; i--) + return_numbers[i-1] = pop_num(); + + undo.push_var(brace_level); + + while (brace_level > keep_braces) { + discard_args(); + eval(true); + brace_level--; + } + + for (guint i = 0; i < args; i++) + push(return_numbers[i]); +} + const gchar * Expressions::format(tecoInt number) { diff --git a/src/expressions.h b/src/expressions.h index 216ec7a..cdb2d3e 100644 --- a/src/expressions.h +++ b/src/expressions.h @@ -27,41 +27,39 @@ namespace SciTECO { template <typename Type> class ValueStack { - class UndoTokenPush : public UndoTokenWithSize<UndoTokenPush> { - /* - * FIXME: saving the UndoStack for each undo taken - * wastes a lot of memory - */ - ValueStack<Type> *stack; - + /* + * NOTE: Since value stacks are usually singleton, + * we pass them as a template parameter, saving space + * in the undo token. + */ + template <ValueStack<Type> &stack> + class UndoTokenPush : public UndoTokenWithSize<UndoTokenPush<stack>> { Type value; guint index; public: - UndoTokenPush(ValueStack<Type> *_stack, - Type _value, guint _index = 0) - : stack(_stack), value(_value), index(_index) {} + UndoTokenPush(Type _value, guint _index = 0) + : value(_value), index(_index) {} void run(void) { - stack->push(value, index); + stack.push(value, index); } }; - class UndoTokenPop : public UndoTokenWithSize<UndoTokenPop> { - ValueStack<Type> *stack; - + template <ValueStack<Type> &stack> + class UndoTokenPop : public UndoTokenWithSize<UndoTokenPop<stack>> { guint index; public: - UndoTokenPop(ValueStack<Type> *_stack, guint _index = 0) - : stack(_stack), index(_index) {} + UndoTokenPop(guint _index = 0) + : index(_index) {} void run(void) { - stack->pop(index); + stack.pop(index); } }; @@ -108,10 +106,12 @@ public: return sp[index] = value; } - inline void + + template <ValueStack<Type> &stack> + static inline void undo_push(Type value, guint index = 0) { - undo.push<UndoTokenPush>(this, value, index); + undo.push<UndoTokenPush<stack>>(value, index); } inline Type @@ -129,10 +129,12 @@ public: return v; } - inline void + + template <ValueStack<Type> &stack> + static inline void undo_pop(guint index = 0) { - undo.push<UndoTokenPop>(this, index); + undo.push<UndoTokenPop<stack>>(index); } inline Type & @@ -143,10 +145,13 @@ public: return sp[index]; } + /** Clear all but `keep_items` items. */ inline void - clear(void) + clear(guint keep_items = 0) { - sp = stack_top; + g_assert(keep_items <= items()); + + sp = stack_top - keep_items; } }; @@ -170,7 +175,6 @@ public: OP_NIL = 0x00, OP_NEW, OP_BRACE, - OP_LOOP, OP_NUMBER, /* * Real operators @@ -194,11 +198,19 @@ private: return op >> 4; } - ValueStack<tecoInt> numbers; - ValueStack<Operator> operators; + /* + * Number and operator stacks are static, so + * they can be passed to the undo token constructors. + * This is OK since Expression is singleton. + */ + typedef ValueStack<tecoInt> NumberStack; + static NumberStack numbers; + + typedef ValueStack<Operator> OperatorStack; + static OperatorStack operators; public: - Expressions() : num_sign(1), radix(10) {} + Expressions() : num_sign(1), radix(10), brace_level(0) {} gint num_sign; inline void @@ -258,13 +270,33 @@ public: void discard_args(void); - gint find_op(Operator op); + /** The nesting level of braces */ + guint brace_level; + + inline void + brace_open(void) + { + push(OP_BRACE); + undo.push_var(brace_level)++; + } + + void brace_return(guint keep_braces, guint args = 0); + + inline void + brace_close(void) + { + if (!brace_level) + throw Error("Missing opening brace"); + undo.push_var(brace_level)--; + eval(true); + } inline void clear(void) { numbers.clear(); operators.clear(); + brace_level = 0; } const gchar *format(tecoInt number); 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(); diff --git a/src/parser.h b/src/parser.h index fe8f08b..b502f46 100644 --- a/src/parser.h +++ b/src/parser.h @@ -25,6 +25,7 @@ #include "sciteco.h" #include "undo.h" #include "error.h" +#include "expressions.h" namespace SciTECO { @@ -373,6 +374,25 @@ extern gint macro_pc; extern gchar *strings[2]; extern gchar escape_char; +struct LoopContext { + /** how many iterations are left */ + tecoInt counter; + /** Program counter of loop start command */ + guint pc : sizeof(guint)*8 - 1; + /** + * Whether the loop represents an argument + * barrier or not (it "passes through" + * stack arguments). + * + * Since the program counter is usually + * a signed integer, it's ok steal one + * bit for the pass_through flag. + */ + bool pass_through : 1; +}; +typedef ValueStack<LoopContext> LoopStack; +extern LoopStack loop_stack; + namespace Execute { void step(const gchar *macro, gint stop_pos); void macro(const gchar *macro, bool locals = true); diff --git a/src/qregisters.cpp b/src/qregisters.cpp index 6be0469..195521c 100644 --- a/src/qregisters.cpp +++ b/src/qregisters.cpp @@ -771,11 +771,11 @@ QRegisters::hook(Hook type) * So this effectively executes: * (typeM[ED]^[) */ - expressions.push(Expressions::OP_BRACE); + expressions.brace_open(); expressions.push(type); reg->execute(); expressions.discard_args(); - expressions.eval(true); + expressions.brace_close(); } catch (Error &error) { const gchar *type_str = type2name[type-1]; diff --git a/src/search.cpp b/src/search.cpp index 3a493ff..d5b314b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -642,7 +642,7 @@ StateSearch::done(const gchar *str) if (eval_colon()) expressions.push(search_reg->get_integer()); else if (IS_FAILURE(search_reg->get_integer()) && - expressions.find_op(Expressions::OP_LOOP) < 0 /* not in loop */) + !loop_stack.items() /* not in loop */) interface.msg(InterfaceCurrent::MSG_ERROR, "Search string not found!"); return &States::start; |