Lua: ¿por qué se permiten las cadenas después de la llamada de función?
luac (3)
Estoy tratando de implementar una función simple de C ++, que comprueba una sintaxis del script Lua. Para eso estoy usando la función del compilador de Lua luaL_loadbufferx()
y comprobando su valor de retorno después.
Recientemente, me encontré con un problema, porque el código, que pensé que debía marcarse como no válido , no se detectó y, en cambio, el script falló más tarde en un tiempo de ejecución (por ejemplo, en lua_pcall()
).
Ejemplo de código Lua (puede probarse en la demo oficial de Lua ):
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 '')''
El objetivo es obviamente capturar todos los errores de sintaxis en tiempo de compilación. Entonces mis preguntas son:
- ¿Qué significa exactamente llamando a un valor de cadena ?
- ¿Por qué se permite esta sintaxis en primer lugar? ¿Es alguna característica de Lua de la que no tengo conocimiento, o la
luaL_loadbufferx()
es defectuosa en este ejemplo en particular? - ¿Es posible detectar tales errores por cualquier otro método sin ejecutarlo? Lamentablemente, mi función no tiene acceso a variables globales en tiempo de compilación, por lo que no puedo simplemente ejecutar el código directamente a través de
lua_pcall()
.
Nota: Estoy usando Lua versión 5.3.4 ( manual aquí ).
Muchas gracias por su ayuda.
Otro enfoque es cambiar la metatabla de cadena haciendo válida una llamada a una cadena.
local mt = getmetatable ""
mt.__call = function (self, args) return self .. args end
print(("x") "y") -- outputs `xy`
Ahora esas llamadas de sintaxis válidas a una cadena darán como resultado la concatenación de cadenas en lugar de errores de tiempo de ejecución.
Ambos myfunc() "!"
y myfunc(){1,2,3}
son expresiones de Lua válidas.
Lua permite llamadas del formulario cadena exp . Ver functioncall
y prefixexp
en la sintaxis de Lua .
Entonces myfunc() "!"
es una llamada de función válida que llama a lo que myfunc
devuelve y lo llama con la cadena "!"
.
Lo mismo ocurre con una llamada del formulario exp table-literal .
Estoy escribiendo una respuesta a mi propia pregunta por si alguien más se topa con un problema similar en el futuro y también busca una solución.
Manual
El manual de Lua (en su sección 3.4.10 - Llamadas de función) básicamente establece que hay tres formas diferentes de proporcionar argumentos a la función Lua.
Los argumentos tienen la siguiente sintaxis:
args ::= ‘(’ [explist] ‘)’ args ::= tableconstructor args ::= LiteralString
Todas las expresiones de argumento se evalúan antes de la llamada. Una llamada de la forma f {campos} es azúcar sintáctica para f ({campos}); es decir, la lista de argumentos es una nueva tabla única. Una llamada de la forma f''string ''(o f "cadena" o f [[cadena]]) es azúcar sintáctica para f ('' cadena ''); es decir, la lista de argumentos es una cadena literal única.
Explicación
Como he señalado en su respuesta , myfunc()"!"
y myfunc(){1,2,3}
son expresiones de Lua válidas. Significa que el compilador de Lua no está haciendo nada incorrecto, considerando que no conoce el valor de retorno de la función en un tiempo de compilación.
El código de ejemplo original dado en la pregunta:
print("Hello " .. myfunc() "!")
Podría ser reescrito como:
print("Hello " .. (myfunc()) ("!"))
Que (cuando se ejecuta) se traduce a:
print("Hello " .. ("everyone") ("!"))
Y como resultado, el mensaje de error de tiempo de ejecución attempt to call a string value
(que podría reescribirse como: la cadena, everyone
no son una función, por lo que no puede llamarlo).
Solución
Por lo que yo entiendo, estas dos formas alternativas de suministrar argumentos no tienen ningún beneficio real sobre la sintaxis estándar de func(arg)
. Es por eso que terminé modificando los archivos del analizador Lua. La desventaja de mantener esta sintaxis alternativa era demasiado grande. Esto es lo que hice (relevante para v5.3.4):
- En el archivo
lparser.c
busqué la función:static void suffixedexp (LexState *ls, expdesc *v)
- Dentro de esta función cambié la declaración de caso:
case ''('': case TK_STRING: case ''{'':
acase ''('':
¡Advertencia! Al hacer esto, modifiqué el lenguaje Lua, por lo que, como dije en su comentario, ya no se puede llamar Lua puro . Si no está seguro de si es exactamente lo que quiere, no puedo recomendar este enfoque.
Con esta ligera modificación, el compilador detecta las dos sintaxis alternativas mencionadas anteriormente como errores. Por supuesto, ya no puedo usarlos dentro de los scripts de Lua, pero para mi aplicación específica está bien.
Todo lo que tengo que hacer es observar este cambio en algún lugar para encontrarlo en caso de actualizar Lua a una versión superior.