Explorar o código

Change from a recursive execution model to a hybrid iterative execution model

Bepsi %!s(int64=6) %!d(string=hai) anos
pai
achega
110c17aa65
Modificáronse 2 ficheiros con 80 adicións e 43 borrados
  1. 59 43
      NTERA.Engine/Runtime/EraRuntime.cs
  2. 21 0
      NTERA.Engine/Runtime/StackFrame.cs

+ 59 - 43
NTERA.Engine/Runtime/EraRuntime.cs

@@ -17,11 +17,13 @@ namespace NTERA.Engine.Runtime
 		public IConsole Console { get; protected set; }
 
 		public Stack<StackFrame> ExecutionStack { get; } = new Stack<StackFrame>();
+		public Stack<ExecutionResult> ExecutionResultStack { get; } = new Stack<ExecutionResult>();
 
-		public List<FunctionDefinition> TotalProcedureDefinitions { get; set; } = new List<FunctionDefinition>(BaseDefinitions.DefaultGlobalFunctions);
-		public Dictionary<string, Variable> GlobalVariables { get; set; } = new Dictionary<string, Variable>();
+		public List<FunctionDefinition> TotalProcedureDefinitions { get; } = new List<FunctionDefinition>(BaseDefinitions.DefaultGlobalFunctions);
+		public Dictionary<string, Variable> GlobalVariables { get; } = new Dictionary<string, Variable>();
 
-		public Value LastInputValue { get; set; }
+
+		public Value LastInputValue { get; protected set; }
 		public AutoResetEvent InputResetEvent { get; } = new AutoResetEvent(false);
 
 		public EraRuntime(IExecutionProvider executionProvider)
@@ -33,8 +35,10 @@ namespace NTERA.Engine.Runtime
 
 			foreach (var variable in BaseDefinitions.DefaultGlobalVariables)
 			{
-				var globalVariable = new Variable(variable.Name, variable.ValueType);
-				globalVariable[0] = variable.CalculatedValue;
+				var globalVariable = new Variable(variable.Name, variable.ValueType)
+				{
+					[0] = variable.CalculatedValue
+				};
 
 				GlobalVariables.Add(variable.Name, globalVariable);
 			}
@@ -54,6 +58,11 @@ namespace NTERA.Engine.Runtime
 			try
 			{
 				Call(ExecutionProvider.DefinedProcedures.First(x => x.Name == "SYSTEM_TITLE"));
+
+				while (ExecutionStack.Count > 0)
+				{
+					ExecuteSet();
+				}
 			}
 			catch (Exception ex)
 			{
@@ -69,7 +78,7 @@ namespace NTERA.Engine.Runtime
 			Thread.Sleep(-1);
 		}
 
-		public ExecutionResult Call(FunctionDefinition function, IList<Parameter> parameters = null)
+		public void Call(FunctionDefinition function, IList<Parameter> parameters = null)
 		{
 			var localVariables = new Dictionary<string, Variable>();
 
@@ -94,15 +103,11 @@ namespace NTERA.Engine.Runtime
 				Variables = localVariables
 			};
 
-			ExecutionResult result;
-
-			ExecutionStack.Push(newContext);
-
 			if (function.Filename == "__GLOBAL")
 			{
 				var resultValue = Functions.StaticFunctions[function.Name].Invoke(this, newContext, parameters);
 
-				result = new ExecutionResult(ExecutionResultType.FunctionReturn, resultValue);
+				ExecutionResultStack.Push(new ExecutionResult(ExecutionResultType.FunctionReturn, resultValue));
 			}
 			else
 			{
@@ -137,49 +142,56 @@ namespace NTERA.Engine.Runtime
 					}
 				}
 
-				var executionContents = ExecutionProvider.GetExecutionNodes(function);
-
-				result = ExecuteSet(newContext, executionContents.ToList());
-			}
 
-			ExecutionStack.Pop();
+				newContext.ExecutionNodes = ExecutionProvider.GetExecutionNodes(function).ToList();
 
-			return result;
+				ExecutionStack.Push(newContext);
+			}
 		}
 
