Forráskód Böngészése

Fix unary expression handling

Bepsi 6 éve
szülő
commit
6af435ddb1

+ 13 - 6
NTERA.Engine/Compiler/Lexer.cs

@@ -128,6 +128,7 @@ namespace NTERA.Engine.Compiler
 		private Token DetermineToken(bool peek, bool useCurrent)
 		{
 			char c = useCurrent ? CurrentChar : GetNextChar(peek);
+			char adv;
 
 			if (TokenCharDictionary.TryGetValue(c, out Token charToken))
 				return charToken;
@@ -238,16 +239,22 @@ namespace NTERA.Engine.Compiler
 						return Token.More;
 
 				case '+':
-					if (peek)
-						GetNextChar();
+					adv = !peek && !IsPeeking
+						? GetNextChar(true)
+						: source[sourceMarker.Pointer + 2];
 
-					if (GetNextChar(true) == '+')
+					if (adv == '=')
+					{
+						GetNextChar();
+						return Token.Append;
+					}
+					else if (adv == '+')
 					{
 						GetNextChar();
 						return Token.Increment;
 					}
-					else
-						return Token.Plus;
+
+					return Token.Plus;
 
 				case '-':
 					if (peek)
@@ -262,7 +269,7 @@ namespace NTERA.Engine.Compiler
 						return Token.Minus;
 
 				case '=':
-					char adv = !peek && !IsPeeking
+					adv = !peek && !IsPeeking
 						? GetNextChar(true)
 						: source[sourceMarker.Pointer + 2];
 

+ 75 - 2
NTERA.Engine/Compiler/Parser.cs

@@ -106,9 +106,14 @@ namespace NTERA.Engine.Compiler
 			errors = Errors;
 			warnings = Warnings;
 
+			if (errors.Count == 0)
+				PostProcess(nodes);
+
 			return nodes;
 		}
 
+		#region Processor
+
 		protected ExecutionNode ParseLine(out ParserError error)
 		{
 			error = null;
@@ -143,6 +148,7 @@ namespace NTERA.Engine.Compiler
 						if (GetNextToken() != Token.Equal
 							&& Enumerator.Current != Token.Increment
 							&& Enumerator.Current != Token.Decrement
+							&& Enumerator.Current != Token.Append
 							&& !Enumerator.Current.IsArithmetic())
 						{
 							error = new ParserError($"Unexpected token, expecting assignment: {Enumerator.Current}", CurrentPosition);
@@ -159,6 +165,12 @@ namespace NTERA.Engine.Compiler
 						{
 							value = OperateNodes(variable, CreateConstant(1, CurrentPosition), Token.Minus);
 						}
+						else if (Enumerator.Current == Token.Append)
+						{
+							value = OperateNodes(variable, Expression(out error), Token.Plus);
+							if (error != null)
+								return null;
+						}
 						else if (Enumerator.Current != Token.Equal)
 						{
 							Token arithmeticToken = Enumerator.Current;
@@ -702,7 +714,7 @@ namespace NTERA.Engine.Compiler
 
 				Token op = operators.Pop();
 
-				if (op.IsUnary() && operands.Count >= 1)
+				if (op.IsUnary() && operands.Count == 1)
 				{
 					var operand = operands.Pop();
 
@@ -811,7 +823,7 @@ namespace NTERA.Engine.Compiler
 				}
 				else if (token.IsArithmetic())
 				{
-					if (token.IsUnary())
+					if (!operands.Any() && token.IsUnary())
 					{
 						operators.Push(token);
 						continue;
@@ -1084,5 +1096,66 @@ namespace NTERA.Engine.Compiler
 				}
 			};
 		}
+
+		#endregion
+
+		#region Post-processor
+
+		protected void PostProcess(List<ExecutionNode> nodes)
+		{
+			Branchify(nodes);
+		}
+
+		protected void Branchify(List<ExecutionNode> nodes)
+		{
+			Stack<ExecutionNode> workingStack = new Stack<ExecutionNode>();
+
+			foreach (var node in nodes)
+			{
+				if (node.Type == "statement" && node["name"].Equals("FOR", StringComparison.OrdinalIgnoreCase))
+				{
+					workingStack.Push(node);
+				}
+			}
+
+			foreach (var forNode in workingStack)
+			{
+				int index = nodes.IndexOf(forNode);
+
+				int endIndex = 0;
+
+				for (int i = index; i < nodes.Count; i++)
+				{
+					var node = nodes[i];
+					if (node.Type == "statement" && node["name"].Equals("NEXT", StringComparison.OrdinalIgnoreCase))
+					{
+						endIndex = i;
+						break;
+					}
+				}
+
+				if (endIndex == 0)
+					throw new ParserException("Could not find matching NEXT for FOR statement");
+
+				List<ExecutionNode> subNodes = new List<ExecutionNode>();
+
+				forNode.Type = "for-context";
+				subNodes.Add(forNode);
+
+				subNodes.AddRange(nodes.Skip(index + 1).Take(endIndex - index - 1));
+
+				nodes.RemoveRange(index, (endIndex - index) + 1);
+
+				ExecutionNode newNode = new ExecutionNode
+				{
+					Type = "for",
+					SubNodes = subNodes.ToArray()
+				};
+
+				nodes.Insert(index, newNode);
+			}
+		}
+
+		#endregion
 	}
 }

