Как языки программирования определяют и сохраняют функции / методы? Я создаю интерпретируемый язык программирования в Ruby и пытаюсь понять, как реализовать объявление функций.
Моя первая идея - сохранить содержание декларации на карте. Например, если я сделал что-то вроде
def a() {
callSomething();
x += 5;
}
Тогда я бы добавил запись в мою карту:
{
'a' => 'callSomething(); x += 5;'
}
Проблема в том, что он станет рекурсивным, потому что мне придется вызывать мой parse
метод для строки, который затем будет вызывать parse
снова, когда он встретится doSomething
, и тогда мне в конечном итоге не хватит места в стеке.
Итак, как интерпретированные языки справляются с этим?
programming-languages
language-design
functions
methods
Дверная ручка
источник
источник
Ответы:
Буду ли я прав, если предположим, что ваша функция "parse" не только анализирует код, но и выполняет его одновременно? Если вы хотите сделать это таким образом, вместо сохранения содержимого функции на карте сохраните местоположение функции.
Но есть и лучший способ. Это требует немного больше усилий, но с ростом сложности дает гораздо лучшие результаты: используйте абстрактное синтаксическое дерево.
Основная идея заключается в том, что вы анализируете код только один раз, когда-либо. Затем у вас есть набор типов данных, представляющих операции и значения, и вы создаете их дерево, например:
будет выглядеть так:
(Это просто текстовое представление структуры гипотетического AST. Фактическое дерево, вероятно, не будет в текстовой форме.) В любом случае, вы анализируете свой код в AST, а затем либо запускаете свой интерпретатор через AST, или используйте второй проход («генерация кода»), чтобы превратить AST в некоторую форму вывода.
В случае с вашим языком, вероятно, вам нужно иметь карту, которая отображает имена функций в функции AST, а не имена функций в строки функций.
источник
(((((((((((((((( x )))))))))))))))))
. На самом деле стеки могут быть намного больше, а грамматическая сложность реального кода довольно ограничена. Конечно, если этот код должен быть читаемым человеком.Вы не должны вызывать parse при просмотре
callSomething()
(я полагаю, вы имели в виду,callSomething
а неdoSomething
). Разница междуa
иcallSomething
заключается в том, что один является определением метода, а другой - вызовом метода.Когда вы увидите новое определение, вы захотите выполнить проверки, связанные с тем, чтобы убедиться, что вы можете добавить это определение, поэтому:
Предполагая, что эти проверки пройдены, вы можете добавить их на свою карту и начать проверку содержимого этого метода.
Когда вы находите вызов метода как
callSomething()
, вы должны выполнить следующие проверки:callSomething
на вашей карте?Если вы обнаружите, что
callSomething()
это нормально, то в данный момент то, что вы хотели бы сделать, зависит от того, как вы хотите к нему подойти. Строго говоря, если вы знаете, что такой вызов в данный момент подходит, вы можете только сохранить имя метода и аргументы, не вдаваясь в подробности. Когда вы запустите свою программу, вы вызовете метод с аргументами, которые вы должны иметь во время выполнения.Если вы хотите пойти дальше, вы можете сохранить не только строку, но и ссылку на фактический метод. Это было бы более эффективно, но если вам нужно управлять памятью, это может сбить с толку. Я бы порекомендовал вам просто держаться за строку сначала. Позже вы можете попытаться оптимизировать.
Обратите внимание, что все это предполагает, что вы выполнили лексизм в своей программе, что означает, что вы распознаете все токены в своей программе и знаете, что они собой представляют . Это не значит, что вы знаете, имеют ли они смысл вместе, что является фазой анализа. Если вы еще не знаете, что такое токены, я советую вам сначала сосредоточиться на получении этой информации.
Надеюсь, это поможет! Добро пожаловать в Программисты SE!
источник
Читая ваш пост, я заметил два вопроса в вашем вопросе. Самый важный из них - как разобрать. Есть много видов анализаторов (например , метод рекурсивного спуска , LR парсеры , Packrat парсеры ) и синтаксический анализатор генераторов (например , GNU Зубр , ANTLR ) , вы можете использовать для обхода текстовой программы «рекурсивно» задан (явно или неявно) грамматики.
Второй вопрос касается формата хранения функций. Когда вы не выполняете синтаксически-ориентированный перевод , вы создаете промежуточное представление вашей программы, которое может быть абстрактным синтаксическим деревом или каким-либо пользовательским промежуточным языком, для дальнейшей обработки с ним (компиляция, преобразование, выполнение, запись в файл и т. д.).
источник
С общей точки зрения, определение функции - это немного больше, чем просто метка или закладка в коде. Большинство других операторов цикла, области видимости и условных операторов аналогичны; они заменяют базовую команду «перейти» или «перейти» на нижних уровнях абстракции. Вызов функции в основном сводится к следующим низкоуровневым компьютерным командам:
«Возвратный» оператор или аналог будет делать следующее:
Следовательно, функции - это просто абстракции в спецификации языка более высокого уровня, которые позволяют людям организовывать код более понятным и интуитивно понятным способом. При компиляции в ассемблер или промежуточный язык (JIL, MSIL, ILX) и, безусловно, при визуализации в виде машинного кода, почти все такие абстракции исчезают.
источник