Jelajahi Sumber

- Implement dynamic variables
- Improve JIT performance and memory usage
- Misc fixes

Bepsi 6 tahun lalu
induk
melakukan
e3009d4119

+ 2 - 2
NTERA.Core/GameInstance.cs

@@ -54,12 +54,12 @@ namespace NTERA.Core
 			//	}
 			//}
 
+			Console.GiveInput(input);
+
 			if (long.TryParse(input, out long number))
 				ScriptEngine.InputInteger(number);
 			else
 				ScriptEngine.InputString(input);
-
-			Console.GiveInput(input);
 		}
 	}
 }

+ 1 - 1
NTERA.Core/IConsole.cs

@@ -37,7 +37,7 @@ namespace NTERA.Core
 		bool UseSetColorStyle { get; set; }
 		void PrintC(string message, bool something);
 		DisplayLineAlignment Alignment { get; set; }
-		void deleteLine(int line);
+		void ClearLine(int line);
 		void PrintTemporaryLine(string line);
 		bool updatedGeneration { get; set; }
 		void PrintFlush(bool something);

+ 1 - 1
NTERA.EmuEra/Game/EraEmu/GameProc/Function/Instraction.Child.cs

@@ -498,7 +498,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc.Function
 			{
 				ExpressionArgument intExpArg = (ExpressionArgument)func.Argument;
 				Int32 delNum = (Int32)intExpArg.Term.GetIntValue(exm);
-				exm.Console.deleteLine(delNum);
+				exm.Console.ClearLine(delNum);
 				exm.Console.RefreshStrings(false);
 			}
 		}

+ 10 - 10
NTERA.EmuEra/Game/EraEmu/GameProc/Process.SystemProc.cs

