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

139 views
Skip to first unread message

mathieu stumpf-guntz

unread,
Jul 30, 2026, 8:08:55 AMJul 30
to lua-l

Hello dear Lua community,


In Lua REPL, direct input of multiline strings fails with a syntax error, while recalling the same input via history navigation (up arrow) executes correctly. This behavior is observed starting from Lua 5.3.x. In Lua 5.2.4, history navigation as a feature did not exist, so the issue cannot be verified in earlier versions.

Steps to Reproduce:

  1. Launch Lua REPL (tested versions: 5.2.4, 5.3.6, 5.5.0).

  2. Input a multiline string directly:

    > [[ \ntest\n]]

    Output: stdin:3: unexpected symbol near '[[test\n]]' (error in all tested versions).

  3. Press the up arrow to recall and re-execute the same input.

Note on reproduction:
    • Lua 5.2.4: History navigation feature did not exist.

    • Lua 5.3.6+: Output: test (correct).

Environment where this was tested:

  • Lua Versions: 5.2.4, 5.3.6, 5.5.0

  • Shells: fish 3.7.0/4.8.1, bash 3.2.57(1)-release/5.2.21

  • Terminal Emulator: JetBrains-JediTerm/Gnome Terminal

  • OS: macOS 26.5.2 (arm64)/Ubuntu 24.04.4 LTS

Observations:

  • Direct input of multiline strings fails consistently across all tested versions.

  • History recall of the same multiline strings works in Lua 5.3.6+ but was not available in 5.2.4.

  • The issue suggests a discrepancy in how the REPL processes direct input versus history recall.

Suggestion:
The fix should ensure consistent interpretation of multiline strings regardless of input method (direct or history recall).

Kind regards,
Matheiu

Sainan

unread,
Jul 30, 2026, 8:22:14 AMJul 30
to lu...@googlegroups.com
Maybe add some context to your AI agent that Lua uses libreadline for 'history recall'. :^)

-- Sainan
Message has been deleted

mathieu stumpf-guntz

unread,
Jul 30, 2026, 9:44:09 AMJul 30
to lua-l
Hello Sainan, thanks for your quick reply.

Do you mean that the bug should rather be addressed to readline maintainers? I have doubt about it, as in, even if the history facility use libreadline, it looks to me more like the nub of the issue lies on the direct input version which fails to interpret this multiline string. 

I just tested in `irb` which for what I know also use readline, and something like `%q{\ntest\n}` don’t cause any issue of interpretation.

I did try to feed the LLM session I used to formulate the initial report with additional contextual information, but nothing substantial came out of it, just suggestion to test even more interpreters. I did with Python, but didn’t went with the full extensive list it was proposing.

Martin Eden

unread,
Jul 30, 2026, 11:42:11 AMJul 30
to lu...@googlegroups.com
On 2026-07-30 14:01, mathieu stumpf-guntz wrote:
>
> In Lua REPL, direct input of multiline strings fails with a syntax
> error, while recalling the same input via history navigation (up
> arrow) executes correctly. This behavior is observed starting from Lua
> 5.3.x. In Lua 5.2.4, history navigation as a feature did not exist, so
> the issue cannot be verified in earlier versions.
>
> *Steps to Reproduce:*
>
> 1.
>
> Launch Lua REPL (tested versions: 5.2.4, 5.3.6, 5.5.0).
>
> 2.
>
> Input a multiline string directly:
>
> > [[ \ntest\n]]
>
> Output: stdin:3: unexpected symbol near '[[test\n]]' (error in all
> tested versions).
>
> 3.
>
> Press the up arrow to recall and re-execute the same input.
>
Works on my machine:

  $ lua
  Lua 5.5.1  Copyright (C) 1994-2026 Lua.org, PUC-Rio
  > [[ \ntest\n]]
   \ntest\n
  >

BTW there are no magic (cursed) sequences in long Lua literals.

