Я делаю переводчик Lisp в JavaScript. JS не имеет устранение хвост рекурсии (TRE), поэтому я реализовал TRE, используя в то время как цикл в JS (псевдокод):Программируемая исключение неконцевой рекурсии
function eval (exp, env)
while true
if exp is self evaluating
return exp
else if ...
...
else if exp is a function call
procedure = eval(car(exp), env)
arguments = eval_operands(cdr(exp), env)
exp = procedure.body
env = extend_env(procedure.env, env)
continue # tail call
Так что я рад, и хвост рекурсивные функции, такие как следующий один не кончатся стека :
(define +
(lambda (n m)
(cond ((zero? n) m)
(else (+ (sub1 n) (add1 m))))))
(+ 10000 1) ;=> 10001
Однако функции, которые не являются хвостовой рекурсией, бегите из стека JS (потому что JS код рецидивирует слишком много на eval_operands
):
(define +
(lambda (n m)
(cond ((zero? n) m)
(else (add1 (+ (sub1 n) m))))) ; not proper tail-recursive
(+ 10000 1) ;=> JS: Maximum call stack size exceeded
Как я имею дело с не-таи l-рекурсивные функции? Каковы варианты? Какие хорошие ресурсы? Я немного прочитал о батуте, стиле экстернализации стека и продолжении прохождения, но все, что я мог найти, - это написать код в этих стилях, а не как использовать эти методы для написания интерпретатора.
Поскольку вы внедряете интерпретатор, производительность не должна быть проблемой для вас, поэтому вы можете сначала выполнить CPS-преобразование (и тогда все ваши вызовы будут выполняться с помощью хвоста). Интерпретировать код CPSed легко, просто используйте связанные списки для прохождения продолжений. –
Не забудьте прочитать проблему FUNARG. :) –