-		public ExecutionResult ExecuteSet(StackFrame context, IList<ExecutionNode> nodes)
+		public void ExecuteSet()
 		{
-			for (int index = 0; index < nodes.Count; index++)
+			var context = ExecutionStack.Peek();
+
+			if (context.ExecutionIndex >= context.ExecutionNodes.Count)
 			{
-				ExecutionNode node = nodes[index];
-				
-				if (node.Type == "for")
-				{
-					ExecutionNode forContext = node[0];
+				ExecutionStack.Pop();
 
-					var iterationVariable = ComputeVariable(context, forContext[0], out var iterationIndex);
+				if (!context.IsAnonymous && context.SelfDefinition.IsReturnFunction)
+					throw new EraRuntimeException("Function did not return a value");
 
-					var beginNumber = ComputeExpression(context, forContext[1]);
-					var endNumber = ComputeExpression(context, forContext[2]);
+				return;
+			}
 
-					for (iterationVariable[iterationIndex] = beginNumber.Real; iterationVariable[iterationIndex].Real < endNumber.Real; iterationVariable[iterationIndex]++)
-					{
-						var subset = node.Skip(1).ToList();
-						ExecuteSet(context, subset);
-					}
+			ExecutionNode node = context.ExecutionNodes[context.ExecutionIndex++];
+			
+			if (node.Type == "for")
+			{
+				ExecutionNode forContext = node[0];
 
-					continue;
-				}
+				var iterationVariable = ComputeVariable(context, forContext[0], out var iterationIndex);
 
-				if (node.Type == "result")
+				var beginNumber = ComputeExpression(context, forContext[1]);
+				var endNumber = ComputeExpression(context, forContext[2]);
+
+				for (iterationVariable[iterationIndex] = beginNumber.Real; iterationVariable[iterationIndex].Real < endNumber.Real; iterationVariable[iterationIndex]++)
 				{
-					return new ExecutionResult(ExecutionResultType.FunctionReturn, ComputeExpression(context, node.Single()));
+					var subset = node.Skip(1).ToList();
+
+					ExecutionStack.Push(context.Clone(subset));
 				}
 
-				ExecuteNode(context, node);
+				return;
+			}
+
+			if (node.Type == "result")
+			{
+				ExecutionResultStack.Push(new ExecutionResult(ExecutionResultType.FunctionReturn, ComputeExpression(context, node.Single())));
+
+				return;
 			}
 
-			return ExecutionResult.None;
+			ExecuteNode(context, node);
 		}
 
 		public void ExecuteNode(StackFrame context, ExecutionNode node)
@@ -210,10 +222,7 @@ namespace NTERA.Engine.Runtime
 					if (procedure == null)
 						throw new EraRuntimeException($"Unknown procedure: '{procedureName}'");
 
-					var executionResult = Call(procedure, node.GetSubtype("parameters").Select(x => ComputeParameter(context, x)).ToArray());
-
-					if (executionResult.Type != ExecutionResultType.None || executionResult.Result.HasValue)
-						throw new EraRuntimeException($"Unexpected result from procedure '{procedureName}': {executionResult.Type}");
+					Call(procedure, node.GetSubtype("parameters").Select(x => ComputeParameter(context, x)).ToArray());
 
 					return;
 
@@ -283,7 +292,14 @@ namespace NTERA.Engine.Runtime
 					if (function == null)
 						throw new EraRuntimeException($"Unknown function: '{functionName}'");
 
-					var executionResult = Call(function, expressionNode.GetSubtype("parameters").Select(x => ComputeParameter(context, x)).ToArray());
+					int currentStackLevel = ExecutionStack.Count;
+
+					Call(function, expressionNode.GetSubtype("parameters").Select(x => ComputeParameter(context, x)).ToArray());
+
+					while (ExecutionStack.Count > currentStackLevel)
+						ExecuteSet();
+
+					var executionResult = ExecutionResultStack.Pop();
 
 					if (executionResult.Type != ExecutionResultType.FunctionReturn || !executionResult.Result.HasValue)
 						throw new EraRuntimeException($"Unexpected result from function '{functionName}': {executionResult.Type}");

+ 21 - 0
NTERA.Engine/Runtime/StackFrame.cs

@@ -10,17 +10,38 @@ namespace NTERA.Engine.Runtime
 		public List<Parameter> Parameters { get; set; }
 
 		public FunctionDefinition SelfDefinition { get; set; }
+
+		public bool IsAnonymous { get; set; }
+
+		public int ExecutionIndex { get; set; }
+
+		public IList<ExecutionNode> ExecutionNodes { get; set; }
+
+		public StackFrame Clone(IList<ExecutionNode> nodes, bool anonymous = true)
+		{
+			return new StackFrame
+			{
+				Variables = Variables,
+				Parameters = Parameters,
+				SelfDefinition = SelfDefinition,
+				IsAnonymous = anonymous,
+				ExecutionIndex = 0,
+				ExecutionNodes = nodes
+			};
+		}
 	}
 
 	public enum ExecutionResultType
 	{
 		None,
+		AnonymousCall,
 		FunctionReturn
 	}
 
 	public class ExecutionResult
 	{
 		public static ExecutionResult None { get; } = new ExecutionResult(ExecutionResultType.None);
+		public static ExecutionResult AnonymousCall { get; } = new ExecutionResult(ExecutionResultType.AnonymousCall);
 
 		public ExecutionResultType Type { get; }