Просмотр исходного кода

Change from interpreter based engine to compiler based

Bepsi 6 лет назад
Родитель
Сommit
4c8a6ad8d1

+ 1 - 0
NTERA.Interpreter.Tests/NTERA.Interpreter.Tests.csproj

@@ -60,6 +60,7 @@
     <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
       <HintPath>..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
     </Reference>
+    <Reference Include="System.Xml" />
   </ItemGroup>
   <Choose>
     <When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">

+ 34 - 56
NTERA.Interpreter/Attributes.cs

@@ -2,60 +2,38 @@
 
 namespace NTERA.Interpreter
 {
-    [Flags]
-    public enum LexerType
-    {
-        Real = 1,
-        String = 2,
-        Both = Real | String
-    }
-
-    [AttributeUsage(AttributeTargets.Field)]
-    public class LexerCharacterAttribute : Attribute
-    {
-        public char Character { get; }
-        public LexerType LexerContext { get; }
-
-        public LexerCharacterAttribute(char character, LexerType lexerContext = LexerType.Both)
-        {
-            Character = character;
-
-            LexerContext = lexerContext;
-        }
-    }
-
-    [AttributeUsage(AttributeTargets.Field)]
-    public class LexerKeywordAttribute : Attribute
-    {
-        public string Keyword { get; }
-        public bool IsLineKeyword { get; }
-
-        public LexerKeywordAttribute(string keyword, bool isLineKeyword = false)
-        {
-            Keyword = keyword;
-            IsLineKeyword = isLineKeyword;
-        }
-    }
-
-    [AttributeUsage(AttributeTargets.Method)]
-    public class KeywordMethodAttribute : Attribute
-    {
-        public Token Token { get; }
-
-        public KeywordMethodAttribute(Token token)
-        {
-            Token = token;
-        }
-    }
-
-    [AttributeUsage(AttributeTargets.Method)]
-    public class BuiltInFunctionAttribute : Attribute
-    {
-        public string Name { get; }
-
-        public BuiltInFunctionAttribute(string name)
-        {
-            Name = name;
-        }
-    }
+	[Flags]
+	public enum LexerType
+	{
+		Real = 1,
+		String = 2,
+		Both = Real | String
+	}
+
+	[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
+	public class LexerCharacterAttribute : Attribute
+	{
+		public char Character { get; }
+		public LexerType LexerContext { get; }
+
+		public LexerCharacterAttribute(char character, LexerType lexerContext = LexerType.Both)
+		{
+			Character = character;
+
+			LexerContext = lexerContext;
+		}
+	}
+
+	[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
+	public class LexerKeywordAttribute : Attribute
+	{
+		public string Keyword { get; }
+		public bool IsLineKeyword { get; }
+
+		public LexerKeywordAttribute(string keyword, bool isLineKeyword = false)
+		{
+			Keyword = keyword;
+			IsLineKeyword = isLineKeyword;
+		}
+	}
 }

+ 39 - 0
NTERA.Interpreter/Compiler/ExecutionNode.cs

@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Xml;
+
+namespace NTERA.Interpreter.Compiler
+{
+	[DebuggerDisplay("{Type}")]
+	public class ExecutionNode
+	{
+		public string Type { get; set; }
+
+		public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string>();
+
+		public string Anchor { get; set; }
+
+		public Marker Symbol { get; set; }
+
+		public ExecutionNode[] SubNodes { get; set; } = new ExecutionNode[0];
+
+		public ExecutionNode this[string type]
+		{
+			get { return SubNodes.First(x => x.Type == type); }
+		}
+
+		public void WriteXml(XmlWriter writer)
+		{
+			writer.WriteStartElement(Type);
+
+			foreach (var kv in Metadata)
+				writer.WriteAttributeString(kv.Key, kv.Value);
+
+			foreach (var node in SubNodes)
+				node.WriteXml(writer);
+
+			writer.WriteEndElement();
+		}
+	}
+}

+ 75 - 0
NTERA.Interpreter/Compiler/FunctionDefinition.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Diagnostics;
+
+namespace NTERA.Interpreter.Compiler
+{
+	[DebuggerDisplay("{Name} ({Parameters.Length})")]
+	public class FunctionDefinition
+	{
+		public string Name { get; }
+
+		public FunctionParameter[] Parameters { get; }
+
+		public FunctionVariable[] Variables { get; }
+
+		public bool IsReturnFunction { get; }
+
+		public string Filename { get; }
+
+		public Marker Position { get; }
+
+		public FunctionDefinition(string name, FunctionParameter[] parameters, FunctionVariable[] methodVariables, bool isReturnFunction, string filename, Marker position)
+		{
+			Name = name;
+			Parameters = parameters;
+			Variables = methodVariables;
+			IsReturnFunction = isReturnFunction;
+			Filename = filename;
+			Position = position;
+		}
+	}
+
+	[Flags]
+	public enum VariableType
+	{
+		None = 0,
+		Constant = 1,
+		Reference = 2,
+		Dynamic = 4,
+	}
+
+	public class FunctionVariable
+	{
+		public string Name { get; }
+
+		public System.ValueType ValueType { get; }
+
+		public VariableType VariableType { get; }
+
+		public Value? DefaultValue { get; }
+
+		public FunctionVariable(string name, System.ValueType valueType, VariableType variableType = VariableType.None, Value? defaultValue = null)
+		{
+			Name = name;
+			ValueType = valueType;
+			VariableType = variableType;
+			DefaultValue = defaultValue;
+		}
+	}
+
+	public class FunctionParameter
+	{
+		public string Name { get; }
+
+		public string[] Indices { get; }
+
+		public Value? DefaultValue { get; }
+
+		public FunctionParameter(string name, string[] indices, Value? defaultValue = null)
+		{
+			Name = name;
+			Indices = indices;
+			DefaultValue = defaultValue;
+		}
+	}
+}

+ 499 - 0
NTERA.Interpreter/Compiler/Lexer.cs

@@ -0,0 +1,499 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace NTERA.Interpreter.Compiler
+{
+	public class Lexer : IEnumerable<Token>
+	{
+		private readonly string source;
+		private Marker sourceMarker;
+		private char currentChar;
+
+		private readonly IEnumerator<Token> currentEnumerator;
+
+		private LexerType _type;
+
+		public LexerType Type
+		{
+			get => _type;
+			internal set
+			{
+				_type = value;
+				InitTokenDictionaries();
+			}
+		}
+
+		public Marker TokenMarker { get; set; }
+
+		public string Identifer { get; set; }
+		public Value Value { get; set; }
+
+		public Lexer(string input, LexerType type = LexerType.Both)
+		{
+			Type = type;
+
+			source = input;
+			sourceMarker = new Marker(-1, 1, 0);
+
+			currentEnumerator = GetTokens();
+			currentEnumerator.MoveNext();
+		}
+
+		public void GoTo(Marker marker)
+		{
+			sourceMarker = marker;
+		}
+
+		char GetNextChar(bool peek = false)
+		{
+			if (sourceMarker.Pointer + 1 >= source.Length)
+			{
+				sourceMarker.Pointer = source.Length;
+				return currentChar = (char)0;
+			}
+
+			if (peek)
+				return currentChar = source[sourceMarker.Pointer + 1];
+
+			sourceMarker.Column++;
+			sourceMarker.Pointer++;
+
+			if ((currentChar = source[sourceMarker.Pointer]) == '\n')
+			{
+				sourceMarker.Column = 0;
+				sourceMarker.Line++;
+			}
+
+			return currentChar;
+		}
+
+		private static Dictionary<string, Token> TokenDictionary;
+
+		private static Dictionary<string, Token> TokenLineDictionary;
+
+		private Dictionary<char, Token> TokenCharDictionary;
+
+		private static Dictionary<char, Token> BothModeTokens;
+		private static Dictionary<char, Token> StringModeTokens;
+
+		private void InitTokenDictionaries()
+		{
+			if (TokenDictionary == null || TokenLineDictionary == null)
+			{
+				TokenDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
+				TokenLineDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
+
+				foreach (Token token in Enum.GetValues(typeof(Token)))
+				{
+					foreach (var attribute in Utility.GetEnumAttributes<Token, LexerKeywordAttribute>(token))
+					{
+						if (attribute.IsLineKeyword)
+							TokenLineDictionary[attribute.Keyword] = token;
+						else
+							TokenDictionary[attribute.Keyword] = token;
+					}
+				}
+			}
+
+			if (BothModeTokens == null || StringModeTokens == null)
+			{
+				BothModeTokens = new Dictionary<char, Token>();
+				StringModeTokens = new Dictionary<char, Token>();
+
+				foreach (Token token in Enum.GetValues(typeof(Token)))
+				{
+					foreach (var attribute in Utility.GetEnumAttributes<Token, LexerCharacterAttribute>(token))
+					{
+						if ((attribute.LexerContext & LexerType.String) > 0)
+							StringModeTokens[attribute.Character] = token;
+
+						BothModeTokens[attribute.Character] = token;
+					}
+				}
+			}
+
+			TokenCharDictionary = Type == LexerType.String ? StringModeTokens : BothModeTokens;
+		}
+
+		private static Regex PowRegex = new Regex(@"(\d+)p(\d+)");
+
+		private static bool IsWhitespace(char c)
+		{
+			return char.IsWhiteSpace(c) && c != '\n';
+		}
+
+		private static bool IsEndOfLine(char c)
+		{
+			return c == '\n' || c == '\r' || c == '\0';
+		}
+
+		private Token DetermineToken(char c)
+		{
+			if (TokenCharDictionary.TryGetValue(c, out Token charToken))
+				return charToken;
+
+			switch (c)
+			{
+				case ';': //semicolon is comment
+					while (currentChar != '\n')
+					{
+						if (currentChar == '\0')
+							return Token.EOF;
+
+						GetNextChar();
+					}
+
+					return Token.NewLine;
+
+				case '[':
+					const string SkipStart = "[SKIPSTART]";
+					const string SkipEnd = "[SKIPEND]";
+
+					if (sourceMarker.Column > 1
+						|| source.Substring(sourceMarker.Pointer, SkipStart.Length) != SkipStart)
+						return Token.Unknown;
+
+					while (GetNextChar() != '\0')
+					{
+						if (currentChar == '[' && source.Substring(sourceMarker.Pointer, SkipEnd.Length) == SkipEnd)
+						{
+							while (true)
+							{
+								switch (GetNextChar())
+								{
+									case '\n':
+										return Token.NewLine;
+									case '\0':
+										return Token.EOF;
+								}
+							}
+						}
+					}
+
+					return Token.EOF;
+
+				case '%':
+					return Type == LexerType.String ? Token.Format : Token.Modulo;
+
+				case '<':
+					if (!Type.HasFlag(LexerType.Real))
+						break;
+
+					if (GetNextChar(true) == '>')
+					{
+						GetNextChar();
+						return Token.NotEqual;
+					}
+					else if (GetNextChar(true) == '=')
+					{
+						GetNextChar();
+						return Token.LessEqual;
+					}
+					else
+						return Token.Less;
+
+				case '>':
+					if (!Type.HasFlag(LexerType.Real))
+						break;
+
+					if (GetNextChar(true) == '=')
+					{
+						GetNextChar();
+						return Token.MoreEqual;
+					}
+					else
+						return Token.More;
+
+				case '+':
+					if (Type == LexerType.String)
+						return Token.Unknown;
+
+					if ((source[sourceMarker.Pointer] == '+' && source[sourceMarker.Pointer + 1] == '+') || source[sourceMarker.Pointer + 2] == '+')
+					{
+						GetNextChar();
+						return Token.Increment;
+					}
+					else
+						return Token.Plus;
+
+				case '-':
+					if (Type == LexerType.String)
+						return Token.Unknown;
+
+					if ((source[sourceMarker.Pointer] == '-' && source[sourceMarker.Pointer + 1] == '-') || source[sourceMarker.Pointer + 2] == '-')
+					{
+						GetNextChar();
+						return Token.Decrement;
+					}
+					else
+						return Token.Minus;
+
+				case '=':
+					if (Type == LexerType.String)
+						return Token.Unknown;
+
+					if ((source[sourceMarker.Pointer] == '=' && source[sourceMarker.Pointer + 1] == '=') || source[sourceMarker.Pointer + 2] == '=')
+						GetNextChar();
+
+					return Token.Equal;
+
+				case '&':
+					if ((source[sourceMarker.Pointer] == '&' && source[sourceMarker.Pointer + 1] == '&') || source[sourceMarker.Pointer + 2] == '&')
+						GetNextChar();
+
+					return Token.And;
+
+				case '|':
+					if ((source[sourceMarker.Pointer] == '|' && source[sourceMarker.Pointer + 1] == '|') || source[sourceMarker.Pointer + 2] == '|')
+						GetNextChar();
+
+					return Token.Or;
+
+				case '"':
+					string str = "";
+					while (GetNextChar() != '"')
+					{
+						if (currentChar == '\\')
+						{
+							switch (char.ToLower(GetNextChar()))
+							{
+								case 'n':
+									str += '\n';
+									break;
+								case 't':
+									str += '\t';
+									break;
+								case '\\':
+									str += '\\';
+									break;
+								case '"':
+									str += '"';
+									break;
+							}
+						}
+						else if (currentChar == '\0')
+							throw new Exception("Unexpected end of file");
+						else
+						{
+							str += currentChar;
+						}
+					}
+
+					Value = new Value(str);
+					return Token.Value;
+
+				case (char)0:
+					return Token.EOF;
+			}
+
+			return Token.Unknown;
+		}
+
+		private IEnumerator<Token> GetTokens()
+		{
+			sourceMarker = new Marker(-1, 1, 0);
+
+			while (true)
+			{
+				while (IsWhitespace(GetNextChar()) && Type != LexerType.String || currentChar == '\r')
+				{
+				}
+
+				TokenMarker = sourceMarker;
+
+				Token token = DetermineToken(currentChar);
+
+				if (token == Token.EOF)
+				{
+					yield return Token.EOF;
+					yield break;
+				}
+
+				if (token != Token.Unknown)
+				{
+					yield return token;
+					continue;
+				}
+
+				StringBuilder bodyBuilder = new StringBuilder(currentChar.ToString());
+
+				while (DetermineToken(GetNextChar(true)) == Token.Unknown
+					   && (!IsWhitespace(GetNextChar(true)) || Type == LexerType.String)
+					   && GetNextChar(true) != '\r')
+				{
+					bodyBuilder.Append(GetNextChar());
+				}
+
+				string result = bodyBuilder.ToString();
+
+				if (double.TryParse(result, NumberStyles.Float, CultureInfo.InvariantCulture, out var real))
+				{
+					Value = real;
+					yield return Token.Value;
+					continue;
+				}
+
+				if (result.StartsWith("0x") && int.TryParse(result.Replace("0x", ""), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out int hexResult))
+				{
+					Value = hexResult;
+					yield return Token.Value;
+					continue;
+				}
+
+				Match powMatch = PowRegex.Match(result);
+				if (powMatch.Success)
+				{
+					int a = int.Parse(powMatch.Groups[1].Value);
+					int b = int.Parse(powMatch.Groups[2].Value);
+
+					Value = a << b;
+					yield return Token.Value;
+					continue;
+				}
+
+				Identifer = bodyBuilder.ToString();
+
+				if (TokenDictionary.TryGetValue(Identifer, out token))
+				{
+					yield return token;
+					continue;
+				}
+
+				if (Type == LexerType.String)
+				{
+					Value = char.IsWhiteSpace(Identifer[0])
+						? Identifer.Substring(1)
+						: Identifer;
+
+					yield return Token.Value;
+					continue;
+				}
+
+				if (TokenLineDictionary.TryGetValue(Identifer, out token))
+				{
+					bodyBuilder = new StringBuilder();
+
+					while (!IsEndOfLine(GetNextChar(true)))
+						bodyBuilder.Append(GetNextChar());
+
+					yield return token;
+
+					string strValue = bodyBuilder.ToString();
+
+					if (strValue.Length > 0 && char.IsWhiteSpace(strValue[0]))
+						strValue = strValue.Substring(1);
+
+					Value = new Value(strValue);
+					yield return Token.Value;
+
+					yield return currentChar == '\0' ? Token.EOF : Token.NewLine;
+
+					continue;
+				}
+
+				yield return Token.Identifer;
+
+				if (currentChar == '\n')
+					yield return Token.NewLine;
+			}
+		}
+
+		public IEnumerator<Token> GetEnumerator()
+		{
+			return currentEnumerator;
+		}
+
+		IEnumerator IEnumerable.GetEnumerator()
+		{
+			return GetEnumerator();
+		}
+
+		private static readonly Dictionary<Token, int> OrderOfOps = new Dictionary<Token, int>
+		{
+			{ Token.Or, 0 }, { Token.And, 0 },
+			{ Token.Equal, 1 }, { Token.NotEqual, 1 },
+			{ 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 }
+		};
+
+		public Value Expression()
+		{
+			Stack<Value> stack = new Stack<Value>();
+			Stack<Token> operators = new Stack<Token>();
+
+			void Operation(Token token)
+			{
+				Value b = stack.Pop();
+				Value a = stack.Pop();
+				Value result = a.Operate(b, token);
+				stack.Push(result);
+			}
+
+			int i = 0;
+			while (true)
+			{
+				if (currentEnumerator.Current == Token.Value)
+				{
+					stack.Push(Value);
+				}
+				else if (currentEnumerator.Current == Token.Identifer)
+				{
+					if (Type == LexerType.String)
+						stack.Push(Identifer);
+					else
+						throw new ParserException("Undeclared variable " + Identifer, TokenMarker);
+				}
+				else if (currentEnumerator.Current == Token.LParen)
+				{
+					currentEnumerator.MoveNext();
+					stack.Push(Expression());
+
+					if (currentEnumerator.Current != Token.RParen)
+						throw new ParserException($"Was expecting [LParen] got [{currentEnumerator.Current}]", TokenMarker);
+				}
+				else if (Type.HasFlag(LexerType.Real) && currentEnumerator.Current.IsArithmetic()
+													  && currentEnumerator.Current.IsUnary() && (i == 0)) // || previousToken == Token.LParen))
+				{
+					stack.Push(0);
+					operators.Push(currentEnumerator.Current);
+				}
+				else if (Type == LexerType.String && currentEnumerator.Current.IsStringOp()
+						 || Type.HasFlag(LexerType.Real) && currentEnumerator.Current.IsArithmetic())
+				{
+					while (operators.Count > 0 && OrderOfOps[currentEnumerator.Current] <= OrderOfOps[operators.Peek()])
+						Operation(operators.Pop());
+					operators.Push(currentEnumerator.Current);
+				}
+				else
+				{
+					if (i == 0)
+					{
+						if (Type == LexerType.String)
+							stack.Push("");
+						else
+							throw new ParserException("Empty expression", TokenMarker);
+					}
+
+					break;
+				}
+
+				i++;
+				currentEnumerator.MoveNext();
+			}
+
+			while (operators.Count > 0)
+				Operation(operators.Pop());
+
+			return Type == LexerType.String
+				? stack.Aggregate((a, b) => b.String + a.String)
+				: stack.Pop();
+		}
+	}
+}

+ 745 - 0
NTERA.Interpreter/Compiler/Parser.cs

@@ -0,0 +1,745 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NTERA.Interpreter.Compiler
+{
+	public class Parser
+	{
+		protected Lexer Lexer { get; }
+
+		protected FunctionDefinition SelfDefinition { get; }
+
+		protected IList<FunctionDefinition> FunctionDefinitions { get; }
+
+		protected VariableDictionary GlobalVariables { get; }
+
+		protected VariableDictionary LocalVariables { get; }
+
+		protected IList<string> StringStatements { get; }
+
+		protected IDictionary<string, IList<IList<string>>> CsvDefinitions { get; }
+
+		protected IEnumerator<Token> Enumerator { get; }
+
+		protected bool hasPeeked = false;
+		protected Token peekedToken = Token.Unknown;
+
+		protected Token GetNextToken(bool peek = false)
+		{
+			if (peek && hasPeeked)
+				return peekedToken;
+
+			if (!hasPeeked)
+				Enumerator.MoveNext();
+
+			peekedToken = Enumerator.Current;
+
+			hasPeeked = peek;
+			return Enumerator.Current;
+		}
+
+		protected Marker CurrentPosition => new Marker(Lexer.TokenMarker.Pointer + SelfDefinition.Position.Pointer,
+			Lexer.TokenMarker.Line + SelfDefinition.Position.Line - 1,
+			Lexer.TokenMarker.Column);
+
+		public Parser(string input, FunctionDefinition selfDefinition, IList<FunctionDefinition> functionDefinitions, VariableDictionary globalVariables, VariableDictionary localVariables, IList<string> stringStatements, IDictionary<string, IList<IList<string>>> csvDefinitions)
+		{
+			Lexer = new Lexer(input);
+			Enumerator = Lexer.GetEnumerator();
+
+			SelfDefinition = selfDefinition;
+			FunctionDefinitions = functionDefinitions;
+			GlobalVariables = globalVariables;
+			LocalVariables = localVariables;
+			StringStatements = stringStatements;
+			CsvDefinitions = csvDefinitions;
+		}
+
+		public IEnumerable<ExecutionNode> Parse(out List<ParserError> errors)
+		{
+			errors = new List<ParserError>();
+			List<ExecutionNode> nodes = new List<ExecutionNode>();
+
+			using (Enumerator)
+			{
+				do
+				{
+					var node = ParseLine(out var error);
+
+					if (error != null)
+					{
+						errors.Add(error);
+						nodes.Add(new ExecutionNode
+						{
+							Type = "error",
+							Metadata =
+							{
+								["message"] = error.ErrorMessage,
+								["symbol"] = error.SymbolMarker.ToString()
+							},
+							Symbol = error.SymbolMarker
+						});
+
+						//resynchronize to a new line
+						while (Enumerator.MoveNext()
+							   && Enumerator.Current != Token.NewLine
+							   && Enumerator.Current != Token.EOF)
+						{
+						}
+					}
+					else if (node != null)
+					{
+						nodes.Add(node);
+					}
+
+					hasPeeked = false;
+				} while (Enumerator.MoveNext());
+			}
+
+			return nodes;
+		}
+
+		protected ExecutionNode ParseLine(out ParserError error)
+		{
+			error = null;
+
+			switch (Enumerator.Current)
+			{
+				case Token.Identifer:
+
+					if (GlobalVariables.ContainsKey(Lexer.Identifer)
+						|| LocalVariables.ContainsKey(Lexer.Identifer))
+					{
+						string variableName = Lexer.Identifer;
+						bool isGlobal = GlobalVariables.ContainsKey(variableName);
+
+						var node = new ExecutionNode
+						{
+							Type = "assignment",
+							Symbol = CurrentPosition
+						};
+
+						var variable = GetVariable(out error);
+						if (error != null)
+							return null;
+
+						if (GetNextToken() != Token.Equal
+							&& Enumerator.Current != Token.Increment
+							&& Enumerator.Current != Token.Decrement
+							&& !Enumerator.Current.IsArithmetic())
+						{
+							error = new ParserError($"Unexpected token, expecting assignment: {Enumerator.Current}", CurrentPosition);
+							return null;
+						}
+
+						ExecutionNode value = null;
+
+						if (Enumerator.Current == Token.Increment)
+						{
+							value = OperateNodes(variable, CreateConstant(1), Token.Plus);
+						}
+						else if (Enumerator.Current == Token.Decrement)
+						{
+							value = OperateNodes(variable, CreateConstant(1), Token.Minus);
+						}
+						else if (Enumerator.Current != Token.Equal)
+						{
+							Token arithmeticToken = Enumerator.Current;
+
+							if (GetNextToken() != Token.Equal)
+							{
+								error = new ParserError($"Unexpected token, expecting assignment: {Enumerator.Current}", CurrentPosition);
+								return null;
+							}
+
+							ExecutionNode newValue = Expression(out error);
+
+							value = OperateNodes(variable, newValue, arithmeticToken);
+						}
+						else
+						{
+							var type = isGlobal
+								? GlobalVariables[variableName].Type
+								: LocalVariables[variableName].Type;
+
+							value = type == ValueType.String
+								? StringExpression(out error)
+								: Expression(out error);
+						}
+
+						if (error != null)
+							return null;
+
+						node.SubNodes = new[]
+						{
+							variable,
+							new ExecutionNode
+							{
+								Type = "value",
+								SubNodes = new[] { value }
+							}
+						};
+
+						return node;
+					}
+					else if (Lexer.Identifer == "CASE")
+					{
+						var node = new ExecutionNode
+						{
+							Type = "case",
+							Symbol = CurrentPosition
+						};
+
+						var value = Expression(out error);
+						if (error != null)
+							return null;
+
+						if (Enumerator.Current == Token.NewLine
+							|| Enumerator.Current == Token.EOF)
+						{
+							node.Metadata["casetype"] = "value";
+							node.SubNodes = new[] { value };
+							return node;
+						}
+
+						if (Enumerator.Current == Token.Identifer)
+						{
+							if (Lexer.Identifer == "TO")
+							{
+								var value2 = Expression(out error);
+								if (error != null)
+									return null;
+
+								node.Metadata["casetype"] = "to";
+								node.SubNodes = new[] { value, value2 };
+								return node;
+							}
+						}
+
+						error = new ParserError($"Unexpected token: {Enumerator.Current}", CurrentPosition);
+						return null;
+					}
+					else //treat as statement
+					{
+						string statementName = Lexer.Identifer;
+
+						var node = new ExecutionNode
+						{
+							Type = "statement",
+							Metadata =
+							{
+								["name"] = statementName
+							},
+							Symbol = CurrentPosition
+						};
+
+						List<ExecutionNode> parameters = new List<ExecutionNode>();
+
+						if (StringStatements.Contains(statementName))
+						{
+							var value = StringExpression(out error);
+							if (error != null)
+								return null;
+
+							if (value != null)
+								parameters.Add(value);
+
+							node.SubNodes = parameters.ToArray();
+
+							return node;
+						}
+
+						if (GetNextToken(true) == Token.NewLine
+							|| GetNextToken(true) == Token.EOF)
+						{
+							return node;
+						}
+						else if (GetNextToken(true) == Token.Colon
+								 || GetNextToken(true) == Token.Equal)
+						{
+							error = new ParserError($"Undeclared variable: {statementName}", node.Symbol);
+							return null;
+						}
+
+						while (Enumerator.Current != Token.NewLine
+							   && Enumerator.Current != Token.EOF)
+						{
+							parameters.Add(Expression(out error));
+							if (error != null)
+							{
+								error = new ParserError($"{error.ErrorMessage} (statement [{statementName}])", error.SymbolMarker);
+								return null;
+							}
+
+							if (Enumerator.Current != Token.Comma
+								&& Enumerator.Current != Token.NewLine
+								&& Enumerator.Current != Token.EOF)
+							{
+								error = new ParserError($"Unexpected token: {Enumerator.Current}", CurrentPosition);
+								return null;
+							}
+						}
+
+						node.SubNodes = parameters.ToArray();
+
+						return node;
+					}
+
+				case Token.Function:
+				case Token.Sharp:
+					while (Enumerator.MoveNext()
+						   && Enumerator.Current != Token.NewLine
+						   && Enumerator.Current != Token.EOF)
+					{
+					}
+
+					return null;
+
+				case Token.NewLine:
+				case Token.EOF:
+					return null;
+
+				default:
+					error = new ParserError($"Unexpected token: {Enumerator.Current}", CurrentPosition);
+					return null;
+			}
+		}
+
+		protected ExecutionNode GetVariable(out ParserError error)
+		{
+			string variableName = Lexer.Identifer;
+
+			var node = new ExecutionNode
+			{
+				Type = "variable",
+				Metadata =
+				{
+					["name"] = variableName
+				},
+				Symbol = CurrentPosition
+			};
+
+			List<ExecutionNode> indices = new List<ExecutionNode>();
+
+			error = null;
+
+			while (GetNextToken(true) == Token.Colon)
+			{
+				GetNextToken();
+				var token = GetNextToken();
+
+				if (token == Token.LParen)
+				{
+					indices.Add(Expression(out error));
+					if (error != null)
+						return null;
+
+					if (Enumerator.Current != Token.RParen)
+					{
+						error = new ParserError("Invalid expression - Expected right bracket", CurrentPosition);
+						return null;
+					}
+				}
+				else if (token == Token.Value)
+				{
+					indices.Add(CreateConstant(Lexer.Value));
+				}
+				else if (token == Token.Identifer)
+				{
+					IList<IList<string>> csvTable = CsvDefinitions
+													.Where(x => x.Key.IndexOf(variableName, StringComparison.OrdinalIgnoreCase) >= 0)
+													.OrderBy(x => x.Key.Equals(variableName, StringComparison.OrdinalIgnoreCase) ? 1 : 2)
+													.FirstOrDefault().Value;
+
+					IList<string> alias = csvTable?.FirstOrDefault(x => x.Count > 1 && x[1] == Lexer.Identifer);
+
+					if (alias != null)
+					{
+						indices.Add(CreateConstant(int.Parse(alias[0])));
+						continue;
+					}
+
+					if (GlobalVariables.ContainsKey(Lexer.Identifer)
+						|| LocalVariables.ContainsKey(Lexer.Identifer))
+					{
+						var subNode = new ExecutionNode
+						{
+							Type = "variable",
+							Metadata =
+							{
+								["name"] = Lexer.Identifer
+							},
+							Symbol = CurrentPosition
+						};
+
+						indices.Add(subNode);
+						continue;
+					}
+
+					if (FunctionDefinitions.Any(x => x.Name == Lexer.Identifer))
+					{
+						indices.Add(Expression(out error));
+						if (error != null)
+							return null;
+
+						continue;
+					}
+
+					error = new ParserError($"Unknown identifier: {Lexer.Identifer}", CurrentPosition);
+					return null;
+				}
+			}
+
+			if (indices.Count > 0)
+			{
+				ExecutionNode indexNode = new ExecutionNode
+				{
+					Type = "index",
+					SubNodes = indices.ToArray()
+				};
+
+				node.SubNodes = new[] { indexNode };
+			}
+
+			return node;
+		}
+
+		protected ExecutionNode GetFunction(out ParserError error)
+		{
+			error = null;
+			Token token;
+			Marker symbolMarker = CurrentPosition;
+			List<ExecutionNode> parameters = new List<ExecutionNode>();
+
+			string functionName = Lexer.Identifer;
+
+			if (GetNextToken() != Token.LParen)
+			{
+				error = new ParserError($"Unexpected token: {Enumerator.Current}", CurrentPosition);
+				return null;
+			}
+
+			while ((token = GetNextToken(true)) == Token.Identifer
+				   || token == Token.Value
+				   || token.IsUnary())
+			{
+				parameters.Add(Expression(out error));
+				if (error != null)
+					return null;
+
+				if (Enumerator.Current != Token.Comma
+					&& Enumerator.Current != Token.RParen)
+				{
+					error = new ParserError($"Unexpected token: {Enumerator.Current}", CurrentPosition);
+					return null;
+				}
+
+				if (Enumerator.Current == Token.RParen)
+					break;
+			}
+
+			if (Enumerator.Current != Token.RParen)
+			{
+				error = new ParserError($"Unexpected token: {Enumerator.Current}", CurrentPosition);
+				return null;
+			}
+
+			var functionDefinition = FunctionDefinitions.FirstOrDefault(x => x.Name == functionName
+																			 && x.Parameters.Length >= parameters.Count);
+
+			if (functionDefinition == null)
+			{
+				error = new ParserError($"No matching method with same amount of parameters: {functionName} ({parameters.Count})", CurrentPosition);
+				return null;
+			}
+
+			return CallMethod(functionName, symbolMarker, parameters.ToArray());
+		}
+
+		private static readonly Dictionary<Token, int> OrderOfOps = new Dictionary<Token, int>
+		{
+			{ Token.Or, 0 }, { Token.And, 0 }, { Token.Not, 0 },
+			{ Token.Equal, 1 }, { Token.NotEqual, 1 },
+			{ 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 }
+		};
+
+		protected ExecutionNode Expression(out ParserError error, bool useModulo = true)
+		{
+			error = null;
+			var operators = new Stack<Token>();
+			var operands = new Stack<ExecutionNode>();
+			Token token;
+
+			void ProcessOperation(out ParserError localError)
+			{
+				localError = null;
+
+				Token op = operators.Pop();
+
+				if (op.IsUnary() && operands.Count >= 1)
+				{
+					var operand = operands.Pop();
+
+					operands.Push(new ExecutionNode
+					{
+						Type = "operation",
+						Metadata =
+						{
+							["type"] = GetOperationName(op),
+							["unary"] = "true"
+						},
+						SubNodes = new[]
+						{
+							operand
+						}
+					});
+				}
+				else if (operands.Count >= 2)
+				{
+					ExecutionNode right = operands.Pop();
+					ExecutionNode left = operands.Pop();
+
+					operands.Push(new ExecutionNode
+					{
+						Type = "operation",
+						Metadata =
+						{
+							["type"] = GetOperationName(op),
+							["unary"] = "false"
+						},
+						SubNodes = new[]
+						{
+							left,
+							right
+						}
+					});
+				}
+				else
+					localError = new ParserError("Invalid expression - not enough operands", CurrentPosition);
+			}
+
+			void AttemptUnaryConversion(out ParserError localError)
+			{
+				localError = null;
+
+				while (operators.Count > 0
+					   && operators.Peek().IsUnary())
+				{
+					ProcessOperation(out localError);
+					if (localError != null)
+						return;
+				}
+			}
+
+			while ((token = GetNextToken()) != Token.NewLine
+				   && token != Token.EOF
+				   && token != Token.Comma
+				   && token != Token.Colon
+				   && token != Token.Format
+				   && token != Token.CloseBracket
+				   && (useModulo || token != Token.Modulo))
+			{
+				if (token == Token.Value)
+				{
+					operands.Push(CreateConstant(Lexer.Value));
+
+					AttemptUnaryConversion(out error);
+					if (error != null)
+						return null;
+				}
+				else if (token == Token.Identifer)
+				{
+					if (GlobalVariables.ContainsKey(Lexer.Identifer)
+						|| LocalVariables.ContainsKey(Lexer.Identifer))
+					{
+						operands.Push(GetVariable(out error));
+						if (error != null)
+							return null;
+					}
+					else if (FunctionDefinitions.Any(x => x.Name == Lexer.Identifer))
+					{
+						operands.Push(GetFunction(out error));
+						if (error != null)
+							return null;
+					}
+					else
+					{
+						error = new ParserError($"Unknown identifier: {Lexer.Identifer}", CurrentPosition);
+						return null;
+					}
+				}
+				else if (token.IsArithmetic())
+				{
+					if (token.IsUnary())
+					{
+						operators.Push(token);
+						continue;
+					}
+
+					if (!operands.Any() && !token.IsUnary())
+					{
+						error = new ParserError($"Invalid unary operator: {token}", CurrentPosition);
+						return null;
+					}
+
+					while (operators.Any() && OrderOfOps[token] <= OrderOfOps[operators.Peek()])
+					{
+						ProcessOperation(out error);
+						if (error != null)
+							return null;
+					}
+
+					operators.Push(token);
+				}
+				else if (token == Token.LParen)
+				{
+					operands.Push(Expression(out var localError));
+					if (localError != null)
+					{
+						error = localError;
+						return null;
+					}
+				}
+				else if (token == Token.RParen)
+				{
+					break;
+				}
+				else
+				{
+					error = new ParserError($"Unexpected token: {token}", CurrentPosition);
+					return null;
+				}
+			}
+
+			while (operators.Any())
+			{
+				ProcessOperation(out error);
+				if (error != null)
+					return null;
+			}
+
+			return operands.Pop();
+		}
+
+		protected ExecutionNode StringExpression(out ParserError error)
+		{
+			error = null;
+			ExecutionNode value = null;
+
+			Lexer.Type = LexerType.String;
+
+			while (Enumerator.MoveNext()
+				   && (Enumerator.Current == Token.Value
+					   || Enumerator.Current == Token.Format
+					   || Enumerator.Current == Token.OpenBracket))
+			{
+				if (Enumerator.Current == Token.Value)
+				{
+					value = value == null
+						? CreateConstant(Lexer.Value)
+						: OperateNodes(value, CreateConstant(Lexer.Value), Token.Plus);
+				}
+				else
+				{
+					List<ExecutionNode> formatParams = new List<ExecutionNode>();
+
+					Marker symbolMarker = CurrentPosition;
+					bool isSpecialFormat = Enumerator.Current == Token.OpenBracket;
+
+					do
+					{
+						Lexer.Type = LexerType.Both;
+						var tempValue = Expression(out error, isSpecialFormat);
+						if (error != null)
+							return null;
+
+						formatParams.Add(tempValue);
+					} while (Enumerator.Current == Token.Comma);
+
+					var formattedValue = CallMethod("_FORMAT", symbolMarker, formatParams.ToArray());
+
+					value = value == null
+						? formattedValue
+						: OperateNodes(value, formattedValue, Token.Plus);
+
+					Lexer.Type = LexerType.String;
+				}
+			}
+
+			Lexer.Type = LexerType.Both;
+
+			return value;
+		}
+
+		private static readonly Dictionary<Token, string> OperationNames = new Dictionary<Token, string>
+		{
+			[Token.Plus] = "add",
+			[Token.Asterisk] = "multiply",
+			[Token.Minus] = "subtract",
+			[Token.Slash] = "divide",
+		};
+
+		private static string GetOperationName(Token token)
+		{
+			return OperationNames.TryGetValue(token, out string result)
+				? result
+				: token.ToString();
+		}
+
+		private ExecutionNode CreateConstant(Value value)
+		{
+			return new ExecutionNode
+			{
+				Type = "constant",
+				Metadata =
+				{
+					["type"] = value.Type.ToString(),
+					["value"] = value.ToString()
+				},
+				Symbol = CurrentPosition
+			};
+		}
+
+		private static ExecutionNode OperateNodes(ExecutionNode left, ExecutionNode right, Token token)
+		{
+			return new ExecutionNode
+			{
+				Type = "operation",
+				Metadata =
+				{
+					["type"] = GetOperationName(token)
+				},
+				SubNodes = new[]
+				{
+					left,
+					right
+				}
+			};
+		}
+
+		private static ExecutionNode CallMethod(string methodName, Marker symbolMarker, params ExecutionNode[] parameters)
+		{
+			return new ExecutionNode
+			{
+				Type = "call",
+				Metadata =
+				{
+					["target"] = methodName
+				},
+				Symbol = symbolMarker,
+				SubNodes = new[]
+				{
+					new ExecutionNode
+					{
+						Type = "parameters",
+						SubNodes = parameters.ToArray()
+					}
+				}
+			};
+		}
+	}
+}

+ 15 - 0
NTERA.Interpreter/Compiler/ParserError.cs

@@ -0,0 +1,15 @@
+namespace NTERA.Interpreter.Compiler
+{
+	public class ParserError
+	{
+		public string ErrorMessage { get; }
+
+		public Marker SymbolMarker { get; }
+
+		public ParserError(string message, Marker marker)
+		{
+			ErrorMessage = message;
+			SymbolMarker = marker;
+		}
+	}
+}

+ 15 - 0
NTERA.Interpreter/Compiler/ParserException.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace NTERA.Interpreter.Compiler
+{
+	public class ParserException : Exception
+	{
+		public ParserException(string message) : base(message)
+		{
+		}
+
+		public ParserException(string message, Marker marker) : base($"{message} (at {marker})")
+		{
+		}
+	}
+}

+ 264 - 0
NTERA.Interpreter/Compiler/Preprocessor.cs

@@ -0,0 +1,264 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NTERA.Interpreter.Compiler
+{
+	public static class Preprocessor
+	{
+		public static IDictionary<FunctionDefinition, string> PreprocessFile(string contents, string filename)
+		{
+			Dictionary<FunctionDefinition, string> procs = new Dictionary<FunctionDefinition, string>();
+
+			Lexer lexer = new Lexer(contents);
+
+			Marker startMarker = lexer.TokenMarker;
+			string currentDefinitionName = null;
+			List<FunctionParameter> currentDefinitionParameters = new List<FunctionParameter>();
+			List<FunctionVariable> currentDefinitionVariables = new List<FunctionVariable>();
+			bool isReturnFunction = false;
+
+			void Commit()
+			{
+				if (currentDefinitionName != null)
+				{
+					string procBody = contents.Substring(startMarker.Pointer,
+						lexer.TokenMarker.Pointer - startMarker.Pointer);
+
+					var definition = new FunctionDefinition(currentDefinitionName,
+						currentDefinitionParameters.ToArray(),
+						currentDefinitionVariables.ToArray(),
+						isReturnFunction,
+						filename,
+						startMarker);
+
+					procs.Add(definition, procBody);
+
+					isReturnFunction = false;
+					currentDefinitionName = null;
+					currentDefinitionParameters.Clear();
+				}
+			}
+
+			using (var enumerator = lexer.GetEnumerator())
+			{
+				do
+				{
+					if (lexer.TokenMarker.Column != 1)
+						continue;
+
+					if (enumerator.Current == Token.Function)
+					{
+						Commit();
+
+						startMarker = lexer.TokenMarker;
+
+						enumerator.MoveNext();
+						if (enumerator.Current != Token.Identifer)
+							throw new ParserException("Invalid function declaration - Expected an identifier", lexer.TokenMarker);
+
+						currentDefinitionName = lexer.Identifer;
+
+						enumerator.MoveNext();
+						if (enumerator.Current == Token.NewLine
+							|| enumerator.Current == Token.EOF)
+							continue;
+
+						if (enumerator.Current != Token.LParen
+							&& enumerator.Current != Token.Comma)
+							throw new ParserException("Invalid function declaration", lexer.TokenMarker);
+
+						enumerator.MoveNext();
+
+						if (enumerator.Current != Token.Identifer
+							&& enumerator.Current != Token.RParen)
+							throw new ParserException("Invalid function declaration", lexer.TokenMarker);
+
+						while (enumerator.Current == Token.Identifer)
+						{
+							string parameterName = lexer.Identifer;
+							List<string> indices = new List<string>();
+							Value? defaultValue = null;
+
+							enumerator.MoveNext();
+
+							while (enumerator.Current == Token.Colon
+								   && enumerator.MoveNext())
+							{
+								if (enumerator.Current == Token.Value)
+								{
+									indices.Add(lexer.Value.Type == ValueType.Real
+										? ((int)lexer.Value).ToString()
+										: lexer.Value.String);
+								}
+								else if (enumerator.Current == Token.Identifer)
+								{
+									indices.Add(lexer.Identifer);
+								}
+
+								enumerator.MoveNext();
+							}
+
+							if (enumerator.Current == Token.Equal)
+							{
+								enumerator.MoveNext();
+
+								bool hasUnaryMinus = false;
+
+								if (enumerator.Current == Token.Minus)
+								{
+									hasUnaryMinus = true;
+									enumerator.MoveNext();
+								}
+
+								if (enumerator.Current == Token.Identifer)
+								{
+									defaultValue = lexer.Identifer;
+								}
+								else if (enumerator.Current == Token.Value)
+								{
+									defaultValue = lexer.Value;
+
+									if (hasUnaryMinus)
+									{
+										if (defaultValue.Value.Type == ValueType.Real)
+											defaultValue = defaultValue * -1;
+										else
+											defaultValue = "-" + defaultValue;
+									}
+								}
+								else
+									throw new ParserException("Invalid function declaration", lexer.TokenMarker);
+
+								enumerator.MoveNext();
+							}
+
+							if (enumerator.Current == Token.Comma
+								|| enumerator.Current == Token.RParen)
+							{
+								enumerator.MoveNext();
+							}
+							else if (enumerator.Current != Token.NewLine
+									 && enumerator.Current != Token.EOF)
+								throw new ParserException("Invalid function declaration", lexer.TokenMarker);
+
+							currentDefinitionParameters.Add(new FunctionParameter(parameterName, indices.ToArray(), defaultValue));
+						}
+
+						if (enumerator.Current == Token.RParen)
+							enumerator.MoveNext();
+
+						if (enumerator.Current != Token.NewLine
+							&& enumerator.Current != Token.EOF)
+							throw new ParserException("Invalid function declaration", lexer.TokenMarker);
+					}
+					else if (enumerator.Current == Token.Sharp)
+					{
+						enumerator.MoveNext();
+
+						switch (enumerator.Current)
+						{
+							case Token.Dims:
+							case Token.Dim:
+							{
+								bool isString = enumerator.Current != Token.Dim;
+
+								enumerator.MoveNext();
+								VariableType variableType = VariableType.None;
+
+								while (enumerator.Current == Token.Const
+									   || enumerator.Current == Token.Ref
+									   || enumerator.Current == Token.Dynamic)
+								{
+									if (enumerator.Current == Token.Const)
+										variableType |= VariableType.Constant;
+									else if (enumerator.Current == Token.Ref)
+										variableType |= VariableType.Reference;
+									else if (enumerator.Current == Token.Dynamic)
+										variableType |= VariableType.Dynamic;
+
+									enumerator.MoveNext();
+								}
+
+								string variable = lexer.Identifer;
+
+								enumerator.MoveNext();
+								Value? defaultValue = null;
+
+								if (enumerator.Current == Token.Comma)
+								{
+									while (enumerator.MoveNext()
+										   && enumerator.Current != Token.Equal
+										   && enumerator.Current != Token.NewLine
+										   && enumerator.Current != Token.EOF)
+									{
+									}
+
+									//arraySize = (int)lexer.Expression().Real;
+								}
+
+								if (enumerator.Current == Token.Equal)
+								{
+									enumerator.MoveNext();
+
+									if (isString)
+										lexer.Type = LexerType.String;
+
+
+									defaultValue = lexer.Expression();
+
+									lexer.Type = LexerType.Both;
+								}
+								else if (enumerator.Current != Token.NewLine
+										 && enumerator.Current != Token.EOF)
+								{
+									throw new ParserException("Invalid function declaration", lexer.TokenMarker);
+								}
+
+								currentDefinitionVariables.Add(new FunctionVariable(variable,
+									isString ? ValueType.String : ValueType.Real,
+									variableType,
+									defaultValue));
+
+								break;
+							}
+							case Token.ReturnFunction:
+							{
+								isReturnFunction = true;
+								break;
+							}
+						}
+					}
+				} while (enumerator.MoveNext());
+			}
+
+			Commit();
+
+			return procs;
+		}
+
+		public static IList<IList<string>> ProcessCSV(IEnumerable<string> lines)
+		{
+			List<IList<string>> csv = new List<IList<string>>();
+
+			foreach (var line in lines)
+			{
+				if (string.IsNullOrWhiteSpace(line)
+					|| line[0] == ';')
+					continue;
+
+				string newLine = line;
+
+				int commentIndex = line.IndexOf(';');
+
+				if (commentIndex >= 0)
+					newLine = line.Substring(0, commentIndex);
+
+				string[] split = newLine.Split(',');
+
+				csv.Add(split.ToList());
+			}
+
+			return csv;
+		}
+	}
+}

+ 119 - 0
NTERA.Interpreter/Compiler/Token.cs

@@ -0,0 +1,119 @@
+namespace NTERA.Interpreter.Compiler
+{
+	public enum Token
+	{
+		Unknown = 0,
+
+		Identifer,
+		Value,
+
+		[LexerCharacter('#', LexerType.Real)]
+		Sharp,
+
+		[LexerCharacter('@', LexerType.Real)]
+		Function,
+
+		[LexerKeyword("DIM")]
+		Dim,
+
+		[LexerKeyword("DIMS")]
+		Dims,
+
+		[LexerKeyword("CONST")]
+		Const,
+
+		[LexerKeyword("REF")]
+		Ref,
+
+		[LexerKeyword("DYNAMIC")]
+		Dynamic,
+
+		[LexerKeyword("FUNCTION")]
+		[LexerKeyword("FUNCTIONS")]
+		ReturnFunction,
+
+		[LexerCharacter('\n', LexerType.Both)]
+		NewLine,
+
+		[LexerCharacter(':', LexerType.Real)]
+		Colon,
+
+		[LexerCharacter(',', LexerType.Real)]
+		Comma,
+
+		[LexerCharacter('{', LexerType.Both)]
+		OpenBracket,
+
+		[LexerCharacter('}', LexerType.Both)]
+		CloseBracket,
+		Format,
+		Modulo,
+
+		Plus,
+		Increment,
+		Minus,
+		Decrement,
+
+		[LexerCharacter('/', LexerType.Real)]
+		Slash,
+
+		[LexerCharacter('*', LexerType.Real)]
+		Asterisk,
+
+		[LexerCharacter('^', LexerType.Real)]
+		Caret,
+		Equal,
+		Less,
+		More,
+		NotEqual,
+		LessEqual,
+		MoreEqual,
+		Or,
+		And,
+
+		[LexerCharacter('!', LexerType.Real)]
+		Not,
+
+		[LexerCharacter('(', LexerType.Real)]
+		LParen,
+
+		[LexerCharacter(')', LexerType.Real)]
+		RParen,
+
+		EOF = -1 //End Of File
+	}
+
+	public static class TokenEnumExtensions
+	{
+		public static bool IsUnary(this Token token)
+		{
+			return token == Token.Plus
+				   || token == Token.Minus
+				   || token == Token.Not;
+		}
+
+		public static bool IsArithmetic(this Token token)
+		{
+			return token == Token.Plus
+				   || token == Token.Minus
+				   || token == Token.Slash
+				   || token == Token.Asterisk
+				   || token == Token.Modulo
+				   || token == Token.Caret
+				   || token == Token.Equal
+				   || token == Token.NotEqual
+				   || token == Token.Less
+				   || token == Token.LessEqual
+				   || token == Token.More
+				   || token == Token.MoreEqual
+				   || token == Token.Or
+				   || token == Token.And
+				   || token == Token.Not;
+		}
+
+		public static bool IsStringOp(this Token token)
+		{
+			return token == Token.Plus;
+		}
+	}
+}

+ 0 - 145
NTERA.Interpreter/Engine.cs

@@ -1,145 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.IO;
-using NTERA.Core;
-using NTERA.EmuEra.Game.EraEmu.Content;
-
-namespace NTERA.Interpreter
-{
-	public class Engine : IScriptEngine
-	{
-        public IConsole Console { get; protected set; }
-
-        public string EntrypointPath { get; protected set; }
-
-        public Dictionary<string, Interpreter> Procedures = new Dictionary<string, Interpreter>();
-
-        public Stack<Interpreter> CallStack = new Stack<Interpreter>();
-
-	    public Interpreter CurrentParser => CallStack.Count > 0 ? CallStack.Peek() : null;
-
-		public bool Initialize(IConsole console)
-		{
-            Console = console;
-
-            EntrypointPath = @"M:\era\eraSemifullTest\erb\SYSTEM_TITLE.ERB"; //@"M:\era\eraSemifullTest\erb\TWTITLE.txt";
-
-		    Preprocess(File.ReadAllText(EntrypointPath));
-
-		    CallStack.Push(new Interpreter(console, File.ReadAllText(EntrypointPath)));
-
-            return true;
-        }
-
-		public void Start()
-		{
-            CurrentParser.Exec();
-		}
-
-		public void InputString(string input)
-		{
-			throw new NotImplementedException();
-		}
-
-		public void InputInteger(long input)
-		{
-			throw new NotImplementedException();
-		}
-
-		public void InputSystemInteger(long input)
-		{
-			throw new NotImplementedException();
-		}
-
-		public CroppedImage GetImage(string name)
-		{
-		    var bitmap = new Bitmap(@"M:\era\eraSemifullTest\resources\bbb.png");
-            return new CroppedImage(name, bitmap, new Rectangle(Point.Empty, bitmap.Size), false);
-		}
-
-	    public Dictionary<string, Interpreter> Preprocess(string contents)
-	    {
-            Dictionary<string, Interpreter> procs = new Dictionary<string, Interpreter>();
-
-	        Lexer lexer = new Lexer(contents);
-
-	        Marker startMarker = lexer.TokenMarker;
-	        string currentProc = null;
-            VariableDictionary locals = new VariableDictionary();
-
-	        void Commit()
-	        {
-	            if (currentProc != null)
-	            {
-	                string procBody = contents.Substring(startMarker.Pointer,
-	                    lexer.TokenMarker.Pointer - startMarker.Pointer);
-
-	                var interpreter = new Interpreter(Console, procBody)
-	                {
-	                    Variables = locals
-	                };
-
-	                procs.Add(currentProc, interpreter);
-	            }
-            }
-
-	        using (var enumerator = lexer.GetEnumerator())
-	            do
-	            {
-	                if (enumerator.Current == Token.Function)
-	                {
-	                    Commit();
-
-	                    enumerator.MoveNext();
-	                    if (enumerator.Current != Token.Identifer)
-	                        throw new InvalidOperationException();
-
-	                    currentProc = lexer.Identifer;
-	                }
-	                else if (enumerator.Current == Token.Sharp)
-	                {
-	                    enumerator.MoveNext();
-
-	                    switch (enumerator.Current)
-	                    {
-	                        case Token.Dim:
-	                        {
-	                            bool isString = enumerator.Current != Token.Dim;
-
-	                            enumerator.MoveNext();
-
-	                            while (enumerator.MoveNext() && lexer.Identifer == "CONST")
-	                            {
-	                            }
-
-	                            string variable = lexer.Identifer;
-
-	                            enumerator.MoveNext();
-	                            enumerator.MoveNext();
-
-	                            if (isString)
-	                                lexer.Type = LexerType.String;
-
-	                            locals[variable] = lexer.Expression();
-
-	                            lexer.Type = LexerType.Both;
-
-	                            break;
-	                        }
-	                        case Token.ReturnFunction:
-	                        {
-	                            break;
-	                        }
-	                    }
-	                }
-
-                    
-	            } while (enumerator.MoveNext());
-
-            Commit();
-
-	        return procs;
-	    }
-	}
-}

+ 0 - 177
NTERA.Interpreter/Interpreter.cs

@@ -1,177 +0,0 @@
-using System;
-using System.Collections.Generic;
-using NTERA.Core;
-
-namespace NTERA.Interpreter
-{
-    public partial class Interpreter
-    {
-        protected Lexer Lexer { get; set; }
-        private Token previousToken;
-        private Token CurrentToken => Tokens.Current;
-
-        public VariableDictionary Variables { get; internal set; } = new VariableDictionary();
-        protected Dictionary<string, Marker> Loops { get; } = new Dictionary<string, Marker>();
-
-        public delegate Value BasicFunction(List<Value> args);
-
-        protected readonly IConsole Console;
-
-        protected Marker LineMarker;
-
-        private bool exit;
-
-        public Interpreter(IConsole console, string input)
-        {
-            this.Console = console;
-            Lexer = new Lexer(input);
-
-            GenerateKeywordDictionary();
-            GenerateFunctionDictionary();
-        }
-
-        private void Error(string text)
-        {
-            throw new ParserException(text, LineMarker);
-        }
-
-        protected bool TryAssertToken(Token tok, bool pullNext = true)
-        {
-            if (pullNext)
-                GetNextToken();
-
-            return CurrentToken == tok;
-        }
-
-        protected void AssertToken(Token tok, bool pullNext = true)
-        {
-            if (!TryAssertToken(tok, pullNext))
-                Error("Expect " + tok + " got " + CurrentToken);
-        }
-
-        protected IEnumerator<Token> Tokens;
-
-        public void Exec()
-        {
-            exit = false;
-
-            Tokens = Lexer.GetEnumerator();
-
-            while (!exit)
-                Line();
-        }
-
-        protected Token GetNextToken()
-        {
-            previousToken = CurrentToken;
-
-            Tokens.MoveNext();
-
-            if (CurrentToken == Token.EOF && previousToken == Token.EOF)
-                Error("Unexpected end of file");
-            
-            return CurrentToken;
-        }
-
-        private void Line()
-        {
-            while (CurrentToken == Token.NewLine)
-                GetNextToken();
-
-            if (CurrentToken == Token.EOF)
-            {
-                exit = true;
-                return;
-            }
-
-            LineMarker = Lexer.TokenMarker;
-            Statement();
-
-            if (CurrentToken != Token.NewLine && CurrentToken != Token.EOF)
-                Error("Expect new line got " + CurrentToken);
-        }
-
-        private void Statement()
-        {
-            Token keyword = CurrentToken;
-            GetNextToken();
-
-            if (KeywordMethods.ContainsKey(keyword))
-                KeywordMethods[keyword]();
-            else
-            {
-                switch (keyword)
-                {
-                    case Token.Input:
-                        Input();
-                        break;
-                        
-                    case Token.Function:
-                        while (GetNextToken() != Token.NewLine) { }
-                        break;
-
-                    case Token.Identifer:
-                        if (CurrentToken != Token.Equal && CurrentToken != Token.Colon)
-                            Error("Expected assignment got " + CurrentToken);
-                        
-                        Let();
-                        break;
-                    case Token.EOF:
-                        exit = true;
-                        break;
-                    default:
-                        Error("Expect keyword got " + keyword);
-                        break;
-                }
-            }
-        }
-
-        public Dictionary<string, BasicFunction> FunctionDictionary { get; protected set; }
-
-        public Dictionary<Token, Action> KeywordMethods { get; set; }
-
-
-        private void Input()
-        {
-            while (true)
-            {
-                AssertToken(Token.Identifer);
-               
-                if (!Variables.ContainsKey(Lexer.Identifer)) Variables.Add(Lexer.Identifer, new Value());
-
-                Console.WaitInput(new Core.Interop.InputRequest() { InputType = Core.Interop.InputType.StrValue });
-                string input = Console.LastInput;
-
-                if (double.TryParse(input, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var d))
-                    Variables[Lexer.Identifer] = new Value(d);
-                else
-                    Variables[Lexer.Identifer] = new Value(input);
-                
-                if (!TryAssertToken(Token.Comma))
-                    break;
-
-                GetNextToken();
-            }
-        }
-
-        private Value RealExpression()
-        {
-            return Lexer.Expression(this);
-        }
-
-        private Value RealExpression(string input)
-        {
-            return new Lexer(input).Expression(this);
-        }
-
-        private Value StringExpression()
-        {
-            Lexer.Type = LexerType.String;
-            GetNextToken();
-            var result = Lexer.Expression(this);
-            Lexer.Type = LexerType.Real;
-
-            return result;
-        }
-    }
-}

+ 0 - 81
NTERA.Interpreter/Interpreter/BuiltInFunctions.cs

@@ -1,81 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-
-namespace NTERA.Interpreter
-{
-    public partial class Interpreter
-    {
-        private void GenerateFunctionDictionary()
-        {
-            FunctionDictionary = new Dictionary<string, BasicFunction>(StringComparer.InvariantCultureIgnoreCase);
-
-            foreach (var method in typeof(Interpreter).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
-            {
-                var attribute = (BuiltInFunctionAttribute)method.GetCustomAttributes(typeof(BuiltInFunctionAttribute), true).FirstOrDefault();
-
-                if (attribute == null)
-                    continue;
-
-                FunctionDictionary[attribute.Name] = args => (Value)method.Invoke(this, new object[] { args });
-            }
-        }
-
-        [BuiltInFunction("abs")]
-        private Value Abs(List<Value> args)
-        {
-            if (args.Count != 1)
-                throw new ArgumentException();
-
-            return new Value(Math.Abs(args[0].Real));
-        }
-
-        [BuiltInFunction("min")]
-        private Value Min(List<Value> args)
-        {
-            if (args.Count != 2)
-                throw new ArgumentException();
-
-            return new Value(Math.Min(args[0].Real, args[1].Real));
-        }
-
-        [BuiltInFunction("max")]
-        private Value Max(List<Value> args)
-        {
-            if (args.Count != 2)
-                throw new ArgumentException();
-
-            return new Value(Math.Max(args[0].Real, args[1].Real));
-        }
-
-        [BuiltInFunction("not")]
-        private Value Not(List<Value> args)
-        {
-            if (args.Count != 1)
-                throw new ArgumentException();
-
-            return new Value(args[0].Real == 0 ? 1 : 0);
-        }
-
-        private readonly Random random = new Random();
-
-        [BuiltInFunction("rand")]
-        private Value Rand(List<Value> args)
-        {
-            if (args.Count != 2)
-                throw new ArgumentException();
-
-            return new Value(random.Next((int)args[0].Real, (int)args[1].Real - 1));
-        }
-
-        [BuiltInFunction("tostr")]
-        private Value ToStr(List<Value> args)
-        {
-            if (args.Count != 2)
-                throw new ArgumentException();
-
-            return ((int)args[0].Real).ToString(args[1].String);
-        }
-    }
-}

+ 0 - 359
NTERA.Interpreter/Interpreter/Keywords.cs

@@ -1,359 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Linq;
-using System.Reflection;
-using System.Text.RegularExpressions;
-using NTERA.Core.Interop;
-
-namespace NTERA.Interpreter
-{
-    public partial class Interpreter
-    {
-        private void GenerateKeywordDictionary()
-        {
-            KeywordMethods = new Dictionary<Token, Action>();
-
-            foreach (var method in typeof(Interpreter).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
-            {
-                var attribute = method.GetCustomAttributes(typeof(KeywordMethodAttribute), true).FirstOrDefault() as KeywordMethodAttribute;
-
-                if (attribute == null)
-                    continue;
-
-                KeywordMethods[attribute.Token] = () => method.Invoke(this, null);
-            }
-        }
-
-        #region Printing
-
-        [KeywordMethod(Token.Print)]
-        private void Print()
-        {
-            Console.Write(RealExpression().ToString());
-        }
-
-        [KeywordMethod(Token.PrintL)]
-        private void PrintL()
-        {
-            Console.PrintSingleLine(ParseFormat(new Lexer(Lexer.Value, LexerType.String).Expression(this)));
-
-            GetNextToken();
-        }
-        
-        [KeywordMethod(Token.PrintHtml)]
-        private void PrintHtml()
-        {
-            AssertToken(Token.Value, false);
-
-            Console.PrintHtml(ParseFormat(new Lexer(Lexer.Value, LexerType.String).Expression(this)), true);
-
-            GetNextToken();
-        }
-        
-        [KeywordMethod(Token.PrintImg)]
-        private void PrintImg()
-        {
-            Console.PrintImg(RealExpression().ToString().Trim().Trim('"'));
-        }
-        
-        [KeywordMethod(Token.PrintButton)]
-        private void PrintButton()
-        {
-            string text = Lexer.Value;
-            
-            AssertToken(Token.Comma);
-            GetNextToken();
-
-            var value = RealExpression();
-
-            Console.PrintButton(text, (long)value.Real);
-        }
-
-        private static readonly Regex RealFormatRegex = new Regex("{(.*?)}");
-        private static readonly Regex StringFormatRegex = new Regex("%(.*?)%");
-
-        private string ParseFormat(string rawInput)
-        {
-            var realEvaluator = new MatchEvaluator(match => new Lexer(match.Groups[1].Value).Expression(this));
-
-            string reals = RealFormatRegex.Replace(rawInput, realEvaluator);
-
-            return StringFormatRegex.Replace(reals, realEvaluator);
-        }
-
-        [KeywordMethod(Token.PrintForm)]
-        private void PrintForm()
-        {
-            AssertToken(Token.Value, false);
-
-            Console.Write(ParseFormat(Lexer.Value));
-
-            GetNextToken();
-        }
-
-        [KeywordMethod(Token.PrintFormL)]
-        private void PrintFormL()
-        {
-            AssertToken(Token.Value, false);
-
-            Console.PrintSingleLine(ParseFormat(Lexer.Value));
-
-            GetNextToken();
-        }
-
-        [KeywordMethod(Token.DrawLine)]
-        private void DrawLine()
-        {
-            Console.PrintBar();
-        }
-
-        [KeywordMethod(Token.DrawLineForm)]
-        private void DrawLineForm()
-        {
-            Console.printCustomBar(RealExpression().ToString().Trim());
-        }
-
-        [KeywordMethod(Token.CustomDrawLine)]
-        private void CustomDrawLine()
-        {
-            Console.printCustomBar(RealExpression().ToString().Trim());
-        }
-
-        [KeywordMethod(Token.Alignment)]
-        private void Alignment()
-        {
-            AssertToken(Token.Value, false);
-
-            Console.Alignment = (DisplayLineAlignment)Enum.Parse(typeof(DisplayLineAlignment), Lexer.Value);
-
-            GetNextToken();
-        }
-
-        [KeywordMethod(Token.SetColor)]
-        private void SetColor()
-        {
-            if (TryAssertToken(Token.Identifer, false))
-            {
-                int argb = (int)((int)RealExpression().Real | 0xFF000000);
-                Color c = Color.FromArgb(argb);
-
-                Console.SetStringStyle(c);
-                return;
-            }
-
-            AssertToken(Token.Value, false);
-
-            if (TryAssertToken(Token.Comma))
-            {
-                int r = (int)Lexer.Value.Real;
-
-                AssertToken(Token.Value);
-
-                int g = (int)Lexer.Value;
-
-                AssertToken(Token.Comma);
-                AssertToken(Token.Value);
-
-                int b = (int)Lexer.Value;
-
-                Console.SetStringStyle(Color.FromArgb(r, g, b));
-
-                GetNextToken();
-            }
-            else
-            {
-                int argb = (int)((int)Lexer.Value.Real | 0xFF000000);
-                Color c = Color.FromArgb(argb);
-
-                Console.SetStringStyle(c);
-            }
-        }
-
-        [KeywordMethod(Token.ResetColor)]
-        private void ResetColor()
-        {
-            Console.ResetStyle();
-
-            GetNextToken();
-        }
-
-        #endregion
-
-        #region Control
-
-        [KeywordMethod(Token.If)]
-        private void If()
-        {
-            bool result = RealExpression();
-
-            //needs to be redone
-        }
-
-        [KeywordMethod(Token.Else)]
-        private void Else()
-        {
-            //needs to be redone
-        }
-
-        [KeywordMethod(Token.EndIf)]
-        private void EndIf()
-        {
-
-        }
-
-        [KeywordMethod(Token.End)]
-        private void End()
-        {
-            exit = true;
-        }
-
-        [KeywordMethod(Token.Let)]
-        private void Let()
-        {
-            int arrayIndex = 0;
-            bool appending = false;
-
-            if (CurrentToken == Token.Colon)
-            {
-                GetNextToken();
-                if (CurrentToken == Token.Value)
-                    arrayIndex = (int)Lexer.Value.Real;
-                else
-                {
-                    arrayIndex = (int)RealExpression(Lexer.Identifer).Real;
-                }
-
-                GetNextToken();
-            }
-
-            if (CurrentToken == Token.Append)
-            {
-                appending = true;
-            }
-            else
-            {
-                AssertToken(Token.Equal, false);
-            }
-
-            string id = Lexer.Identifer;
-
-
-            var typeHint = Variables[id].Type;
-            Value value;
-
-
-            if (typeHint == ValueType.Real)
-            {
-                GetNextToken();
-                value = RealExpression();
-            }
-            else
-                value = ParseFormat(StringExpression());
-
-
-            if (appending)
-                Variables[id, arrayIndex] += value;
-            else
-                Variables[id, arrayIndex] = value;
-        }
-
-        [KeywordMethod(Token.For)]
-        private void For()
-        {
-            AssertToken(Token.Identifer, false);
-            string var = Lexer.Identifer;
-
-            AssertToken(Token.Equal);
-
-            GetNextToken();
-            Value v = RealExpression();
-
-            if (Loops.ContainsKey(var))
-            {
-                Loops[var] = LineMarker;
-            }
-            else
-            {
-                Variables[var] = v;
-                Loops.Add(var, LineMarker);
-            }
-
-            AssertToken(Token.To, false);
-
-            GetNextToken();
-            v = RealExpression();
-
-            if (Variables[var].Operate(v, Token.More).Real == 1)
-            {
-                while (true)
-                {
-                    while (!(GetNextToken() == Token.Identifer && previousToken == Token.Next)) { }
-
-                    if (Lexer.Identifer == var)
-                    {
-                        Loops.Remove(var);
-                        AssertToken(Token.NewLine);
-                        break;
-                    }
-                }
-            }
-        }
-
-        [KeywordMethod(Token.Next)]
-        private void Next()
-        {
-            AssertToken(Token.Identifer, false);
-            string var = Lexer.Identifer;
-            Variables[var] = Variables[var].Operate(new Value(1), Token.Plus);
-            Lexer.GoTo(new Marker(Loops[var].Pointer - 1, Loops[var].Line, Loops[var].Column - 1));
-        }
-
-        [KeywordMethod(Token.Times)]
-        private void Times()
-        {
-            AssertToken(Token.Identifer, false);
-
-            string var = Lexer.Identifer;
-
-            AssertToken(Token.Comma);
-
-            GetNextToken();
-            var arg2 = RealExpression();
-
-            Variables[var] = Variables[var].Operate(arg2, Token.Asterisk);
-        }
-
-        #endregion
-
-        #region Global
-
-        [KeywordMethod(Token.Sharp)]
-        void Global()
-        {
-            if (TryAssertToken(Token.Dim, false))
-            {
-                if (TryAssertToken(Token.Const))
-                {
-                    GetNextToken();
-                }
-
-                AssertToken(Token.Identifer, false);
-
-                string identifier = Lexer.Identifer;
-
-                AssertToken(Token.Equal);
-                GetNextToken();
-
-                Variables[identifier] = Lexer.Value;
-
-                GetNextToken();
-                return;
-            }
-
-            throw new Exception("Unknown global evalution");
-        }
-
-        #endregion
-    }
-}

+ 0 - 52
NTERA.Interpreter/Interpreter/VariableDictionary.cs

@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace NTERA.Interpreter
-{
-    public class VariableDictionary : Dictionary<string, Dictionary<int, Value>>
-    {
-        public VariableDictionary() : base(StringComparer.InvariantCultureIgnoreCase)
-        {
-            InitDefaults();
-        }
-
-        public new Value this[string key]
-        {
-            get => this[key, 0];
-            set => this[key, 0] = value;
-        }
-
-        public Value this[string key, int arrayIndex]
-        {
-            get => base[key][arrayIndex];
-            set
-            {
-                if (base.TryGetValue(key, out var dict))
-                    dict[arrayIndex] = value;
-                else
-                    Add(key, arrayIndex, value);
-            }
-        }
-
-        public void Add(string key, Value value)
-        {
-            Add(key, 0, value);
-        }
-
-        public void Add(string key, int arrayIndex, Value value)
-        {
-            base.Add(key, new Dictionary<int, Value>
-            {
-                [arrayIndex] = value
-            });
-        }
-
-        private void InitDefaults()
-        {
-            Add("LOCAL", 0);
-            Add("LOCALS", "");
-            Add("RESULT", 0);
-            Add("RESULTS", "");
-        }
-    }
-}

+ 0 - 427
NTERA.Interpreter/Lexer.cs

@@ -1,427 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Text;
-
-namespace NTERA.Interpreter
-{
-    public class Lexer : IEnumerable<Token>
-    {
-        private readonly string source;
-        private Marker sourceMarker;
-        private char currentChar;
-
-        private IEnumerator<Token> currentEnumerator;
-
-        private LexerType _type;
-        public LexerType Type
-        {
-            get => _type;
-            internal set
-            {
-                _type = value;
-                InitTokenDictionaries();
-            }
-        }
-
-        public Marker TokenMarker { get; set; }
-
-        public string Identifer { get; set; }
-        public Value Value { get; set; }
-
-        public Lexer(string input, LexerType type = LexerType.Both)
-        {
-            Type = type;
-
-            source = input;
-            sourceMarker = new Marker(-1, 1, 0);
-
-            currentEnumerator = GetTokens();
-            currentEnumerator.MoveNext();
-        }
-
-        public void GoTo(Marker marker)
-        {
-            sourceMarker = marker;
-        }
-
-        char GetNextChar(bool peek = false)
-        {
-            if (sourceMarker.Pointer + 1 >= source.Length)
-            {
-                sourceMarker.Pointer = source.Length;
-                return currentChar = (char)0;
-            }
-
-            if (peek)
-                return currentChar = source[sourceMarker.Pointer + 1];
-
-            sourceMarker.Column++;
-            sourceMarker.Pointer++;
-
-            if ((currentChar = source[sourceMarker.Pointer]) == '\n')
-            {
-                sourceMarker.Column = 1;
-                sourceMarker.Line++;
-            }
-            return currentChar;
-        }
-
-        private Dictionary<string, Token> TokenDictionary;
-
-        private Dictionary<string, Token> TokenLineDictionary;
-
-        private Dictionary<char, Token> TokenCharDictionary;
-
-        private void InitTokenDictionaries()
-        {
-            TokenDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
-            TokenLineDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
-            TokenCharDictionary = new Dictionary<char, Token>();
-
-            foreach (Token token in Enum.GetValues(typeof(Token)))
-            {
-                foreach (var attribute in Utility.GetEnumAttributes<Token, LexerKeywordAttribute>(token))
-                {
-                    if (attribute.IsLineKeyword)
-                        TokenLineDictionary[attribute.Keyword] = token;
-                    else
-                        TokenDictionary[attribute.Keyword] = token;
-                }
-
-                foreach (var attribute in Utility.GetEnumAttributes<Token, LexerCharacterAttribute>(token))
-                {
-                    if ((attribute.LexerContext & Type) > 0)
-                        TokenCharDictionary[attribute.Character] = token;
-                }
-            }
-        }
-
-        private static bool IsWhitespace(char c)
-        {
-            return char.IsWhiteSpace(c) && c != '\n';
-        }
-
-        private static bool IsEndOfLine(char c)
-        {
-            return c == '\n' || c == '\r' || c == '\0';
-        }
-
-        private static bool IsEscape(char c)
-        {
-            return c == '%' || c == '{';
-        }
-
-        private Token DetermineToken(char c)
-        {
-            if (TokenCharDictionary.TryGetValue(currentChar, out Token charToken))
-                return charToken;
-
-            switch (currentChar)
-            {
-                case ';': //semicolon is comment
-                    while (currentChar != '\n')
-                        GetNextChar();
-
-                    return Token.NewLine;
-                case '<':
-                    if (!Type.HasFlag(LexerType.Real))
-                        break;
-
-                    if (GetNextChar(true) == '>')
-                    {
-                        GetNextChar();
-                        return Token.NotEqual;
-                    }
-                    else if (GetNextChar(true) == '=')
-                    {
-                        GetNextChar();
-                        return Token.LessEqual;
-                    }
-                    else
-                        return Token.Less;
-
-                case '>':
-                    if (!Type.HasFlag(LexerType.Real))
-                        break;
-
-                    if (GetNextChar(true) == '=')
-                    {
-                        GetNextChar();
-                        return Token.MoreEqual;
-                    }
-                    else
-                        return Token.More;
-
-                case '+':
-                    if (GetNextChar(true) == '=')
-                    {
-                        GetNextChar();
-                        return Token.Append;
-                    }
-                    else
-                        return Token.Plus;
-
-                case '%':
-
-                    StringBuilder builder = new StringBuilder();
-                    while (GetNextChar() != '%')
-                        builder.Append(currentChar);
-
-                    Value = $"%{builder}%";
-                    return Token.Value;
-
-                case '"':
-                    string str = "";
-                    while (GetNextChar() != '"')
-                    {
-                        if (currentChar == '\\')
-                        {
-                            switch (char.ToLower(GetNextChar()))
-                            {
-                                case 'n': str += '\n'; break;
-                                case 't': str += '\t'; break;
-                                case '\\': str += '\\'; break;
-                                case '"': str += '"'; break;
-                            }
-                        }
-                        else
-                        {
-                            str += currentChar;
-                        }
-                    }
-                    Value = new Value(str);
-                    return Token.Value;
-
-                case (char)0:
-                    return Token.EOF;
-            }
-
-            return Token.Unknown;
-        }
-
-        private IEnumerator<Token> GetTokens()
-        {
-            sourceMarker = new Marker(-1, 1, 0);
-
-            while (true)
-            {
-                while (IsWhitespace(GetNextChar()) && Type != LexerType.String || currentChar == '\r') { }
-
-                TokenMarker = sourceMarker;
-
-                Token token = DetermineToken(currentChar);
-
-                if (token == Token.EOF)
-                {
-                    yield return Token.EOF;
-                    yield break;
-                }
-
-                if (token != Token.Unknown)
-                {
-                    yield return token;
-                    continue;
-                }
-
-                StringBuilder bodyBuilder = new StringBuilder(currentChar.ToString());
-
-                while ((!IsEscape(GetNextChar(true)) || Type != LexerType.String)
-                        && DetermineToken(GetNextChar(true)) == Token.Unknown
-                        && (!IsWhitespace(GetNextChar(true)) || Type == LexerType.String)
-                        && GetNextChar(true) != '\r')
-                {
-                    bodyBuilder.Append(GetNextChar());
-                }
-
-                string result = bodyBuilder.ToString();
-
-                if (double.TryParse(result, NumberStyles.Float, CultureInfo.InvariantCulture, out var real))
-                {
-                    Value = real;
-                    yield return Token.Value;
-                    continue;
-                }
-
-                if (result.StartsWith("0x") && int.TryParse(result.Replace("0x", ""), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out int hexResult))
-                {
-                    Value = hexResult;
-                    yield return Token.Value;
-                    continue;
-                }
-
-                Identifer = bodyBuilder.ToString();
-
-                if (TokenDictionary.TryGetValue(Identifer, out token))
-                {
-                    yield return token;
-                    continue;
-                }
-
-                if (Type == LexerType.String && char.IsWhiteSpace(Identifer[0]))
-                    Identifer = Identifer.Substring(1);
-
-                if (TokenLineDictionary.TryGetValue(Identifer, out token))
-                {
-                    bodyBuilder = new StringBuilder();
-
-                    while (!IsEndOfLine(GetNextChar(true)))
-                        bodyBuilder.Append(GetNextChar());
-                    
-                    yield return token;
-
-                    string strValue = bodyBuilder.ToString();
-
-                    if (strValue.Length > 0 && char.IsWhiteSpace(strValue[0]))
-                        strValue = strValue.Substring(1);
-
-                    Value = new Value(strValue);
-                    yield return Token.Value;
-
-                    yield return currentChar == '\0' ? Token.EOF : Token.NewLine;
-
-                    continue;
-                }
-
-                yield return Token.Identifer;
-            }
-        }
-
-        public IEnumerator<Token> GetEnumerator()
-        {
-            return currentEnumerator;
-        }
-
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return GetEnumerator();
-        }
-
-        private static readonly Dictionary<Token, int> OrderOfOps = new Dictionary<Token, int>
-        {
-            { Token.Or, 0 }, { Token.And, 0 },
-            { Token.Equal, 1 }, { Token.NotEqual, 1 },
-            { 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 }
-        };
-
-        public Value Expression(Interpreter context = null)
-        {
-            Stack<Value> stack = new Stack<Value>();
-            Stack<Token> operators = new Stack<Token>();
-
-            void Operation(Token token)
-            {
-                Value b = stack.Pop();
-                Value a = stack.Pop();
-                Value result = a.Operate(b, token);
-                stack.Push(result);
-            }
-
-            int i = 0;
-            while (true)
-            {
-                if (currentEnumerator.Current == Token.Value)
-                {
-                    stack.Push(Value);
-                }
-                else if (currentEnumerator.Current == Token.Identifer)
-                {
-                    if (context != null)
-                    {
-                        if (context.Variables.ContainsKey(Identifer))
-                        {
-                            string varName = Identifer;
-                            int index = 0;
-                            currentEnumerator.MoveNext();
-                            if (currentEnumerator.Current == Token.Colon)
-                            {
-                                currentEnumerator.MoveNext();
-                                index = (int)Expression(context).Real;
-                            }
-
-                            stack.Push(context.Variables[varName, index]);
-
-                            i++;
-                            continue;
-                        }
-
-                        if (context.FunctionDictionary.ContainsKey(Identifer))
-                        {
-                            string name = Identifer;
-                            List<Value> args = new List<Value>();
-                            currentEnumerator.MoveNext();
-
-                            if (currentEnumerator.Current != Token.LParen)
-                                throw new ParserException($"Was expecting [LParen] got [{currentEnumerator.Current}]", TokenMarker);
-
-                            while (currentEnumerator.MoveNext() && currentEnumerator.Current != Token.RParen)
-                            {
-                                args.Add(Expression(context));
-                                if (currentEnumerator.Current != Token.Comma)
-                                    break;
-                            }
-
-                            stack.Push(context.FunctionDictionary[name](args));
-                            currentEnumerator.MoveNext();
-                            i++;
-                            continue;
-                        }
-                    }
-                    
-                    if (Type == LexerType.String)
-                        stack.Push(Identifer);
-                    else
-                        throw new ParserException("Undeclared variable " + Identifer, TokenMarker);
-                }
-                else if (currentEnumerator.Current == Token.LParen)
-                {
-                    currentEnumerator.MoveNext();
-                    stack.Push(Expression());
-
-                    if (currentEnumerator.Current != Token.RParen)
-                        throw new ParserException($"Was expecting [LParen] got [{currentEnumerator.Current}]", TokenMarker);
-                }
-                else if (Type.HasFlag(LexerType.Real) && currentEnumerator.Current.IsArithmetic()
-                         && currentEnumerator.Current.IsUnary() && (i == 0)) // || previousToken == Token.LParen))
-                {
-                    stack.Push(0);
-                    operators.Push(currentEnumerator.Current);
-                }
-                else if (Type == LexerType.String && currentEnumerator.Current.IsStringOp()
-                        || Type.HasFlag(LexerType.Real) && currentEnumerator.Current.IsArithmetic())
-                {
-                    while (operators.Count > 0 && OrderOfOps[currentEnumerator.Current] <= OrderOfOps[operators.Peek()])
-                        Operation(operators.Pop());
-                    operators.Push(currentEnumerator.Current);
-                }
-                else
-                {
-                    if (i == 0)
-                    {
-                        if (Type == LexerType.String)
-                            stack.Push("");
-                        else
-                            throw new ParserException("Empty expression", TokenMarker);
-                    }
-
-                    break;
-                }
-
-                i++;
-                currentEnumerator.MoveNext();
-            }
-
-            while (operators.Count > 0)
-                Operation(operators.Pop());
-
-            return Type == LexerType.String
-                ? stack.Aggregate((a, b) => b.String + a.String)
-                : stack.Pop();
-        }
-    }
-}

+ 16 - 16
NTERA.Interpreter/Marker.cs

@@ -1,21 +1,21 @@
 namespace NTERA.Interpreter
 {
-    public struct Marker
-    {
-        public int Pointer { get; set; }
-        public int Line { get; set; }
-        public int Column { get; set; }
+	public struct Marker
+	{
+		public int Pointer { get; set; }
+		public int Line { get; set; }
+		public int Column { get; set; }
 
-        public Marker(int pointer, int line, int column) : this()
-        {
-            Pointer = pointer;
-            Line = line;
-            Column = column;
-        }
+		public Marker(int pointer, int line, int column) : this()
+		{
+			Pointer = pointer;
+			Line = line;
+			Column = column;
+		}
 
-        public override string ToString()
-        {
-            return $"line {Line}, column {Column}";
-        }
-    }
+		public override string ToString()
+		{
+			return $"line {Line}, column {Column}";
+		}
+	}
 }

+ 13 - 8
NTERA.Interpreter/NTERA.Interpreter.csproj

@@ -38,19 +38,21 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Compiler\ExecutionNode.cs" />
+    <Compile Include="Compiler\FunctionDefinition.cs" />
+    <Compile Include="Compiler\Parser.cs" />
+    <Compile Include="Compiler\ParserError.cs" />
+    <Compile Include="Compiler\Preprocessor.cs" />
     <Compile Include="Attributes.cs" />
-    <Compile Include="Engine.cs" />
-    <Compile Include="Interpreter.cs" />
-    <Compile Include="Interpreter\BuiltInFunctions.cs" />
-    <Compile Include="Interpreter\Keywords.cs" />
-    <Compile Include="Lexer.cs" />
+    <Compile Include="Compiler\Lexer.cs" />
     <Compile Include="Marker.cs" />
-    <Compile Include="ParserException.cs" />
+    <Compile Include="Compiler\ParserException.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="Token.cs" />
+    <Compile Include="Compiler\Token.cs" />
     <Compile Include="Utility.cs" />
     <Compile Include="Value.cs" />
-    <Compile Include="Interpreter\VariableDictionary.cs" />
+    <Compile Include="Variable.cs" />
+    <Compile Include="VariableDictionary.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\NTERA.Core\NTERA.Core.csproj">
@@ -58,5 +60,8 @@
       <Name>NTERA.Core</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Interpreter\" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>

+ 0 - 17
NTERA.Interpreter/ParserException.cs

@@ -1,17 +0,0 @@
-using System;
-
-namespace NTERA.Interpreter
-{
-    public class ParserException : Exception
-    {
-        public ParserException(string message) : base(message)
-        {
-
-        }
-
-        public ParserException(string message, Marker marker) : base(message + " at " + marker)
-        {
-
-        }
-    }
-}

+ 0 - 154
NTERA.Interpreter/Token.cs

@@ -1,154 +0,0 @@
-namespace NTERA.Interpreter
-{
-    public enum Token
-    {
-        Unknown = 0,
-
-        Identifer,
-        Value,
-
-        [LexerCharacter('#', LexerType.Real)]
-        Sharp,
-        [LexerCharacter('@', LexerType.Real)]
-        Function,
-
-        [LexerKeyword("DIM")]
-        Dim,
-        [LexerKeyword("CONST")]
-        Const,
-        [LexerKeyword("FUNCTION")]
-        ReturnFunction,
-
-        //Eralang print keywords 
-        [LexerKeyword("DRAWLINE", false)]
-        DrawLine,
-        [LexerKeyword("CUSTOMDRAWLINE", true)]
-        CustomDrawLine,
-        [LexerKeyword("DRAWLINEFORM", true)]
-        DrawLineForm,
-        [LexerKeyword("PRINT", true)]
-        Print,
-        [LexerKeyword("PRINTL", true)]
-        PrintL,
-        [LexerKeyword("PRINTFORM", true)]
-        PrintForm,
-        [LexerKeyword("PRINTFORML", true)]
-        PrintFormL,
-        [LexerKeyword("HTML_PRINT", true)]
-        PrintHtml,
-        [LexerKeyword("PRINT_IMG", true)]
-        PrintImg,
-        [LexerKeyword("PRINTBUTTON", false)]
-        PrintButton,
-        [LexerKeyword("ALIGNMENT", true)]
-        Alignment,
-        [LexerKeyword("SETCOLOR", false)]
-        SetColor,
-        [LexerKeyword("RESETCOLOR", false)]
-        ResetColor,
-
-        //Eralang arithmetic keywords
-        [LexerKeyword("TIMES")]
-        Times,
-
-        //Standard BASIC keywords
-        [LexerKeyword("IF")]
-        If,
-        [LexerKeyword("ENDIF")]
-        EndIf,
-        [LexerKeyword("THEN")]
-        Then,
-        [LexerKeyword("ELSE")]
-        Else,
-        [LexerKeyword("FOR")]
-        For,
-        [LexerKeyword("TO")]
-        To,
-        [LexerKeyword("NEXT")]
-        Next,
-        [LexerKeyword("INPUT")]
-        Input,
-        [LexerKeyword("LET")]
-        Let,
-        [LexerKeyword("GOSUB")]
-        Gosub,
-        [LexerKeyword("RETURN")]
-        Return,
-        [LexerKeyword("REM")]
-        Rem,
-        [LexerKeyword("END")]
-        End,
-
-        [LexerCharacter('\n', LexerType.Both)]
-        NewLine,
-        [LexerCharacter(':', LexerType.Real)]
-        Colon,
-        [LexerCharacter(',', LexerType.Both)]
-        Comma,
-        
-        Plus,
-        Append,
-        [LexerCharacter('-', LexerType.Real)]
-        Minus,
-        [LexerCharacter('/', LexerType.Real)]
-        Slash,
-        [LexerCharacter('*', LexerType.Real)]
-        Asterisk,
-        [LexerCharacter('^', LexerType.Real)]
-        Caret,
-        [LexerCharacter('=', LexerType.Real)]
-        Equal,
-        Less,
-        More,
-        NotEqual,
-        LessEqual,
-        MoreEqual,
-        [LexerKeyword("OR")]
-        [LexerCharacter('|', LexerType.Real)]
-        Or,
-        [LexerKeyword("AND")]
-        [LexerCharacter('&', LexerType.Real)]
-        And,
-        [LexerCharacter('!', LexerType.Real)]
-        Not,
-
-        [LexerCharacter('(', LexerType.Real)]
-        LParen,
-        [LexerCharacter(')', LexerType.Real)]
-        RParen,
-        
-        EOF = -1   //End Of File
-    }
-
-    public static class TokenEnumExtensions
-    {
-        public static bool IsUnary(this Token token)
-        {
-            return token == Token.Plus
-                || token == Token.Minus;
-        }
-
-        public static bool IsArithmetic(this Token token)
-        {
-            return token == Token.Plus
-                || token == Token.Minus
-                || token == Token.Slash
-                || token == Token.Asterisk
-                || token == Token.Caret
-                || token == Token.Equal
-                || token == Token.NotEqual
-                || token == Token.Less
-                || token == Token.LessEqual
-                || token == Token.More
-                || token == Token.MoreEqual
-                || token == Token.Or
-                || token == Token.And
-                || token == Token.Not;
-        }
-
-        public static bool IsStringOp(this Token token)
-        {
-            return token == Token.Plus;
-        }
-    }
-}

+ 7 - 7
NTERA.Interpreter/Utility.cs

@@ -3,11 +3,11 @@ using System.Linq;
 
 namespace NTERA.Interpreter
 {
-    public static class Utility
-    {
-        public static IEnumerable<TAttribute> GetEnumAttributes<TEnum, TAttribute>(TEnum enumValue)
-        {
-            return typeof(TEnum).GetField(enumValue.ToString()).GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
-        }
-    }
+	public static class Utility
+	{
+		public static IEnumerable<TAttribute> GetEnumAttributes<TEnum, TAttribute>(TEnum enumValue)
+		{
+			return typeof(TEnum).GetField(enumValue.ToString()).GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
+		}
+	}
 }

+ 109 - 109
NTERA.Interpreter/Value.cs

@@ -1,114 +1,114 @@
 using System;
+using NTERA.Interpreter.Compiler;
 
 namespace NTERA.Interpreter
 {
-    public enum ValueType
-    {
-        Real,
-        String
-    }
-
-    public struct Value
-    {
-        public ValueType Type { get; set; }
-
-        public double Real { get; }
-        public string String { get; }
-
-        public Value(bool boolean) : this(boolean ? 1d : 0d)
-        {
-
-        }
-
-        public Value(double real) : this()
-        {
-            Type = ValueType.Real;
-            Real = real;
-            String = real.ToString("N");
-        }
-
-        public Value(string str)
-            : this()
-        {
-            Type = ValueType.String;
-            String = str;
-            Real = Double.NaN;
-        }
-
-        public Value Operate(Value b, Token tok)
-        {
-            Value a = this;
-
-            bool isStringOperation = a.Type == ValueType.String || b.Type == ValueType.String;
-
-            if (isStringOperation)
-            {
-                switch (tok)
-                {
-                    case Token.Plus: return new Value(a.String + b.String);
-                    case Token.Equal: return new Value(a.String == b.String);
-                    case Token.NotEqual: return new Value(a.String != b.String);
-                }
-            }
-            else
-            {
-                switch (tok)
-                {
-                    case Token.Plus: return new Value(a.Real + b.Real);
-                    case Token.Equal: return new Value(a.Real == b.Real);
-                    case Token.NotEqual: return new Value(a.Real != b.Real);
-                    case Token.Minus: return new Value(a.Real - b.Real);
-                    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.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);
-                    case Token.MoreEqual: return new Value(a.Real >= b.Real ? 1 : 0);
-                }
-            }
-
-            throw new Exception($"Invalid operation on value ({tok}) on {(isStringOperation ? "string" : "double")}");
-        }
-
-        public static Value operator +(Value value1, Value value2)
-        {
-            return value1.Operate(value2, Token.Plus);
-        }
-
-        public override string ToString()
-        {
-            return String;
-        }
-
-        public static implicit operator double(Value value)
-        {
-            return value.Real;
-        }
-
-        public static implicit operator string(Value value)
-        {
-            return value.String;
-        }
-
-        public static implicit operator bool(Value value)
-        {
-            return value.Real != 0;
-        }
-
-        public static implicit operator Value(double value)
-        {
-            return new Value(value);
-        }
-
-        public static implicit operator Value(string value)
-        {
-            return new Value(value);
-        }
-
-        public static implicit operator Value(bool value)
-        {
-            return new Value(value);
-        }
-    }
+	public enum ValueType
+	{
+		Real,
+		String
+	}
+
+	public struct Value
+	{
+		public ValueType Type { get; set; }
+
+		public double Real { get; }
+		public string String { get; }
+
+		public Value(bool boolean) : this(boolean ? 1d : 0d)
+		{
+		}
+
+		public Value(double real) : this()
+		{
+			Type = ValueType.Real;
+			Real = real;
+			String = real.ToString("N");
+		}
+
+		public Value(string str)
+			: this()
+		{
+			Type = ValueType.String;
+			String = str;
+			Real = Double.NaN;
+		}
+
+		public Value Operate(Value b, Token tok)
+		{
+			Value a = this;
+
+			bool isStringOperation = a.Type == ValueType.String || b.Type == ValueType.String;
+
+			if (isStringOperation)
+			{
+				switch (tok)
+				{
+					case Token.Plus: return new Value(a.String + b.String);
+					case Token.Equal: return new Value(a.String == b.String);
+					case Token.NotEqual: return new Value(a.String != b.String);
+				}
+			}
+			else
+			{
+				switch (tok)
+				{
+					case Token.Plus: return new Value(a.Real + b.Real);
+					case Token.Equal: return new Value(a.Real == b.Real);
+					case Token.NotEqual: return new Value(a.Real != b.Real);
+					case Token.Minus: return new Value(a.Real - b.Real);
+					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.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);
+					case Token.MoreEqual: return new Value(a.Real >= b.Real ? 1 : 0);
+				}
+			}
+
+			throw new Exception($"Invalid operation on value ({tok}) on {(isStringOperation ? "string" : "double")}");
+		}
+
+		public static Value operator +(Value value1, Value value2)
+		{
+			return value1.Operate(value2, Token.Plus);
+		}
+
+		public override string ToString()
+		{
+			return String;
+		}
+
+		public static implicit operator double(Value value)
+		{
+			return value.Real;
+		}
+
+		public static implicit operator string(Value value)
+		{
+			return value.String;
+		}
+
+		public static implicit operator bool(Value value)
+		{
+			return value.Real != 0;
+		}
+
+		public static implicit operator Value(double value)
+		{
+			return new Value(value);
+		}
+
+		public static implicit operator Value(string value)
+		{
+			return new Value(value);
+		}
+
+		public static implicit operator Value(bool value)
+		{
+			return new Value(value);
+		}
+	}
 }

+ 24 - 0
NTERA.Interpreter/Variable.cs

@@ -0,0 +1,24 @@
+namespace NTERA.Interpreter
+{
+	public class Variable
+	{
+		public string Name { get; }
+
+		public int[] Index { get; }
+
+		protected VariableDictionary Dictionary { get; }
+
+		public Value Value
+		{
+			get => Dictionary[Name, Index[0]];
+			set => Dictionary[Name, Index[0]] = value;
+		}
+
+		public Variable(string name, int[] index, VariableDictionary dictionary)
+		{
+			Name = name;
+			Index = index;
+			Dictionary = dictionary;
+		}
+	}
+}

+ 52 - 0
NTERA.Interpreter/VariableDictionary.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+
+namespace NTERA.Interpreter
+{
+	public class VariableDictionary : Dictionary<string, Dictionary<int, Value>>
+	{
+		public VariableDictionary() : base(StringComparer.InvariantCultureIgnoreCase)
+		{
+			InitDefaults();
+		}
+
+		public new Value this[string key]
+		{
+			get => this[key, 0];
+			set => this[key, 0] = value;
+		}
+
+		public Value this[string key, int arrayIndex]
+		{
+			get => base[key][arrayIndex];
+			set
+			{
+				if (base.TryGetValue(key, out var dict))
+					dict[arrayIndex] = value;
+				else
+					Add(key, arrayIndex, value);
+			}
+		}
+
+		public void Add(string key, Value value)
+		{
+			Add(key, 0, value);
+		}
+
+		public void Add(string key, int arrayIndex, Value value)
+		{
+			base.Add(key, new Dictionary<int, Value>
+			{
+				[arrayIndex] = value
+			});
+		}
+
+		private void InitDefaults()
+		{
+			Add("LOCAL", 0);
+			Add("LOCALS", "");
+			Add("RESULT", 0);
+			Add("RESULTS", "");
+		}
+	}
+}

+ 2 - 0
NTERA.sln.DotSettings

@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=NTERA/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>