@@ -221,7 +221,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 			}
 			else//入力が正しくないならもう一回選択肢を書き直し、正しい選択を要求する。
 			{//RESUELASTLINEと同様の処理を行うように変更
-				console.deleteLine(1);
+				console.ClearLine(1);
 				console.PrintTemporaryLine("Invalid value");
 				console.updatedGeneration = true;
 				openingInput();
@@ -487,7 +487,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 			{
                 if (console.LastLineIsEmpty)
                 {
-                    console.deleteLine(2);
+                    console.ClearLine(2);
                     console.PrintTemporaryLine("Invalid value");
                 }
 				console.updatedGeneration = true;
@@ -568,7 +568,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 				if (!CallFunction(ablName, false, false))
 				{
 					//見つからなければ終了
-					console.deleteLine(1);
+					console.ClearLine(1);
 					console.PrintTemporaryLine("Invalid value");
 					console.updatedGeneration = true;
 					endCallShowAblupSelect();
@@ -588,7 +588,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 			{
                 if (console.LastLineIsEmpty)
                 {
-                    console.deleteLine(2);
+                    console.ClearLine(2);
                     console.PrintTemporaryLine("Invalid value");
                 }
 				console.updatedGeneration = true;
@@ -704,14 +704,14 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 
 					//console.Print("お金が足りません。");
 					//console.NewLine();
-					console.deleteLine(1);
+					console.ClearLine(1);
 					console.PrintTemporaryLine("お金が足りません。");
 				}
 				else
 				{
 					//console.Print("売っていません。");
 					//console.NewLine();
-					console.deleteLine(1);
+					console.ClearLine(1);
 					console.PrintTemporaryLine("売っていません。");
 				}
 				//購入に失敗した場合、endCallEventShop()に戻す。
@@ -735,7 +735,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 			{
                 if (console.LastLineIsEmpty)
                 {
-                    console.deleteLine(2);
+                    console.ClearLine(2);
                     console.PrintTemporaryLine("Invalid value");
                 }
 				console.updatedGeneration = true;
@@ -876,7 +876,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 				available = dataIsAvailable[_systemResult];
 			else
 			{//入力しなおし
-				console.deleteLine(1);
+				console.ClearLine(1);
 				console.PrintTemporaryLine("Invalid value");
 				console.updatedGeneration = true;
 				SetWaitInput();
@@ -908,7 +908,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 
 			if (_systemResult != 0)//「はい」でもない
 			{//入力しなおし
-				console.deleteLine(1);
+				console.ClearLine(1);
 				console.PrintTemporaryLine("Invalid value");
 				console.updatedGeneration = true;
 				SetWaitInput();
@@ -962,7 +962,7 @@ namespace NTERA.EmuEra.Game.EraEmu.GameProc
 				available = dataIsAvailable[dataIsAvailable.Length - 1];
 			else
 			{//入力しなおし
-				console.deleteLine(1);
+				console.ClearLine(1);
 				console.PrintTemporaryLine("Invalid value");
 				console.updatedGeneration = true;
 				SetWaitInput();

+ 11 - 0
NTERA.Engine/Compiler/Lexer.cs

@@ -151,6 +151,7 @@ namespace NTERA.Engine.Compiler
 					const string SkipEnd = "[SKIPEND]";
 
 					if (sourceMarker.Column > 1
+						|| source.Length < sourceMarker.Pointer + SkipStart.Length
 						|| source.Substring(sourceMarker.Pointer, SkipStart.Length) != SkipStart)
 						return Token.Unknown;
 
@@ -221,6 +222,11 @@ namespace NTERA.Engine.Compiler
 						GetNextChar();
 						return Token.NotEqual;
 					}
+					else if (GetNextChar(true) == '<')
+					{
+						GetNextChar();
+						return Token.ShiftLeft;
+					}
 					else if (GetNextChar(true) == '=')
 					{
 						GetNextChar();
@@ -235,6 +241,11 @@ namespace NTERA.Engine.Compiler
 						GetNextChar();
 						return Token.MoreEqual;
 					}
+					else if (GetNextChar(true) == '>')
+					{
+						GetNextChar();
+						return Token.ShiftRight;
+					}
 					else
 						return Token.More;
 

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

@@ -678,7 +678,7 @@ namespace NTERA.Engine.Compiler
 			{ Token.Less, 1 }, { Token.More, 1 }, { Token.LessEqual, 1 }, { Token.MoreEqual, 1 },
 			{ Token.Plus, 2 }, { Token.Minus, 2 },
 			{ Token.Asterisk, 3 }, { Token.Slash, 3 }, { Token.Modulo, 3 },
-			{ Token.Caret, 4 }
+			{ Token.Caret, 4 }, { Token.ShiftLeft, 4 }, { Token.ShiftRight, 4 }
 		};
 
 		protected ExecutionNode Expression(out ParserError error, bool useModulo = true, bool ternaryString = false)

+ 2 - 2
NTERA.Engine/Compiler/Preprocessor.cs

@@ -448,7 +448,7 @@ namespace NTERA.Engine.Compiler
 			{ Token.Less, 1 }, { Token.More, 1 }, { Token.LessEqual, 1 }, { Token.MoreEqual, 1 },
 			{ Token.Plus, 2 }, { Token.Minus, 2 },
 			{ Token.Asterisk, 3 }, { Token.Slash, 3 },
-			{ Token.Caret, 4 }
+			{ Token.Caret, 4 }, { Token.ShiftLeft, 4 }, { Token.ShiftRight, 4 }
 		};
 
 		public static Value ConstantExpression(Lexer lexer, ICollection<FunctionVariable> constantDefinitions = null)
@@ -498,7 +498,7 @@ namespace NTERA.Engine.Compiler
 				else if (currentEnumerator.Current == Token.LParen)
 				{
 					currentEnumerator.MoveNext();
-					stack.Push(ConstantExpression(lexer));
+					stack.Push(ConstantExpression(lexer, constantDefinitions));
 
 					if (currentEnumerator.Current != Token.RParen)
 						throw new ParserException($"Was expecting [LParen] got [{currentEnumerator.Current}]", lexer.TokenMarker);

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

@@ -62,6 +62,9 @@
 		Minus,
 		Decrement,
 
+		ShiftLeft,
+		ShiftRight,
+
 		[LexerCharacter('/')]
 		Slash,
 
@@ -113,6 +116,8 @@
 				   || token == Token.Asterisk
 				   || token == Token.Modulo
 				   || token == Token.Caret
+				   || token == Token.ShiftLeft
+				   || token == Token.ShiftRight
 				   || token == Token.Equal
 				   || token == Token.NotEqual
 				   || token == Token.Less

+ 2 - 0
NTERA.Engine/Marker.cs

@@ -2,6 +2,8 @@
 {
 	public struct Marker
 	{
+		public static Marker Zero { get; } = new Marker();
+
 		public int Pointer { get; set; }
 		public int Line { get; set; }
 		public int Column { get; set; }

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

@@ -54,6 +54,8 @@
     <Compile Include="Runtime\BaseDefinitions.cs" />
     <Compile Include="Runtime\Base\Functions.cs" />
     <Compile Include="Runtime\Base\Keywords.cs" />
+    <Compile Include="Runtime\Base\Variables.cs" />
+    <Compile Include="Runtime\DynamicVariable.cs" />
     <Compile Include="Runtime\EraRuntime.cs" />
     <Compile Include="Runtime\EraRuntimeException.cs" />
     <Compile Include="Runtime\StackFrame.cs" />

+ 32 - 0
NTERA.Engine/Runtime/Base/Keywords.cs

@@ -47,6 +47,14 @@ namespace NTERA.Engine.Runtime.Base
 			return output;
 		}
 
+		[Keyword("RESTART")]
+		public static void Restart(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			runtime.Initialize(runtime.Console);
+
+			runtime.Call(runtime.ExecutionProvider.DefinedProcedures.First(x => x.Name == "SYSTEM_TITLE"));
+		}
+
 		[Keyword("INPUT")]
 		public static void Input(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{
@@ -63,6 +71,18 @@ namespace NTERA.Engine.Runtime.Base
 			context.Variables["RESULTS"][0] = runtime.LastInputValue;
 		}
 
+		[Keyword("LOADGLOBAL")]
+		public static void LoadGlobal(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+
+		}
+
+		[Keyword("SAVEGLOBAL")]
+		public static void SaveGlobal(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+
+		}
+
 		[Keyword("TIMES")]
 		public static void Times(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{
@@ -126,6 +146,18 @@ namespace NTERA.Engine.Runtime.Base
 			runtime.Console.ResetStyle();
 		}
 
+		[Keyword("CLEARLINE")]
+		public static void ClearLine(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			runtime.Console.ClearLine((int)runtime.ComputeExpression(context, node.Single()).Real);
+		}
+
+		[Keyword("REDRAW")]
+		public static void Redraw(EraRuntime runtime, StackFrame context, ExecutionNode node)
+		{
+			runtime.Console.SetRedraw((long)runtime.ComputeExpression(context, node.Single()).Real);
+		}
+
 		[Keyword("DRAWLINE")]
 		public static void DrawLine(EraRuntime runtime, StackFrame context, ExecutionNode node)
 		{

+ 44 - 0
NTERA.Engine/Runtime/Base/Variables.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace NTERA.Engine.Runtime.Base
+{
+	public static class Variables
+	{
+		public class VariableAttribute : Attribute
+		{
+			public string Name { get; }
+			public ValueType Type { get; }
+
+			public VariableAttribute(string name, ValueType type)
+			{
+				Name = name;
+				Type = type;
+			}
+		}
+
+		public static Dictionary<VariableAttribute, Func<EraRuntime, int[], Value>> StaticVariables { get; } = _getFunctions();
+
+		private static Dictionary<VariableAttribute, Func<EraRuntime, int[], Value>> _getFunctions()
+		{
+			var output = new Dictionary<VariableAttribute, Func<EraRuntime, int[], Value>>();
+
+			foreach (MethodInfo method in typeof(Variables).GetMethods(BindingFlags.Public | BindingFlags.Static))
+			{
+				var variable = method.GetCustomAttribute<VariableAttribute>();
+
+				if (variable != null)
+					output[variable] = (Func<EraRuntime, int[], Value>)Delegate.CreateDelegate(typeof(Func<EraRuntime, int[], Value>), method);
+			}
+
+			return output;
+		}
+
+		[Variable("LINECOUNT", ValueType.Real)]
+		public static Value LineCount(EraRuntime runtime, int[] index)
+		{
+			return runtime.Console.LineCount;
+		}
+	}
+}

+ 26 - 0
NTERA.Engine/Runtime/DynamicVariable.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NTERA.Engine.Runtime
+{
+	public class DynamicVariable : Variable
+	{
+		protected EraRuntime Runtime { get; }
+		protected Func<EraRuntime, int[], Value> Predicate { get; }
+
+		public DynamicVariable(string name, ValueType type, EraRuntime runtime, Func<EraRuntime, int[], Value> predicate) : base(name, type)
+		{
+			Runtime = runtime;
+			Predicate = predicate;
+		}
+
+		public override Value this[params int[] index]
+		{
+			get => Predicate(Runtime, index);
+			set => throw new EraRuntimeException("Cannot assign to a dynamic variable");
+		}
+	}
+}

+ 22 - 8
NTERA.Engine/Runtime/EraRuntime.cs

@@ -29,10 +29,21 @@ namespace NTERA.Engine.Runtime
 		public EraRuntime(IExecutionProvider executionProvider)
 		{
 			ExecutionProvider = executionProvider;
+		}
+
+		public bool Initialize(IConsole console)
+		{
+			Console = console;
+
+			ExecutionStack.Clear();
+			ExecutionResultStack.Clear();
+			TotalProcedureDefinitions.Clear();
+			GlobalVariables.Clear();
+
 
 			TotalProcedureDefinitions.AddRange(ExecutionProvider.DefinedProcedures);
 			TotalProcedureDefinitions.AddRange(BaseDefinitions.DefaultGlobalFunctions);
-
+			
 			foreach (var variable in BaseDefinitions.DefaultGlobalVariables)
 			{
 				var globalVariable = new Variable(variable.Name, variable.ValueType)
@@ -42,11 +53,12 @@ namespace NTERA.Engine.Runtime
 
 				GlobalVariables.Add(variable.Name, globalVariable);
 			}
-		}
 
-		public bool Initialize(IConsole console)
-		{
-			Console = console;
+			foreach (var kv in Variables.StaticVariables)
+			{
+				GlobalVariables[kv.Key.Name] = new DynamicVariable(kv.Key.Name, kv.Key.Type, this, kv.Value);
+			}
+
 			return true;
 		}
 
@@ -176,14 +188,16 @@ namespace NTERA.Engine.Runtime
 
 				iterationVariable[iterationIndex] = beginNumber;
 
+				var subset = node.Skip(1);
+
 				for (double i = beginNumber.Real; i < endNumber.Real; i++)
 				{
-					var subset = node.Skip(1).ToList();
+					var currentSubset = subset.ToList();
 
 					var variableNode = Parser.GetVariable(iterationVariable.Name, Marker.Zero, iterationIndex.Select(x => Parser.CreateConstant(x, Marker.Zero)).ToArray());
 
 					//increment the iterationVariable
-					subset.Add(new ExecutionNode
+					currentSubset.Add(new ExecutionNode
 					{
 						Type = "assignment",
 						Symbol = Marker.Zero,
@@ -198,7 +212,7 @@ namespace NTERA.Engine.Runtime
 						}
 					});
 
-					ExecutionStack.Push(context.Clone(subset));
+					ExecutionStack.Push(context.Clone(currentSubset));
 				}
 
 				return;

+ 26 - 28
NTERA.Engine/Runtime/JITCompiler.cs

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Runtime;
 using System.Text;
 using System.Threading.Tasks;
 using NTERA.Engine.Compiler;
@@ -16,6 +17,7 @@ namespace NTERA.Engine.Runtime
 		public ICollection<FunctionVariable> DefinedConstants { get; protected set; }
 		public CSVDefinition CSVDefinition { get; protected set; }
 
+		protected Dictionary<FunctionDefinition, string> ProcedureFiles { get; set; }
 		public Dictionary<FunctionDefinition, ExecutionNode[]> CompiledProcedures { get; protected set; } = new Dictionary<FunctionDefinition, ExecutionNode[]>();
 
 		public string InputDirectory { get; }
@@ -70,8 +72,11 @@ namespace NTERA.Engine.Runtime
 			});
 #endif
 
+			DefinedConstants = preprocessedConstants.ToArray();
+
 			Console.WriteLine("Preprocessing functions...");
 
+			ProcedureFiles = new Dictionary<FunctionDefinition, string>();
 			ConcurrentDictionary<FunctionDefinition, string> preprocessedFunctions = new ConcurrentDictionary<FunctionDefinition, string>();
 
 #if DEBUG
@@ -83,51 +88,44 @@ namespace NTERA.Engine.Runtime
 			}, file =>
 #endif
 			{
-				foreach (var kv in Preprocessor.PreprocessCodeFile(File.ReadAllText(file), Path.GetFileName(file), preprocessedConstants.ToArray()))
+				foreach (var kv in Preprocessor.PreprocessCodeFile(File.ReadAllText(file), Path.GetFileName(file), DefinedConstants))
+				{
 					preprocessedFunctions[kv.Key] = kv.Value;
+					ProcedureFiles[kv.Key] = file;
+				}
 #if DEBUG
 			}
 #else
 			});
 #endif
 			DefinedProcedures = preprocessedFunctions.Select(kv => kv.Key).ToArray();
-			var DeclaredProcedures = preprocessedFunctions.ToDictionary(kv => kv.Key, kv => kv.Value);
 
 			var declaredFunctions = BaseDefinitions.DefaultGlobalFunctions.ToList();
-			declaredFunctions.AddRange(DeclaredProcedures.Keys.Where(x => x.IsReturnFunction));
+			declaredFunctions.AddRange(DefinedProcedures.Where(x => x.IsReturnFunction));
+			DefinedFunctions = declaredFunctions;
 
-			var procedures = DeclaredProcedures.Keys.Where(x => !x.IsReturnFunction).ToList();
+			GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
+			GC.Collect();
+		}
 
+		public IEnumerable<ExecutionNode> GetExecutionNodes(FunctionDefinition function)
+		{
+			if (CompiledProcedures.ContainsKey(function))
+				return CompiledProcedures[function];
 
-			Console.WriteLine("Compiling functions...");
+			string filename = ProcedureFiles[function];
+			var preprocessed = Preprocessor.PreprocessCodeFile(File.ReadAllText(filename), Path.GetFileName(filename), DefinedConstants);
 
-#if DEBUG
-			foreach (var kv in DeclaredProcedures)
-#else
-			Parallel.ForEach(DeclaredProcedures, new ParallelOptions
-			{
-				MaxDegreeOfParallelism = Threads
-			}, kv =>
-#endif
-			{
-				Parser parser = new Parser(kv.Value, kv.Key, declaredFunctions, procedures, BaseDefinitions.DefaultGlobalVariables, kv.Key.Variables, BaseDefinitions.DefaultKeywords, csvDefinition, preprocessedConstants.ToArray());
+			Parser parser = new Parser(preprocessed.Single(x => x.Key.Name == function.Name).Value, function, DefinedFunctions, DefinedProcedures.Where(x => !x.IsReturnFunction).ToArray(), BaseDefinitions.DefaultGlobalVariables, function.Variables, BaseDefinitions.DefaultKeywords, CSVDefinition, DefinedConstants);
 
-				var nodes = parser.Parse(out var localErrors, out var localWarnings);
+			var nodes = parser.Parse(out var localErrors, out var localWarnings)?.ToArray();
 
-				if (localErrors.Count > 0)
-					throw new ParserException($"Failed to compile '{kv.Key.Name}'");
+			if (localErrors.Count > 0)
+				throw new ParserException($"Failed to compile '{function.Name}'");
 
-				CompiledProcedures.Add(kv.Key, nodes.ToArray());
-#if DEBUG
-			}
-#else
-			});
-#endif
-		}
+			CompiledProcedures.Add(function, nodes);
 
-		public IEnumerable<ExecutionNode> GetExecutionNodes(FunctionDefinition function)
-		{
-			return CompiledProcedures[function];
+			return nodes;
 		}
 	}
 }

