Я знаю, что вы создаете динамический язык, но я думаю, что те же принципы применимы и к нединамическому языку - у вас все еще есть таблица символов, и вам все еще нужно обрабатывать источник за несколько проходов.
Если вы создаете семантическое дерево до фазы генерации кода, это легко. Вызов функции указывает на объект (узел), который будет содержать семантическое определение для функции. Или это просто узел, который говорит (семантически) вызвать эту функцию. Поскольку при вызове функции не нужно знать, что содержит функция, просто работает указатель на запись в таблице символов.
Если вы выполняете оптимизацию (рекурсию хвостовой части?), То вам нужно выполнить два прохода, прежде чем проанализировать ее для этого типа оптимизации. Это нормально для всех компиляторов, которые я видел, так как этот этап происходит после семантического / лексического анализа.
Полагаю, диаграмма в этой статье хорошо показывает то, о чем я говорю (однако у нее есть дополнительный бит двух разных языков ввода.)