1 Allow inline construction of anonymous arrays and "named arguments"
4 http://sourceforge.net/tracker/index.php?func=detail&aid=1592340&group_id=11005&atid=311005
5 [ 1592340 ] Array literals and named arguments
6 anonArrayNamedArgs3.diff 2006-11-16 02:34
8 This patch allows arrays to be created in-line as anonymous entities. For
11 1, 2, 3, # assigns elements [0] to [2] \
12 ["word"] = "val", # assigns element ["word"] \
13 , # don't assign to [3] \
14 4, # assigns element [4] = 4 \
15 [7] = 7, 8, # assigns elements [7] and [8] \
16 ["sub"] = { "a", "b" } # assigns an array to element ["sub"] \
18 The anonymous array is available in contexts where a whole array is needed;
20 val0 = { [0] = "zero"}[0]
21 my_array += { [val0] = 4 + 5 - 9 }
23 The patch also allows macro functions to be called with array element style
24 assignments. These cannot use simple numeric or numeric string indices, but
25 multidimensional numeric indices are allowed.
27 The array element style ("named") arguments are inserted into the called
28 routine's $args array, and accessible using array syntax. Simple argument
29 expressions are counted from the first ($1), with numeric index 1 in $args,
30 up to $n_args. Note that $args[] and $n_args may now be different since
31 the former will include the count of "named" arguments.
37 a = returnArgs(1, 2, 3, 4, 5, [6,7]=8, ["hello"] = "hi")
38 b = { , 1, 2, 3, 4, 5, [6,7]=8, ["hello"] = "hi" }
39 Here the arrays a and b are equal. (Note the comma at the start of b's
42 a = returnArgs(1, [2]="no") # fails: name for arg cannot be numeric
44 Finally, calling a built-in macro function always makes sure an extra
45 argument is present at argList[nArgs] - in calls using positional values
46 only, this argument will have a tag value of NO_TAG. If "named" arguments
47 have been passed, the argument will have tag ARRAY_TAG, and the named
48 arguments be present in the array, indexed by name. (The positional
49 arguments will not be in this array but can be added to it readily.)
50 Use the ArrayGet() function or iterate over entries as required.
54 doc/help.etx | 100 +++++++++-
55 source/interpret.c | 502 ++++++++++++++++++++++++++++++++++++++++++++---------
56 source/interpret.h | 6
57 source/parse.y | 97 +++++++++-
58 4 files changed, 608 insertions(+), 97 deletions(-)
60 diff --quilt old/doc/help.etx new/doc/help.etx
63 @@ -2043,14 +2043,30 @@ Macro Language
65 The syntax of a function or subroutine call is:
67 function_name(arg1, arg2, ...)
69 - where arg1, arg2, etc. represent the argument values which are passed to
70 - the routine being called. A function or subroutine call can be on a line by
71 - itself, as above, or if it returns a value, can be invoked within a character
72 - or numeric expression:
73 + where arg1, arg2, etc. represent the arguments which are passed to
74 + the routine being called. Arguments can be one of two kinds: positional or
75 + named. A positional argument is passed as an expression in the argument
76 + list. A named argument is passed by indicating the name as an array key,
77 + between square brackets, followed by the "=" operator and an expression
78 + for the argument's value. Named arguments can use any valid array key value
79 + as long as it cannot be converted into an integer value.
81 + For example, the call
83 + result = func(["prompt"] = "Available values are:", \
85 + ["buttons"] = { "OK", "Change", "Cancel" })
87 + provides six positional arguments (with the values of variables a, b, c, d, e
88 + and f), and the named arguments "prompt", with a string value, and "buttons"
89 + with an array value.
91 + A function or subroutine call can be on a line by itself, as above, or if it
92 + returns a value, can be invoked within a character or numeric expression:
94 a = fn1(b, c) + fn2(d)
95 dialog("fn3 says: " fn3())
97 Arguments are passed by value. This means that you can not return values via
98 @@ -2093,15 +2109,18 @@ Macro Language
99 Subroutine definitions can not appear within other definitions, or within
100 macro menu item definitions. They can only appear in (macro) files, such as
101 the autoload macro file, cf. Preferences_. Macro files can be loaded with
102 File -> Load Macro File or with the load_macro_file() action.
104 - The arguments with which a user-defined subroutine or function was invoked,
105 - are presented as $1, $2, ... , $9 or $args[expr], where expr can be evaluated
106 - to an integer from 1 to the number of arguments. The number of arguments can
107 - be read from $n_args or $args[]. The array $args[expr] is the only way to
108 - access arguments beyond the first 9.
109 + Within the body of a user-defined subroutine or function, the first nine
110 + positional arguments can be retrieved using the identifiers $1, $2, ... , $9.
111 + Both positional and named arguments can be accessed using the $args array:
112 + if the key is numeric, the corresponding positional argument (numbered from 1)
113 + can be retrieved; otherwise the key is a name, and the name argument's value
114 + is retrieved. The identifier $n_args provides the number of positional
115 + arguments passed to the function. You can test for the presence of named
116 + arguments in the $args array using the "in" operator.
118 To return a value from a subroutine, and/or to exit from the subroutine
119 before the end of the subroutine body, use the return statement:
121 return <value to return>
122 @@ -2236,11 +2255,11 @@ Macro Language
123 When duplicate keys are encountered using the + and & operators, the values
124 from the array on the right side of the operators are used for the result.
125 All of the above operators are array only, meaning both the left and right
126 sides of the operator must be arrays. The results are also arrays.
128 - Array keys can also contain multiple dimensions:
129 + Array keys can contain multiple "dimensions":
131 x[1, 1, 1] = "string"
133 These are used in the expected way, e.g.:
135 @@ -2281,10 +2300,71 @@ Macro Language
136 if (("1" $sub_sep "2") in myArray)
141 + Note that if an array contains a value that is itself an array, you can
142 + apply the index operator more than once. For example
144 + subarray["a"] = "value"
145 + mainarray[1] = subarray
147 + for (i in mainarray) {
148 + if ("a" in mainarray[i])
149 + value_a = mainarray[i]["a"]
153 +4>Array Initializing Expressions
155 + You can build arrays using array expressions. These are contained in braces,
156 + "{" and "}", and contain a list of possibly empty value assignments.
159 + myarray = { ["a"] = "first", \
160 + ["col" colno] = x * 5, \
161 + [x, y] = 2 * func() }
163 + If the keys are numeric (or convertible to plian integers) and in increasing
164 + sequence, only the first is required; thus
166 + myarray = { [5] = a, b, c, \
167 + [1,2] = "2-D key", \
170 + creates entries with keys "5", "6", "7", "20", "21" and ("1" $sub_sep "2").
171 + If no key value is given for the first entry, "0" is used. If you want to skip
172 + a value in a sequence, just provide an empty value, thus
174 + myarray = { a, b, , c }
176 + creates entries with keys "0", "1" and "3". The entry for key "2" is not
179 + If a later array entry has the same key value as an earlier one, the later
180 + value overwrites the earlier one. For example
182 + myarray = { a, b, c, [1] = d, e }
184 + overwrites the myarray["1"] entry, initialized with the value of b, with the
185 + value of d. Similarly the myarray["2"] entry is overwritten.
187 + You can use array initializing expressions as part of other expressions. They
188 + can be passed as arguments to functions:
190 + result = func({ ["message"] = "The value is", \
191 + ["value"] = 4, "OK" })
193 + Or you can use them to add to arrays, as in
195 + myarray += { [newkey] = newvalue }
197 + The built-in variable $empty_array evaluates to an empty array. You can also
198 + build an empty array using array initializing expressions as follows:
202 3>Looping and Conditionals
204 NEdit supports looping constructs: for and while, and conditional statements:
205 if and else, with essentially the same syntax as C:
207 diff --quilt old/source/interpret.c new/source/interpret.c
208 --- old/source/interpret.c
209 +++ new/source/interpret.c
210 @@ -120,18 +120,29 @@ static int assign(void);
211 static int callSubroutine(void);
212 static int fetchRetVal(void);
213 static int branch(void);
214 static int branchTrue(void);
215 static int branchFalse(void);
216 +static int branchIf(Boolean trueOrFalse);
217 static int branchNever(void);
218 static int arrayRef(void);
219 static int arrayAssign(void);
220 static int arrayRefAndAssignSetup(void);
221 static int beginArrayIter(void);
222 static int arrayIter(void);
223 static int inArray(void);
224 static int deleteArrayElement(void);
225 +static int anonArrayOpen(void);
226 +static int anonArraySkip(void);
227 +static int anonArrayNextVal(void);
228 +static int anonArrayIndexVal(void);
229 +static int anonArrayClose(void);
230 +static int namedArg1(void);
231 +static int namedArgN(void);
232 +static int namedArg1orN(Boolean isFirst);
233 +static int swapTop2(void);
234 +static int makeArrayKeyFromArgs(int nArgs, char **keyString, int leaveParams);
235 static void freeSymbolTable(Symbol *symTab);
236 static int errCheck(const char *s);
237 static int execError(const char *s1, const char *s2);
238 static rbTreeNode *arrayEmptyAllocator(void);
239 static rbTreeNode *arrayAllocateNode(rbTreeNode *src);
240 @@ -214,21 +225,24 @@ static int (*OpFns[N_OPS])() = {returnNo
241 add, subtract, multiply, divide, modulo, negate, increment, decrement,
242 gt, lt, ge, le, eq, ne, bitAnd, bitOr, and, or, not, power, concat,
243 assign, callSubroutine, fetchRetVal, branch, branchTrue, branchFalse,
244 branchNever, arrayRef, arrayAssign, beginArrayIter, arrayIter, inArray,
245 deleteArrayElement, pushArraySymVal,
246 - arrayRefAndAssignSetup, pushArgVal, pushArgCount, pushArgArray};
247 + arrayRefAndAssignSetup, pushArgVal, pushArgCount, pushArgArray,
248 + anonArrayOpen, anonArraySkip, anonArrayNextVal, anonArrayIndexVal,
249 + anonArrayClose, namedArg1, namedArgN, swapTop2,
252 -/* Stack-> symN-sym0(FP), argArray, nArgs, oldFP, retPC, argN-arg1, next, ... */
253 -#define FP_ARG_ARRAY_INDEX (-1)
254 -#define FP_ARG_COUNT_INDEX (-2)
255 -#define FP_FUNCTION_NAME (-3) /* !! */
256 -#define FP_SYMBOL_TABLE (-4) /* !! */
257 -#define FP_OLD_FP_INDEX (-5)
258 -#define FP_RET_PC_INDEX (-6)
259 +/* Stack-> symN-sym0(FP), nArgs, oldFP, retPC, argArray, argN-arg1, next, ... */
260 +#define FP_ARG_COUNT_INDEX (-1)
261 +#define FP_FUNCTION_NAME (-2) /* !! */
262 +#define FP_SYMBOL_TABLE (-3) /* !! */
263 +#define FP_OLD_FP_INDEX (-4)
264 +#define FP_RET_PC_INDEX (-5)
265 +#define FP_ARG_ARRAY_INDEX (-6)
267 -#define FP_TO_ARGS_DIST (0 - FP_RET_PC_INDEX) /* should be 0 - (above index) */
268 +#define FP_TO_ARGS_DIST (0 - FP_ARG_ARRAY_INDEX) /* should be 0 - (above index) */
270 #define FP_GET_ITEM(xFrameP,xIndex) (*(xFrameP + xIndex))
271 #define FP_GET_ARG_ARRAY_CACHE(xFrameP) (FP_GET_ITEM(xFrameP, FP_ARG_ARRAY_INDEX))
272 #define FP_GET_ARG_COUNT(xFrameP) (FP_GET_ITEM(xFrameP, FP_ARG_COUNT_INDEX).val.n)
273 #define FP_GET_OLD_FP(xFrameP) ((FP_GET_ITEM(xFrameP, FP_OLD_FP_INDEX)).val.dataval)
274 @@ -236,10 +250,17 @@ static int (*OpFns[N_OPS])() = {returnNo
275 #define FP_ARG_START_INDEX(xFrameP) (-(FP_GET_ARG_COUNT(xFrameP) + FP_TO_ARGS_DIST))
276 #define FP_GET_ARG_N(xFrameP,xN) (FP_GET_ITEM(xFrameP, xN + FP_ARG_START_INDEX(xFrameP)))
277 #define FP_GET_SYM_N(xFrameP,xN) (FP_GET_ITEM(xFrameP, xN))
278 #define FP_GET_SYM_VAL(xFrameP,xSym) (FP_GET_SYM_N(xFrameP, xSym->value.val.n))
280 +#define PUSH_CHECK_TOO_MUCH(n) \
281 + (StackP + (n) > &TheStack[STACK_SIZE])
283 +#define PUSH_CHECK(n) \
284 + if (PUSH_CHECK_TOO_MUCH(n)) \
285 + return execError(StackOverflowMsg, "");
288 ** Initialize macro language global variables. Must be called before
289 ** any macros are even parsed, because the parser uses action routine
290 ** symbols to comprehend hyphenated names.
292 @@ -407,11 +428,11 @@ void SwapCode(Inst *start, Inst *boundar
294 #define reverseCode(L, H) \
295 do { register Inst t, *l = L, *h = H - 1; \
296 while (l < h) { t = *h; *h-- = *l; *l++ = t; } } while (0)
297 /* double-reverse method: reverse elements of both parts then whole lot */
298 - /* eg abcdefABCD -1-> edcbaABCD -2-> edcbaDCBA -3-> DCBAedcba */
299 + /* eg abcdeABCD -1-> edcbaABCD -2-> edcbaDCBA -3-> ABCDabcde */
300 reverseCode(start, boundary); /* 1 */
301 reverseCode(boundary, end); /* 2 */
302 reverseCode(start, end); /* 3 */
305 @@ -486,11 +507,12 @@ int ExecuteMacro(WindowInfo *window, Pro
307 RestartData *context;
308 static DataValue noValue = {NO_TAG, {0}};
314 /* Create an execution context (a stack, a stack pointer, a frame pointer,
315 and a program counter) which will retain the program state across
316 preemption and resumption of execution */
317 context = (RestartData *)XtMalloc(sizeof(RestartData));
318 context->stack = (DataValue *)XtMalloc(sizeof(DataValue) * STACK_SIZE);
319 @@ -498,13 +520,20 @@ int ExecuteMacro(WindowInfo *window, Pro
320 context->stackP = context->stack;
321 context->pc = prog->code;
322 context->runWindow = window;
323 context->focusWindow = window;
325 + haveNamedArgs = (nArgs < 0);
329 /* Push arguments and call information onto the stack */
330 for (i=0; i<nArgs; i++)
331 - *(context->stackP++) = args[i];
332 + *(context->stackP++) = args[i];
334 + if (!haveNamedArgs)
335 + *(context->stackP++) = noValue; /* cached arg array */
337 context->stackP->val.subr = NULL; /* return PC */
338 context->stackP->tag = NO_TAG;
341 @@ -517,16 +546,14 @@ int ExecuteMacro(WindowInfo *window, Pro
342 context->stackP->tag = STRING_TAG;
343 context->stackP->val.str.rep = prog->name ? prog->name : "<exec-macro>";
344 context->stackP->val.str.len = strlen(context->stackP->val.str.rep);
347 - context->stackP->tag = NO_TAG; /* nArgs */
348 - context->stackP->val.n = nArgs;
349 + context->stackP->tag = INT_TAG; /* nArgs */
350 + context->stackP->val.n = nArgs - haveNamedArgs;
353 - *(context->stackP++) = noValue; /* cached arg array */
355 context->frameP = context->stackP;
357 /* Initialize and make room on the stack for local variables */
358 for (s = prog->localSymList; s != NULL; s = s->next) {
359 FP_GET_SYM_VAL(context->frameP, s) = noValue;
360 @@ -595,23 +622,31 @@ int ContinueMacro(RestartData *continuat
366 +** Set up a new stack frame, with no caller arguments, in the current context,
367 +** and set up execution for Program *prog.
369 ** If a macro is already executing, and requests that another macro be run,
370 ** this can be called instead of ExecuteMacro to run it in the same context
371 ** as if it were a subroutine. This saves the caller from maintaining
372 ** separate contexts, and serializes processing of the two macros without
375 +/* TODO: this function should really return a status (if fails PUSH_CHECK) */
376 void RunMacroAsSubrCall(Program *prog)
379 static DataValue noValue = {NO_TAG, {0}};
381 /* See subroutine "callSubroutine" for a description of the stack frame
382 for a subroutine call */
383 + /* if (PUSH_CHECK_TOO_MUCH(4)) return MACRO_ERROR; */
385 + *(StackP++) = noValue; /* cached arg array */
387 StackP->tag = NO_TAG;
388 StackP->val.inst = PC; /* return PC */
391 StackP->tag = NO_TAG;
392 @@ -625,22 +660,22 @@ void RunMacroAsSubrCall(Program *prog)
393 StackP->tag = STRING_TAG;
394 StackP->val.str.rep = prog->name ? prog->name : "<run-macro>";
395 StackP->val.str.len = strlen(StackP->val.str.rep);
398 - StackP->tag = NO_TAG; /* nArgs */
399 + StackP->tag = INT_TAG; /* nArgs */
403 - *(StackP++) = noValue; /* cached arg array */
407 for (s = prog->localSymList; s != NULL; s = s->next) {
408 + /* if (PUSH_CHECK_TOO_MUCH(1)) return MACRO_ERROR; */
409 FP_GET_SYM_VAL(FrameP, s) = noValue;
412 + /* return MACRO_DONE? MACRO_PREEMPT? MACRO_TIME_LIMIT? */
415 void FreeRestartData(RestartData *context)
417 XtFree((char *)context->stack);
418 @@ -1321,16 +1356,19 @@ static int pushArgArray(void)
419 nArgs = FP_GET_ARG_COUNT(FrameP);
420 resultArray = &FP_GET_ARG_ARRAY_CACHE(FrameP);
421 if (resultArray->tag != ARRAY_TAG) {
422 resultArray->tag = ARRAY_TAG;
423 resultArray->val.arrayPtr = ArrayNew();
426 + /* load arguments from positional arg list if not already done */
427 + if (nArgs && !ArrayGet(resultArray, longAsStr(argNum + 1), &argVal)) {
428 for (argNum = 0; argNum < nArgs; ++argNum) {
429 argVal = FP_GET_ARG_N(FrameP, argNum);
430 if (!ArrayInsert(resultArray, AllocStringOfNumber(argNum + 1),
432 - return(execError("array insertion failure", NULL));
433 + return(execError("argument array insertion failure", NULL));
439 @@ -1386,10 +1424,278 @@ static int pushArraySymVal(void)
445 +** create an anonymous array and next index number value (0) on the stack (for
446 +** array construction expressions)
448 +** Before: Prog-> [next], ...
449 +** TheStack-> next, ...
450 +** After: Prog-> [next], ...
451 +** TheStack-> [empty-array, 0], next, ...
453 +static int anonArrayOpen(void)
457 + DISASM_RT(PC-1, 1);
460 + /* make an empty array */
461 + dataVal.tag = ARRAY_TAG;
462 + dataVal.val.arrayPtr = ArrayNew();
464 + /* push the default next index value first */
467 + /* and the empty array */
474 +** cause the auto-incrementing next index number value to increase without
475 +** actually creating an entry in the anonymous array (for array construction
478 +** Before: Prog-> [next], ...
479 +** TheStack-> [anon-array, next-index], next, ...
480 +** After: Prog-> [next], ...
481 +** TheStack-> [anon-array, next-index+1], next, ...
483 +static int anonArraySkip(void)
485 + DataValue anonArray;
488 + DISASM_RT(PC-1, 1);
494 + /* we need to increment the index for next time */
497 + /* push the default next index value first, then the array */
498 + PUSH_INT(nextIndex)
505 +** add an entry to the anonymous array at the stack head, using the numeric
506 +** index just below that; restack the incremented index and anonymous array
507 +** (for array construction expressions)
509 +** Before: Prog-> [next], ...
510 +** TheStack-> [expr, anon-array, next-index], next, ...
511 +** After: Prog-> [next], ...
512 +** TheStack-> [anon-array, next-index+1], next, ...
514 +static int anonArrayNextVal(void)
516 + DataValue exprVal, anonArray;
518 + char numString[TYPE_INT_STR_SIZE(int)];
520 + DISASM_RT(PC-1, 1);
527 + sprintf(numString, "%d", nextIndex);
528 + if (!ArrayInsert(&anonArray, AllocStringCpy(numString), &exprVal)) {
529 + return(execError("array insertion failure", NULL));
532 + /* we need to increment the index for next time */
535 + /* push the default next index value first, then the array */
536 + PUSH_INT(nextIndex)
544 +** Before: Prog-> [nDim], next, ...
545 +** TheStack-> [expr, indnDim, ... ind1, anon-array, next-index], next, ...
546 +** After: Prog-> nDim, [next], ...
547 +** TheStack-> [anon-array, new-next-index], next, ...
549 +static int anonArrayIndexVal(void)
552 + char *keyString = NULL;
553 + DataValue exprVal, anonArray;
554 + int nextIndex, index;
560 + DISASM_RT(PC-2, 2);
561 + STACKDUMP(nDim+3, 3);
565 + /* the next nDim stack entries form the index */
566 + errNum = makeArrayKeyFromArgs(nDim, &keyString, 0);
567 + if (errNum != STAT_OK) {
574 + /* if our index is numeric (or can be converted to a number) we must
575 + change the next index value */
576 + if (nDim == 1 && StringToNum(keyString, &index)) {
577 + nextIndex = index + 1;
580 + if (!ArrayInsert(&anonArray, keyString, &exprVal)) {
581 + return(execError("array insertion failure", NULL));
584 + /* push the default next index value first, then the array */
585 + PUSH_INT(nextIndex)
592 +** finish building an anonymous array by removing the next index number value
593 +** from the stack (for array construction expressions)
595 +** Before: Prog-> [next], ...
596 +** TheStack-> [anon-array, next-index], next, ...
597 +** After: Prog-> [next], ...
598 +** TheStack-> [anon-array], next, ...
600 +static int anonArrayClose(void)
602 + DataValue anonArray;
603 + DataValue next_index;
605 + DISASM_RT(PC-1, 1);
608 + /* remove top two elements */
611 + /* put back the array content */
618 +** create an $args array for the named arg with index of nDim elements, and
619 +** value expr; leave result on top of stack
621 +** Before: Prog-> [nDim], next, ...
622 +** TheStack-> [expr, indnDim, ... ind1], argN-arg1, next, ...
623 +** After: Prog-> nDim, [next], ...
624 +** TheStack-> args, argN-arg1, next, ...
626 +static int namedArg1(void)
628 + return namedArg1orN(True);
632 +** add the named arg with index of nDim elements, and value expr to the $args
633 +** array at the top of the stack
635 +** Before: Prog-> [nDim], next, ...
636 +** TheStack-> [expr, indnDim, ... ind1, args], argN-arg1, next, ...
637 +** After: Prog-> nDim, [next], ...
638 +** TheStack-> [args], argN-arg1, next, ...
640 +static int namedArgN()
642 + return namedArg1orN(False);
646 +** implementation for namedArg1(), namedArgN()
648 +static int namedArg1orN(Boolean isFirst)
651 + char *keyString = NULL;
652 + DataValue exprVal, argsArray;
655 + nDim = (PC++)->value;
657 + DISASM_RT(PC-2, 2);
658 + STACKDUMP(nDim + (isFirst ? 2 : 1), 3);
662 + /* the next nDim stack entries form the index */
663 + errNum = makeArrayKeyFromArgs(nDim, &keyString, 0);
664 + if (errNum != STAT_OK) {
668 + /* if our index is numeric (or can be converted to a number) we must
669 + change the next index value */
670 + if (nDim == 1 && StringToNum(keyString, &index)) {
671 + return execError("named argument name must not be numeric", NULL);
675 + /* make a new empty array */
676 + argsArray.tag = ARRAY_TAG;
677 + argsArray.val.arrayPtr = NULL;
680 + /* use the array at the top of the stack */
684 + if (!ArrayInsert(&argsArray, keyString, &exprVal)) {
685 + return(execError("named argument insertion failure", NULL));
688 + /* and (re)push the array */
695 +** exchange top two values on the stack
697 +static int swapTop2(void)
699 + DataValue dv1, dv2;
701 + DISASM_RT(PC-1, 1);
713 ** assign top value to next symbol
715 ** Before: Prog-> [symbol], next, ...
716 ** TheStack-> [value], next, ...
717 ** After: Prog-> symbol, [next], ...
718 @@ -2020,53 +2326,62 @@ static int concat(void)
722 ** Call a subroutine or function (user defined or built-in). Args are the
723 ** subroutine's symbol, and the number of arguments which have been pushed
725 +** on the stack. If this value is less than zero, use the absolute value,
726 +** but note that the last one is already the $args array so don't set aside
729 ** For a macro subroutine, the return address, frame pointer, number of
730 ** arguments and space for local variables are added to the stack, and the
731 ** PC is set to point to the new function. For a built-in routine, the
732 ** arguments are popped off the stack, and the routine is just called.
734 ** Before: Prog-> [subrSym], nArgs, next, ...
735 -** TheStack-> argN-arg1, next, ...
736 +** TheStack-> argArray?, argN-arg1, next, ...
737 ** After: Prog-> next, ... -- (built-in called subr)
738 ** TheStack-> retVal?, next, ...
739 ** or: Prog-> (in called)next, ... -- (macro code called subr)
740 -** TheStack-> symN-sym1(FP), argArray, nArgs, oldFP, retPC, argN-arg1, next, ...
741 +** TheStack-> symN-sym1(FP), nArgs, oldFP, retPC, argArray, argN-arg1, next, ...
743 static int callSubroutine(void)
747 static DataValue noValue = {NO_TAG, {0}};
759 + haveNamedArgs = (nArgs < 0);
760 + nArgs = (haveNamedArgs) ? -nArgs - 1 : nArgs;
763 - STACKDUMP(nArgs, 3);
764 + STACKDUMP(nArgs + haveNamedArgs, 3);
767 ** If the subroutine is built-in, call the built-in routine
769 if (sym->type == C_FUNCTION_SYM) {
772 + if (!haveNamedArgs)
773 + PUSH(noValue) /* push dummy named arg array */
775 /* "pop" stack back to the first argument in the call stack */
777 + StackP -= nArgs + 1;
779 /* Call the function and check for preemption */
780 PreemptRequest = False;
781 - if (!sym->value.val.subr(FocusWindow, StackP,
782 - nArgs, &result, &errMsg))
783 + /* NB nArgs < 0 implies presence of named args array in last position */
784 + if (!sym->value.val.subr(FocusWindow, StackP, nArgs, &result, &errMsg))
785 return execError(errMsg, sym->name);
786 if (PC->func == fetchRetVal) {
787 if (result.tag == NO_TAG) {
788 return execError("%s does not return a value", sym->name);
790 @@ -2082,42 +2397,46 @@ static int callSubroutine(void)
791 ** Push all of the required information to resume, and make space on the
792 ** stack for local variables (and initialize them), on top of the argument
793 ** values which are already there.
795 if (sym->type == MACRO_FUNCTION_SYM) {
796 - prog = (Program *)sym->value.val.str.rep;
797 + PUSH_CHECK(3 + !haveNamedArgs)
799 - StackP->tag = NO_TAG; /* return PC */
800 - StackP->val.inst = PC;
803 - StackP->tag = NO_TAG; /* old FrameP */
804 - StackP->val.dataval = FrameP;
807 - StackP->tag = NO_TAG;
808 - StackP->val.sym = prog->localSymList; /* symbol table */
811 - StackP->tag = STRING_TAG;
812 - StackP->val.str.rep = sym->name; /* function name */
813 - StackP->val.str.len = strlen(sym->name);
816 - StackP->tag = NO_TAG; /* nArgs */
817 - StackP->val.n = nArgs;
819 + prog = (Program *)sym->value.val.str.rep;
821 + if (!haveNamedArgs)
822 + *(StackP++) = noValue; /* push dummy named arg array */
824 + StackP->tag = NO_TAG; /* return PC */
825 + StackP->val.inst = PC;
828 - *(StackP++) = noValue; /* cached arg array */
829 + StackP->tag = NO_TAG; /* old FrameP */
830 + StackP->val.dataval = FrameP;
835 - for (s = prog->localSymList; s != NULL; s = s->next) {
836 - FP_GET_SYM_VAL(FrameP, s) = noValue;
840 + StackP->tag = NO_TAG;
841 + StackP->val.sym = prog->localSymList; /* symbol table */
844 + StackP->tag = STRING_TAG;
845 + StackP->val.str.rep = sym->name; /* function name */
846 + StackP->val.str.len = strlen(sym->name);
849 + StackP->tag = NO_TAG; /* nArgs */
850 + StackP->val.n = nArgs;
855 + for (s = prog->localSymList; s != NULL; s = s->next) {
857 + FP_GET_SYM_VAL(FrameP, s) = noValue;
864 ** Call an action routine
866 @@ -2125,11 +2444,17 @@ static int callSubroutine(void)
867 String argList[MAX_ARGS];
868 Cardinal numArgs = nArgs;
874 + if (haveNamedArgs) {
876 + "%s action routine called with named argument array",
880 /* Create a fake event with a timestamp suitable for actions which need
881 timestamps, a marker to indicate that the call was from a macro
882 (to stop shell commands from putting up their own separate banner) */
883 disp=XtDisplay(InitiatingWindow->shell);
884 win=XtWindow(InitiatingWindow->shell);
885 @@ -2184,11 +2509,11 @@ static int returnVal(void)
889 ** Return from a subroutine call
890 ** Before: Prog-> [next], ...
891 -** TheStack-> retVal?, ...(FP), argArray, nArgs, oldFP, retPC, argN-arg1, next, ...
892 +** TheStack-> retVal?, ...(FP), nArgs, oldFP, retPC, argArray, argN-arg1, next, ...
893 ** After: Prog-> next, ..., (in caller)[FETCH_RET_VAL?], ...
894 ** TheStack-> retVal?, next, ...
896 static int returnValOrNone(int valOnStack)
898 @@ -2261,38 +2586,30 @@ static int branch(void)
899 ** After: either: Prog-> branchDest, [next], ...
900 ** After: or: Prog-> branchDest, next, ..., (branchdest)[next]
902 static int branchTrue(void)
907 - DISASM_RT(PC-1, 2);
911 - addr = PC + PC->value;
917 + return branchIf(True);
919 static int branchFalse(void)
921 + return branchIf(False);
923 +static int branchIf(Boolean trueOrFalse)
933 addr = PC + PC->value;
939 + if (!value == !trueOrFalse)
945 ** Ignore the address following the instruction and continue. Why? So
946 @@ -3192,11 +3509,19 @@ static void disasmInternal(Inst *inst, i
947 "ARRAY_DELETE", /* deleteArrayElement */
948 "PUSH_ARRAY_SYM", /* pushArraySymVal */
949 "ARRAY_REF_ASSIGN_SETUP", /* arrayRefAndAssignSetup */
950 "PUSH_ARG", /* $arg[expr] */
951 "PUSH_ARG_COUNT", /* $arg[] */
952 - "PUSH_ARG_ARRAY" /* $arg */
953 + "PUSH_ARG_ARRAY", /* $arg */
954 + "ARRAY_OPEN", /* anonArrayOpen: "{...}" */
955 + "ARRAY_SKIP", /* anonArraySkip: "{ , ...}" */
956 + "ARRAY_NEXT_VAL", /* anonArrayNextVal: "{ expr }" */
957 + "ARRAY_INDEX_VAL", /* anonArrayIndexVal: "{ [i]=expr }" */
958 + "ARRAY_CLOSE", /* anonArrayClose: "{...}" */
959 + "NAMED_ARG1", /* namedArg1: "fn([...]=..., ...)" */
960 + "NAMED_ARGN", /* namedArgN: "fn(..., [...]=...)" */
961 + "SWAP_TOP2", /* swapTop2: cf namedArgN */
966 for (i = 0; i < nInstr; ++i) {
967 @@ -3222,11 +3547,18 @@ static void disasmInternal(Inst *inst, i
968 else if (j == OP_CONCAT) {
969 printd("nExpr=%d", inst[i+1].value);
972 else if (j == OP_SUBR_CALL) {
973 - printd("%s (%d arg)", inst[i+1].sym->name, inst[i+2].value);
974 + int args = inst[i+2].value;
975 + printd("%s ", inst[i+1].sym->name);
977 + printd("%d+args[] (%d)", -args - 1, args);
980 + printd("%d args", args);
984 else if (j == OP_BEGIN_ARRAY_ITER) {
985 printd("%s in", inst[i+1].sym->name);
987 @@ -3236,12 +3568,16 @@ static void disasmInternal(Inst *inst, i
990 inst[i+3].value, &inst[i+3] + inst[i+3].value);
993 - else if (j == OP_ARRAY_REF || j == OP_ARRAY_DELETE ||
994 - j == OP_ARRAY_ASSIGN) {
995 + else if (j == OP_ARRAY_REF ||
996 + j == OP_ARRAY_DELETE ||
997 + j == OP_ARRAY_ASSIGN ||
998 + j == OP_ANONARRAY_INDEX_VAL ||
999 + j == OP_NAMED_ARG1 ||
1000 + j == OP_NAMED_ARGN) {
1001 printd("nDim=%d", inst[i+1].value);
1004 else if (j == OP_ARRAY_REF_ASSIGN_SETUP) {
1005 printd("binOp=%s ", inst[i+1].value ? "true" : "false");
1006 @@ -3273,11 +3609,11 @@ static void disasm(Inst *inst, int nInst
1009 #endif /* #ifdef DEBUG_DISASSEMBLER */
1011 #ifdef DEBUG_STACK /* for run-time stack dumping */
1012 -#define STACK_DUMP_ARG_PREFIX "Arg"
1013 +#define STACK_DUMP_ARG_PREFIX " $"
1014 static void stackdumpframe(DataValue *arrow, DataValue *outpt, DataValue *fp,
1015 DataValue *sp, char topMark)
1017 DataValue *baseF = &FP_GET_ITEM(fp, FP_OLD_FP_INDEX);
1018 DataValue *oldFP = baseF ? baseF->val.dataval : NULL;
1019 @@ -3330,16 +3666,16 @@ static void stackdumpframe(DataValue *ar
1020 (dv == arg1) ? "----" :
1021 (dv == fnNm) ? "====" : "";
1022 printd("%4.4s", leadIn);
1023 printd("%8p%c", dv, topMark);
1025 - case FP_ARG_ARRAY_INDEX: pos = "args[]"; break; /* argument array */
1026 case FP_ARG_COUNT_INDEX: pos = "NArgs"; break; /* num. arguments */
1027 case FP_FUNCTION_NAME: pos = "FnName"; break;
1028 case FP_SYMBOL_TABLE: pos = "FnSyms"; break;
1029 case FP_OLD_FP_INDEX: pos = "OldFP"; break;
1030 case FP_RET_PC_INDEX: pos = "RetPC"; break;
1031 + case FP_ARG_ARRAY_INDEX: pos = "args[]"; break; /* argument array */
1033 if (offset < -FP_TO_ARGS_DIST &&
1034 offset >= -FP_TO_ARGS_DIST - nArgs)
1036 sprintf(pos = buffer, STACK_DUMP_ARG_PREFIX "%d",
1037 diff --quilt old/source/parse.y new/source/parse.y
1038 --- old/source/parse.y
1039 +++ new/source/parse.y
1040 @@ -65,11 +65,11 @@ static int nextSymIsField = 0;
1041 enum operations oper;
1043 %token <sym> NUMBER STRING SYMBOL FIELD
1044 %token DELETE ARG_LOOKUP
1045 %token IF WHILE DO ELSE FOR BREAK CONTINUE RETURN
1046 -%type <nArgs> arglistopt arglist catlist
1047 +%type <nArgs> arglistopt arglist catlist fnarglsopt fnarglist fnarg
1048 %type <inst> cond comastmts comastmtlst for while do else and or arrayexpr mark
1050 %type <oper> operassign incrdecr
1051 %token <oper> '=' ADDEQ SUBEQ MULEQ DIVEQ MODEQ ANDEQ OREQ
1052 %token <oper> INCR DECR
1053 @@ -244,11 +244,11 @@ simpstmt: /* simple variable assignmen
1054 ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED(0); ADD_IMMED(1);
1056 ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED(1);
1059 - | SYMBOL '(' arglistopt ')' {
1060 + | SYMBOL '(' fnarglsopt ')' {
1061 ADD_OP(OP_SUBR_CALL);
1062 ADD_SYM(PromoteToGlobal($1)); ADD_IMMED($3);
1066 @@ -270,10 +270,63 @@ arglist: blank expr bla
1067 | arglist ',' blank expr blank { $$ = $1 + 1; }
1069 catlist: numexpr %prec CONCAT { $$ = 1; }
1070 | catlist numexpr %prec CONCAT { $$ = $1 + 1; }
1075 + | '[' arglist ']' '=' expr {
1076 + $$ = $2; /* how many index elements to read? */
1078 + | dot field '=' expr {
1079 + $$ = 1; /* how many index elements to read? */
1082 +fnarglsopt: blank { $$ = 0; }
1083 + | fnarglist { $$ = $1; }
1085 +fnarglist: blank fnarg blank {
1087 + /* named argument code already knows about index length (see
1088 + rule for arg: above); it needs to be assembled into an
1089 + array, which must be created. */
1090 + ADD_OP(OP_NAMED_ARG1); ADD_IMMED($2);
1091 + $$ = -1; /* negative single arg for named arg array */
1094 + /* a normal positional argument - leave value on stack */
1098 + | fnarglist ',' blank fnarg blank {
1100 + /* named arg: $4 == how many indices to process */
1102 + /* first named arg: create the array */
1103 + ADD_OP(OP_NAMED_ARG1); ADD_IMMED($4);
1104 + $$ = -($1 + 1); /* make arg count negative */
1107 + /* another named arg: add to array */
1108 + ADD_OP(OP_NAMED_ARGN); ADD_IMMED($4);
1109 + $$ = $1; /* no new positional args */
1113 + /* positional arg */
1115 + ADD_OP(OP_SWAP_TOP2); /* keep arg array as last */
1119 + $$ = $1 + 1; /* no named args yet */
1127 ADD_OP(OP_CONCAT); ADD_IMMED($1);
1130 @@ -309,21 +362,58 @@ field: FIELD {
1132 arrayexpr: numexpr {
1136 + /* anonymous arrays eg: array = { , "hi", .a=2, ["b"]="three" } */
1138 + /* create an empty array into which to add things */
1139 + ADD_OP(OP_ANONARRAY_OPEN);
1142 +arrconstr: arrconstr0 arrlistopt '}' {
1143 + /* we're done: the array is complete */
1144 + ADD_OP(OP_ANONARRAY_CLOSE);
1147 +arrlistopt: arrlist
1150 + | arrlist ',' arrentry
1153 + /* missing entry will skip an index value */
1154 + ADD_OP(OP_ANONARRAY_SKIP);
1156 + | blank expr blank {
1157 + /* make a suitable index >= 0 and add expr there */
1158 + ADD_OP(OP_ANONARRAY_NEXT_VAL);
1160 + | blank '[' arglist ']' blank '=' blank expr blank {
1161 + /* build the index from arglistopt and add expr there */
1162 + ADD_OP(OP_ANONARRAY_INDEX_VAL);
1165 + | blank dot field blank '=' blank expr blank {
1166 + /* build the index from arglistopt and add expr there */
1167 + ADD_OP(OP_ANONARRAY_INDEX_VAL);
1171 numexpr: '(' blank expr blank ')'
1172 | NUMBER { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); }
1173 | STRING { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); }
1174 | SYMBOL { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); }
1175 - | SYMBOL '(' arglistopt ')' {
1176 + | SYMBOL '(' fnarglsopt ')' {
1177 ADD_OP(OP_SUBR_CALL);
1178 ADD_SYM(PromoteToGlobal($1)); ADD_IMMED($3);
1179 ADD_OP(OP_FETCH_RET_VAL);
1182 | ARG_LOOKUP '[' blank numexpr blank ']' { ADD_OP(OP_PUSH_ARG); }
1183 | ARG_LOOKUP '[' blank ']' { ADD_OP(OP_PUSH_ARG_COUNT); }
1185 | ARG_LOOKUP { ADD_OP(OP_PUSH_ARG_ARRAY); }
1186 | numexpr '[' arglistopt ']' {
1187 ADD_OP(OP_ARRAY_REF); ADD_IMMED($3);
1189 | numexpr dot field {
1190 @@ -401,10 +491,11 @@ or: OR {
1193 dot: '.' %prec '.' {
1198 blank: /* nothing */
1202 diff --quilt old/source/interpret.h new/source/interpret.h
1203 --- old/source/interpret.h
1204 +++ new/source/interpret.h
1205 @@ -46,11 +46,15 @@ enum operations {OP_RETURN_NO_VAL, OP_RE
1206 OP_GE, OP_LE, OP_EQ, OP_NE, OP_BIT_AND, OP_BIT_OR, OP_AND, OP_OR, OP_NOT,
1207 OP_POWER, OP_CONCAT, OP_ASSIGN, OP_SUBR_CALL, OP_FETCH_RET_VAL, OP_BRANCH,
1208 OP_BRANCH_TRUE, OP_BRANCH_FALSE, OP_BRANCH_NEVER, OP_ARRAY_REF,
1209 OP_ARRAY_ASSIGN, OP_BEGIN_ARRAY_ITER, OP_ARRAY_ITER, OP_IN_ARRAY,
1210 OP_ARRAY_DELETE, OP_PUSH_ARRAY_SYM, OP_ARRAY_REF_ASSIGN_SETUP, OP_PUSH_ARG,
1211 - OP_PUSH_ARG_COUNT, OP_PUSH_ARG_ARRAY, N_OPS};
1212 + OP_PUSH_ARG_COUNT, OP_PUSH_ARG_ARRAY,
1213 + OP_ANONARRAY_OPEN, OP_ANONARRAY_SKIP, OP_ANONARRAY_NEXT_VAL,
1214 + OP_ANONARRAY_INDEX_VAL, OP_ANONARRAY_CLOSE,
1215 + OP_NAMED_ARG1, OP_NAMED_ARGN, OP_SWAP_TOP2,
1218 enum typeTags {NO_TAG, INT_TAG, STRING_TAG, ARRAY_TAG};
1220 enum execReturnCodes {MACRO_TIME_LIMIT, MACRO_PREEMPT, MACRO_DONE, MACRO_ERROR};