Re: Inconsistent Multiline String Handling in Lua REPL (Direct Input vs. History Recall)

mathieu stumpf-guntz <[email protected]> Fri, 31 Jul 2026 04:12:53 -0700 (PDT)
Newsgroups gmane.comp.lang.lua.general
Message-ID <[email protected]>
------=_Part_8378_1929262368.1785496373207
Content-Type: multipart/alternative; 
	boundary="----=_Part_8379_829851902.1785496373207"

------=_Part_8379_829851902.1785496373207
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

Hi Jure,

Thank you for the clear explanation, that's exactly the mechanism I ended=
=20
up tracing through in the source.=20

I was curious to dig a bit deeper in the actual mechanism, so I realigned=
=20
my copy of Lua source code with Github=E2=80=99s master version, where HEAD=
 point=20
to 7579fc9d7ed90240487251dfb69168f8e64e9294 at that moment I fetched it. I=
=20
was a bit of a surprise that there is no longer a src directory and no=20
specific architecture target in the Makefile.=20

So I guess the code observation align with your feedback Jure. Apparently=
=20
once the initial `addreturn` fails and `multiline` takes over, the=20
assembled buffer is only ever retried as a statement. The expression path=
=20
is never revisited, which is what causes the inconsistency with history=20
recall (where the whole buffer arrives in one shot and `addreturn` gets a=
=20
chance to run on it).

I experimented and was able to get a small patch against this commit=20
version. It adds the missing retry in `loadline`: after `multiline` fails=
=20
as a statement, attempt `addreturn` once more on the assembled buffer. If=
=20
that also fails, regenerate the statement error via `luaL_loadbufferx` so=
=20
the stack invariant is presumably preserved.

