Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I improve the recursion capabilities of my ECMAScript implementation?

After some resent tests I have found my implementation cannot handle very much recursion. Although after I ran a few tests in Firefox I found that this may be more common than I originally thought. I believe the basic problem is that my implementation requires 3 calls to make a function call. The first call is made to a method named Call that makes sure the call is being made to a callable object and gets the value of any arguments that are references. The second call is made to a method named Call which is defined in the ICallable interface. This method creates the new execution context and builds the lambda expression if it has not been created. The final call is made to the lambda that the function object encapsulates. Clearly making a function call is quite heavy but I am sure that with a little bit of tweaking I can make recursion a viable tool when using this implementation.

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;
}
like image 701
ChaosPandion Avatar asked Nov 05 '22 13:11

ChaosPandion


1 Answers

I cannot believe how easy this was to get working. Basically in my compiler I check to see if the function is returning the result of calling itself. If so I instead return the arguments that are being passed. Then I simply grab any reference values and re-invoke the backing lambda. With this in place I was able to make millions of recursive calls.

I would like to thank DrJokepu for inspiring this solution.

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;
}
like image 90
ChaosPandion Avatar answered Nov 14 '22 22:11

ChaosPandion