#reflection #lua #abstract-syntax-tree
Вопрос:
У меня есть скрипт, написанный на Lua 5.1, который импортирует сторонний модуль и вызывает из него некоторые функции. Я хотел бы получить список вызовов функций из модуля с их аргументами (когда они известны до выполнения).
Итак, мне нужно написать другой скрипт, который берет исходный код моего первого скрипта, анализирует его и извлекает информацию из его кода.
Рассмотрим минимальный пример.
У меня есть следующий модуль:
local mod = {}
function mod.foo(a, ...)
print(a, ...)
end
return mod
И следующий код драйвера:
local M = require "mod"
M.foo('a', 1)
M.foo('b')
Каков лучший способ извлечения данных с помощью «использования» вхождений M.foo
функции?
В идеале я хотел бы получить информацию с именем вызываемой функции и значениями ее аргументов. Из приведенного выше примера кода было бы достаточно, чтобы получить отображение следующим образом: {'foo': [('a', 1), ('b')]}
.
Я не уверен, есть ли у Lua функции для отражения для получения этой информации. Поэтому, вероятно, мне нужно будет использовать один из существующих анализаторов для Lua, чтобы получить полный AST и найти интересующие меня вызовы функций.
Есть еще какие-нибудь предложения?
Комментарии:
1. Видеть lua.org/manual/5.1/manual.html#3.8 и lua.org/manual/5.1/manual.html#5.9
Ответ №1:
Если вы не можете изменить файлы, вы можете прочитать файлы в строку, затем проанализировать mod
файл и найти в нем все функции, а затем использовать эту информацию для анализа целевого файла для всех применений библиотеки модов
functions = {}
for func in modFile:gmatch("function mod%.(%w )") do
functions[func] = {}
end
for func, call in targetFile:gmatch("M%.(%w )%(([^%)] )%)") do
args = {}
for arg in string.gmatch(call, "([^,] )") do
table.insert(args, arg)
end
table.insert(functions[func], args)
end
Результирующая таблица затем может быть сериализована
['foo'] = {{"'a'", " 1"}, {"'b'"}}
3 возможные ошибки:
M
это не очень уникальное имя и может отличаться, возможно, в соответствии с непреднамеренными вызовами функций в другую библиотеку.- В этом примере не обрабатывается, если внутри списка arg выполняется вызов функции. например
myfunc(getStuff(), true)
- Результирующая таблица не знает типизации аргументов, поэтому все они сохраняются как строковые представления.
Если вы хотите изменить целевой файл, вы можете создать оболочку вокруг своего required
модуля
function log(mod)
local calls = {}
local wrapper = {
__index = function(_, k)
if mod[k] then
return function(...)
calls[k] = calls[k] or {}
table.insert(calls[k], {...})
return mod[k](...)
end
end
end,
}
return setmetatable({},wrapper), calls
end
затем вы используете эту функцию вот так.
local M, calls = log(require("mod"))
M.foo('a', 1)
M.foo('b')
Если ваш модуль не просто function
s, вам нужно будет обработать это в оболочке, эта оболочка предполагает, что все индексы являются функцией.
после всех ваших звонков вы можете сериализовать calls
таблицу, чтобы получить историю всех сделанных звонков. Для примера кода таблица выглядит следующим образом
{
['foo'] = {{'a', 1}, {'b'}}
}
Комментарии:
1. Спасибо, это очень хорошее решение. Но в идеале я хотел бы получить те же данные без изменения сценария. Я понимаю , что это невозможно с помощью обертки
require
, поэтому я пока оставлю вопрос открытым, может быть, у кого-то будут другие идеи.2. Я добавил решение, которое работает путем чтения целевых файлов.
3. Хм, использование регулярных выражений-еще один вариант избежать использования полного анализатора Lua. Спасибо, у меня есть идея. Я подумал, может быть, в языке есть какие-то специальные библиотеки или инструменты отражения, которые упростили бы эту задачу, но, видимо, мне придется разобрать код.