Parcourir la source

Implement more keywords and base functions

Bepsi il y a 6 ans
Parent
commit
939bc1abb6

+ 19 - 2
NTERA.Engine/Compiler/ExecutionNode.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
 using System.Xml;
@@ -6,7 +7,7 @@ using System.Xml;
 namespace NTERA.Engine.Compiler
 {
 	[DebuggerDisplay("{Type}")]
-	public class ExecutionNode
+	public class ExecutionNode : IEnumerable<ExecutionNode>
 	{
 		public string Type { get; set; }
 
@@ -23,6 +24,12 @@ namespace NTERA.Engine.Compiler
 			get { return SubNodes.First(x => x.Type == type); }
 		}
 
+		public ExecutionNode this[int index]
+		{
+			get => SubNodes[index];
+			set => SubNodes[index] = value;
+		}
+
 		public void WriteXml(XmlWriter writer)
 		{
 			writer.WriteStartElement(Type);
@@ -35,5 +42,15 @@ namespace NTERA.Engine.Compiler
 
 			writer.WriteEndElement();
 		}
+
+		public IEnumerator<ExecutionNode> GetEnumerator()
+		{
+			return ((IEnumerable<ExecutionNode>)SubNodes).GetEnumerator();
+		}
+
+		IEnumerator IEnumerable.GetEnumerator()
+		{
+			return SubNodes.GetEnumerator();
+		}
 	}
 }

+ 1 - 1
NTERA.Engine/Compiler/FunctionDefinition.cs

@@ -51,7 +51,7 @@ namespace NTERA.Engine.Compiler
 
 		public Value? DefaultValue { get; }
 
-		public Value CalculatedValue => DefaultValue ?? ValueType == ValueType.String ? new Value("") : new Value(0d);
+		public Value CalculatedValue => DefaultValue.HasValue ? DefaultValue.Value : ValueType == ValueType.String ? new Value("") : new Value(0d); //for some reason, this can't be null coalesce (??), as it disregards the value
 
 		public FunctionVariable(string name, ValueType valueType, VariableType variableType = VariableType.None, Value? defaultValue = null)
 		{

+ 12 - 3
NTERA.Engine/Compiler/Parser.cs

@@ -894,9 +894,6 @@ namespace NTERA.Engine.Compiler
 
 			if (!implicitString)
 			{
-				if (char.IsWhiteSpace(Lexer.CurrentChar))
-					Lexer.GetNextChar();
-
 				if (Lexer.CurrentChar == '@')
 				{
 					canFormat = true;
@@ -908,6 +905,11 @@ namespace NTERA.Engine.Compiler
 					Lexer.GetNextChar();
 				}
 			}
+			else
+			{
+				if (char.IsWhiteSpace(Lexer.CurrentChar))
+					Lexer.GetNextChar();
+			}
 
 			StringBuilder currentBlock = new StringBuilder();
 			void commitBlock()
@@ -956,6 +958,13 @@ namespace NTERA.Engine.Compiler
 							? expressionValue
 							: OperateNodes(value, expressionValue, Token.Plus);
 					}
+					else if (Lexer.CurrentChar == 'n')
+					{
+						currentBlock.Append('\n');
+
+						Lexer.GetNextChar();
+						continue;
+					}
 
 					currentBlock.Append(Lexer.CurrentChar);
 

+ 1 - 0
NTERA.Engine/NTERA.Engine.csproj

@@ -55,6 +55,7 @@
     <Compile Include="Runtime\Base\Functions.cs" />
     <Compile Include="Runtime\Base\Keywords.cs" />
     <Compile Include="Runtime\EraRuntime.cs" />
+    <Compile Include="Runtime\EraRuntimeException.cs" />
     <Compile Include="Runtime\StackFrame.cs" />
     <Compile Include="Runtime\IExecutionProvider.cs" />
     <Compile Include="Runtime\JITCompiler.cs" />

+ 12 - 0
NTERA.Engine/Runtime/Base/Functions.cs

@@ -42,6 +42,18 @@ namespace NTERA.Engine.Runtime.Base
 			throw new NotImplementedException("Advanced formatting hasn't been implemented yet");
 		}
 