--- a/lua.c
+++ b/lua.c
@@ -666,6 +666,21 @@ static int loadline (lua_State *L) {
     return -1;  /* no input */
   if ((status =3D addreturn(L)) !=3D LUA_OK)
     status =3D multiline(L);
+  if (status !=3D LUA_OK) {
+    lua_pop(L, 1);  /* remove error message; stack: [1]=3Dassembled line *=
/
+    if (addreturn(L) =3D=3D LUA_OK)
+      status =3D LUA_OK;
+    else {
+      size_t len;
+      const char *s =3D lua_tolstring(L, 1, &len);
+      status =3D luaL_loadbufferx(L, s, len, "=3Dstdin", "t");
+    }
+  }
   line =3D lua_tostring(L, 1);

--- a/testes/main.lua
+++ b/testes/main.lua
@@ -382,6 +382,24 @@ prepfile("a =3D [[b\nc\nd\ne]]\na")
 RUN([[lua -e"_PROMPT=3D'' _PROMPT2=3D''" -i -- < %s > %s]], prog, out)
 checkprogout("b\nc\nd\ne\n\n")
=20
+-- test that multi-line expressions work through continuation lines
+-- (not only when recalled from history as a single buffer)
+
+-- multi-line long string used in an expression
+prepfile('[[\ntest\n]] =3D=3D "test\\n"\n')
+RUN([[lua -e"_PROMPT=3D'' _PROMPT2=3D''" -i < %s > %s]], prog, out)
+checkprogout("true\n")
+
+-- multi-line parenthesized arithmetic expression
+prepfile("(\n1 +\n2 +\n3\n)\n")
+RUN([[lua -e"_PROMPT=3D'' _PROMPT2=3D''" -i < %s > %s]], prog, out)
+checkprogout("6\n")
+
+-- genuinely invalid multi-line input should still error
+prepfile("(\n1 +\n)\n")
+RUN([[lua -e"_PROMPT=3D'' _PROMPT2=3D''" -i < %s > %s 2>&1]], prog, out)
+assert(string.find(getoutput(), "near"))
+

With this, the original session now behaves consistently whether input is=
=20
typed manually, pasted, or recalled from history:

  > [[
  >> test
  >> ]] =3D=3D "test\n"
  true

Side note on compilation: it's been a bit harder to get the compilation=20
right to run the test suite on Darwin now that the makefile no longer=20
provides the expected settings to exercise dynamic readline loading. But=20
after some trial and error it turns out that the following made the job to=
=20
allow to run `testes/main.lua`:

#  make MYCFLAGS=3D'-std=3Dc99 -DLUA_USE_POSIX -DLUA_USE_DLOPEN=20
-DLUA_READLINELIB=3D"\"/opt/homebrew/opt/readline/lib/libreadline.dylib\""'=
=20
MYLDFLAGS=3D"" MYLIBS=3D"" CC=3Dcc

did the trick. Full testes/main.lua passes with no regressions on Darwin=20
25.5.0 ARM64.

Let me know if you try also to apply it on your side, what do you think of=
=20
this change in behavior, and I will be happy to hear if there is a cleaner=
=20
approach you would suggest.

Kind regards,
Mathieu


On Friday, July 31, 2026 at 7:51:54=E2=80=AFAM UTC jure bagic wrote:

> Small correction,
> the <eof> in error does not necessarily indicate incomplete statement. It=
=20
> can be an expression, the main point is that once the initial input was n=
ot=20
> compiled sucessfully the REPL will now expect a statement (because there=
=20
> will be no 'return' prepended and your input will be invalid syntax).
>
> -- Jure
>
> On Fri, Jul 31, 2026, 09:11 jure bagic <[email protected]> wrote:
>
>> Lua REPL accepts lines of text, when you press enter the REPL first puts=
=20
>> "return" before your input and tries to compile it.
>>
>> For example
>> > 69*enter*
>> is actually
>> > return 69*enter*
>>
>> Try doing
>> > 4, 2, 0*enter*
>>
>> If such expression compiles, REPL skips trying to compile your input as=
=20
>> statement.
>> Then the compiled function (with prepended 'return' followed by your=20
>> input) is called in order to obtain those values on the stack and finall=
y=20
>> those values are used as arguments to global function 'print' (part of=
=20
>> basic library).
>>
>> Otherwise, when compilation with implicit 'return' fails, REPL tries to=
=20
>> compile your input as statement using a clever hack (in my opinion). As=
=20
>> long as the input cannot be compiled (syntax error), it checks end of th=
e=20
>> error message to see of it ends with '<eof>' string. This indicates an=
=20
>> incomplete statement as syntax error will be "near <eof>" (end of file =
=3D=3D=20
>> end of input) because another token was expected before input suddenly=
=20
>> ended. And then it prompts second time (with '>>') and so on...
>>
>> So when you do history, you are sending entire expression at once, in=20
>> other words your input can contain multiple newlines because technically=
=20
>> you only pressed enter once.
>>
>> -- Jure
>>
>> On Fri, Jul 31, 2026, 06:46 mathieu stumpf-guntz <[email protected]>=20
>> wrote:
>>
>>> Hello Martin and Roberto,
>>>
>>> Thank you very much for all of your insights, that=E2=80=99s very enlig=
htening,=20
>>> though I=E2=80=99ll have to meditate and play a bit more around with it=
 before=20
>>> integrating the full lesson here I guess.
>>>
>>> To respond your question Martin, maybe in a broader fashion than the wa=
y=20
>>> you where initially conducting it, what I would expect is a bit less=20
>>> surprise in behaviour while interacting with the interpreter. Though I =
can=20
>>> get the point that, this or that surprising behavior can be the expecte=
d=20
>>> result according to the grammar and the documentation. Even sitting fir=
mly=20
>>> in that scope, having a consistent behavior for the same input whatever=
 the=20
>>> input method seems to dwell on an other level of expectation. Consider =
the=20
>>> following session:
>>> Lua 5.3.6 Copyright (C) 1994-2020 Lua.org, PUC-Rio
>>> > [[
>>> >> test
>>> >> ]] =3D=3D "test\n"
>>> stdin:3: unexpected symbol near '[[test
>>> ]]'
>>> > [[
>>> test
>>> ]] =3D=3D "test\n"
>>> true
>>> > [[
>>> test
>>> ]] =3D=3D "test\n"
>>> false
>>> > [[
>>> test
>>> ]] =3D=3D "test\n"
>>> false
>>> > [[
>>> test
>>> ]] =3D=3D "test\n"
>>> true
>>>
>>> So what happen here:
>>> - I typed manually the boolean test and got printed an error informing=
=20
>>> me of an ill formed construction, fair enough.
>>> - Through history facility I select again this ill formed construction=
=20
>>> and launch interpretation which eventually returns true and prints=20
>>> accordingly.
>>> - Then I copy/pasted the whole code snippet in the interpreter, and was=
=20
>>> informed this time that this is false, the matching value representatio=
n=20
>>> being printed.
>>> - Then picking this very last entry in history and running it, I still=
=20
>>> get false printed.
>>> - Finally, picking instead in history the earliest item available, we=
=20
>>> are back to true being printed.
>>>
>>> Just reading the session transcript, one could certainly spot what=E2=
=80=99s=20
>>> happening in the directly typed entry thanks the extra `>` hint. But=20
>>> without the previous explanations, one would certainly have a hard time=
=20
>>> inferring what has actually been done in the subsequent interactions.
>>>
>>> I hope this clarify what I mean when I say I would expect a more=20
>>> consistent behavior in the REPL.=20
>>>
>>> Kind regards,
>>> Mathieu
>>> On Thursday, July 30, 2026 at 4:42:55=E2=80=AFPM UTC Martin Eden wrote:
>>>
>>>>
>>>> On 2026-07-30 18:05, mathieu stumpf-guntz wrote:=20
>>>> > Not sure what you mean here, but in my previous examples I didn=E2=
=80=99t=20
>>>> mean=20
>>>> > I used any magic sequence, the "\n" was actually literal new lines.=
=20
>>>> So=20
>>>> > to make it clear, the test conducted was more like `echo -e=20
>>>> > '[[\ntest\n]]' | lua`,  which ends up (in all configurations I was=
=20
>>>> > able to try) with=20
>>>> > lua: stdin:3: unexpected symbol near '[[test=20
>>>> > ]]'=20
>>>>
>>>> Well that's a different thing! It gives same error for me too.=20
>>>>
>>>> So as=20
>>>>
>>>>   $ echo -e '123' | lua=20
>>>>   lua: stdin:1: unexpected symbol near '123'=20
>>>>
>>>> What result you expect?=20
>>>>
>>>> "lua" program in interactive mode silently places expressions=20
>>>> inside "print()". That's neat feature to use it as calculator.=20
>>>>
>>>> But neither "[[\ntest\n]]" nor "123" is valid Lua code:=20
>>>> they are expressions, not statements.=20
>>>>
>>>> -- Martin=20
>>>>
>>>>
>>>> --=20
>>> You received this message because you are subscribed to the Google=20
>>> Groups "lua-l" group.
>>> To unsubscribe from this group and stop receiving emails from it, send=
=20
>>> an email to [email protected].
>>> To view this discussion visit=20
>>> https://groups.google.com/d/msgid/lua-l/72c88ccc-765c-4d7f-9b19-cf6d5b4=
a509fn%40googlegroups.com=20
>>> <https://groups.google.com/d/msgid/lua-l/72c88ccc-765c-4d7f-9b19-cf6d5b=
4a509fn%40googlegroups.com?utm_medium=3Demail&utm_source=3Dfooter>
>>> .
>>>
>>

--=20
You received this message because you are subscribed to the Google Groups "=
lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/lua-l/16be5=
8aa-3298-4845-a28a-f411df51cdfen%40googlegroups.com.

------=_Part_8379_829851902.1785496373207
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

Hi Jure,<br /><br />Thank you for the clear explanation, that's exactly the=
 mechanism I ended up tracing through in the source. <br /><br />I was curi=
ous to dig a bit deeper in the actual mechanism, so I realigned my copy of =
Lua source code with Github=E2=80=99s master version, where HEAD point to 7=
579fc9d7ed90240487251dfb69168f8e64e9294 at that moment I fetched it. I was =
a bit of a surprise that there is no longer a src directory and no specific=
 architecture target in the Makefile.=C2=A0<br /><br />So I guess the code =
observation align with your feedback Jure. Apparently once the initial `add=
return` fails and `multiline` takes over, the assembled buffer is only ever=
 retried as a statement. The expression path is never revisited, which is w=
hat causes the inconsistency with history recall (where the whole buffer ar=
rives in one shot and `addreturn` gets a chance to run on it).<br /><br />I=
 experimented and was able to get a small patch against this commit version=
. It adds the missing retry in `loadline`: after `multiline` fails as a sta=
tement, attempt `addreturn` once more on the assembled buffer. If that also=
 fails, regenerate the statement error via `luaL_loadbufferx` so the stack =
invariant is presumably preserved.<br /><br />--- a/lua.c<br />+++ b/lua.c<=
br />@@ -666,6 +666,21 @@ static int loadline (lua_State *L) {<br />=C2=A0 =
=C2=A0 =C2=A0return -1; =C2=A0/* no input */<br />=C2=A0 =C2=A0if ((status =
=3D addreturn(L)) !=3D LUA_OK)<br />=C2=A0 =C2=A0 =C2=A0status =3D multilin=
e(L);<br />+ =C2=A0if (status !=3D LUA_OK) {<br />+ =C2=A0 =C2=A0lua_pop(L,=
 1); =C2=A0/* remove error message; stack: [1]=3Dassembled line */<br />+ =
=C2=A0 =C2=A0if (addreturn(L) =3D=3D LUA_OK)<br />+ =C2=A0 =C2=A0 =C2=A0sta=
tus =3D LUA_OK;<br />+ =C2=A0 =C2=A0else {<br />+ =C2=A0 =C2=A0 =C2=A0size_=
t len;<br />+ =C2=A0 =C2=A0 =C2=A0const char *s =3D lua_tolstring(L, 1, &am=
p;len);<br />+ =C2=A0 =C2=A0 =C2=A0status =3D luaL_loadbufferx(L, s, len, "=
=3Dstdin", "t");<br />+ =C2=A0 =C2=A0}<br />+ =C2=A0}<br />=C2=A0 =C2=A0lin=
e =3D lua_tostring(L, 1);<br /><br />--- a/testes/main.lua<br />+++ b/teste=
s/main.lua<br />@@ -382,6 +382,24 @@ prepfile("a =3D [[b\nc\nd\ne]]\na")<br=
 />=C2=A0RUN([[lua -e"_PROMPT=3D'' _PROMPT2=3D''" -i -- &lt; %s &gt; %s]], =
prog, out)<br />=C2=A0checkprogout("b\nc\nd\ne\n\n")<br />=C2=A0<br />+-- t=
est that multi-line expressions work through continuation lines<br />+-- (n=
ot only when recalled from history as a single buffer)<br />+<br />+-- mult=
i-line long string used in an expression<br />+prepfile('[[\ntest\n]] =3D=
=3D "test\\n"\n')<br />+RUN([[lua -e"_PROMPT=3D'' _PROMPT2=3D''" -i &lt; %s=
 &gt; %s]], prog, out)<br />+checkprogout("true\n")<br />+<br />+-- multi-l=
ine parenthesized arithmetic expression<br />+prepfile("(\n1 +\n2 +\n3\n)\n=
")<br />+RUN([[lua -e"_PROMPT=3D'' _PROMPT2=3D''" -i &lt; %s &gt; %s]], pro=
g, out)<br />+checkprogout("6\n")<br />+<br />+-- genuinely invalid multi-l=
ine input should still error<br />+prepfile("(\n1 +\n)\n")<br />+RUN([[lua =
-e"_PROMPT=3D'' _PROMPT2=3D''" -i &lt; %s &gt; %s 2&gt;&amp;1]], prog, out)=
<br />+assert(string.find(getoutput(), "near"))<br />+<br /><br />With this=
, the original session now behaves consistently whether input is typed manu=
ally, pasted, or recalled from history:<br /><br />=C2=A0 &gt; [[<br />=C2=
=A0 &gt;&gt; test<br />=C2=A0 &gt;&gt; ]] =3D=3D "test\n"<br />=C2=A0 true<=
br /><br />Side note on compilation: it's been a bit harder to get the comp=
ilation right to run the test suite on Darwin now that the makefile no long=
er provides the expected settings to exercise dynamic readline loading. But=
 after some trial and error it turns out that the following made the job to=
 allow to run `testes/main.lua`:<br /><br />#=C2=A0 make MYCFLAGS=3D'-std=
=3Dc99 -DLUA_USE_POSIX -DLUA_USE_DLOPEN -DLUA_READLINELIB=3D"\"/opt/homebre=
w/opt/readline/lib/libreadline.dylib\""' MYLDFLAGS=3D"" MYLIBS=3D"" CC=3Dcc=
<br /><br />did the trick. Full testes/main.lua passes with no regressions =
on Darwin 25.5.0 ARM64.<br /><br />Let me know if you try also to apply it =
on your side, what do you think of this change in behavior, and I will be h=
appy to hear if there is a cleaner approach you would suggest.<br /><br />K=
ind regards,<br />Mathieu<br /><br /><br /><div class=3D"gmail_quote"><div =
dir=3D"auto" class=3D"gmail_attr">On Friday, July 31, 2026 at 7:51:54=E2=80=
=AFAM UTC jure bagic wrote:<br/></div><blockquote class=3D"gmail_quote" sty=
le=3D"margin: 0 0 0 0.8ex; border-left: 1px solid rgb(204, 204, 204); paddi=
ng-left: 1ex;"><div dir=3D"auto">Small correction,<div dir=3D"auto">the &lt=
;eof&gt; in error does not necessarily indicate incomplete statement. It ca=
n be an expression, the main point is that once the initial input was not c=
ompiled sucessfully the REPL will now expect a statement (because there wil=
l be no &#39;return&#39; prepended and your input will be invalid syntax).<=
/div><div dir=3D"auto"><br></div><div dir=3D"auto">-- Jure</div></div><br><=
div class=3D"gmail_quote"><div dir=3D"ltr" class=3D"gmail_attr">On Fri, Jul=
 31, 2026, 09:11 jure bagic &lt;<a href data-email-masked rel=3D"nofollow">=
[email protected]</a>&gt; wrote:<br></div><blockquote class=3D"gmail_quote=
" style=3D"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><=
div dir=3D"auto"><div dir=3D"auto">Lua REPL accepts lines of text, when you=
 press enter the REPL first puts &quot;return&quot; before your input and t=
ries to compile it.</div><div dir=3D"auto"><br></div><div dir=3D"auto">For =
example</div><div dir=3D"auto">&gt; 69*enter*</div><div dir=3D"auto">is act=
ually</div><div dir=3D"auto">&gt; return 69*enter*</div><div dir=3D"auto"><=
br></div><div dir=3D"auto">Try doing</div><div dir=3D"auto">&gt; 4, 2, 0*en=
ter*</div><div dir=3D"auto"><br></div><div dir=3D"auto">If such expression =
compiles, REPL skips trying to compile your input as statement.</div><div d=
ir=3D"auto">Then the compiled function (with prepended &#39;return&#39; fol=
lowed by your input) is called in order to obtain those values on the stack=
 and finally those values are used as arguments to global function &#39;pri=
nt&#39; (part of basic library).</div><div dir=3D"auto"><br></div><div dir=
=3D"auto">Otherwise, when compilation with implicit &#39;return&#39; fails,=
 REPL tries to compile your input as statement using a clever hack (in my o=
pinion). As long as the input cannot be compiled (syntax error), it checks =
end of the error message to see of it ends with &#39;&lt;eof&gt;&#39; strin=
g. This indicates an incomplete statement as syntax error will be &quot;nea=
r &lt;eof&gt;&quot; (end of file =3D=3D end of input) because another token=
 was expected before input suddenly ended. And then it prompts second time =
(with &#39;&gt;&gt;&#39;) and so on...</div><div dir=3D"auto"><br></div><di=
v dir=3D"auto">So when you do history, you are sending entire expression at=
 once, in other words your input can contain multiple newlines because tech=
nically you only pressed enter once.</div><div dir=3D"auto"><br></div><div =
dir=3D"auto">-- Jure</div></div><br><div class=3D"gmail_quote"><div dir=3D"=
ltr" class=3D"gmail_attr">On Fri, Jul 31, 2026, 06:46 mathieu stumpf-guntz =
&lt;<a href rel=3D"noreferrer nofollow" data-email-masked>[email protected]=
m</a>&gt; wrote:<br></div><blockquote class=3D"gmail_quote" style=3D"margin=
:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div>Hello Martin =
and Roberto,</div><div><br></div><div>Thank you very much for all of your i=
nsights, that=E2=80=99s very enlightening, though I=E2=80=99ll have to medi=
tate and play a bit more around with it before integrating the full lesson =
here I guess.</div><div><br></div><div>To respond your question Martin, may=
be in a broader fashion than the way you where initially conducting it, wha=
t I would expect is a bit less surprise in behaviour while interacting with=
 the interpreter. Though I can get the point that, this or that surprising =
behavior can be the expected result according to the grammar and the docume=
ntation. Even sitting firmly in that scope, having a consistent behavior fo=
r the same input whatever the input method seems to dwell on an other level=
 of expectation. Consider the following session:<br><div style=3D"backgroun=
d-color:rgb(30,31,34);color:rgb(188,190,196)"><span style=3D"font-family:&q=
uot;JetBrains Mono&quot;,monospace">Lua 5.3.6  Copyright (C) 1994-2020 Lua.=
org, PUC-Rio<br>&gt; [[<br>&gt;&gt; test<br>&gt;&gt; ]] =3D=3D &quot;test\n=
&quot;<br>stdin:3: unexpected symbol near &#39;[[test<br>]]&#39;<br>&gt; [[=
</span></div><div style=3D"background-color:rgb(30,31,34);color:rgb(188,190=
,196)"><span style=3D"font-family:&quot;JetBrains Mono&quot;,monospace">tes=
t</span></div><div style=3D"background-color:rgb(30,31,34);color:rgb(188,19=
0,196)"><span style=3D"font-family:&quot;JetBrains Mono&quot;,monospace">]]=
 =3D=3D &quot;test\n&quot;<br>true<br>&gt; [[</span></div><div style=3D"bac=
kground-color:rgb(30,31,34);color:rgb(188,190,196)"><span style=3D"font-fam=
ily:&quot;JetBrains Mono&quot;,monospace">test</span></div><div style=3D"ba=
ckground-color:rgb(30,31,34);color:rgb(188,190,196)"><span style=3D"font-fa=
mily:&quot;JetBrains Mono&quot;,monospace">]] =3D=3D &quot;test\n&quot;<br>=
false<br>&gt; [[</span></div><div style=3D"background-color:rgb(30,31,34);c=
olor:rgb(188,190,196)"><span style=3D"font-family:&quot;JetBrains Mono&quot=
;,monospace">test</span></div><div style=3D"background-color:rgb(30,31,34);=
color:rgb(188,190,196)"><span style=3D"font-family:&quot;JetBrains Mono&quo=
t;,monospace">]] =3D=3D &quot;test\n&quot;<br>false<br>&gt; [[</span></div>=
<div style=3D"background-color:rgb(30,31,34);color:rgb(188,190,196)"><span =
style=3D"font-family:&quot;JetBrains Mono&quot;,monospace">test</span></div=
><div style=3D"background-color:rgb(30,31,34);color:rgb(188,190,196)"><span=
 style=3D"font-family:&quot;JetBrains Mono&quot;,monospace">]] =3D=3D &quot=
;test\n&quot;<br>true</span></div><br>So what happen here:</div><div>- I ty=
ped manually the boolean test and got printed an error informing=20
me of an ill formed construction, fair enough.</div><div>- Through history =
facility I=20
select again this ill formed construction and launch interpretation=20
which eventually returns true and prints accordingly.<br>- Then I copy/past=
ed the whole code snippet in the interpreter, and was informed this time th=
at this is false, the matching value representation being printed.</div><di=
v>- Then picking this very last entry in history and running it, I still ge=
t false printed.</div><div>- Finally, picking instead in history the earlie=
st item available, we are back to true being printed.</div><div><br></div><=
div></div><div>Just reading the session transcript, one could certainly spo=
t what=E2=80=99s happening in the directly typed entry thanks the extra `&g=
t;` hint. But without the previous explanations, one would certainly have a=
 hard time inferring what has actually been done in the subsequent interact=
ions.</div><div><br></div><div>I hope this clarify what I mean when I say I=
 would expect a more consistent behavior in the REPL.=C2=A0</div><div><br><=
/div><div>Kind regards,<br>Mathieu</div><div class=3D"gmail_quote"><div dir=
=3D"auto" class=3D"gmail_attr">On Thursday, July 30, 2026 at 4:42:55=E2=80=
=AFPM UTC Martin Eden wrote:<br></div><blockquote class=3D"gmail_quote" sty=
le=3D"margin:0 0 0 0.8ex;border-left:1px solid rgb(204,204,204);padding-lef=
t:1ex">
<br>On 2026-07-30 18:05, mathieu stumpf-guntz wrote:
<br>&gt; Not sure what you mean here, but in my previous examples I didn=E2=
=80=99t mean=20
<br>&gt; I used any magic sequence, the &quot;\n&quot; was actually literal=
 new lines. So=20
<br>&gt; to make it clear, the test conducted was more like `echo -e=20
<br>&gt; &#39;[[\ntest\n]]&#39; | lua`,=C2=A0 which ends up (in all configu=
rations I was=20
<br>&gt; able to try) with
<br>&gt; lua: stdin:3: unexpected symbol near &#39;[[test
<br>&gt; ]]&#39;
<br>
<br>Well that&#39;s a different thing! It gives same error for me too.
<br>
<br>So as
<br>
<br> =C2=A0 $ echo -e &#39;123&#39; | lua
<br> =C2=A0 lua: stdin:1: unexpected symbol near &#39;123&#39;
<br>
<br>What result you expect?
<br>
<br>&quot;lua&quot; program in interactive mode silently places expressions
<br>inside &quot;print()&quot;. That&#39;s neat feature to use it as calcul=
ator.
<br>
<br>But neither &quot;[[\ntest\n]]&quot; nor &quot;123&quot; is valid Lua c=
ode:
<br>they are expressions, not statements.
<br>
<br>-- Martin
<br>
<br>
<br></blockquote></div>

<p></p>

-- <br>
You received this message because you are subscribed to the Google Groups &=
quot;lua-l&quot; group.<br>
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to <a href rel=3D"noreferrer noreferrer nofollow" data-email-masked>lu=
[email protected]</a>.<br>
To view this discussion visit <a href=3D"https://groups.google.com/d/msgid/=
lua-l/72c88ccc-765c-4d7f-9b19-cf6d5b4a509fn%40googlegroups.com?utm_medium=
=3Demail&amp;utm_source=3Dfooter" rel=3D"noreferrer noreferrer nofollow" ta=
rget=3D"_blank" data-saferedirecturl=3D"https://www.google.com/url?hl=3Den&=
amp;q=3Dhttps://groups.google.com/d/msgid/lua-l/72c88ccc-765c-4d7f-9b19-cf6=
d5b4a509fn%2540googlegroups.com?utm_medium%3Demail%26utm_source%3Dfooter&am=
p;source=3Dgmail&amp;ust=3D1785580177637000&amp;usg=3DAOvVaw1HMGwONxQft-Hex=
Ftxn1Yy">https://groups.google.com/d/msgid/lua-l/72c88ccc-765c-4d7f-9b19-cf=
6d5b4a509fn%40googlegroups.com</a>.<br>
</blockquote></div>
</blockquote></div>
</blockquote></div>

<p></p>

-- <br />
You received this message because you are subscribed to the Google Groups &=
quot;lua-l&quot; group.<br />
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to <a href=3D"mailto:[email protected]">lua-l+unsubsc=
[email protected]</a>.<br />
To view this discussion visit <a href=3D"https://groups.google.com/d/msgid/=
lua-l/16be58aa-3298-4845-a28a-f411df51cdfen%40googlegroups.com?utm_medium=
=3Demail&utm_source=3Dfooter">https://groups.google.com/d/msgid/lua-l/16be5=
8aa-3298-4845-a28a-f411df51cdfen%40googlegroups.com</a>.<br />

------=_Part_8379_829851902.1785496373207--

------=_Part_8378_1929262368.1785496373207
Content-Type: text/x-diff; charset=US-ASCII; name=multiline-repl-fix.patch
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename=multiline-repl-fix.patch
X-Attachment-Id: 8a99dc26-55a8-4768-874e-6fa531643b5e
Content-ID: <8a99dc26-55a8-4768-874e-6fa531643b5e>

diff --git c/lua.c w/lua.c
index 858a04c0..79ae3026 100644
--- c/lua.c
+++ w/lua.c
@@ -666,6 +666,21 @@ static int loadline (lua_State *L) {
     return -1;  /* no input */
   if ((status = addreturn(L)) != LUA_OK)  /* 'return ...' did not work? */
     status = multiline(L);  /* try as command, maybe with continuation lines */
+  if (status != LUA_OK) {
+    /* Multiline assembled a complete buffer that failed as a statement.
+       Retry as an expression, so that multi-line expressions behave the
+       same way as single-line ones or history-recalled ones. */
+    lua_pop(L, 1);  /* remove error message; stack: [1]=assembled line */
+    if (addreturn(L) == LUA_OK)
+      status = LUA_OK;  /* stack: [1]=line, [2]=compiled function */
+    else {
+      /* Still not valid; regenerate the statement error for reporting. */
+      size_t len;
+      const char *s = lua_tolstring(L, 1, &len);
+      status = luaL_loadbufferx(L, s, len, "=stdin", "t");
+      /* stack: [1]=line, [2]=error message */
+    }
+  }
   line = lua_tostring(L, 1);
   if (line[0] != '\0')  /* non empty? */
     lua_saveline(line);  /* keep history */
diff --git c/testes/main.lua w/testes/main.lua
index 98d36951..c9c96377 100644
--- c/testes/main.lua
+++ w/testes/main.lua
@@ -382,6 +382,24 @@ prepfile("a = [[b\nc\nd\ne]]\na")
 RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i -- < %s > %s]], prog, out)
 checkprogout("b\nc\nd\ne\n\n")
 
+-- test that multi-line expressions work through continuation lines
+-- (not only when recalled from history as a single buffer)
+
+-- multi-line long string used in an expression
+prepfile('[[\ntest\n]] == "test\\n"\n')
+RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
+checkprogout("true\n")
+
+-- multi-line parenthesized arithmetic expression
+prepfile("(\n1 +\n2 +\n3\n)\n")
+RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
+checkprogout("6\n")
+
+-- genuinely invalid multi-line input should still error
+prepfile("(\n1 +\n)\n")
+RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s 2>&1]], prog, out)
+assert(string.find(getoutput(), "near"))
+
 -- input interrupted in continuation line
 prepfile("a.\n")
 RUN([[lua -i < %s > /dev/null 2> %s]], prog, out)

------=_Part_8378_1929262368.1785496373207--