+ 1 - 0
NTERA.Engine/Compiler/Preprocessor.cs

@@ -142,6 +142,7 @@ namespace NTERA.Engine.Compiler
 					isReturnFunction = false;
 					currentDefinitionName = null;
 					currentDefinitionParameters.Clear();
+					currentDefinitionVariables.Clear();
 				}
 			}
 

+ 1 - 0
NTERA.Engine/Compiler/Token.cs

@@ -58,6 +58,7 @@
 
 		Plus,
 		Increment,
+		Append,
 		Minus,
 		Decrement,
 

+ 13 - 10
NTERA.Engine/Runtime/Base/Functions.cs

@@ -16,11 +16,11 @@ namespace NTERA.Engine.Runtime.Base
 			}
 		}
 
-		public static Dictionary<string, Func<EraRuntime, StackFrame, IList<Value>, Value>> StaticFunctions { get; } = _getFunctions();
+		public static Dictionary<string, Func<EraRuntime, StackFrame, IList<Parameter>, Value>> StaticFunctions { get; } = _getFunctions();
 
-		private static Dictionary<string, Func<EraRuntime, StackFrame, IList<Value>, Value>> _getFunctions()
+		private static Dictionary<string, Func<EraRuntime, StackFrame, IList<Parameter>, Value>> _getFunctions()
 		{
-			var output = new Dictionary<string, Func<EraRuntime, StackFrame, IList<Value>, Value>>();
+			var output = new Dictionary<string, Func<EraRuntime, StackFrame, IList<Parameter>, Value>>();
 
 			foreach (MethodInfo method in typeof(Functions).GetMethods(BindingFlags.Public | BindingFlags.Static))
 			{
@@ -34,22 +34,25 @@ namespace NTERA.Engine.Runtime.Base
 		}
 
 		[Function("__FORMAT")]
-		public static Value Format(EraRuntime runtime, StackFrame context, IList<Value> parameters)
+		public static Value Format(EraRuntime runtime, StackFrame context, IList<Parameter> parameters)
 		{
 			if (parameters.Count == 1)
-				return parameters[0].String;
+				return parameters[0].Value.String;
 
-			throw new NotImplementedException("Advanced formatting hasn't been implemented yet");
+			if (parameters.Count >= 3)
+				throw new NotImplementedException("Advanced formatting hasn't been implemented yet");
+
+			return parameters[0].Value.String.PadLeft((int)parameters[1].Value.Real);
 		}
 
 		[Function("TOSTR")]
-		public static Value ToStr(EraRuntime runtime, StackFrame context, IList<Value> parameters)
+		public static Value ToStr(EraRuntime runtime, StackFrame context, IList<Parameter> parameters)
 		{
 			if (parameters.Count == 1)
 				return parameters[0].ToString();
 
 			if (parameters.Count == 2)
-				return ((int)parameters[0]).ToString(parameters[1].String);
+				return ((int)parameters[0].Value).ToString(parameters[1].Value.String);
 
 			throw new EraRuntimeException("Invalid amount of parameters");
 		}
@@ -57,9 +60,9 @@ namespace NTERA.Engine.Runtime.Base
 		private static Random _random = new Random();
 
 		[Function("RAND")]
-		public static Value Rand(EraRuntime runtime, StackFrame context, IList<Value> parameters)
+		public static Value Rand(EraRuntime runtime, StackFrame context, IList<Parameter> parameters)
 		{
-			return _random.Next((int)parameters[0].Real, (int)parameters[1].Real);
+			return _random.Next((int)parameters[0].Value.Real, (int)parameters[1].Value.Real);
 		}
 	}
 }

