Поскольку ваш вопрос был не очень специфичны, на него можно ответить только общим алгоритмом. Я также не утверждаю, что это «самый разумный» способ, просто тот, который работает.
Во-первых, нам нужно определить проблемное пространство.
Ваш язык сценариев имеет локальные переменные (с использованием термина Lua): переменные, которые не являются глобальными. Это переменные, которые теоретически могут быть захвачены лямбдой.
Теперь давайте предположим, что ваш язык сценариев не имеет значения динамически выберите локальные переменные. Это означает, что просто путем проверки дерева синтаксиса можно видеть следующее:
- Какие локальные переменные захватываются функцией.
- Какими локальными переменными являются не, захваченные функцией.
- Какие функции фиксируют локальные переменные за пределами их области.
- Какие функции не включают локальные переменные за пределами их сферы действия.
С учетом этой информации, локальные переменные теперь разделены на две группы: чистых локальных переменных, и захватили локальные переменные. Я буду называть этих «чистых местных жителей» и «захваченных местных жителей».
Чистые местные жители, за отсутствием лучшего термина, регистрируются. Когда вы компилируете свой байтовый код, чистые местные жители просты в обращении. Это конкретные индексы стека, или они являются конкретными именами регистра. Однако вы выполняете управление стеком, чистым местным жителям назначаются фиксированные местоположения в определенной области. Если вы обладаете силой JIT, тогда они станут регистрами или ближайшим к этому возможным.
Самое первое, что вам нужно понять о захваченных местных жителей, это: они должны управляться вашим менеджером памяти. Они существуют независимо от текущего стека вызовов и области видимости, поэтому они должны быть отдельно стоящими объектами, которые являются , на которые ссылаются функциями, которые их фиксируют. Это позволяет нескольким функциям фиксировать одни и те же локальные и, следовательно, ссылаться на другие личные данные.
Поэтому, когда вы вводите область, содержащую захваченные lambdas, вы будете выделять кусочек памяти, который содержит все захваченные местные жители, которые являются частью этой конкретной области. Например:
comp(threshold)
{
local data;
return lambda(x)
{
return x < (threshold + data);
};
}
Корневой сфера применения функции comp
имеет два локальных переменных. Оба они захвачены. Следовательно, количество захваченных локалей равно 2, а число чистых локалей равно нулю.
Таким образом, ваш компилятор (в байтовый код) будет выделять 0 регистров/стековых переменных для чистых локалей и будет выделять отдельно стоящий объект, который содержит две переменные. Предполагая, что вы используете сбор мусора, вам понадобится что-то, чтобы ссылаться на него, чтобы оно продолжало жить. Это легко: вы ссылаетесь на него в регистре/стеке, которое напрямую не доступно скрипту. Итак, вы действительно выделяете переменную register/stack, но скрипт не может напрямую коснуться ее.
Теперь давайте посмотрим, что делает lambda
. Он создает функцию. Опять же, мы знаем, что эта функция захватывает некоторые переменные за пределами ее области. И мы знаем которые переменные захватываются. Мы видим, что он захватывает две переменные, но мы также видим, что эти две переменные происходят из одного и того же свободностоящего блока памяти.
Итак, что такое lambda
, это создать объект функции, который имеет ссылку на некоторый байт-код и ссылку на переменные, с которыми он связан. Байт-код будет использовать эту ссылку для получения своих захваченных переменных. Ваш компилятор знает, какие переменные являются чистыми локальными для функции (например, аргумент x
) и которые представляют собой локально захваченные локали (например, порог). Таким образом, он может выяснить, как получить доступ к каждой переменной.
Теперь, когда lambda
завершает работу, он возвращает объект функции. На данный момент захваченные переменные ссылаются на две вещи: лямбда-функцию и стек : текущая область действия функции. Однако, когда return
заканчивается, текущая область действия уничтожается, и все вещи, на которые ранее ссылались, больше не ссылаются. Поэтому, когда он возвращает объект функции, только функция лямбда имеет ссылку на захваченные переменные.
Это все довольно сложно.Более простая реализация будет заключаться в том, чтобы просто сделать все локальные переменные эффективно захваченными; все локальные переменные захватываются местными жителями. Если вы это сделаете, то ваш компилятор может быть намного проще (и, вероятно, быстрее). Когда вводится новая область, все локали этой области выделяются в блоке памяти. Когда функция создается, она ссылается на все внешние области, которые она использует (если они есть). И когда объект выходит из него, он удаляет ссылку на локали, которые он выделил. Если никто не ссылается на него, тогда память может быть освобождена.
Это очень просто и просто.
Это не связано с gamedev и должно быть перенесено в stackoverflow. – bummzack
Вы должны каким-то образом зафиксировать контекст. C++ имеет два варианта: вы можете захватывать по ссылке (в этом случае лямбда тайно хранит указатель/ссылку в экземпляре и не может с пользой пережить область) или по значению (в этом случае лямбда-объект должен тайно хранить копировать в экземпляре). Ваш язык может разрешить третий вариант - независимо от того, что вы используете в качестве стека, если лямбда-объект может сохранить ссылку на стек стека, и если сами фреймы кадров собираются с мусором (или похожими), то наличие лямбда может продлить существование этой локальной переменной. –
Другим вариантом является устранение замыкания посредством преобразования CPS. Это то, что делают реализации Схемы (и это не трудно понять, прочитайте статьи Лямбды Ultimate XXX от Ги Льюиса Стил, создателя Схемы). Для этого вам понадобится сборка мусора, поскольку она соответствует третьей (стандартной) версии @SteveJessop. –