diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2016-02-15 14:00:32 +0100 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2016-02-15 15:00:55 +0100 |
commit | de7394372c77b695d4779606ea7587dfe61a35de (patch) | |
tree | db10983d46c0b4b3b5a7b67eeb457e58abb07fff | |
parent | f08187e454f56954b41d95615ca2e370ba19667e (diff) | |
download | sciteco-de7394372c77b695d4779606ea7587dfe61a35de.tar.gz |
revised looping implementation, aggregating loops, sane $$ semantics, some optimizationa and additional checks
* undo tokens emitted by the expression stack no longer waste
memory by pointing to the stack implementation.
This uses some ugly C++ constant template arguments but
saves 4 or 8 byte per undo token depending on the architecture.
* Round braces are counted now and the return command $$ will
use this information to discard all non-relevant brace levels.
* It is an error to close a brace when none have been opened.
* The bracing rules are still very liberal, allowing you to
close braces in macros belonging to a higher call frame
or leave them open at the end of a macro.
While this is technically possible, it is perhaps a good
idea to stricten these rules in some future release.
* Loops no longer (ab)use the expression stack to store
program counters and loop counters.
This removes flow control from the responsibility of the
expression stack which is much safer now since we can control
where we jump to.
This also eased implemented proper semantics for $$.
* It is an error to leave loops open at the end of a macro
or trying to close a loop opened in the caller of the macro.
Similarily it is only possible to close a loop from the
current invocation frame.
This means it is now impossible to accidentally jump to invalid
PCs.
* Even though loop context stacks could be attached directly to
the macro invocation frame, this would be inefficient.
Instead there's a loop frame pointer now that is part of the
invocation frame. All frames will reuse the same stack structure.
* Loops are automatically discarded when returning using $$.
* Special aggregating forms of the loop start (":<") and loop
end (":>") commands are possible now and have been implemented.
This improves SciTECO's capability as a stack-oriented language.
It is no longer necessary to write recursive macros to generate
stack values of arbitrary length dynamically or to process them.
* All expression and loop stacks are still fixed-size.
It may be a good idea to implement dynamic resizing (TODO).
* Added some G_UNLIKELYs to Execute::macro(). Should improve
the branch prediction of modern CPUs.
* Local Q-Register tables are allocated on the stack now instead
of on the heap (the bulk of a table is stored on the heap anyway).
Should improve performance of macro invocations.
* Document that "F<" will jump to the beginning of the macro
if there is no loop.
This is not in standard TECO, but I consider it a useful feature.
-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; |