+ 2 - 0
NTERA.Engine/Value.cs

@@ -61,6 +61,8 @@ namespace NTERA.Engine
 					case Token.Asterisk: return new Value(a.Real * b.Real);
 					case Token.Slash: return new Value(a.Real / b.Real);
 					case Token.Caret: return new Value(Math.Pow(a.Real, b.Real));
+					case Token.ShiftLeft: return new Value((int)a.Real << (int)b.Real);
+					case Token.ShiftRight: return new Value((int)a.Real >> (int)b.Real);
 					case Token.Less: return new Value(a.Real < b.Real ? 1 : 0);
 					case Token.More: return new Value(a.Real > b.Real ? 1 : 0);
 					case Token.LessEqual: return new Value(a.Real <= b.Real ? 1 : 0);

+ 1 - 1
NTERA.Engine/Variable.cs

@@ -17,7 +17,7 @@ namespace NTERA.Engine
 			Type = type;
 		}
 
-		public Value this[params int[] index]
+		public virtual Value this[params int[] index]
 		{
 			get
 			{

+ 3 - 1
NTERA/Console/ConsoleRenderer.cs

@@ -105,7 +105,9 @@ namespace NTERA.Console
 
 		public void MouseHoverEvent(Point mousePoint, Control control)
 		{
-			foreach (var item in Items.SelectMany(x => x))
+			var items = Items.SelectMany(x => x).ToArray();
+
+			foreach (var item in items)
 			{
 				if (!item.InvalidateOnMouseStateChange || !item.Region.Contains(mousePoint))
 					continue;

+ 1 - 1
NTERA/Console/EraConsoleInstance.cs

@@ -172,7 +172,7 @@ namespace NTERA.Console
 		}
 
 		public DisplayLineAlignment Alignment { get; set; } = DisplayLineAlignment.LEFT;
-		public void deleteLine(int line)
+		public void ClearLine(int line)
 		{
 		}