+ 17 - 2
NTERA.Engine/Runtime/Base/Keywords.cs

@@ -55,6 +55,20 @@ namespace NTERA.Engine.Runtime.Base
 			variable[index] *= runtime.ComputeExpression(context, node[1]);
 		}
 
+		[Keyword("VARSIZE")]
+		public static void Varsize(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			Variable variable = runtime.ComputeVariable(context, node[0], out var index);
+
+			int maxArrayDimensions = variable.Max(x => x.Key.Length);
+			var resultVariable = runtime.GlobalVariables["RESULT"];
+
+			for (int i = 0; i < maxArrayDimensions; i++)
+			{
+				resultVariable[i] = variable.Where(x => x.Key.Length > i).Max(x => x.Key[i]) + 1;
+			}
+		}
+
 		[Keyword("ALIGNMENT")]
 		public static void Alignment(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{
@@ -135,7 +149,8 @@ namespace NTERA.Engine.Runtime.Base
 			runtime.Console.PrintHtml(htmlValue.String, false);
 		}
 
-		[Keyword("PRINT", true, true)]
+		[Keyword("PRINT", true, false)]
+		[Keyword("PRINTFORM", true, true)]
 		public static void Print(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{
 			var value = runtime.ComputeExpression(context, node.Single());
@@ -144,7 +159,7 @@ namespace NTERA.Engine.Runtime.Base
 		}
 
 		[Keyword("PRINTFORML", true, true)]
-		[Keyword("PRINTL", true, true)]
+		[Keyword("PRINTL", true, false)]
 		public static void PrintLine(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{
 			var value = runtime.ComputeExpression(context, node.Single());

+ 65 - 57
NTERA.Engine/Runtime/EraRuntime.cs

@@ -16,7 +16,9 @@ namespace NTERA.Engine.Runtime
 
 		public Stack<StackFrame> ExecutionStack { get; } = new Stack<StackFrame>();
 
-		protected List<FunctionDefinition> TotalProcedureDefinitions = new List<FunctionDefinition>(BaseDefinitions.DefaultGlobalFunctions);
+		public List<FunctionDefinition> TotalProcedureDefinitions { get; set; } = new List<FunctionDefinition>(BaseDefinitions.DefaultGlobalFunctions);
+		public Dictionary<string, Variable> GlobalVariables { get; set; } = new Dictionary<string, Variable>();
+		public Dictionary<string, Variable> SemiGlobalVariables { get; set; } = new Dictionary<string, Variable>();
 
 		public EraRuntime(IExecutionProvider executionProvider)
 		{
@@ -24,6 +26,14 @@ namespace NTERA.Engine.Runtime
 
 			TotalProcedureDefinitions.AddRange(ExecutionProvider.DefinedProcedures);
 			TotalProcedureDefinitions.AddRange(BaseDefinitions.DefaultGlobalFunctions);
+
+			foreach (var variable in BaseDefinitions.DefaultGlobalVariables)
+			{
+				var globalVariable = new Variable(variable.Name, variable.ValueType);
+				globalVariable[0] = variable.CalculatedValue;
+
+				GlobalVariables.Add(variable.Name, globalVariable);
+			}
 		}
 
 		public bool Initialize(IConsole console)
@@ -55,10 +65,9 @@ namespace NTERA.Engine.Runtime
 			System.Threading.Thread.Sleep(-1);
 		}
 
-		public ExecutionResult Call(FunctionDefinition function, IList<Value> parameters = null)
+		public ExecutionResult Call(FunctionDefinition function, IList<Parameter> parameters = null)
 		{
 			var localVariables = new Dictionary<string, Variable>();
-			var globalVariables = new Dictionary<string, Variable>();
 
 			foreach (var variable in function.Variables)
 			{
@@ -68,28 +77,24 @@ namespace NTERA.Engine.Runtime
 				localVariables.Add(variable.Name, localVariable);
 			}
 
-			foreach (var variable in BaseDefinitions.DefaultGlobalVariables)
+			foreach (var variable in GlobalVariables)
 			{
-				var globalVariable = new Variable(variable.Name, variable.ValueType);
-				globalVariable[0] = variable.CalculatedValue;
-
-				globalVariables.Add(variable.Name, globalVariable);
+				localVariables.Add(variable.Key, variable.Value);
 			}
 
-			var context = new StackFrame
+			var newContext = new StackFrame
 			{
 				SelfDefinition = function,
-				GlobalVariables = globalVariables,
-				LocalVariables = localVariables
+				Variables = localVariables
 			};
 
 			ExecutionResult result;
 
-			ExecutionStack.Push(context);
+			ExecutionStack.Push(newContext);
 
 			if (function.Filename == "__GLOBAL")
 			{
-				var resultValue = Base.Functions.StaticFunctions[function.Name].Invoke(this, context, parameters);
+				var resultValue = Base.Functions.StaticFunctions[function.Name].Invoke(this, newContext, parameters);
 
 				result = new ExecutionResult(ExecutionResultType.FunctionReturn, resultValue);
 			}
@@ -108,15 +113,27 @@ namespace NTERA.Engine.Runtime
 						else
 							throw new EraRuntimeException($"Unable to assign parameter #{index + 1}");
 
-						var paramVariable = ComputeVariable(context, parameter.Name);
-							
-						paramVariable[parameter.Index] = parameters[index];
+
+						var localVariable = function.Variables.FirstOrDefault(x => x.Name == parameter.Name);
+
+						if (localVariable != null && localVariable.VariableType.HasFlag(VariableType.Reference))
+						{
+							if (parameters[index].BackingVariable == null)
+								throw new EraRuntimeException("Expected a variable to pass through as REF");
+
+							newContext.Variables[localVariable.Name] = parameters[index].BackingVariable;
+						}
+						else
+						{
+							var paramVariable = ComputeVariable(newContext, parameter.Name);
+							paramVariable[parameter.Index] = parameters[index];
+						}
 					}
 				}
 
 				var executionContents = ExecutionProvider.GetExecutionNodes(function);
 
-				result = ExecuteSet(context, executionContents.ToList());
+				result = ExecuteSet(newContext, executionContents.ToList());
 			}
 
 			ExecutionStack.Pop();
@@ -129,47 +146,23 @@ namespace NTERA.Engine.Runtime
 			for (int index = 0; index < nodes.Count; index++)
 			{
 				ExecutionNode node = nodes[index];
-
-				if (node.Type == "statement")
+				
+				if (node.Type == "for")
 				{
-					if (node["name"].Equals("FOR", StringComparison.OrdinalIgnoreCase))
-					{
-						int endIndex = -1;
-						int forLevel = 0;
-
-						for (int subIndex = index + 1; subIndex < nodes.Count; subIndex++)
-						{
-							ExecutionNode subNode = nodes[subIndex];
-
-							if (subNode.Type != "statement")
-								continue;
-
-							if (subNode["name"].Equals("FOR", StringComparison.OrdinalIgnoreCase))
-								forLevel++;
+					ExecutionNode forContext = node[0];
 
-							if (subNode["name"].Equals("NEXT", StringComparison.OrdinalIgnoreCase))
-							{
-								if (forLevel == 0)
-								{
-									endIndex = subIndex;
-									break;
-								}
+					var iterationVariable = ComputeVariable(context, forContext[0], out var iterationIndex);
 
-								forLevel--;
-							}
-						}
-
-						if (endIndex == -1)
-							throw new EraRuntimeException("Could not find matching NEXT for FOR statement");
-
-						//string variableName = 
+					var beginNumber = ComputeExpression(context, forContext[1]);
+					var endNumber = ComputeExpression(context, forContext[2]);
 
-						//for (context.LocalVariables[])
-						ExecuteSet(context, nodes.Skip(index).Take(endIndex - index).ToArray());
-
-						index = endIndex;
-						continue;
+					for (iterationVariable[iterationIndex] = beginNumber.Real; iterationVariable[iterationIndex].Real < endNumber.Real; iterationVariable[iterationIndex]++)
+					{
+						var subset = node.Skip(1).ToList();
+						ExecuteSet(context, subset);
 					}
+
+					continue;
 				}
 
 				if (node.Type == "result")
@@ -211,7 +204,7 @@ namespace NTERA.Engine.Runtime
 					if (procedure == null)
 						throw new EraRuntimeException($"Unknown procedure: '{procedureName}'");
 
-					var executionResult = Call(procedure, node.GetSubtype("parameters").Select(x => ComputeExpression(context, x)).ToArray());
+					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}");
@@ -242,13 +235,25 @@ namespace NTERA.Engine.Runtime
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
 		public Variable ComputeVariable(StackFrame context, string variableName)
 		{
-			if (context.LocalVariables.TryGetValue(variableName, out var variable)
-				|| context.GlobalVariables.TryGetValue(variableName, out variable))
+			if (context.Variables.TryGetValue(variableName, out var variable))
 				return variable;
 
 			throw new EraRuntimeException($"Unable to retrieve variable '{variableName}'");
 		}
 
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
+		public Parameter ComputeParameter(StackFrame context, ExecutionNode variableNode)
+		{
+			if (variableNode.Type == "variable")
+			{
+				var variable = ComputeVariable(context, variableNode, out int[] index);
+
+				return new Parameter(variable[index], variable, index);
+			}
+
+			return new Parameter(ComputeExpression(context, variableNode));
+		}
+
 		public Value ComputeExpression(StackFrame context, ExecutionNode expressionNode)
 		{
 			switch (expressionNode.Type)
@@ -272,7 +277,7 @@ namespace NTERA.Engine.Runtime
 					if (function == null)
 						throw new EraRuntimeException($"Unknown function: '{functionName}'");
 
-					var executionResult = Call(function, expressionNode.GetSubtype("parameters").Select(x => ComputeExpression(context, x)).ToArray());
+					var executionResult = Call(function, expressionNode.GetSubtype("parameters").Select(x => ComputeParameter(context, x)).ToArray());
 
 					if (executionResult.Type != ExecutionResultType.FunctionReturn || !executionResult.Result.HasValue)
 						throw new EraRuntimeException($"Unexpected result from function '{functionName}': {executionResult.Type}");
@@ -293,6 +298,9 @@ namespace NTERA.Engine.Runtime
 						case "subtract":
 							operatorToken = Token.Minus;
 							break;
+						case "multiply":
+							operatorToken = Token.Asterisk;
+							break;
 						default: throw new EraRuntimeException($"Unknown operation type: '{operationType}'");
 					}
 

+ 22 - 2
NTERA.Engine/Runtime/StackFrame.cs

@@ -5,8 +5,9 @@ namespace NTERA.Engine.Runtime
 {
 	public class StackFrame
 	{
-		public Dictionary<string, Variable> LocalVariables { get; set; }
-		public Dictionary<string, Variable> GlobalVariables { get; set; }
+		public Dictionary<string, Variable> Variables { get; set; }
+
+		public List<Parameter> Parameters { get; set; }
 
 		public FunctionDefinition SelfDefinition { get; set; }
 	}
@@ -32,4 +33,23 @@ namespace NTERA.Engine.Runtime
 			Result = value;
 		}
 	}
+
+	public class Parameter
+	{
+		public Value Value { get; }
+		public Variable BackingVariable { get; }
+		public int[] BackingVariableIndex { get; }
+
+		public Parameter(Value value, Variable backingVariable = null, int[] variableIndex = null)
+		{
+			Value = value;
+			BackingVariable = backingVariable;
+			BackingVariableIndex = variableIndex ?? new[] { 0 };
+		}
+
+		public static implicit operator Value(Parameter parameter)
+		{
+			return parameter.Value;
+		}
+	}
 }

