public void RewindForTailCall(ErlangStackFrame newFrame) { bool foundFrame = false; int offset = 0; foreach (var frame in stack) { if (ErlangStackFrame.IsTailCallCandidate(frame, newFrame)) { foundFrame = true; break; } offset++; } Debug.Assert(foundFrame); // frame should have been found due to IsTailcallCandidateOnStack() for (int i = 0; i <= offset; i++) { stack.Pop(); } stack.Push(newFrame); }
public override ErlangValue Evaluate(ErlangProcess process) { ErlangValue last = null; ErlangExpressionBlockExpression def = this; ErlangCompiledModule module = GetModule(); var children = def.GetChildren(); bool doTailCall = false; do { doTailCall = false; // ensure we're reset from the last loop // evaluate each expression Debug.Assert(children.Length > 0); for (int i = 0; !doTailCall && i < children.Length; i++) { var expression = children[i]; string moduleName = null; ErlangFunctionInvocationExpression function = null; ErlangStackFrame tailCallCandidate = null; if (UseTailCalls && i == children.Length - 1 && expression is ErlangFunctionInvocationExpression && expression.IsLastChild) { // if last expression and it's a function invocation function = (ErlangFunctionInvocationExpression)expression; moduleName = function.Module ?? module.Name; tailCallCandidate = process.CallStack.GetTailCallCandidate( moduleName, function.Function, function.Parameters.Length); doTailCall = tailCallCandidate != null; } if (doTailCall) { // evaluate parameters var evaledParams = new ErlangValue[function.Parameters.Length]; for (int j = 0; j < function.Parameters.Length; j++) { var value = function.Parameters[j].Evaluate(process); if (value.Kind == ErlangValueKind.Error) { return(value); } evaledParams[j] = value; } // prepare new frame var newFrame = new ErlangStackFrame(tailCallCandidate.Module, tailCallCandidate.Function, tailCallCandidate.Airity); process.CallStack.RewindForTailCall(newFrame); // find the new definition var group = module.GetFunction(function.Function, evaledParams.Length); def = group.GetFunctionOverload(process, evaledParams); if (def == null) { return(new ErlangAtom("no_such_tailcall_function")); } children = def.GetChildren(); } else { // not a tailcall, just invoke normally last = children[i].Evaluate(process); if (last.Kind == ErlangValueKind.Error) { return(last); // decrease scope? } } } } while (doTailCall); return(last); }
public static bool TryBindParameter(ErlangExpression expression, ErlangValue value, ErlangStackFrame frame, bool bindBinary = false) { var type = expression.GetType(); if (bindBinary && type == typeof(ErlangBinaryExpression)) { var bin = (ErlangBinaryExpression)expression; switch (bin.Operator) { case ErlangOperatorKind.Equals: // if the right is a variable, bind to the left then to the right if (bin.Right is ErlangVariableExpression) { if (TryBindParameter(bin.Left, value, frame)) { return(TryBindParameter(bin.Right, value, frame)); } } break; // TODO: bind list concatenation '++', etc. } return(false); } else if (type == typeof(ErlangVariableExpression)) { var variable = (ErlangVariableExpression)expression; if (variable.Variable == "_") { // always matches, never binds return(true); } else { var current = frame.GetVariable(variable.Variable); if (current == null) { // set the value frame.SetVariable(variable.Variable, value); return(true); } else { // ensure the same value return(current.Equals(value)); } } } else if (type == typeof(ErlangAtomExpression) && value.Kind == ErlangValueKind.Atom) { return(((ErlangAtomExpression)expression).Atom == ((ErlangAtom)value).Name); } else if (type == typeof(ErlangConstantExpression) && value.Kind == ErlangValueKind.Number) { return(((ErlangConstantExpression)expression).Value == (ErlangNumber)value); } else if (type == typeof(ErlangTupleExpression) && value.Kind == ErlangValueKind.Tuple) { var tuple1 = (ErlangTupleExpression)expression; var tuple2 = (ErlangTuple)value; if (tuple1.Elements.Length == tuple2.Airity) { for (int j = 0; j < tuple1.Elements.Length; j++) { if (!TryBindParameter(tuple1.Elements[j], tuple2.Values[j], frame)) { return(false); } } return(true); } } else if (type == typeof(ErlangListExpression) && value.Kind == ErlangValueKind.List) { // TODO: support list comprehensions var expressionList = (ErlangListExpression)expression; var ErlangList = (ErlangList)value; if (ErlangList.Value == null) { // if the Erlang list is empty, the expression list must be, too return(expressionList.Elements.Length == 0 && expressionList.Tail == null); } // gather Erlang list values var head = ErlangList; int i; for (i = 0; i < expressionList.Elements.Length && head != null && head.Value != null; i++) { if (!TryBindParameter(expressionList.Elements[i], head.Value, frame)) { return(false); } if (head.Tail != null && head.Tail.Kind == ErlangValueKind.List) { head = (ErlangList)head.Tail; } else { head = null; } } if (expressionList.Elements.Length > 0 && i < expressionList.Elements.Length) { // didn't make it through the expression list return(false); } // expressionList.Tail == null and head.Value == null matches if (expressionList.Tail == null) { if (head == null || head.Value == null) { return(true); } else { return(false); } } else { if (head == null) { return(false); } else { return(TryBindParameter(expressionList.Tail, head, frame)); } } } return(false); }
internal static bool IsTailCallCandidate(ErlangStackFrame a, ErlangStackFrame b) { return(a.Module == b.Module && a.Function == b.Function && a.Airity == b.Airity); }
public void Push(ErlangStackFrame frame) { stack.Push(frame); }