5

I'm trying to implement a simple C++ function, which checks a syntax of Lua script. For that I'm using Lua's compiler function luaL_loadbufferx() and checking its return value afterwards.

Recently, I have ran into a problem, because the code, that I thought should be marked invalid, was not detected and instead the script failed later at a runtime (eg. in lua_pcall()).

Example Lua code (can be tested on official Lua demo):

function myfunc()
   return "everyone"
end

-- Examples of unexpected behaviour:
-- The following lines pass the compile time check without errors.
print("Hello " .. myfunc() "!") -- Runtime error: attempt to call a string value
print("Hello " .. myfunc() {1,2,3}) -- Runtime error: attempt to call a string value

-- Other examples:
-- The following lines contain examples of invalid syntax, which IS detected by compiler.
print("Hello " myfunc() .. "!") -- Compile error: ')' expected near 'myfunc'
print("Hello " .. myfunc() 5) -- Compile error: ')' expected near '5'
print("Hello " .. myfunc() .. ) -- Compile error: unexpected symbol near ')'

The goal is obviously to catch all syntax errors at compile time. So my questions are:

  1. What exactly is meant by calling a string value?
  2. Why is this syntax allowed in the first place? Is it some Lua feature I'm unaware of, or the luaL_loadbufferx() is faulty in this particular example?
  3. Is it possible to detect such errors by any other method without running it? Unfortunately, my function doesn't have access to global variables at compile time, so I can't just just run the code directly via lua_pcall().

Note: I'm using Lua version 5.3.4 (manual here).

Thank you very much for your help.

Electrix
  • 331
  • 5
  • 14

3 Answers3

5

Both myfunc() "!" and myfunc(){1,2,3} are valid Lua expressions.

Lua allows calls of the form exp string. See functioncall and prefixexp in the Syntax of Lua.

So myfunc() "!" is a valid function call that calls whatever myfunc returns and call it with the string "!".

The same thing happens for a call of the form exp table-literal.

lhf
  • 70,581
  • 9
  • 108
  • 149
  • Thank you very much for explanation. I'll try to investigate, if it is possible to turn off this Lua feature, because missing ".." is a very common mistake when writing a text-heavy script. Aside from that I don't see any other way of accomplishing what i want. – Electrix Jul 07 '17 at 18:18
  • 1
    @Electrix, There is no way to turn off this Lua feature because it is in the grammar. You can change the parser, but then it won't be Lua any longer. – lhf Jul 07 '17 at 18:31
3

Another approach is to change string's metatable making a call to a string valid.

local mt = getmetatable ""
mt.__call = function (self, args) return self .. args end
print(("x") "y") -- outputs `xy`

Now those valid syntax calls to a string will result in string concatenation instead of runtime errors.

Tymur Gubayev
  • 468
  • 4
  • 14
  • +1, Thanks for the great idea. While this will not help to detect "broken" syntax, it repairs it on-the-fly. I'll definitely consider this approach. – Electrix Jul 08 '17 at 08:56
2

I'm writing answer to my own question just in case anyone else stumbles upon the similar problem in the future and also looks for solution.


Manual

Lua manual (in its section 3.4.10 – Function Calls) basically states, that there are three different ways of providing arguments to Lua function.

Arguments have the following syntax:

  args ::= ‘(’ [explist] ‘)’
  args ::= tableconstructor
  args ::= LiteralString
All argument expressions are evaluated before the call. A call of the form f{fields} is syntactic sugar for f({fields}); that is, the argument list is a single new table. A call of the form f'string' (or f"string" or f[[string]]) is syntactic sugar for f('string'); that is, the argument list is a single literal string.

Explanation

As lhf pointed out in his answer, both myfunc()"!" and myfunc(){1,2,3} are valid Lua expressions. It means the Lua compiler is doing nothing wrong, considering it doesn't know the function return value at a compile time.

The original example code given in the question:

print("Hello " .. myfunc() "!")
Could be then rewritten as:
print("Hello " .. (myfunc()) ("!"))
Which (when executed) translates to:
print("Hello " .. ("everyone") ("!"))
And thus resulting in the runtime error message attempt to call a string value (which could be rewritten as: the string everyone is not a function, so you can't call it).

Solution

As far as I understand, these two alternative ways of supplying arguments have no real benefit over the standard func(arg) syntax. That's why I ended up modyfing the Lua parser files. The disadventage of keeping this alternative syntax was too big. Here is what I've done (relevant for v5.3.4):

  1. In file lparser.c i searched for function:
    static void suffixedexp (LexState *ls, expdesc *v)
  2. Inside this function i changed the case statement:
    case '(': case TK_STRING: case '{':
    to
    case '(':

Warning! By doing this I have modified the Lua language, so as lhf stated in his comment, it can no longer be called pure Lua. If you are unsure whether it is exactly what you want, I can't recommend this approach.

With this slight modification compiler detects the two above mentioned alternative syntaxes as errors. Of course, I can no longer use them inside Lua scripts, but for my specific application it's just fine.

All I need to do is to note this change somewhere to find it in case of upgrading Lua to higher version.

Electrix
  • 331
  • 5
  • 14
  • 1
    You'll break common code such as `require"foo"`. But you're aware of that. – lhf Jul 08 '17 at 00:23
  • 1
    >`these two alternative ways of supplying arguments have no real benefit` That feature is heavily used when building various DSL. Cutting it out only cripples your freedom in building syntaxes – Vlad Jul 08 '17 at 06:27
  • @Vlad, in my case, the Lua is used in a big game project to write simple quests (what varies is often a *quality* of authors). Although sometimes these quest contain advanced syntax, most of the code is about words, sentences, headings etc. These quests are then internally processed by the game core, and while there are few of them *special* (something like configs for other quests), they keep the same simple syntax and I honestly don't think this alternative argument syntax will ever be required. Still, if you have other idea how to approach this problem, I'll definitely consider it. – Electrix Jul 08 '17 at 07:30