+ 29 - 12
NTERA.Engine/Variable.cs

@@ -1,8 +1,9 @@
-using System.Collections.Generic;
+using System.Collections;
+using System.Collections.Generic;
 
 namespace NTERA.Engine
 {
-	public class Variable
+	public class Variable : IEnumerable<KeyValuePair<int[], Value>>
 	{
 		public string Name { get; }
 
@@ -18,9 +19,25 @@ namespace NTERA.Engine
 
 		public Value this[params int[] index]
 		{
-			get => Dictionary[index];
+			get
+			{
+				if (Dictionary.TryGetValue(index, out var value))
+					return value;
+
+				return Type == ValueType.String ? new Value("") : new Value(0d);
+			}
 			set => Dictionary[index] = value;
 		}
+
+		public IEnumerator<KeyValuePair<int[], Value>> GetEnumerator()
+		{
+			return Dictionary.GetEnumerator();
+		}
+
+		IEnumerator IEnumerable.GetEnumerator()
+		{
+			return ((IEnumerable)Dictionary).GetEnumerator();
+		}
 	}
 
 	public class ZeroIntegerArrayEqualityComparer : IEqualityComparer<int[]>
@@ -37,18 +54,18 @@ namespace NTERA.Engine
 			if (y == null || y.Length == 0)
 				y = new[] { 0 };
 
-			int xNonZeroLength = x.Length - 1;
-			int yNonZeroLength = y.Length - 1;
+			int xNonZeroLength = x.Length;
+			int yNonZeroLength = y.Length;
 
-			for (; xNonZeroLength > 0; xNonZeroLength++)
+			for (; xNonZeroLength > 0; xNonZeroLength--)
 			{
-				if (x[xNonZeroLength] != 0)
+				if (x[xNonZeroLength - 1] != 0)
 					break;
 			}
 
-			for (; yNonZeroLength > 0; yNonZeroLength++)
+			for (; yNonZeroLength > 0; yNonZeroLength--)
 			{
-				if (y[yNonZeroLength] != 0)
+				if (y[yNonZeroLength - 1] != 0)
 					break;
 			}
 
@@ -73,11 +90,11 @@ namespace NTERA.Engine
 			if (obj.Length == 0)
 				return 17 * 23; //equal to int[] { 0 }
 
-			int nonZeroLength = obj.Length - 1;
+			int nonZeroLength = obj.Length;
 
-			for (; nonZeroLength > 0; nonZeroLength++)
+			for (; nonZeroLength > 0; nonZeroLength--)
 			{
-				if (obj[nonZeroLength] != 0)
+				if (obj[nonZeroLength - 1] != 0)
 					break;
 			}