-- Martin


mathieu stumpf-guntz

unread,
Jul 30, 2026, 12:05:04 PMJul 30
to lua-l
Hello Martin, thanks for your additional insights. 

Works on my machine:

  $ lua
  Lua 5.5.1  Copyright (C) 1994-2026 Lua.org, PUC-Rio
  > [[ \ntest\n]]
   \ntest\n
  >

Could test quickly 5.5.1 using `mise`, latest it enabled me to try was 5.5.0 where I got:

$lua 
Lua 5.5.0 Copyright (C) 1994-2025 Lua.org, PUC-Rio
> [[
>> ]]
stdin:2: unexpected symbol near '[[]]'

>BTW there are no magic (cursed) sequences in long Lua literals.

Not sure what you mean here, but in my previous examples I didn’t mean I used any magic sequence, the "\n" was actually literal new lines. So to make it clear, the test conducted was more like `echo -e '[[\ntest\n]]' | lua`,  which ends up (in all configurations I was able to try) with
lua: stdin:3: unexpected symbol near '[[test
]]'

Roberto Ierusalimschy

unread,
Jul 30, 2026, 12:19:55 PMJul 30
to lu...@googlegroups.com
> In Lua REPL, direct input of multiline strings fails with a syntax error,
> while recalling the same input via history navigation (up arrow) executes
> correctly. This behavior is observed starting from Lua 5.3.x. In Lua 5.2.4,
> history navigation as a feature did not exist, so the issue cannot be
> verified in earlier versions.

This is documented behavior:

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.

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,

> a = 10 +
>> 20

works, but

> 10 +
>> 20

doesn't.

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.

-- Roberto

Martin Eden

unread,
Jul 30, 2026, 12:42:55 PMJul 30
to lu...@googlegroups.com

On 2026-07-30 18:05, mathieu stumpf-guntz wrote:
> Not sure what you mean here, but in my previous examples I didn’t mean
> I used any magic sequence, the "\n" was actually literal new lines. So
> to make it clear, the test conducted was more like `echo -e
> '[[\ntest\n]]' | lua`,  which ends up (in all configurations I was
> able to try) with
> lua: stdin:3: unexpected symbol near '[[test
> ]]'

Well that's a different thing! It gives same error for me too.

So as

  $ echo -e '123' | lua
  lua: stdin:1: unexpected symbol near '123'

What result you expect?

"lua" program in interactive mode silently places expressions
inside "print()". That's neat feature to use it as calculator.

But neither "[[\ntest\n]]" nor "123" is valid Lua code:
they are expressions, not statements.

-- Martin


mathieu stumpf-guntz

unread,
Jul 31, 2026, 12:46:51 AM (14 days ago) Jul 31
to lua-l
Hello Martin and Roberto,

Thank you very much for all of your insights, that’s very enlightening, though I’ll have to meditate and play a bit more around with it before integrating the full lesson here I guess.

To respond your question Martin, maybe in a broader fashion than the way you where initially conducting it, what 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 documentation. Even sitting firmly in that scope, having a consistent behavior for the same input whatever the input method seems to dwell on an other level of expectation. Consider the following session:
Lua 5.3.6 Copyright (C) 1994-2020 Lua.org, PUC-Rio
> [[
>> test
>> ]] == "test\n"

stdin:3: unexpected symbol near '[[test
]]'
> [[
test
]] == "test\n"
true
> [[
test
]] == "test\n"
false
> [[
test
]] == "test\n"
false
> [[
test
]] == "test\n"
true

So what happen here:
- I typed manually the boolean test and got printed an error informing me of an ill formed construction, fair enough.
- Through history facility I select again this ill formed construction and launch interpretation which eventually returns true and prints accordingly.
- Then I copy/pasted the whole code snippet in the interpreter, and was informed this time that this is false, the matching value representation being printed.
- Then picking this very last entry in history and running it, I still get false printed.
- Finally, picking instead in history the earliest item available, we are back to true being printed.

