Я говорю о C и/или C++ здесь, поскольку это - единственные языки, я знаю используемый для интерпретаторов, где следующее могло быть проблемой:
Если у нас есть интерпретируемый язык X, как может, библиотека, записанная для него, добавляет функции к языку, который можно затем назвать из программ, записанных на языке?
Пример PHP:
substr( $str, 5, 10 );
Это легко для PHP, хранящего все зарегистрированные имена функций в массиве и перерывающего его, поскольку функция вызвана в сценарии. Однако, поскольку, очевидно, нет никакой оценки в C (++), как функция может затем быть вызвана? Я предполагаю, что PHP не имеет 100 МБ кода как:
if( identifier == "substr" )
{
return PHP_SUBSTR(...);
} else if( ... ) {
...
}
Ха ха это было бы довольно забавно. Я надеюсь, что Вы поняли мой вопрос до сих пор.
На самом деле языки сценариев делают что-то вроде того, что вы упомянули.
Они оборачивают функции и регистрируют эти функции в движке интерпретатора.
Пример Lua:
static int io_read (lua_State *L) {
return g_read(L, getiofile(L, IO_INPUT), 1);
}
static int f_read (lua_State *L) {
return g_read(L, tofile(L), 2);
}
...
static const luaL_Reg flib[] = {
{"close", io_close},
{"flush", f_flush},
{"lines", f_lines},
{"read", f_read},
{"seek", f_seek},
{"setvbuf", f_setvbuf},
{"write", f_write},
{"__gc", io_gc},
{"__tostring", io_tostring},
{NULL, NULL}
};
...
luaL_register(L, NULL, flib); /* file methods */
Интерпретаторы, вероятно, просто сохранят хэш-карту имен функций для определения функции (которая будет включать информацию о параметрах, тип возвращаемого значения, расположение / определение функции и т. Д.). , вы можете просто выполнить поиск по хэш-карте для имени функции (когда ваш интерпретатор найдет его). Если он существует, используйте информацию о функции в хеш-таблице, чтобы оценить его.
Очевидно, что вам нужно добавить положения для разных уровней охвата и т. Д., Но в этом суть.
Практически все компиляторы имеют «таблицу символов», которую они используют для поиска того, что представляет собой идентификатор. Таблица символов будет содержать имя функции, имена переменных, имена типов и т. Д. Все, что имеет имя, помещается в таблицу символов, которая в основном представляет собой карту имен всего, что компилятор знает об этом имени (здесь я упрощаю ). Затем, когда компилятор встречает идентификатор, он ищет его в таблице символов и обнаруживает, что это функция. Если вы используете интерпретатор, то в таблице символов будет информация о том, где найти функцию и продолжить интерпретацию.Если это компилятор, таблица символов будет иметь адрес, в котором эта функция будет находиться в скомпилированном коде (или заполнитель для заполнения адреса позже). Затем может быть произведена сборка, которая, по сути, говорит: поместите аргументы в стек и возобновите выполнение по некоторому адресу.
Так, например, интерпретатор смотрит на
substr( $str, 5, 10 );
и находит «substr» в своей таблице символов:
symbolTableEntry entry = symbolTable["substr"];
оттуда он будет собирать $ str
, 5
и 10
в качестве аргументов и посмотрите на запись
, чтобы убедиться, что аргументы действительны для функции. Затем он будет искать в записи
, чтобы узнать, куда перейти с упорядоченными аргументами.
В C++ вы, вероятно, будете использовать тот же механизм, что и Nick D, но, используя его возможности OO:
typedef luaFunction boost::function<void(*)(lua_State&)>
std::map<std::string, luaFunction > symbolTable;
symbolTable["read"] = f_read;
symbolTable["close"] = f_close; // etc.
// ...
luaFunction& f = symbolTable[*symbolIterator++];
f(currentLuaState);