Re: Inconsistent Multiline String Handling in Lua REPL (Direct Input vs. History Recall)
mathieu stumpf-guntz <[email protected]> Mon, 3 Aug 2026 22:50:52 -0700 (PDT)
| Newsgroups | gmane.comp.lang.lua.general |
|---|---|
| Message-ID | <[email protected]> |
------=_Part_32884_804580086.1785822652600 Content-Type: multipart/alternative; boundary="----=_Part_32885_1476891830.1785822652600" ------=_Part_32885_1476891830.1785822652600 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi TopchetoEU, Thanks for this feedback. It was genuinely valuable and highlighted a fully= =20 relevant point. So, yes, as we seems to agree, multiline input with valid expression should= =20 keep expression semantics. And yes, the previous patch instead was=20 implicitly treating them as statement-only and losing return-value printing= . That behavior has been corrected in the attached patch: multiline=20 expression handling is now aligned with single-line expectations. In the same spirit, the test suite was further hardened around multiline=20 REPL behavior, including additional corner cases beyond the original=20 example, so this class of regression stays covered. For those curious to play with it, the attached patch should be applied=20 against 7579fc9d7ed90240487251dfb69168f8e64e9294 On Sunday, August 2, 2026 at 9:33:04=E2=80=AFPM UTC TopchetoEU wrote: > Wouldn't trying to parse multiline inputs as expressions be a more useful= =20 > behavior here? Under the current circumstances a perfectly valid expressi= on=20 > would be "promoted" to a statement, provided it is multiline. So for this= =20 > input: > > > function some_func(...) --[[ do something useful... ]] return ... end > > some_func(1, 2, > >> 3, 4) > > You would never see the return values of `some_func`, because it was=20 > promoted to a statement. > > Also, you have this weirdness where a perfectly valid expression is not a= =20 > valid statement, so lua complains. However, I can't think of a valid reas= on=20 > to break up non-call expressions, other than tables (of course writting o= ut=20 > a plain table in the REPL seems rather misguided...) > > > On Thursday, July 30th, 2026 at 7:20 PM, Roberto Ierusalimschy < > [email protected]> wrote: > > > > In Lua REPL, direct input of multiline strings fails with a syntax=20 > error, > > > while recalling the same input via history navigation (up arrow)=20 > executes > > > correctly. This behavior is observed starting from Lua 5.3.x. In Lua= =20 > 5.2.4, > > > history navigation as a feature did not exist, so the issue cannot be > > > verified in earlier versions. > >=20 > > This is documented behavior: > >=20 > > In interactive mode, Lua repeatedly prompts and waits for a line. > > After reading a line, Lua first tries to interpret the line as an > > expression. If it succeeds, it prints its value. Otherwise, it > > interprets the line as a chunk. If you write an incomplete chunk, > > the interpreter waits for its completion by issuing a different > > prompt. > >=20 > > Note the "tries to interpret THE LINE as an expression" and "If you > > write an incomplete CHUNK": Lua only accepts entire chunks as multiline > > input; it does not accept expressions. So, > >=20 > > > a =3D 10 + > > >> 20 > >=20 > > works, but > >=20 > > > 10 + > > >> 20 > >=20 > > doesn't. > >=20 > > The idiosyncrasy is the fact that it accepts when it comes through > > history navigation; it is because then the input, although having > > multiple lines, is not really a "multiline" input (e.g., no different > > prompt); it goes all in one step. > >=20 > > -- Roberto > >=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/20260730161948.GA11249%40arraial.= inf.puc-rio.br > . > >=20 > --=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/53bac= 3e2-ae29-42be-9828-65f0b1c23440n%40googlegroups.com. ------=_Part_32885_1476891830.1785822652600 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable <div>Hi TopchetoEU,</div><div><br /></div><div>Thanks for this feedback. It= was genuinely valuable and highlighted a fully relevant point.</div><div><= br /></div><div>So, yes, as we seems to agree, multiline input with valid e= xpression should keep expression semantics. And yes, the previous patch ins= tead was implicitly treating them as statement-only and losing return-value= printing.</div><div><br /></div><div>That behavior has been corrected in t= he attached patch: multiline expression handling is now aligned with single= -line expectations.</div><div><br /></div>In the same spirit, the test suit= e was further hardened around multiline REPL behavior, including additional= corner cases beyond the original example, so this class of regression stay= s covered.<br /><div><br /></div><div>For those curious to play with it, th= e attached patch should be applied against=C2=A07579fc9d7ed90240487251dfb69= 168f8e64e9294</div><div><br /></div><div><br /></div><div class=3D"gmail_qu= ote"><div dir=3D"auto" class=3D"gmail_attr">On Sunday, August 2, 2026 at 9:= 33:04=E2=80=AFPM UTC TopchetoEU wrote:<br/></div><blockquote class=3D"gmail= _quote" style=3D"margin: 0 0 0 0.8ex; border-left: 1px solid rgb(204, 204, = 204); padding-left: 1ex;">Wouldn't trying to parse multiline inputs as = expressions be a more useful behavior here? Under the current circumstances= a perfectly valid expression would be "promoted" to a statement,= provided it is multiline. So for this input: <br> <br> > function some_func(...) --[[ do something useful... ]] return = ... end <br> > some_func(1, 2, <br> >> 3, 4) <br> <br>You would never see the return values of `some_func`, because it was pr= omoted to a statement. <br> <br>Also, you have this weirdness where a perfectly valid expression is not= a valid statement, so lua complains. However, I can't think of a valid= reason to break up non-call expressions, other than tables (of course writ= ting out a plain table in the REPL seems rather misguided...) <br> <br> <br>On Thursday, July 30th, 2026 at 7:20 PM, Roberto Ierusalimschy <<a h= ref data-email-masked rel=3D"nofollow">[email protected]</a>> wrote: <br> <br>> > In Lua REPL, direct input of multiline strings fails with a s= yntax error, <br>> > while recalling the same input via history navigation (up arr= ow) executes <br>> > correctly. This behavior is observed starting from Lua 5.3.x.= In Lua 5.2.4, <br>> > history navigation as a feature did not exist, so the issue c= annot be <br>> > verified in earlier versions. <br>>=20 <br>> This is documented behavior: <br>>=20 <br>> In interactive mode, Lua repeatedly prompts and waits for a li= ne. <br>> After reading a line, Lua first tries to interpret the line as= an <br>> expression. If it succeeds, it prints its value. Otherwise, = it <br>> interprets the line as a chunk. If you write an incomplete ch= unk, <br>> the interpreter waits for its completion by issuing a differen= t <br>> prompt. <br>>=20 <br>> Note the "tries to interpret THE LINE as an expression" = and "If you <br>> write an incomplete CHUNK": Lua only accepts entire chunks as= multiline <br>> input; it does not accept expressions. So, <br>>=20 <br>> > a =3D 10 + <br>> >> 20 <br>>=20 <br>> works, but <br>>=20 <br>> > 10 + <br>> >> 20 <br>>=20 <br>> doesn't. <br>>=20 <br>> The idiosyncrasy is the fact that it accepts when it comes through <br>> history navigation; it is because then the input, although having <br>> multiple lines, is not really a "multiline" input (e.g.,= no different <br>> prompt); it goes all in one step. <br>>=20 <br>> -- Roberto <br>>=20 <br>> -- <br>> You received this message because you are subscribed to the Google= Groups "lua-l" group. <br>> To unsubscribe from this group and stop receiving emails from it, = send an email to <a href data-email-masked rel=3D"nofollow">lua-l+un...@goo= glegroups.com</a>. <br>> To view this discussion visit <a href=3D"https://groups.google.com= /d/msgid/lua-l/20260730161948.GA11249%40arraial.inf.puc-rio.br" target=3D"_= blank" rel=3D"nofollow" data-saferedirecturl=3D"https://www.google.com/url?= hl=3Den&q=3Dhttps://groups.google.com/d/msgid/lua-l/20260730161948.GA11= 249%2540arraial.inf.puc-rio.br&source=3Dgmail&ust=3D178590666802200= 0&usg=3DAOvVaw1zUvov7S2DCrCMlMOECs_S">https://groups.google.com/d/msgid= /lua-l/20260730161948.GA11249%40arraial.inf.puc-rio.br</a>. <br>>=20 <br></blockquote></div> <p></p> -- <br /> You received this message because you are subscribed to the Google Groups &= quot;lua-l" 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/53bac3e2-ae29-42be-9828-65f0b1c23440n%40googlegroups.com?utm_medium= =3Demail&utm_source=3Dfooter">https://groups.google.com/d/msgid/lua-l/53bac= 3e2-ae29-42be-9828-65f0b1c23440n%40googlegroups.com</a>.<br /> ------=_Part_32885_1476891830.1785822652600-- ------=_Part_32884_804580086.1785822652600 Content-Type: text/x-diff; charset=US-ASCII; name=flexible-multiline-repl.diff Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=flexible-multiline-repl.diff X-Attachment-Id: 5e1cb168-46cc-4cda-8776-261a2bf68620 Content-ID: <5e1cb168-46cc-4cda-8776-261a2bf68620> diff --git c/lua.c w/lua.c index 858a04c0..b7e28140 100644 --- c/lua.c +++ w/lua.c @@ -608,7 +608,7 @@ static int pushline (lua_State *L, int firstline) { */ static int addreturn (lua_State *L) { const char *line = lua_tostring(L, -1); /* original line */ - const char *retline = lua_pushfstring(L, "return %s;", line); + const char *retline = lua_pushfstring(L, "return %s", line); int status = luaL_loadbufferx(L, retline, strlen(retline), "=stdin", "t"); if (status == LUA_OK) lua_remove(L, -2); /* remove modified line */ @@ -641,8 +641,25 @@ static int multiline (lua_State *L) { checklocal(line); for (;;) { /* repeat until gets a complete statement */ int status = luaL_loadbufferx(L, line, len, "=stdin", "t"); /* try it */ - if (!incomplete(L, status) || !pushline(L, 0)) + int stmt_incomplete = incomplete(L, status); + int expr_incomplete = 0; + int exprstatus = LUA_OK; + if (!stmt_incomplete && status != LUA_OK) { + const char *retline = lua_pushfstring(L, "return %s", line); + exprstatus = luaL_loadbufferx(L, retline, strlen(retline), "=stdin", "t"); + expr_incomplete = incomplete(L, exprstatus); + lua_pop(L, 2); /* remove expression result and generated "return" line */ + } + if (!(stmt_incomplete || expr_incomplete) || !pushline(L, 0)) { + if (expr_incomplete && !stmt_incomplete && status != LUA_OK) { + lua_pop(L, 1); /* remove statement error */ + const char *retline = lua_pushfstring(L, "return %s;", line); + exprstatus = luaL_loadbufferx(L, retline, strlen(retline), "=stdin", "t"); + lua_remove(L, -2); /* remove generated "return" line */ + return exprstatus; /* report expression-side incomplete error */ + } return status; /* should not or cannot try to add continuation line */ + } lua_remove(L, -2); /* remove error message (from incomplete line) */ lua_pushliteral(L, "\n"); /* add newline... */ lua_insert(L, -2); /* ...between the two lines */ @@ -664,8 +681,22 @@ static int loadline (lua_State *L) { lua_settop(L, 0); if (!pushline(L, 1)) return -1; /* no input */ - if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */ + if ((status = addreturn(L)) != LUA_OK) { /* 'return ...' did not work? */ + size_t len; + const char *s; status = multiline(L); /* try as command, maybe with continuation lines */ + /* + ** With assembled multiline input, prefer expression semantics whenever + ** possible (so calls print return values), then fall back to statement. + */ + s = lua_tolstring(L, 1, &len); + lua_pop(L, 1); /* remove previous statement result/error */ + if (addreturn(L) == LUA_OK) + status = LUA_OK; /* stack: [1]=line, [2]=compiled expression */ + else + status = luaL_loadbufferx(L, s, len, "=stdin", "t"); + /* stack: [1]=line, [2]=statement result/error */ + } line = lua_tostring(L, 1); if (line[0] != '\0') /* non empty? */ lua_saveline(line); /* keep history */ @@ -791,4 +822,3 @@ int main (int argc, char **argv) { lua_close(L); return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE; } - diff --git c/testes/main.lua w/testes/main.lua index 98d36951..56d12803 100644 --- c/testes/main.lua +++ w/testes/main.lua @@ -382,6 +382,121 @@ 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") + +-- multi-line function call expression should print returned values +prepfile[[ +function some_func(...) return ... end +some_func(1, 2, +3, 4) +]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("1\t2\t3\t4\n") + +-- multi-line statement should still run through statement fallback +prepfile("a =\n1\na\n") +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("1\n") + +-- nested multiline call expression should still print returned values +prepfile[[ +function g(a, b) return a + b end +function f(x, y) return x, y end +f(g(1, +2), +3) +]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("3\t3\n") + +-- multiline method-call expression should print returned values +prepfile[[ +t = {tag = "ok", foo = function (self, a, b) return a, b, self.tag end} +t:foo( +1, 2) +]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("1\t2\tok\n") + +-- multiline table-constructor expression should print evaluated result +prepfile[[ +({ + a = 1, + b = 2, +}).a +]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("1\n") + +-- parenthesized multiline prefix expression should print call result +prepfile[[ +( + function () return 1 end +)() +]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("1\n") + +-- multiline local declaration should still compile via statement path +prepfile[[ +do + local x = + 1 + print(x) +end +]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("1\n") + +-- multiline assignment still favors statement fallback when needed +prepfile[[ +a = 40 + + 2 +a +]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("42\n") + +-- comments in continued call should not break multiline expression handling +prepfile[[ +function h(a, b) return a + b end +h(1, -- keep parsing through continuation +2) +]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkprogout("3\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")) + +-- unfinished long construct should still report eof marker +prepfile("[[\nabc\n") +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s 2>&1]], prog, out) +assert(string.find(getoutput(), "near <eof>", 1, true)) + +-- unfinished call should still report eof marker +prepfile("f(\n") +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s 2>&1]], prog, out) +assert(string.find(getoutput(), "near <eof>", 1, true)) + +-- if both expression and statement forms fail, keep pointing at offending token +prepfile("(\n1 +\n$\n)\n") +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s 2>&1]], prog, out) +assert(string.find(getoutput(), "near '$'", 1, true)) + -- input interrupted in continuation line prepfile("a.\n") RUN([[lua -i < %s > /dev/null 2> %s]], prog, out) ------=_Part_32884_804580086.1785822652600--