Just reading the session transcript, one could certainly spot what’s happening in the directly typed entry thanks the extra `>` hint. But without the previous explanations, one would certainly have a hard time 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 consistent behavior in the REPL. 

Kind regards,
Mathieu

jure bagic

unread,
Jul 31, 2026, 3:12:04 AM (14 days ago) Jul 31
to lua-l
Lua REPL accepts lines of text, when you press enter the REPL first puts "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 statement.
Then the compiled function (with prepended 'return' followed 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 'print' (part of basic library).

Otherwise, when compilation with implicit 'return' fails, REPL tries to compile your input as statement using a clever hack (in my opinion). As long as the input cannot be compiled (syntax error), it checks end of the error message to see of it ends with '<eof>' string. This indicates an incomplete statement as syntax error will be "near <eof>" (end of file == end of input) because another token was expected before input suddenly ended. And then it prompts second time (with '>>') and so on...

So when you do history, you are sending entire expression at once, in other words your input can contain multiple newlines because technically you only pressed enter once.

-- Jure

--
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 email to lua-l+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/lua-l/72c88ccc-765c-4d7f-9b19-cf6d5b4a509fn%40googlegroups.com.

jure bagic

unread,
Jul 31, 2026, 3:51:54 AM (14 days ago) Jul 31
to lua-l
Small correction,
the <eof> in error does not necessarily indicate incomplete statement. It can be an expression, the main point is that once the initial input was not compiled sucessfully the REPL will now expect a statement (because there will be no 'return' prepended and your input will be invalid syntax).

-- Jure

mathieu stumpf-guntz

unread,
Jul 31, 2026, 7:12:53 AM (13 days ago) Jul 31
to lua-l
Hi Jure,

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

I was curious to dig a bit deeper in the actual mechanism, so I realigned my copy of Lua source code with Github’s master version, where HEAD point to 7579fc9d7ed90240487251dfb69168f8e64e9294 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. 

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

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 statement, 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.

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

--- a/testes/main.lua
+++ b/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"))
+

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


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

Side note on compilation: it's been a bit harder to get the compilation right to run the test suite on Darwin now that the makefile no longer 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`:

#  make MYCFLAGS='-std=c99 -DLUA_USE_POSIX -DLUA_USE_DLOPEN -DLUA_READLINELIB="\"/opt/homebrew/opt/readline/lib/libreadline.dylib\""' MYLDFLAGS="" MYLIBS="" CC=cc

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

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 happy to hear if there is a cleaner approach you would suggest.

Kind regards,
Mathieu
multiline-repl-fix.patch

TopchetoEU

unread,
Aug 2, 2026, 5:33:04 PM (11 days ago) Aug 2
to lu...@googlegroups.com
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:

> 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 promoted to a statement.

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 writting out a plain table in the REPL seems rather misguided...)
> --
> 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 email to lua-l+un...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/lua-l/20260730161948.GA11249%40arraial.inf.puc-rio.br.
>

mathieu stumpf-guntz

unread,
Aug 4, 2026, 1:50:52 AM (10 days ago) Aug 4
to lua-l
Hi TopchetoEU,

Thanks for this feedback. It was genuinely valuable and highlighted a fully relevant point.

So, yes, as we seems to agree, multiline input with valid expression should keep expression semantics. And yes, the previous patch instead was implicitly treating them as statement-only and losing return-value printing.

That behavior has been corrected in the attached patch: multiline expression handling is now aligned with single-line expectations.

In the same spirit, the test suite was further hardened around multiline REPL behavior, including additional corner cases beyond the original example, so this class of regression stays covered.

For those curious to play with it, the attached patch should be applied against 7579fc9d7ed90240487251dfb69168f8e64e9294
flexible-multiline-repl.diff
Reply all
Reply to author
Forward
0 new messages