+		[Function("TOSTR")]
+		public static Value ToStr(EraRuntime runtime, StackFrame context, IList<Value> parameters)
+		{
+			if (parameters.Count == 1)
+				return parameters[0].ToString();
+
+			if (parameters.Count == 2)
+				return ((int)parameters[0]).ToString(parameters[1].String);
+
+			throw new EraRuntimeException("Invalid amount of parameters");
+		}
+
 		private static Random _random = new Random();
 
 		[Function("RAND")]

+ 101 - 10
NTERA.Engine/Runtime/Base/Keywords.cs

@@ -1,13 +1,16 @@
 using System;
 using System.Collections.Generic;
+using System.Drawing;
 using System.Linq;
 using System.Reflection;
+using NTERA.Core.Interop;
 using NTERA.Engine.Compiler;
 
 namespace NTERA.Engine.Runtime.Base
 {
 	public static class Keywords
 	{
+		[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
 		private class KeywordAttribute : Attribute
 		{
 			public string Name { get; }
@@ -35,35 +38,123 @@ namespace NTERA.Engine.Runtime.Base
 
 			foreach (MethodInfo method in typeof(Keywords).GetMethods(BindingFlags.Public | BindingFlags.Static))
 			{
-				var keyword = method.GetCustomAttribute<KeywordAttribute>();
+				var keywords = method.GetCustomAttributes<KeywordAttribute>();
 
-				if (keyword != null)
-					output[keyword.Name] = (runtime, set, node) => { method.Invoke(null, new object[] { runtime, set, node }); };
+				foreach (KeywordAttribute keyword in keywords)
+					output[keyword.Name] = (runtime, context, node) => { method.Invoke(null, new object[] { runtime, context, node }); };
 			}
 
 			return output;
 		}
 
+		[Keyword("TIMES")]
+		public static void Times(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			string variableName = node[0].Metadata["name"];
+
+			if (context.GlobalVariables.ContainsKey(variableName))
+				context.GlobalVariables[variableName] *= runtime.ComputeExpression(context, node[1]);
+			else if (context.GlobalVariables.ContainsKey(variableName))
+				context.GlobalVariables[variableName] *= runtime.ComputeExpression(context, node[1]);
+			else
+				throw new EraRuntimeException("Invalid parameters specified for TIMES");
+		}
+
+		[Keyword("ALIGNMENT")]
+		public static void Alignment(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			string alignmentType = runtime.ComputeExpression(context, node[0]).String;
+
+			if (!Enum.TryParse(alignmentType, true, out DisplayLineAlignment alignmentValue))
+				throw new EraRuntimeException($"Unable to parse alignment type: '{alignmentType}'");
+
+			runtime.Console.Alignment = alignmentValue;
+		}
+
+		[Keyword("SETCOLOR")]
+		public static void SetColor(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			if (node.SubNodes.Length == 1)
+			{
+				uint argb = (uint)runtime.ComputeExpression(context, node[0]).Real;
+				argb |= 0xFF000000U;
+
+				Color c = Color.FromArgb((int)argb);
+
+				runtime.Console.SetStringStyle(c);
+			}
+			else if (node.SubNodes.Length == 3)
+			{
+				int r = (int)runtime.ComputeExpression(context, node[0]).Real;
+				int g = (int)runtime.ComputeExpression(context, node[1]).Real;
+				int b = (int)runtime.ComputeExpression(context, node[2]).Real;
+
+				runtime.Console.SetStringStyle(Color.FromArgb(r, g, b));
+			}
+			else
+				throw new EraRuntimeException("Unable to parse color");
+		}
+
+		[Keyword("RESETCOLOR")]
+		public static void ResetColor(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			runtime.Console.ResetStyle();
+		}
+
 		[Keyword("DRAWLINE")]
-		public static void DrawLine(EraRuntime runtime, StackFrame set, ExecutionNode node)
+		public static void DrawLine(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{
 			runtime.Console.PrintBar();
 		}
 
 		[Keyword("DRAWLINEFORM", true, true)]
-		public static void DrawLineForm(EraRuntime runtime, StackFrame set, ExecutionNode node)
+		public static void DrawLineForm(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			var value = runtime.ComputeExpression(context, node.Single());
+
+			runtime.Console.printCustomBar(value.ToString());
+		}
+
+		[Keyword("PRINT_IMG")]
+		public static void PrintImg(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			var value = runtime.ComputeExpression(context, node.Single());
+
+			runtime.Console.PrintImg(value.ToString());
+		}
+
+		[Keyword("PRINTBUTTON")]
+		public static void PrintButton(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			var textValue = runtime.ComputeExpression(context, node[0]);
+			var intValue = runtime.ComputeExpression(context, node[1]);
+
+			runtime.Console.PrintButton(textValue.String, (long)intValue.Real);
+		}
+
+		[Keyword("HTML_PRINT")]
+		public static void PrintHtml(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			var htmlValue = runtime.ComputeExpression(context, node.Single());
+
+			runtime.Console.PrintHtml(htmlValue.String, false);
+		}
+
+		[Keyword("PRINT", true, true)]
+		public static void Print(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{
-			var value = runtime.ComputeExpression(set, node.SubNodes.Single());
+			var value = runtime.ComputeExpression(context, node.Single());
 
-			runtime.Console.printCustomBar(value.ToString().Trim());
+			runtime.Console.Write(value.ToString());
 		}
 
 		[Keyword("PRINTFORML", true, true)]
-		public static void PrintLine(EraRuntime runtime, StackFrame set, ExecutionNode node)
+		[Keyword("PRINTL", true, true)]
+		public static void PrintLine(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{
-			var value = runtime.ComputeExpression(set, node.SubNodes.Single());
+			var value = runtime.ComputeExpression(context, node.Single());
 
-			runtime.Console.PrintSingleLine(value.ToString().Trim());
+			runtime.Console.PrintSingleLine(value.ToString());
 		}
 	}
 }

+ 38 - 19
NTERA.Engine/Runtime/EraRuntime.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Drawing;
 using System.Linq;
 using NTERA.Core;
 using NTERA.EmuEra.Game.EraEmu.Content;
@@ -94,14 +95,14 @@ namespace NTERA.Engine.Runtime
 						else if (index >= function.Parameters.Length && function.Parameters.Last().IsArrayParameter)
 							parameter = function.Parameters.Last();
 						else
-							throw new Exception($"Unable to assign parameter #{index + 1}");
+							throw new EraRuntimeException($"Unable to assign parameter #{index + 1}");
 
 						if (localVariables.ContainsKey(parameter.Name))
 							localVariables[parameter.Name] = parameters[index];
 						else if (globalVariables.ContainsKey(parameter.Name))
 							globalVariables[parameter.Name] = parameters[index];
 						else
-							throw new Exception($"Unable to assign parameter '{parameter.Name}' to a variable");
+							throw new EraRuntimeException($"Unable to assign parameter '{parameter.Name}' to a variable");
 					}
 
 				var executionContents = ExecutionProvider.GetExecutionNodes(function);
@@ -122,7 +123,7 @@ namespace NTERA.Engine.Runtime
 
 				if (node.Type == "result")
 				{
-					return new ExecutionResult(ExecutionResultType.FunctionReturn, ComputeExpression(context, node.SubNodes.Single()));
+					return new ExecutionResult(ExecutionResultType.FunctionReturn, ComputeExpression(context, node.Single()));
 				}
 				
 				ExecuteNode(context, node);
@@ -139,24 +140,40 @@ namespace NTERA.Engine.Runtime
 					string statement = node.Metadata["name"];
 
 					if (!Base.Keywords.StaticKeywords.TryGetValue(statement, out var keywordAction))
-						throw new Exception($"Unknown statement: '{statement}'");
+						throw new EraRuntimeException($"Unknown statement: '{statement}'");
 
 					keywordAction(this, context, node);
 
 					return;
+
 				case "assignment":
 					string variableName = node["variable"].Metadata["name"];
 
 					if (context.LocalVariables.ContainsKey(variableName))
-						context.LocalVariables[variableName] = ComputeExpression(context, node["value"].SubNodes.Single());
+						context.LocalVariables[variableName] = ComputeExpression(context, node["value"].Single());
 					else if (context.GlobalVariables.ContainsKey(variableName))
-						context.GlobalVariables[variableName] = ComputeExpression(context, node["value"].SubNodes.Single());
+						context.GlobalVariables[variableName] = ComputeExpression(context, node["value"].Single());
 					else
-						throw new Exception($"Unknown variable: '{variableName}'");
+						throw new EraRuntimeException($"Unknown variable: '{variableName}'");
 
 					return;
+
+				case "call":
+					string procedureName = node.Metadata["target"];
+					var procedure = TotalProcedureDefinitions.FirstOrDefault(func => !func.IsReturnFunction && func.Name.Equals(procedureName, StringComparison.OrdinalIgnoreCase));
+
+					if (procedure == null)
+						throw new EraRuntimeException($"Unknown procedure: '{procedureName}'");
+
+					var executionResult = Call(procedure, node["parameters"].Select(x => ComputeExpression(context, x)).ToArray());
+
+					if (executionResult.Type != ExecutionResultType.None || executionResult.Result.HasValue)
+						throw new EraRuntimeException($"Unexpected result from procedure '{procedureName}': {executionResult.Type}");
+
+					return;
+
 				default:
-					throw new Exception($"Unknown node type: '{node.Type}'");
+					throw new EraRuntimeException($"Unknown node type: '{node.Type}'");
 			}
 		}
 
@@ -179,19 +196,19 @@ namespace NTERA.Engine.Runtime
 					else if (context.GlobalVariables.ContainsKey(variableName))
 						return context.GlobalVariables[variableName];
 					else
-						throw new Exception($"Unable to retrieve variable '{variableName}'");
+						throw new EraRuntimeException($"Unable to retrieve variable '{variableName}'");
 
 				case "call":
 					string functionName = expressionNode.Metadata["target"];
 					var function = TotalProcedureDefinitions.FirstOrDefault(func => func.IsReturnFunction && func.Name.Equals(functionName, StringComparison.OrdinalIgnoreCase));
 
 					if (function == null)
-						throw new Exception($"Unknown function: '{functionName}'");
+						throw new EraRuntimeException($"Unknown function: '{functionName}'");
 
-					var executionResult = Call(function, expressionNode["parameters"].SubNodes.Select(x => ComputeExpression(context, x)).ToArray());
+					var executionResult = Call(function, expressionNode["parameters"].Select(x => ComputeExpression(context, x)).ToArray());
 
 					if (executionResult.Type != ExecutionResultType.FunctionReturn || !executionResult.Result.HasValue)
-						throw new Exception($"Unexpected result from function '{functionName}': {executionResult.Type}");
+						throw new EraRuntimeException($"Unexpected result from function '{functionName}': {executionResult.Type}");
 
 					return executionResult.Result.Value;
 
@@ -205,28 +222,28 @@ namespace NTERA.Engine.Runtime
 					{
 						case "add": operatorToken = Token.Plus; break;
 						case "subtract": operatorToken = Token.Minus; break;
-						default: throw new Exception($"Unknown operation type: '{operationType}'");
+						default: throw new EraRuntimeException($"Unknown operation type: '{operationType}'");
 					}
 
 					if (isUnary)
 					{
-						Value innerValue = ComputeExpression(context, expressionNode.SubNodes.Single());
+						Value innerValue = ComputeExpression(context, expressionNode.Single());
 
 						switch (operatorToken)
 						{
 							case Token.Plus: return innerValue;
 							case Token.Minus: return innerValue * -1;
-							default: throw new Exception($"Unsupported unary operation type: '{operationType}'");
+							default: throw new EraRuntimeException($"Unsupported unary operation type: '{operationType}'");
 						}
 					}
 					
-					var left = ComputeExpression(context, expressionNode.SubNodes[0]);
-					var right = ComputeExpression(context, expressionNode.SubNodes[1]);
+					var left = ComputeExpression(context, expressionNode[0]);
+					var right = ComputeExpression(context, expressionNode[1]);
 
 					return left.Operate(right, operatorToken);
 
 				default:
-					throw new Exception($"Unknown expression type: '{expressionNode.Type}'");
+					throw new EraRuntimeException($"Unknown expression type: '{expressionNode.Type}'");
 			}
 		}
 
@@ -247,7 +264,9 @@ namespace NTERA.Engine.Runtime
 
 		public CroppedImage GetImage(string name)
 		{
-			throw new NotImplementedException();
+			var bitmap = new Bitmap(@"M:\era\eraSemifullTest\resources\bbb.png");
+			return new CroppedImage(name, bitmap, new Rectangle(Point.Empty, bitmap.Size), false);
+
 		}
 	}
 }

+ 11 - 0
NTERA.Engine/Runtime/EraRuntimeException.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace NTERA.Engine.Runtime
+{
+	public class EraRuntimeException : Exception
+	{
+		public EraRuntimeException(string message) : base(message) { }
+
+		public EraRuntimeException(string message, Exception innerException) : base(message, innerException) { }
+	}
+}