Re: [PATCH] zsh_eval_context: add math context

dana <[email protected]>
Newsgroups gmane.comp.shells.zsh.devel
Message-ID <[email protected]>
On Thu 21 May 2026, at 16:23, dana wrote:
> this adds a 'math' context to zsh_eval_context when a math function is
> executed. also adds tests promised in w/54575

i realised this is annoying sometimes, e.g. (( a(b(c())) )) pushes math
three times for c()

here's a different implementation that pushes it at the start of
evaluation rather than before function execution

i didn't want to do it like this initially because i was worried that
it might cause problems for expressions that reference zsh_eval_context
itself. but i can't actually think of a case where it would:

- (( $zsh_eval_context )) and (( zsh_eval_context )) would only have
  worked previously at the very top level and even then it's not a
  useful thing to do
- in (( $#zsh_eval_context )) the literal value is substituted via
  parameter expansion before the start of evaluation
- in (( #zsh_eval_context )) the result always comes from a preceding
  context (the top-most one)
- (( ##zsh_eval_context )) is always invalid
- in (( fn() )), it pushes a new context for the function call either
  way

so i think it's fine. but please correct if i'm wrong

dana


diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 635589be4..933081b34 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -1082,7 +1082,7 @@ Code specified by the tt(-c) option to the command line that invoked
 the shell.
 )
 item(tt(cmdsubst))(
-Command substitution using of the tt(`)var(...)tt(`),
+Command substitution using the tt(`)var(...)tt(`),
 tt($+LPAR())var(...)tt(RPAR()), tt(${{)var(name)tt(}) var(...)tt(}),
 tt(${|)var(...)tt(}), or tt(${ )var(...)tt( }) constructs.
 )
@@ -1120,6 +1120,12 @@ The tt(>LPAR())var(...)tt(RPAR()) form of process substitution.
 item(tt(loadautofunc))(
 Code read directly from a file to define an autoloaded function.
 )
+item(tt(math))(
+Code executed in the tt($+LPAR()LPAR())var(...)tt(RPAR()RPAR()) or
+tt(LPAR()LPAR())var(...)tt(RPAR()RPAR()) constructs,
+by the tt(let) builtin, in an array subscript, or in
+another arithmetic context.
+)
 item(tt(outsubst))(
 The tt(<LPAR())var(...)tt(RPAR()) form of process substitution.
 )
diff --git a/Src/exec.c b/Src/exec.c
index 2c730b910..22649ae62 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -263,6 +263,11 @@ static int doneps4;
 static char *STTYval;
 static char *blank_env[] = { NULL };
 
+/* total allocated elements in zsh_eval_context array */
+static int zsh_eval_context_len;
+/* number of in-use elements in zsh_eval_context array */
+static int zsh_eval_context_alen;
+
 /* Execution functions. */
 
 static int (*execfuncs[WC_COUNT-WC_CURSH]) (Estate, int) = {
@@ -1241,30 +1246,44 @@ execstring(char *s, int dont_change_job, int exiting, char *context)
     popheap();
 }
 
+/* append a context to the zsh_eval_context array */
+
 /**/
 mod_export void
-execode(Eprog p, int dont_change_job, int exiting, char *context)
+zsh_eval_context_push(char *context)
 {
-    struct estate s;
-    static int zsh_eval_context_len;
-    int alen;
-
     if (!zsh_eval_context_len) {
 	zsh_eval_context_len = 16;
-	alen = 0;
+	zsh_eval_context_alen = 0;
 	zsh_eval_context = (char **)zalloc(zsh_eval_context_len *
 					   sizeof(*zsh_eval_context));
-    } else {
-	alen = arrlen(zsh_eval_context);
-	if (zsh_eval_context_len == alen + 1) {
-	    zsh_eval_context_len *= 2;
-	    zsh_eval_context = zrealloc(zsh_eval_context,
-					zsh_eval_context_len *
-					sizeof(*zsh_eval_context));
-	}
-    }
-    zsh_eval_context[alen] = context;
-    zsh_eval_context[alen+1] = NULL;
+    } else if (zsh_eval_context_len == zsh_eval_context_alen + 1) {
+	zsh_eval_context_len *= 2;
+	zsh_eval_context = zrealloc(zsh_eval_context,
+				    zsh_eval_context_len *
+				    sizeof(*zsh_eval_context));
+    }
+    zsh_eval_context[zsh_eval_context_alen] = context;
+    zsh_eval_context[++zsh_eval_context_alen] = NULL;
+}
+
+/* remove the last context from the zsh_eval_context array */
+
+/**/
+mod_export void
+zsh_eval_context_pop()
+{
+    if (zsh_eval_context_alen)
+	zsh_eval_context[--zsh_eval_context_alen] = NULL;
+}
+
+/**/
+mod_export void
+execode(Eprog p, int dont_change_job, int exiting, char *context)
+{
+    struct estate s;
+
+    zsh_eval_context_push(context);
 
     s.prog = p;
     s.pc = p->prog;
@@ -1279,7 +1298,7 @@ execode(Eprog p, int dont_change_job, int exiting, char *context)
      * zsh_eval_context may have been altered by a recursive
      * call, but that's OK since we're using the global value.
      */
-    zsh_eval_context[alen] = NULL;
+    zsh_eval_context_pop();
 }
 
 /* Execute a simplified command. This is used to execute things that
diff --git a/Src/math.c b/Src/math.c
index d97dae238..ff0165a87 100644
--- a/Src/math.c
+++ b/Src/math.c
@@ -1493,7 +1493,9 @@ matheval(char *s)
 	x.u.l = 0;
 	return x;
     }
+    zsh_eval_context_push("math");
     x = mathevall(s, MPREC_TOP, &junk);
+    zsh_eval_context_pop();
     mtok = xmtok;
     if (*junk)
 	zerr("bad math expression: illegal character: %c", *junk);
@@ -1531,7 +1533,9 @@ mathevalarg(char *s, char **ss)
 	zerr("bad math expression: empty string");
 	return (zlong)0;
     }
+    zsh_eval_context_push("math");
     x = mathevall(s, MPREC_ARG, ss);
+    zsh_eval_context_pop();
     if (mtok == COMMA)
 	(*ss)--;
     mtok = xmtok;
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index e192d480d..218bca739 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -1649,11 +1649,96 @@
    print $zsh_eval_context[1]
    [[ $ZSH_EVAL_CONTEXT = ${(j.:.)zsh_eval_context} ]] || print Not equal!
    (( icontext = ${#zsh_eval_context} + 1 ))
-   contextfn() { print $(print $zsh_eval_context[icontext,-1]); }
+   contextfn() { print -r - $zsh_eval_context[icontext,-1] }
+   functions -M contextfn
    contextfn
+   () { contextfn }
+   () { trap contextfn EXIT }
+   eval contextfn
+   print -r - $( contextfn )
+   print -r - ${ contextfn }
+   : ${| contextfn }
+   (( contextfn(0) ))
+   : $(( contextfn(0) ))
+   let 'contextfn(0)'
+   : /(e*contextfn*)
+   : /(o+contextfn)
+   cat <( contextfn )
+   cat < <( contextfn )
+   cat =( contextfn )
+   : >( contextfn )
+   : > >( contextfn )
+   source <( <<< contextfn )
+   $ZTST_testdir/../Src/zsh -fc 'print -r - $zsh_eval_context'
+   () { () { print -r - $( : $(( contextfn(0) )) ) } }
 0:$ZSH_EVAL_CONTEXT and $zsh_eval_context
 >toplevel
->shfunc cmdsubst
+>shfunc
+>shfunc shfunc
+>trap shfunc
+>eval shfunc
+>cmdsubst shfunc
+>cmdsubst shfunc
+>cmdsubst shfunc
+>math shfunc
+>math shfunc
+>math shfunc
+>globqual shfunc
+>globsort shfunc
+>outsubst shfunc
+>outsubst shfunc
+>equalsubst shfunc
+>insubst shfunc
+>insubst shfunc
+>file shfunc
+>cmdarg
+>shfunc shfunc cmdsubst math shfunc
+
+  (( icontext = ${#zsh_eval_context} + 1 ))
+  contextfn() {
+    print -r - $#zsh_eval_context[icontext,-1] $zsh_eval_context[icontext,-1]
+  }
+  cmd='() { eval contextfn }'
+  repeat 48 cmd="() { $cmd }"
+  eval $cmd
+0:zsh_eval_context resizing
+*>52 eval shfunc * shfunc eval shfunc
+
+  (( icontext = ${#zsh_eval_context} + 1 ))
+  context1() {
+    print -r - $zsh_eval_context[icontext,-1]
+    context2
+  }
+  context2() {
+    print -r - $zsh_eval_context[icontext,-1]
+  }
+  functions -M context1
+  functions -M context2
+  echo '((...))'
+  (( context1(context2()) ))
+  echo '$((...))'
+  : $(( context1(context2()) ))
+  echo let
+  let 'context1(context2())'
+  echo subscript
+  arr=( ) exp='context1()+2'
+  : $arr[$exp]
+0:more math context
+>((...))
+>math shfunc
+>math shfunc
+>math shfunc shfunc
+>$((...))
+>math shfunc
+>math shfunc
+>math shfunc shfunc
+>let
+>math shfunc
+>math shfunc
+>math shfunc shfunc
+>subscript
+>math shfunc
+>math shfunc shfunc
 
    foo="123456789"
    print ${foo:3}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.