re-fresh
[nedit-bw.git] / MultipleAssignment.diff
blobcb1aea27737feb91dcfefef4f4a359fd9ee0abcd
1 From: Tony Balinski <ajbj@free.fr>
2 Subject: Assign multiple lvalues in one statement
4 This patch allows you to assign a set of variables in a lvalue list from the
5 content of an array. This has the appearance of a tuple. The keys used to
6 retrieve the values from the array expression on the right are numeric values
7 starting with 1. If a key is missing, your macro will fail.
9 Given the following (implemented as new built-in functions in this patch):
11 define args {
12 return $args
14 define n_args {
15 i = 0;
16 while (i in $1)
17 ++i
18 return i
21 You can assign a list of symbols to the content of such an array using a list
22 assign, thus:
24 (x, y, z) = args(a, b, c)
26 This is equivalent to
28 x = a
29 y = b
30 z = c
32 This technique can be useful for retrieving argument values in your functions:
34 (str, regex, count) = $args
36 or for returning "tuples" from them:
38 define myfunc {
39 ...
40 return args(isOk, message)
42 ...
43 (success, msg) = myfunc()
45 If you need to count the number of variables that can be assigned in this way,
46 use the n_args() function:
48 ...
49 result = myfunc() # we expect result is an array
50 if (n_args(result) >= 2)
51 (success, msg) = result
53 ---
55 source/built-ins.h | 2
56 source/interpret.c | 23 ++++++++-
57 source/interpret.h | 3 -
58 source/macro.c | 70 +++++++++++++++++++++++++++++
59 source/parse.y | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++
60 5 files changed, 220 insertions(+), 3 deletions(-)
62 diff --quilt old/source/interpret.c new/source/interpret.c
63 --- old/source/interpret.c
64 +++ new/source/interpret.c
65 @@ -382,9 +382,9 @@ Inst *GetPC(void)
67 ** Swap the positions of two contiguous blocks of code. The first block
68 ** running between locations start and boundary, and the second between
69 -** boundary and end.
70 +** boundary and end. Return the moved boundary value.
72 -void SwapCode(Inst *start, Inst *boundary, Inst *end)
73 +Inst *SwapCode(Inst *start, Inst *boundary, Inst *end)
75 #define reverseCode(L, H) \
76 do { register Inst t, *l = L, *h = H - 1; \
77 @@ -394,6 +394,8 @@ void SwapCode(Inst *start, Inst *boundar
78 reverseCode(start, boundary); /* 1 */
79 reverseCode(boundary, end); /* 2 */
80 reverseCode(start, end); /* 3 */
81 + /* new pos of boundary is start + length of old top portion */
82 + return start + (end - boundary);
86 @@ -729,6 +731,23 @@ void SetMacroFocusWindow(WindowInfo *win
90 +** install a list assign (multi-assign) rvalue expression holder into the local
91 +** namespace.
92 +*/
93 +Symbol *InstallMultiAssignExpr(void)
95 + static const char symbolName[] = "list assign expr";
96 + Symbol *sym = LookupSymbol(symbolName);
97 + DataValue value;
98 + value.tag = NO_TAG;
99 + value.val.n = 0;
100 + if (!sym) {
101 + sym = InstallSymbol(symbolName, LOCAL_SYM, value);
103 + return sym;
107 ** install an array iteration symbol
108 ** it is tagged as an integer but holds an array node pointer
110 diff --quilt old/source/interpret.h new/source/interpret.h
111 --- old/source/interpret.h
112 +++ new/source/interpret.h
113 @@ -155,8 +155,9 @@ Symbol *LookupStringConstSymbol(const ch
114 Symbol *InstallStringConstSymbol(const char *str);
115 Symbol *LookupSymbol(const char *name);
116 Symbol *InstallSymbol(const char *name, enum symTypes type, DataValue value);
117 +Symbol *InstallMultiAssignExpr(void);
118 Program *FinishCreatingProgram(AccumulatorData *acc);
119 -void SwapCode(Inst *start, Inst *boundary, Inst *end);
120 +Inst *SwapCode(Inst *start, Inst *boundary, Inst *end);
121 void StartLoopAddrList(void);
122 int AddBreakAddr(Inst *addr);
123 int AddContinueAddr(Inst *addr);
124 diff --quilt old/source/macro.c new/source/macro.c
125 --- old/source/macro.c
126 +++ new/source/macro.c
127 @@ -1514,6 +1514,76 @@ static int escapedStringLength(char *str
131 +** Built-in macro subroutine to generate an args array, eg
132 +** array = args(arg1, arg2, ...)
133 +** return args(arg1, arg2, ...)
134 +** This is done by augmenting the $args array value, located at argList[nArgs],
135 +** then returning that. This function builds an array with non-named arguments
136 +** start at index 1; it is equivalent to
137 +** define args {
138 +** return $args
139 +** }
141 +static int argsMS(WindowInfo *window, DataValue *argList, int nArgs,
142 + DataValue *result, char **errMsg)
144 + DataValue *argsArray = &argList[nArgs];
145 + DataValue *argVal;
146 + int argNum;
148 + if (argsArray->tag != ARRAY_TAG) {
149 + argsArray->tag = ARRAY_TAG;
150 + argsArray->val.arrayPtr = ArrayNew();
153 + for (argNum = 1; argNum <= nArgs; ++argNum) {
154 + argVal = &argList[argNum - 1];
155 + if (!ArrayInsert(argsArray, AllocStringOfNumber(argNum), argVal)) {
156 + M_FAILURE("argument array insertion failure in %s");
160 + *result = *argsArray;
161 + return True;
165 +** Built-in macro subroutine to count the number of args in an args array, eg
166 +** n = n_args(argsArray)
167 +** This is done by counting how many values in the array have consecutive
168 +** numeric indices starting at 1, copying $n_args relationship with $args.
169 +** The result is also the last valid index in the sequence.
170 +** This function is equivalent to
171 +** define n_args {
172 +** for (i = 0; i in $1; ++i)
173 +** dummy = 0
174 +** return i
175 +** }
177 +static int nArgsMS(WindowInfo *window, DataValue *argList, int nArgs,
178 + DataValue *result, char **errMsg)
180 + DataValue *argsArray = &argList[0];
181 + DataValue val;
183 + if (nArgs != 1 || argsArray->tag != ARRAY_TAG) {
184 + M_FAILURE("%n can only take one argument, an array");
186 + else {
187 + int argNum = 0;
188 + do {
189 + ++argNum;
190 + } while (ArrayGet(argsArray, longAsStr(argNum), &val));
192 + /* the above loop terminates with argNum 1 beyond the last index */
193 + result->tag = INT_TAG;
194 + result->val.n = argNum - 1;
197 + return True;
201 ** Built-in macro subroutine for getting the length of a string
203 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
204 diff --quilt old/source/parse.y new/source/parse.y
205 --- old/source/parse.y
206 +++ new/source/parse.y
207 @@ -82,6 +82,15 @@ static int nextSymIsField = 0;
208 /* set to 1 when we don't want a full symbol, just a name (string) for a
209 field name following a '.' */
212 +** Positions holder for instruction reordering of code generated for the
213 +** left-hand lvalue list in a multi-assignment statement.
215 +typedef struct LVinst {
216 + Inst *start, *mid, *next;
217 + int nArgs;
218 +} LVinst;
222 %union {
223 @@ -89,6 +98,7 @@ static int nextSymIsField = 0;
224 Inst *inst;
225 int num;
226 enum operations oper;
227 + LVinst lvinst;
228 struct {
229 AccumulatorData *acc;
230 Symbol *sym;
231 @@ -105,6 +115,7 @@ static int nextSymIsField = 0;
232 %type <oper> operassign incrdecr
233 %token <oper> '=' ADDEQ SUBEQ MULEQ DIVEQ MODEQ ANDEQ OREQ
234 %token <oper> INCR DECR
235 +%type <lvinst> lvlist lventry
237 %nonassoc IF_NO_ELSE
238 %nonassoc ELSE
239 @@ -328,6 +339,120 @@ simpstmt: /* simple variable assignmen
240 ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED(1);
242 | funccall
243 + | lvlistexpr
247 +** Implementing a multi-assignment to a list of lvalues
249 +** Each accepting lvalue requires a push phase P and an assign phase A.
251 +** For arrays, P includes pushing the indices and the destination array value.
252 +** P always also includes pushing N, the position of the lval in the list, i.e.
253 +** the index into the r.h.s. array expression supplying values.
255 +** The assign phase uses N (on the stack) and the stored result E of the source
256 +** array expression - this gives the indexed r.h.s. value to assign. Then, if
257 +** the destination is an array element, the destination array and indices are
258 +** retrieved from the stack and the assignment performed.
260 +** Given "(a, b, c) = e", we need to end up with the command sequence:
261 +** Pc Pb Pa Ee Aa Ab Ac
262 +** However, we have to generate Px and Ax together in the parse. Hence we
263 +** must reorder, using PC mark positions Mx. For the example, we generate:
264 +** read a: M0[Pa]M1[Aa]Mt here Mt is the current GetPC() value
265 +** read b: M0[Pa]M1[Aa]M2[Pb]M3[Ab]Mt now swap(M0, M2, M3)
266 +** M0[Pb Pa]M1[Aa Ab]Mt
267 +** and c: M0[Pb Pa]M1[Aa Ab]M2[Pc]M3[Ac]Mt swap(M0, M2, M3)
268 +** M0[Pc Pb Pa]M1[Aa Ab Ac]Mt
269 +** then e: M0[Pc Pb Pa]M1[Aa Ab Ac]M2[Ee]Mt swap(M1, M2, Mt)
270 +** [Pc Pb Pa][Ee][Aa Ab Ac]
271 +** which is what we want. At each swap, we must retain intermediate positions.
272 +** Below for lvlist, M0 is $$.start, M1 is $$.mid, M2 is $$.next. lventry's $$
273 +** gives the mid value between the P and A phases (M1).
275 +lvlistexpr: '(' lvlist ')' blank '=' blank expr {
276 + /* store expression value */
277 + ADD_OP(OP_ASSIGN); ADD_SYM(InstallMultiAssignExpr());
278 + /* swap expression evaluation code into position */
279 + SwapCode($2.mid, $2.next, GetPC());
283 +** lvlist: code for pushing the expression's index is generated here, after the code
284 +** for assigning to the lvalue. In other words, the Px portion is split, with
285 +** the assign code in front of the push index code. This needs to be swapped.
286 +** Only then do we have the situation described for overall list assignment.
288 +lvlist: lventry {
289 + /* start case */
290 + $$.nArgs = $1.nArgs;
291 + /* add code to push the rvalue expression index needed */
292 + ADD_OP(OP_PUSH_IMMED); ADD_IMMED($$.nArgs);
293 + $$.start = $1.start;
294 + $$.next = GetPC();
295 + /* swap this code in front of the lvalue assignment code */
296 + $$.mid = SwapCode($1.mid, $1.next, $$.next);
298 + | lvlist ',' blank lventry {
299 + /* recursive step case - starts similarly */
300 + $$.nArgs = $1.nArgs + $4.nArgs;
301 + /* add code to push the rvalue expression index needed */
302 + ADD_OP(OP_PUSH_IMMED); ADD_IMMED($$.nArgs);
303 + $$.start = $1.start;
304 + $$.next = GetPC();
305 + /* swap this code in front of the lvalue assignment code */
306 + $4.mid = SwapCode($4.mid, $4.next, $$.next);
307 + /*
308 + ** now swap Px code to midpoint and change midpoint
309 + ** $1.start[P...]$1.mid[A...]$1.next[Px]$4.mid[Ax]$$.next --->
310 + ** $1.start[Px][P...]$1.mid[A...]$4.mid[Ax]$$.next
311 + */
312 + SwapCode($1.start, $1.next, $4.mid);
313 + $$.start = $1.start; /* keep start point */
314 + $$.mid = $1.mid + ($4.mid - $1.next); /* adjust mid point */
317 +/* lventry's value is the PC position between the Px and Ax parts */
318 +lventry: mark SYMBOL {
319 + $$.nArgs = 1;
320 + $$.start = $1;
321 + /* Push code: null */
322 + $$.mid = GetPC();
323 + /* Assign code: stack: N, ... */
324 + ADD_OP(OP_PUSH_SYM); ADD_SYM(InstallMultiAssignExpr());
325 + /* stack: E, N, ... */
326 + ADD_OP(OP_SWAP_TOP2); /* stack: N, E, ... */
327 + ADD_OP(OP_ARRAY_REF); ADD_IMMED(1); /* stack: E[N] ... */
328 + ADD_OP(OP_ASSIGN); ADD_SYM($2);
329 + $$.next = GetPC();
331 + | mark initarraylv '[' arglist ']' {
332 + $$.nArgs = 1;
333 + $$.start = $1;
334 + /* Push code dealt with in "initarraylv '[' arglist ']'" */
335 + $$.mid = GetPC();
336 + /* Assign code: stack: N, ... */
337 + ADD_OP(OP_PUSH_SYM); ADD_SYM(InstallMultiAssignExpr());
338 + /* stack: E, N, ... */
339 + ADD_OP(OP_SWAP_TOP2); /* stack: N, E, ... */
340 + ADD_OP(OP_ARRAY_REF); ADD_IMMED(1); /* stack: E[N] ... */
341 + ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED($4);
342 + $$.next = GetPC();
344 + | mark initarraylv dot field {
345 + $$.nArgs = 1;
346 + $$.start = $1;
347 + /* Push code dealt with in "initarraylv dot field" */
348 + $$.mid = GetPC();
349 + /* Assign code: stack: N, ... */
350 + ADD_OP(OP_PUSH_SYM); ADD_SYM(InstallMultiAssignExpr());
351 + /* stack: E, N, ... */
352 + ADD_OP(OP_SWAP_TOP2); /* stack: N, E, ... */
353 + ADD_OP(OP_ARRAY_REF); ADD_IMMED(1); /* stack: E[N] ... */
354 + ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED(1);
355 + $$.next = GetPC();
359 evalsym: SYMBOL {
360 diff --quilt old/source/built-ins.h new/source/built-ins.h
361 --- old/source/built-ins.h
362 +++ new/source/built-ins.h
363 @@ -59,6 +59,8 @@ MS(get_style_by_name, getStyleByName)
364 MS(get_style_at_pos, getStyleAtPos)
365 MS(filename_dialog, filenameDialog)
366 MS(call, call)
367 +MS(args, args)
368 +MS(n_args, nArgs)
370 MV(cursor, cursor)
371 MV(line, line)