Assign multiple lvalues in one statement This patch allows you to assign a set of variables in a lvalue list from the content of an array. This has the appearance of a tuple. The keys used to retrieve the values from the array expression on the right are numeric values starting with 1. If a key is missing, your macro will fail. Given the following (implemented as new built-in functions in this patch): define args { return $args } define n_args { i = 0; while (i in $1) ++i return i } You can assign a list of symbols to the content of such an array using a list assign, thus: (x, y, z) = args(a, b, c) This is equivalent to x = a y = b z = c This technique can be useful for retrieving argument values in your functions: (str, regex, count) = $args or for returning "tuples" from them: define myfunc { ... return args(isOk, message) } ... (success, msg) = myfunc() If you need to count the number of variables that can be assigned in this way, use the n_args() function: ... result = myfunc() # we expect result is an array if (n_args(result) >= 2) (success, msg) = result diff -ur nedit_official nedit_mod diff -ur nedit_official/source/interpret.c nedit_mod/source/interpret.c --- nedit_official/source/interpret.c 2007-10-18 15:26:43.000000000 +0200 +++ nedit_mod/source/interpret.c 2008-02-27 23:36:37.000000000 +0100 @@ -126,6 +126,7 @@ static int arrayIter(void); static int inArray(void); static int deleteArrayElement(void); +static int swapTop2(void); static void freeSymbolTable(Symbol *symTab); static int errCheck(const char *s); static int execError(const char *s1, const char *s2); @@ -208,7 +209,9 @@ assign, callSubroutine, fetchRetVal, branch, branchTrue, branchFalse, branchNever, arrayRef, arrayAssign, beginArrayIter, arrayIter, inArray, deleteArrayElement, pushArraySymVal, - arrayRefAndAssignSetup, pushArgVal, pushArgCount, pushArgArray}; + arrayRefAndAssignSetup, pushArgVal, pushArgCount, pushArgArray, + swapTop2, +}; /* Stack-> symN-sym0(FP), argArray, nArgs, oldFP, retPC, argN-arg1, next, ... */ #define FP_ARG_ARRAY_CACHE_INDEX (-1) @@ -385,9 +388,9 @@ /* ** Swap the positions of two contiguous blocks of code. The first block ** running between locations start and boundary, and the second between -** boundary and end. +** boundary and end. Return the moved boundary value. */ -void SwapCode(Inst *start, Inst *boundary, Inst *end) +Inst *SwapCode(Inst *start, Inst *boundary, Inst *end) { #define reverseCode(L, H) \ do { register Inst t, *l = L, *h = H - 1; \ @@ -397,6 +400,8 @@ reverseCode(start, boundary); /* 1 */ reverseCode(boundary, end); /* 2 */ reverseCode(start, end); /* 3 */ + /* new pos of boundary is start + length of old top portion */ + return start + (end - boundary); } /* @@ -663,6 +668,23 @@ } /* +** install a list assign (multi-assign) rvalue expression holder into the local +** namespace. +*/ +Symbol *InstallMultiAssignExpr(void) +{ + static const char symbolName[] = "list assign expr"; + Symbol *sym = LookupSymbol(symbolName); + DataValue value; + value.tag = NO_TAG; + value.val.n = 0; + if (!sym) { + sym = InstallSymbol(symbolName, LOCAL_SYM, value); + } + return sym; +} + +/* ** install an array iteration symbol ** it is tagged as an integer but holds an array node pointer */ @@ -1299,6 +1321,24 @@ } /* +** exchange top two values on the stack +*/ +static int swapTop2(void) +{ + DataValue dv1, dv2; + + DISASM_RT(PC-1, 1); + STACKDUMP(2, 3); + + POP(dv1) + POP(dv2) + PUSH(dv1) + PUSH(dv2) + + return STAT_OK; +} + +/* ** assign top value to next symbol ** ** Before: Prog-> [symbol], next, ... @@ -2926,7 +2966,8 @@ "ARRAY_REF_ASSIGN_SETUP", /* arrayRefAndAssignSetup */ "PUSH_ARG", /* $arg[expr] */ "PUSH_ARG_COUNT", /* $arg[] */ - "PUSH_ARG_ARRAY" /* $arg */ + "PUSH_ARG_ARRAY", /* $arg */ + "SWAP_TOP2", /* swapTop2 */ }; int i, j; diff -ur nedit_official/source/interpret.h nedit_mod/source/interpret.h --- nedit_official/source/interpret.h 2007-01-12 17:17:42.000000000 +0100 +++ nedit_mod/source/interpret.h 2008-02-27 23:50:25.000000000 +0100 @@ -40,7 +40,6 @@ enum symTypes {CONST_SYM, GLOBAL_SYM, LOCAL_SYM, ARG_SYM, PROC_VALUE_SYM, C_FUNCTION_SYM, MACRO_FUNCTION_SYM, ACTION_ROUTINE_SYM}; -#define N_OPS 43 enum operations {OP_RETURN_NO_VAL, OP_RETURN, OP_PUSH_SYM, OP_DUP, OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_NEGATE, OP_INCR, OP_DECR, OP_GT, OP_LT, OP_GE, OP_LE, OP_EQ, OP_NE, OP_BIT_AND, OP_BIT_OR, OP_AND, OP_OR, OP_NOT, @@ -48,7 +47,8 @@ OP_BRANCH_TRUE, OP_BRANCH_FALSE, OP_BRANCH_NEVER, OP_ARRAY_REF, OP_ARRAY_ASSIGN, OP_BEGIN_ARRAY_ITER, OP_ARRAY_ITER, OP_IN_ARRAY, OP_ARRAY_DELETE, OP_PUSH_ARRAY_SYM, OP_ARRAY_REF_ASSIGN_SETUP, OP_PUSH_ARG, - OP_PUSH_ARG_COUNT, OP_PUSH_ARG_ARRAY}; + OP_PUSH_ARG_COUNT, OP_PUSH_ARG_ARRAY, OP_SWAP_TOP2, + N_OPS}; enum typeTags {NO_TAG, INT_TAG, STRING_TAG, ARRAY_TAG}; @@ -137,13 +137,14 @@ int AddImmediate(int value, char **msg); int AddBranchOffset(Inst *to, char **msg); Inst *GetPC(void); +Symbol *InstallMultiAssignExpr(void); Symbol *InstallIteratorSymbol(void); Symbol *LookupStringConstSymbol(const char *value); Symbol *InstallStringConstSymbol(const char *str); Symbol *LookupSymbol(const char *name); Symbol *InstallSymbol(const char *name, enum symTypes type, DataValue value); Program *FinishCreatingProgram(void); -void SwapCode(Inst *start, Inst *boundary, Inst *end); +Inst *SwapCode(Inst *start, Inst *boundary, Inst *end); void StartLoopAddrList(void); int AddBreakAddr(Inst *addr); int AddContinueAddr(Inst *addr); diff -ur nedit_official/source/macro.c nedit_mod/source/macro.c --- nedit_official/source/macro.c 2007-10-04 18:04:25.000000000 +0200 +++ nedit_mod/source/macro.c 2008-02-29 00:29:08.000000000 +0100 @@ -155,6 +155,10 @@ static Boolean continueWorkProc(XtPointer clientData); static int escapeStringChars(char *fromString, char *toString); static int escapedStringLength(char *string); +static int argsMS(WindowInfo *window, DataValue *argList, int nArgs, + DataValue *result, char **errMsg); +static int nArgsMS(WindowInfo *window, DataValue *argList, int nArgs, + DataValue *result, char **errMsg); static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs, DataValue *result, char **errMsg); static int minMS(WindowInfo *window, DataValue *argList, int nArgs, @@ -407,7 +411,8 @@ DataValue* result, char** errMsg); /* Built-in subroutines and variables for the macro language */ -static BuiltInSubr MacroSubrs[] = {lengthMS, getRangeMS, tPrintMS, +static BuiltInSubr MacroSubrs[] = {argsMS, nArgsMS, + lengthMS, getRangeMS, tPrintMS, dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS, setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS, searchStringMS, substringMS, replaceSubstringMS, readFileMS, @@ -426,7 +431,8 @@ getStyleByNameMS, getStyleAtPosMS, filenameDialogMS }; #define N_MACRO_SUBRS (sizeof MacroSubrs/sizeof *MacroSubrs) -static const char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print", +static const char *MacroSubrNames[N_MACRO_SUBRS] = {"args", "n_args", + "length", "get_range", "t_print", "dialog", "string_dialog", "replace_range", "replace_selection", "set_cursor_pos", "get_character", "min", "max", "search", "search_string", "substring", "replace_substring", "read_file", @@ -1797,6 +1803,77 @@ } /* +** Built-in macro subroutine to generate an args array, eg +** array = args(arg1, arg2, ...) +** return args(arg1, arg2, ...) +** This is done by augmenting the $args array value, located at argList[nArgs], +** then returning that. This function builds an array with non-named arguments +** start at index 1; it is equivalent to +** define args { +** return $args +** } +*/ +static int argsMS(WindowInfo *window, DataValue *argList, int nArgs, + DataValue *result, char **errMsg) +{ + DataValue *argsArray = &argList[nArgs]; + DataValue *argVal; + int argNum; + + if (argsArray->tag != ARRAY_TAG) { + argsArray->tag = ARRAY_TAG; + argsArray->val.arrayPtr = ArrayNew(); + } + for (argNum = 1; argNum <= nArgs; ++argNum) { + char stringStorage[TYPE_INT_STR_SIZE(int)]; + NString numStr; + sprintf(stringStorage, "%d", argNum); + AllocNStringCpy(&numStr, stringStorage); + argVal = &argList[argNum - 1]; + if (!ArrayInsert(argsArray, numStr.rep, argVal)) { + M_FAILURE("argument array insertion failure in %s"); + } + } + *result = *argsArray; + return True; +} + +/* +** Built-in macro subroutine to count the number of args in an args array, eg +** n = n_args(argsArray) +** This is done by counting how many values in the array have consecutive +** numeric indices starting at 1, copying $n_args relationship with $args. +** The result is also the last valid index in the sequence. +** This function is equivalent to +** define n_args { +** for (i = 0; i in $1; ++i) +** dummy = 0 +** return i +** } +*/ +static int nArgsMS(WindowInfo *window, DataValue *argList, int nArgs, + DataValue *result, char **errMsg) +{ + DataValue *argsArray = &argList[0]; + DataValue val; + + if (nArgs != 1 || argsArray->tag != ARRAY_TAG) { + M_FAILURE("%n can only take one argument, an array"); + } + else { + char intString[TYPE_INT_STR_SIZE(int)]; + int argNum = 0; + do { + sprintf(intString, "%d", ++argNum); + } while (ArrayGet(argsArray, intString, &val)); + /* the above loop terminates with argNum 1 beyond the last index */ + result->tag = INT_TAG; + result->val.n = argNum - 1; + } + return True; +} + +/* ** Built-in macro subroutine for getting the length of a string */ static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs, diff -ur nedit_official/source/parse.y nedit_mod/source/parse.y --- nedit_official/source/parse.y 2007-01-12 17:17:42.000000000 +0100 +++ nedit_mod/source/parse.y 2008-02-27 23:52:39.000000000 +0100 @@ -46,25 +46,33 @@ static int follow2(char expect1, int yes1, char expect2, int yes2, int no); static int follow_non_whitespace(char expect, int yes, int no); static Symbol *matchesActionRoutine(char **inPtr); +static Symbol *constNumber(int n); static char *ErrMsg; static char *InPtr; extern Inst *LoopStack[]; /* addresses of break, cont stmts */ extern Inst **LoopStackPtr; /* to fill at the end of a loop */ +typedef struct LVListinst_ { + Inst *start, *mid, *next; + int nArgs; +} LVListInst; + %} %union { Symbol *sym; Inst *inst; int nArgs; + LVListInst lvinst; } %token NUMBER STRING SYMBOL %token DELETE ARG_LOOKUP %token IF WHILE ELSE FOR BREAK CONTINUE RETURN %type arglist -%type cond comastmts for while else and or arrayexpr +%type cond comastmts for while else and or arrayexpr mark %type evalsym +%type lvlist lventry %nonassoc IF_NO_ELSE %nonassoc ELSE @@ -108,6 +116,8 @@ stmts: stmt | stmts stmt ; +mark: /* nothing */ { $$ = GetPC(); } /* record instruction address */ + ; stmt: simpstmt '\n' blank | IF '(' cond ')' blank block %prec IF_NO_ELSE { SET_BR_OFF($3, GetPC()); @@ -258,7 +268,108 @@ ADD_OP(OP_PUSH_SYM); ADD_SYM($1); ADD_OP(OP_DECR); ADD_OP(OP_ASSIGN); ADD_SYM($1); } + | lvlistexpr ; + +/* Implementing an assignment to a list of lvalues + + Each accepting lvalue requires a push phase P and an assign phase A. + + For arrays, P includes pushing the indices and the destination array value. + P always also includes pushing N, the position of the lval in the list, i.e. + the index into the r.h.s. array expression supplying values. + + The assign phase uses N (on the stack) and the stored result E of the source + array expression - this gives the indexed r.h.s. value to assign. Then, if + the destination is an array element, the destination array and indices are + retrieved from the stack and the assignment performed. + + Given "(a, b, c) = e", we need to end up with the command sequence: + Pc Pb Pa Ee Aa Ab Ac + However, we have to generate Px and Ax together in the parse. Hence we + must reorder, using PC mark positions Mx. For the example, we generate: + read a: M0[Pa]M1[Aa]Mt here Mt is the current GetPC() value + read b: M0[Pa]M1[Aa]M2[Pb]M3[Ab]Mt now swap(M0, M2, M3) + M0[Pb Pa]M1[Aa Ab]Mt + and c: M0[Pb Pa]M1[Aa Ab]M2[Pc]M3[Ac]Mt swap(M0, M2, M3) + M0[Pc Pb Pa]M1[Aa Ab Ac]Mt + then e: M0[Pc Pb Pa]M1[Aa Ab Ac]M2[Ee]Mt swap(M1, M2, Mt) + [Pc Pb Pa][Ee][Aa Ab Ac] + which is what we want. At each swap, we must retain intermediate positions. + Below for lvlist, M0 is $$.start, M1 is $$.mid, M2 is $$.next. lventry's $$ + gives the mid value between the P and A phases (M1). +*/ +lvlistexpr: '(' lvlist ')' blank '=' blank expr { + /* store expression value */ + ADD_OP(OP_ASSIGN); ADD_SYM(InstallMultiAssignExpr()); + /* swap expression evaluation code into position */ + SwapCode($2.mid, $2.next, GetPC()); + } + ; +/* lvlist: code for pushing the expression's index is generated here, after the code + for assigning to the lvalue. In other words, the Px portion is split, with + the assign code in front of the push index code. This needs to be swapped. + Only then do we have the situation described for overall list assignment. +*/ +lvlist: lventry { + /* start case */ + $$.nArgs = $1.nArgs; + Symbol *sym = constNumber($$.nArgs); + /* add code to push the rvalue expression index needed */ + ADD_OP(OP_PUSH_SYM); ADD_SYM(sym); + $$.start = $1.start; + $$.next = GetPC(); + /* swap this code in front of the lvalue assignment code */ + $$.mid = SwapCode($1.mid, $1.next, $$.next); + } + | lvlist ',' blank lventry { + /* recursive step case - starts similarly */ + $$.nArgs = $1.nArgs + $4.nArgs; + Symbol *sym = constNumber($$.nArgs); + /* add code to push the rvalue expression index needed */ + ADD_OP(OP_PUSH_SYM); ADD_SYM(sym); + $$.start = $1.start; + $$.next = GetPC(); + /* swap this code in front of the lvalue assignment code */ + $4.mid = SwapCode($4.mid, $4.next, $$.next); + + /* now swap Px code to midpoint and change midpoint + $1.start[P...]$1.mid[A...]$1.next[Px]$4.mid[Ax]$$.next ---> + $1.start[Px][P...]$1.mid[A...]$4.mid[Ax]$$.next */ + SwapCode($1.start, $1.next, $4.mid); + $$.start = $1.start; /* keep start point */ + $$.mid = $1.mid + ($4.mid - $1.next); /* adjust mid point */ + } + ; +/* lventry's value is the PC position between the Px and Ax parts */ +lventry: mark SYMBOL { + $$.nArgs = 1; + $$.start = $1; + /* Push code: null */ + $$.mid = GetPC(); + /* Assign code: stack: N, ... */ + ADD_OP(OP_PUSH_SYM); ADD_SYM(InstallMultiAssignExpr()); + /* stack: E, N, ... */ + ADD_OP(OP_SWAP_TOP2); /* stack: N, E, ... */ + ADD_OP(OP_ARRAY_REF); ADD_IMMED(1); /* stack: E[N] ... */ + ADD_OP(OP_ASSIGN); ADD_SYM($2); + $$.next = GetPC(); + } + | mark initarraylv '[' arglist ']' { + $$.nArgs = 1; + $$.start = $1; + /* Push code dealt with in "initarraylv '[' arglist ']'" */ + $$.mid = GetPC(); + /* Assign code: stack: N, ... */ + ADD_OP(OP_PUSH_SYM); ADD_SYM(InstallMultiAssignExpr()); + /* stack: E, N, ... */ + ADD_OP(OP_SWAP_TOP2); /* stack: N, E, ... */ + ADD_OP(OP_ARRAY_REF); ADD_IMMED(1); /* stack: E[N] ... */ + ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED($4); + $$.next = GetPC(); + } + ; + evalsym: SYMBOL { $$ = $1; ADD_OP(OP_PUSH_SYM); ADD_SYM($1); } @@ -517,13 +628,10 @@ /* process number tokens */ if (isdigit((unsigned char)*InPtr)) { /* number */ - char name[28]; - sscanf(InPtr, "%d%n", &value.val.n, &len); - sprintf(name, "const %d", value.val.n); + int n; + sscanf(InPtr, "%d%n", &n, &len); InPtr += len; - value.tag = INT_TAG; - if ((yylval.sym=LookupSymbol(name)) == NULL) - yylval.sym = InstallSymbol(name, CONST_SYM, value); + yylval.sym = constNumber(n); return NUMBER; } @@ -696,6 +804,20 @@ } } +static Symbol *constNumber(int n) +{ + char name[28]; + Symbol *sym; + DataValue value; + + value.tag = INT_TAG; + value.val.n = n; + sprintf(name, "const %d", n); + if ((sym=LookupSymbol(name)) == NULL) + sym = InstallSymbol(name, CONST_SYM, value); + return sym; +} + /* ** look ahead for >=, etc. */