Как я могу улучшить возможности рекурсии моей реализации ECMAScript? - PullRequest
5 голосов
/ 24 мая 2010

После нескольких тестов я обнаружил, что моя реализация не может справиться с рекурсией. Хотя после того, как я провел несколько тестов в Firefox, я обнаружил, что это может быть более распространенным, чем я думал. Я считаю, что основная проблема заключается в том, что моя реализация требует 3 вызова для вызова функции. Первый вызов выполняется для метода с именем Call, который гарантирует, что вызов выполняется для вызываемого объекта, и получает значение любых аргументов, являющихся ссылками. Второй вызов выполняется для метода с именем Call, который определен в интерфейсе ICallable. Этот метод создает новый контекст выполнения и создает лямбда-выражение, если оно не было создано. Последний вызов сделан для лямбды, которую инкапсулирует объект функции. Ясно, что вызов функции довольно сложен, но я уверен, что с помощью небольшой настройки я смогу сделать рекурсию жизнеспособным инструментом при использовании этой реализации.

public static object Call(ExecutionContext context, object value, object[] args)
{
    var func = Reference.GetValue(value) as ICallable;
    if (func == null)
    {
        throw new TypeException();
    }
    if (args != null && args.Length > 0)
    {
        for (int i = 0; i < args.Length; i++)
        {
            args[i] = Reference.GetValue(args[i]);
        }
    }
    var reference = value as Reference;
    if (reference != null)
    {
        if (reference.IsProperty)
        {
            return func.Call(reference.Value, args);
        }
        else
        {
            return func.Call(((EnviromentRecord)reference.Value).ImplicitThisValue(), args);
        }
    }
    return func.Call(Undefined.Value, args);
}

public object Call(object thisObject, object[] arguments)
{
    var lexicalEnviroment = Scope.NewDeclarativeEnviroment();
    var variableEnviroment = Scope.NewDeclarativeEnviroment();
    var thisBinding = thisObject ?? Engine.GlobalEnviroment.GlobalObject;
    var newContext = new ExecutionContext(Engine, lexicalEnviroment, variableEnviroment, thisBinding);
    Engine.EnterContext(newContext);
    var result = Function.Value(newContext, arguments);
    Engine.LeaveContext();
    return result;
}

1 Ответ

2 голосов
/ 24 мая 2010

Я не могу поверить, как легко было работать. В основном в моем компиляторе я проверяю, возвращает ли функция результат вызова самой себя. Если это так, я вместо этого возвращаю аргументы, которые передаются. Затем я просто беру любые ссылочные значения и повторно вызываю лямбда-поддержку. Благодаря этому я смог сделать миллионы рекурсивных звонков.

Я хотел бы поблагодарить DrJokepu за вдохновляющее решение.

public object Call(object thisObject, object[] arguments)
{
    var lexicalEnviroment = Scope.NewDeclarativeEnviroment();
    var variableEnviroment = Scope.NewDeclarativeEnviroment();
    var thisBinding = thisObject ?? Engine.GlobalEnviroment.GlobalObject;
    var newContext = new ExecutionContext(Engine, lexicalEnviroment, variableEnviroment, thisBinding);
    var result = default(object);
    var callArgs = default(object[]);

    Engine.EnterContext(newContext);
    while (true)
    {
        result = Function.Value(newContext, arguments);
        callArgs = result as object[];
        if (callArgs == null)
        {
            break;
        }
        for (int i = 0; i < callArgs.Length; i++)
        {
            callArgs[i] = Reference.GetValue(callArgs[i]);
        }
        arguments = callArgs;
    }
    Engine.LeaveContext();

    return result;
}
...