Browse Source

Reimplement string parsing

Bepsi 6 years ago
parent
commit
7a987cdeb6

+ 1 - 2
NTERA.Interpreter.Tests/KeywordTests.cs

@@ -59,8 +59,7 @@ namespace NTERA.Interpreter.Tests
 
             Common.Run(code, console.Object);
 
-            console.Verify(x => x.Write("big guy 4 u"), Moq.Times.Once);
-            console.Verify(x => x.NewLine(), Moq.Times.Once);
+            console.Verify(x => x.PrintSingleLine("big guy 4 u", false), Moq.Times.Once);
         }
 
         [Test]

+ 15 - 0
NTERA.Interpreter.Tests/app.config

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.2.0.0" newVersion="4.2.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 12 - 1
NTERA.Interpreter/Attributes.cs

@@ -2,14 +2,25 @@
 
 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)
+        public LexerCharacterAttribute(char character, LexerType lexerContext = LexerType.Both)
         {
             Character = character;
+
+            LexerContext = lexerContext;
         }
     }
 

+ 1 - 1
NTERA.Interpreter/Engine.cs

@@ -18,7 +18,7 @@ namespace NTERA.Interpreter
 		{
             Console = console;
 
-            EntrypointPath = @"M:\era\eraSemifullTest\erb\SYSTEM_TITLE.ERB";
+            EntrypointPath = @"M:\era\eraSemifullTest\erb\SYSTEM_TITLE.ERB"; //@"M:\era\eraSemifullTest\erb\TWTITLE.txt";
             Interpreter = new Interpreter(console, File.ReadAllText(EntrypointPath));
 
             return true;

+ 169 - 78
NTERA.Interpreter/Interpreter.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using NTERA.Core;
 
 namespace NTERA.Interpreter
@@ -8,7 +9,7 @@ namespace NTERA.Interpreter
     {
         protected Lexer Lexer { get; set; }
         private Token prevToken;
-        private Token lastToken;
+        private Token CurrentToken => tokens.Current;
 
         public VariableDictionary Variables { get; } = new VariableDictionary();
         protected Dictionary<string, Marker> Loops { get; } = new Dictionary<string, Marker>();
@@ -43,13 +44,13 @@ namespace NTERA.Interpreter
             if (pullNext)
                 GetNextToken();
 
-            return lastToken == tok;
+            return CurrentToken == tok;
         }
 
         protected void AssertToken(Token tok, bool pullNext = true)
         {
             if (!TryAssertToken(tok, pullNext))
-                Error("Expect " + tok + " got " + lastToken);
+                Error("Expect " + tok + " got " + CurrentToken);
         }
 
         private IEnumerator<Token> tokens;
@@ -68,23 +69,22 @@ namespace NTERA.Interpreter
 
         protected Token GetNextToken()
         {
-            prevToken = lastToken;
+            prevToken = CurrentToken;
 
             tokens.MoveNext();
-            lastToken = tokens.Current;
 
-            if (lastToken == Token.EOF && prevToken == Token.EOF)
+            if (CurrentToken == Token.EOF && prevToken == Token.EOF)
                 Error("Unexpected end of file");
             
-            return lastToken;
+            return CurrentToken;
         }
 
         private void Line()
         {
-            while (lastToken == Token.NewLine)
+            while (CurrentToken == Token.NewLine)
                 GetNextToken();
 
-            if (lastToken == Token.EOF)
+            if (CurrentToken == Token.EOF)
             {
                 exit = true;
                 return;
@@ -93,56 +93,44 @@ namespace NTERA.Interpreter
             lineMarker = Lexer.TokenMarker;
             Statement();
 
-            if (lastToken != Token.NewLine && lastToken != Token.EOF)
-                Error("Expect new line got " + lastToken);
+            if (CurrentToken != Token.NewLine && CurrentToken != Token.EOF)
+                Error("Expect new line got " + CurrentToken);
         }
 
         private void Statement()
         {
-            while (true)
-            {
-                Token keyword = lastToken;
-                GetNextToken();
-
-                if (KeywordMethods.ContainsKey(keyword))
-                    KeywordMethods[keyword]();
-                else
-                {
-                    switch (keyword)
-                    {
-                        case Token.Input:
-                            Input();
-                            break;
-
-                        case Token.Function:
-                        case Token.Global:
-                        case Token.Dim:
-                        case Token.Const:
-                            while (GetNextToken() != Token.NewLine) { }
-                            break;
-
-                        case Token.Identifer:
-                            if (lastToken == Token.Equal)
-                                Let();
-                            else
-                                Error("Expected assignment got " + lastToken);
-                            break;
-                        case Token.EOF:
-                            exit = true;
-                            break;
-                        default:
-                            Error("Expect keyword got " + keyword);
-                            break;
-                    }
-                }
+            Token keyword = CurrentToken;
+            GetNextToken();
 
-                if (lastToken == Token.Colon)
+            if (KeywordMethods.ContainsKey(keyword))
+                KeywordMethods[keyword]();
+            else
+            {
+                switch (keyword)
                 {
-                    GetNextToken();
-                    continue;
+                    case Token.Input:
+                        Input();
+                        break;
+                        
+                    case Token.Function:
+                    case Token.Dim:
+                    case Token.Const:
+                        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;
                 }
-
-                break;
             }
         }
 
@@ -185,10 +173,26 @@ namespace NTERA.Interpreter
         };
 
         private Value RealExpression()
+        {
+            return RealExpression(Lexer, tokens);
+        }
+
+        private Value RealExpression(string input)
+        {
+            return RealExpression(new Lexer(input));
+        }
+
+        private Value RealExpression(Lexer lexer, IEnumerator<Token> enumerator = null)
         {
             Stack<Value> stack = new Stack<Value>();
             Stack<Token> operators = new Stack<Token>();
 
+            if (enumerator == null)
+            {
+                enumerator = lexer.GetTokens().GetEnumerator();
+                enumerator.MoveNext();
+            }
+            
             void Operation(Token token)
             {
                 Value b = stack.Pop();
@@ -200,27 +204,27 @@ namespace NTERA.Interpreter
             int i = 0;
             while (true)
             {
-                if (lastToken == Token.Value)
+                if (enumerator.Current == Token.Value)
                 {
-                    stack.Push(Lexer.Value);
+                    stack.Push(lexer.Value);
                 }
-                else if (lastToken == Token.Identifer)
+                else if (enumerator.Current == Token.Identifer)
                 {
-                    if (Variables.ContainsKey(Lexer.Identifer))
+                    if (Variables.ContainsKey(lexer.Identifer))
                     {
-                        stack.Push(Variables[Lexer.Identifer]);
+                        stack.Push(Variables[lexer.Identifer]);
                     }
-                    else if (FunctionDictionary.ContainsKey(Lexer.Identifer))
+                    else if (FunctionDictionary.ContainsKey(lexer.Identifer))
                     {
-                        string name = Lexer.Identifer;
+                        string name = lexer.Identifer;
                         List<Value> args = new List<Value>();
-                        GetNextToken();
-                        AssertToken(Token.LParen, false);
+                        enumerator.MoveNext();
+                        //AssertToken(Token.LParen, false);
                         
-                        while (!TryAssertToken(Token.RParen))
+                        while (enumerator.MoveNext() && enumerator.Current != Token.RParen)
                         {
-                            args.Add(RealExpression());
-                            if (lastToken != Token.Comma)
+                            args.Add(RealExpression(lexer, enumerator));
+                            if (enumerator.Current != Token.Comma)
                                 break;
                         }
 
@@ -228,27 +232,27 @@ namespace NTERA.Interpreter
                     }
                     else
                     {
-                        Error("Undeclared variable " + Lexer.Identifer);
+                        Error("Undeclared variable " + lexer.Identifer);
                     }
                 }
-                else if (lastToken == Token.LParen)
+                else if (enumerator.Current == Token.LParen)
                 {
-                    GetNextToken();
-                    stack.Push(RealExpression());
-                    AssertToken(Token.RParen, false);
+                    enumerator.MoveNext();
+                    stack.Push(RealExpression(lexer, enumerator));
+                    //AssertToken(Token.RParen, false);
                 }
-                else if (lastToken.IsArithmetic())
+                else if (enumerator.Current.IsArithmetic())
                 {
-                    if (lastToken.IsUnary() && (i == 0 || prevToken == Token.LParen))
+                    if (enumerator.Current.IsUnary() && (i == 0 || prevToken == Token.LParen))
                     {
                         stack.Push(0);
-                        operators.Push(lastToken);
+                        operators.Push(enumerator.Current);
                     }
                     else
                     {
-                        while (operators.Count > 0 && OrderOfOps[lastToken] <= OrderOfOps[operators.Peek()])
+                        while (operators.Count > 0 && OrderOfOps[enumerator.Current] <= OrderOfOps[operators.Peek()])
                             Operation(operators.Pop());
-                        operators.Push(lastToken);
+                        operators.Push(enumerator.Current);
                     }
                 }
                 else
@@ -259,7 +263,7 @@ namespace NTERA.Interpreter
                 }
 
                 i++;
-                GetNextToken();
+                enumerator.MoveNext();
             }
 
             while (operators.Count > 0)
@@ -270,10 +274,97 @@ namespace NTERA.Interpreter
 
         private Value StringExpression()
         {
-            foreach (var t in Lexer.ReturnAsLine(Token.Unknown)) { }
+            Lexer.Type = LexerType.String;
+            GetNextToken();
+            var result = StringExpression(Lexer, tokens);
+            Lexer.Type = LexerType.Real;
+
+            return result;
+        }
+
+        private Value StringExpression(Lexer lexer, IEnumerator<Token> enumerator = null)
+        {
+            Stack<Value> stack = new Stack<Value>();
+            Stack<Token> operators = new Stack<Token>();
+
+            if (enumerator == null)
+            {
+                enumerator = lexer.GetTokens().GetEnumerator();
+                enumerator.MoveNext();
+            }
+
+            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 (enumerator.Current == Token.Value)
+                {
+                    stack.Push(lexer.Value.String);
+                }
+                else if (enumerator.Current == Token.Identifer)
+                {
+                    if (Variables.ContainsKey(lexer.Identifer))
+                    {
+                        stack.Push(Variables[lexer.Identifer]);
+                    }
+                    else if (FunctionDictionary.ContainsKey(lexer.Identifer))
+                    {
+                        string name = lexer.Identifer;
+                        List<Value> args = new List<Value>();
+                        enumerator.MoveNext();
+                        AssertToken(Token.LParen, false);
+
+                        while (!TryAssertToken(Token.RParen))
+                        {
+                            args.Add(StringExpression(lexer, enumerator));
+                            if (enumerator.Current != Token.Comma)
+                                break;
+                        }
+
+                        stack.Push(FunctionDictionary[name](args));
+                    }
+                    else
+                    {
+                        if (stack.Count > 0)
+                            operators.Push(Token.Plus);
+
+                        stack.Push(lexer.Identifer);
+                    }
+                }
+                else if (enumerator.Current == Token.LParen)
+                {
+                    enumerator.MoveNext();
+                    stack.Push(StringExpression(lexer, enumerator));
+                    AssertToken(Token.RParen, false);
+                }
+                else if (enumerator.Current.IsStringOp())
+                {
+                    while (operators.Count > 0 && OrderOfOps[enumerator.Current] <= OrderOfOps[operators.Peek()])
+                        Operation(operators.Pop());
+                    operators.Push(enumerator.Current);
+                }
+                else
+                {
+                    if (i == 0)
+                        Error("Empty expression");
+                    break;
+                }
+
+                i++;
+                enumerator.MoveNext();
+            }
+
+            while (operators.Count > 0)
+                Operation(operators.Pop());
 
-            lastToken = Token.NewLine;
-            return Lexer.Value;
+            return stack.Aggregate((a, b) => b.String + a.String);
         }
     }
 }

+ 14 - 5
NTERA.Interpreter/Interpreter/BuiltInFunctions.cs

@@ -25,7 +25,7 @@ namespace NTERA.Interpreter
         [BuiltInFunction("abs")]
         private Value Abs(List<Value> args)
         {
-            if (args.Count < 1)
+            if (args.Count != 1)
                 throw new ArgumentException();
 
             return new Value(Math.Abs(args[0].Real));
@@ -34,7 +34,7 @@ namespace NTERA.Interpreter
         [BuiltInFunction("min")]
         private Value Min(List<Value> args)
         {
-            if (args.Count < 2)
+            if (args.Count != 2)
                 throw new ArgumentException();
 
             return new Value(Math.Min(args[0].Real, args[1].Real));
@@ -43,7 +43,7 @@ namespace NTERA.Interpreter
         [BuiltInFunction("max")]
         private Value Max(List<Value> args)
         {
-            if (args.Count < 2)
+            if (args.Count != 2)
                 throw new ArgumentException();
 
             return new Value(Math.Max(args[0].Real, args[1].Real));
@@ -52,7 +52,7 @@ namespace NTERA.Interpreter
         [BuiltInFunction("not")]
         private Value Not(List<Value> args)
         {
-            if (args.Count < 1)
+            if (args.Count != 1)
                 throw new ArgumentException();
 
             return new Value(args[0].Real == 0 ? 1 : 0);
@@ -63,10 +63,19 @@ namespace NTERA.Interpreter
         [BuiltInFunction("rand")]
         private Value Rand(List<Value> args)
         {
-            if (args.Count < 2)
+            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);
+        }
     }
 }

+ 143 - 41
NTERA.Interpreter/Interpreter/Keywords.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
-using System.Globalization;
 using System.Linq;
 using System.Reflection;
 using System.Text.RegularExpressions;
@@ -37,14 +36,20 @@ namespace NTERA.Interpreter
         [KeywordMethod(Token.PrintL)]
         private void PrintL()
         {
-            console.PrintSingleLine(RealExpression().ToString());
+            console.PrintSingleLine(ParseFormat(StringExpression(new Lexer(Lexer.Value, LexerType.String))));
+
+            GetNextToken();
         }
 
 
         [KeywordMethod(Token.PrintHtml)]
         private void PrintHtml()
         {
-            console.PrintHtml(RealExpression().ToString().Trim().Trim('"'), true);
+            AssertToken(Token.Value, false);
+
+            console.PrintHtml(ParseFormat(StringExpression(new Lexer(Lexer.Value, LexerType.String))), true);
+
+            GetNextToken();
         }
 
 
@@ -58,19 +63,46 @@ namespace NTERA.Interpreter
         [KeywordMethod(Token.PrintButton)]
         private void PrintButton()
         {
-            console.PrintButton(RealExpression().ToString(), 0);
+            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 => RealExpression(new Lexer(match.Groups[1].Value)).ToString());
+
+            string reals = RealFormatRegex.Replace(rawInput, realEvaluator);
+
+            return StringFormatRegex.Replace(reals, realEvaluator);
         }
 
-        private static readonly Regex FormRegex = new Regex("{(.*?)}");
+        [KeywordMethod(Token.PrintForm)]
+        private void PrintForm()
+        {
+            AssertToken(Token.Value, false);
+
+            console.Write(ParseFormat(Lexer.Value));
+
+            GetNextToken();
+        }
 
         [KeywordMethod(Token.PrintFormL)]
         private void PrintFormL()
         {
-            string rawString = RealExpression().ToString();
+            AssertToken(Token.Value, false);
 
-            var evaluator = new MatchEvaluator(match => Variables[match.Groups[1].Value].ToString());
+            console.PrintSingleLine(ParseFormat(Lexer.Value));
 
-            console.PrintSingleLine(FormRegex.Replace(rawString, evaluator));
+            GetNextToken();
         }
 
         [KeywordMethod(Token.DrawLine)]
@@ -85,6 +117,12 @@ namespace NTERA.Interpreter
             console.printCustomBar(RealExpression().ToString().Trim());
         }
 
+        [KeywordMethod(Token.CustomDrawLine)]
+        private void CustomDrawLine()
+        {
+            console.printCustomBar(RealExpression().ToString().Trim());
+        }
+
         [KeywordMethod(Token.Alignment)]
         private void Alignment()
         {
@@ -100,37 +138,45 @@ namespace NTERA.Interpreter
         {
             if (TryAssertToken(Token.Identifer, false))
             {
-                console.SetStringStyle(Color.FromName(Lexer.Value));
+                int argb = (int)((int)RealExpression().Real | 0xFF000000);
+                Color c = Color.FromArgb(argb);
+
+                console.SetStringStyle(c);
+                return;
             }
-            else
-            {
-                AssertToken(Token.Value, false);
 
-                if (TryAssertToken(Token.Comma))
-                {
-                    int r = (int)Lexer.Value.Real;
+            AssertToken(Token.Value, false);
 
-                    AssertToken(Token.Value);
+            if (TryAssertToken(Token.Comma))
+            {
+                int r = (int)Lexer.Value.Real;
 
-                    int g = (int)Lexer.Value;
+                AssertToken(Token.Value);
 
-                    AssertToken(Token.Comma);
-                    AssertToken(Token.Value);
+                int g = (int)Lexer.Value;
 
-                    int b = (int)Lexer.Value;
+                AssertToken(Token.Comma);
+                AssertToken(Token.Value);
 
-                    console.SetStringStyle(Color.FromArgb(r, g, b));
-                }
-                else
-                {
-                    int hexResult = int.Parse(Lexer.Identifer.Substring(1), NumberStyles.HexNumber);
+                int b = (int)Lexer.Value;
 
-                    int argb = (int)(hexResult | 0xFF000000);
-                    Color c = Color.FromArgb(argb);
+                console.SetStringStyle(Color.FromArgb(r, g, b));
 
-                    console.SetStringStyle(c);
-                }
+                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();
         }
@@ -152,11 +198,11 @@ namespace NTERA.Interpreter
                 int i = ifcounter;
                 while (true)
                 {
-                    if (lastToken == Token.If)
+                    if (CurrentToken == Token.If)
                     {
                         i++;
                     }
-                    else if (lastToken == Token.Else)
+                    else if (CurrentToken == Token.Else)
                     {
                         if (i == ifcounter)
                         {
@@ -164,7 +210,7 @@ namespace NTERA.Interpreter
                             return;
                         }
                     }
-                    else if (lastToken == Token.EndIf)
+                    else if (CurrentToken == Token.EndIf)
                     {
                         if (i == ifcounter)
                         {
@@ -184,11 +230,11 @@ namespace NTERA.Interpreter
             int i = ifcounter;
             while (true)
             {
-                if (lastToken == Token.If)
+                if (CurrentToken == Token.If)
                 {
                     i++;
                 }
-                else if (lastToken == Token.EndIf)
+                else if (CurrentToken == Token.EndIf)
                 {
                     if (i == ifcounter)
                     {
@@ -216,24 +262,51 @@ namespace NTERA.Interpreter
         [KeywordMethod(Token.Let)]
         private void Let()
         {
-            if (lastToken != Token.Equal)
+            int arrayIndex = 0;
+            bool appending = false;
+
+            if (CurrentToken == Token.Colon)
             {
-                AssertToken(Token.Identifer, false);
-                AssertToken(Token.Equal);
+                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();
-                Variables[id] = RealExpression();
+                value = RealExpression();
             }
             else
-                Variables[id] = StringExpression();
+                value = StringExpression();
+
+
+            if (appending)
+                Variables[id, arrayIndex] += value;
+            else
+                Variables[id, arrayIndex] = value;
         }
 
         [KeywordMethod(Token.For)]
@@ -285,7 +358,6 @@ namespace NTERA.Interpreter
             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));
-            lastToken = Token.NewLine;
         }
 
         [KeywordMethod(Token.Times)]
@@ -302,7 +374,37 @@ namespace NTERA.Interpreter
 
             Variables[var] = Variables[var].Operate(arg2, Token.Asterisk);
         }
-        
+
+        #endregion
+
+        #region Global
+
+        [KeywordMethod(Token.Global)]
+        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
     }
 }

+ 32 - 1
NTERA.Interpreter/Interpreter/VariableDictionary.cs

@@ -3,13 +3,44 @@ using System.Collections.Generic;
 
 namespace NTERA.Interpreter
 {
-    public class VariableDictionary : Dictionary<string, Value>
+    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);

+ 149 - 109
NTERA.Interpreter/Lexer.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Text;
 
 namespace NTERA.Interpreter
@@ -8,19 +9,30 @@ namespace NTERA.Interpreter
     {
         private readonly string source;
         private Marker sourceMarker;
-        private char lastChar;
+        private char currentChar;
+
+        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)
+        public Lexer(string input, LexerType type = LexerType.Both)
         {
+            Type = type;
+
             source = input;
             sourceMarker = new Marker(-1, 1, 0);
-
-            InitTokenDictionaries();
         }
 
         public void GoTo(Marker marker)
@@ -28,30 +40,40 @@ namespace NTERA.Interpreter
             sourceMarker = marker;
         }
 
-        char GetChar()
+        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 (sourceMarker.Pointer >= source.Length)
-                return lastChar = (char)0;
-
-            if ((lastChar = source[sourceMarker.Pointer]) == '\n')
+            if ((currentChar = source[sourceMarker.Pointer]) == '\n')
             {
                 sourceMarker.Column = 1;
                 sourceMarker.Line++;
             }
-            return lastChar;
+            return currentChar;
         }
 
-        private readonly Dictionary<string, Token> TokenDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
+        private Dictionary<string, Token> TokenDictionary;
 
-        private readonly Dictionary<string, Token> TokenLineDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
+        private Dictionary<string, Token> TokenLineDictionary;
 
-        private readonly Dictionary<char, Token> TokenCharDictionary = new Dictionary<char, Token>();
+        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))
@@ -64,115 +86,102 @@ namespace NTERA.Interpreter
 
                 foreach (var attribute in Utility.GetEnumAttributes<Token, LexerCharacterAttribute>(token))
                 {
-                    TokenCharDictionary[attribute.Character] = 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 == '{';
+        }
+
         public IEnumerable<Token> GetTokens()
         {
             while (true)
             {
-                GetChar();
-
-                while (lastChar == ' ' || lastChar == '\t' || lastChar == '\r')
-                    GetChar();
+                while (IsWhitespace(GetNextChar()) && Type != LexerType.String) { }
 
                 TokenMarker = sourceMarker;
 
-                if (char.IsLetter(lastChar))
-                {
-                    Identifer = lastChar.ToString();
-                    while (char.IsLetterOrDigit(GetChar()) || lastChar == '_')
-                        Identifer += lastChar;
-
-                    if (TokenDictionary.TryGetValue(Identifer, out Token token))
-                    {
-                        yield return token;
-                        continue;
-                    }
-
-                    if (TokenLineDictionary.TryGetValue(Identifer, out token))
-                    {
-                        foreach (Token t in ReturnAsLine(token))
-                            yield return t;
-                        continue;
-                    }
-
-                    switch (Identifer.ToUpper())
-                    {
-                        case "REM":
-                            while (lastChar != '\n')
-                                GetChar();
-
-                            continue;
-                        default:
-                        {
-                            yield return Token.Identifer;
-
-                            sourceMarker.Pointer--;
-
-                            continue;
-                        }
-                    }
-                }
-
-                if (char.IsDigit(lastChar))
-                {
-                    string num = "";
-
-                    do
-                    {
-                        num += lastChar;
-                    }
-                    while (char.IsDigit(GetChar()) || lastChar == '.');
-
-                    if (!double.TryParse(num, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var real))
-                        throw new Exception("ERROR while parsing number");
-
-                    Value = new Value(real);
-                    yield return Token.Value;
-                    
-                    sourceMarker.Pointer--;
-
-                    continue;
-                }
-
-                if (TokenCharDictionary.TryGetValue(lastChar, out Token charToken))
+                if (TokenCharDictionary.TryGetValue(currentChar, out Token charToken))
                 {
                     yield return charToken;
                     continue;
                 }
-                
-                switch (lastChar)
+
+                switch (currentChar)
                 {
-                    case '\'':
-                        while (lastChar != '\n')
-                            GetChar();
+                    case ';': //semicolon is comment
+                        while (currentChar != '\n')
+                            GetNextChar();
                         continue;
                     case '<':
-                        GetChar();
-                        if (lastChar == '>')
+                        if (!Type.HasFlag(LexerType.Real))
+                            break;
+                        
+                        if (GetNextChar(true) == '>')
+                        {
+                            GetNextChar();
                             yield return Token.NotEqual;
-                        else if (lastChar == '=')
+                        }
+                        else if (GetNextChar(true) == '=')
+                        {
+                            GetNextChar();
                             yield return Token.LessEqual;
+                        }
                         else
                             yield return Token.Less;
                         continue;
                     case '>':
-                        GetChar();
-                        if (lastChar == '=')
+                        if (!Type.HasFlag(LexerType.Real))
+                            break;
+
+                        if (GetNextChar(true) == '=')
+                        {
+                            GetNextChar();
                             yield return Token.MoreEqual;
+                        }
                         else
                             yield return Token.More;
                         continue;
+                    case '+':
+                        if (GetNextChar(true) == '=')
+                        {
+                            GetNextChar();
+                            yield return Token.Append;
+                        }
+                        else
+                            yield return Token.Plus;
+                        continue;
+                    case '%':
+
+                        StringBuilder builder = new StringBuilder();
+                        while (GetNextChar() != '%')
+                            builder.Append(currentChar);
+
+                        Value = $"%{builder}%";
+                        yield return Token.Value;
+
+                        continue;
                     case '"':
                         string str = "";
-                        while (GetChar() != '"')
+                        while (GetNextChar() != '"')
                         {
-                            if (lastChar == '\\')
+                            if (currentChar == '\\')
                             {
-                                switch (char.ToLower(GetChar()))
+                                switch (char.ToLower(GetNextChar()))
                                 {
                                     case 'n': str += '\n'; break;
                                     case 't': str += '\t'; break;
@@ -182,7 +191,7 @@ namespace NTERA.Interpreter
                             }
                             else
                             {
-                                str += lastChar;
+                                str += currentChar;
                             }
                         }
                         Value = new Value(str);
@@ -190,31 +199,62 @@ namespace NTERA.Interpreter
                         continue;
                     case (char)0:
                         yield return Token.EOF;
-                        break;
-                    default:
-                        yield return Token.Unknown;
-                        continue;
+                        yield break;
                 }
 
-                break;
-            }
-        }
+                StringBuilder bodyBuilder = new StringBuilder(currentChar.ToString());
 
-        public IEnumerable<Token> ReturnAsLine(Token token)
-        {
-            StringBuilder bodyBuilder = new StringBuilder();
+                while (!TokenCharDictionary.ContainsKey(GetNextChar(true)) 
+                       && !IsEndOfLine(GetNextChar(true)) 
+                       && (!IsWhitespace(GetNextChar(true)) || Type == LexerType.String)
+                       && (!IsEscape(GetNextChar(true)) || Type != LexerType.String))
+                {
+                    bodyBuilder.Append(GetNextChar());
+                }
+
+                string result = bodyBuilder.ToString();
 
-            while (lastChar != '\n' && lastChar != '\0')
-                bodyBuilder.Append(GetChar());
-            
-            bodyBuilder.Capacity -= 1;
+                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;
+                }
 
-            yield return token;
+                Identifer = bodyBuilder.ToString();
 
-            Value = new Value(bodyBuilder.ToString().TrimEnd('\0', '\r', '\n'));
-            yield return Token.Value;
+                if (TokenDictionary.TryGetValue(Identifer, out Token token))
+                {
+                    yield return token;
+                    continue;
+                }
 
-            yield return lastChar == '\0' ? Token.EOF : Token.NewLine;
+                if (TokenLineDictionary.TryGetValue(Identifer, out token))
+                {
+                    bodyBuilder = new StringBuilder();
+
+                    while (!IsEndOfLine(GetNextChar(true)))
+                        bodyBuilder.Append(GetNextChar());
+                    
+                    yield return token;
+
+                    Value = new Value(bodyBuilder.ToString().Substring(1));
+                    yield return Token.Value;
+
+                    yield return currentChar == '\0' ? Token.EOF : Token.NewLine;
+
+                    continue;
+                }
+
+                yield return Token.Identifer;
+            }
         }
     }
 }

+ 29 - 22
NTERA.Interpreter/Token.cs

@@ -3,14 +3,14 @@ namespace NTERA.Interpreter
 {
     public enum Token
     {
-        Unknown,
+        Unknown = 0,
 
         Identifer,
         Value,
 
-        [LexerCharacter('#')]
+        [LexerCharacter('#', LexerType.Real)]
         Global,
-        [LexerCharacter('@')]
+        [LexerCharacter('@', LexerType.Real)]
         Function,
 
         [LexerKeyword("DIM")]
@@ -21,6 +21,8 @@ namespace NTERA.Interpreter
         //Eralang print keywords 
         [LexerKeyword("DRAWLINE", false)]
         DrawLine,
+        [LexerKeyword("CUSTOMDRAWLINE", true)]
+        CustomDrawLine,
         [LexerKeyword("DRAWLINEFORM", true)]
         DrawLineForm,
         [LexerKeyword("PRINT", true)]
@@ -31,16 +33,18 @@ namespace NTERA.Interpreter
         PrintForm,
         [LexerKeyword("PRINTFORML", true)]
         PrintFormL,
-        [LexerKeyword("HTML_PRINT", false)]
+        [LexerKeyword("HTML_PRINT", true)]
         PrintHtml,
         [LexerKeyword("PRINT_IMG", true)]
         PrintImg,
-        [LexerKeyword("PRINTBUTTON", true)]
+        [LexerKeyword("PRINTBUTTON", false)]
         PrintButton,
         [LexerKeyword("ALIGNMENT", true)]
         Alignment,
         [LexerKeyword("SETCOLOR", false)]
         SetColor,
+        [LexerKeyword("RESETCOLOR", false)]
+        ResetColor,
 
         //Eralang arithmetic keywords
         [LexerKeyword("TIMES")]
@@ -74,26 +78,24 @@ namespace NTERA.Interpreter
         [LexerKeyword("END")]
         End,
 
-        [LexerCharacter('\n')]
+        [LexerCharacter('\n', LexerType.Both)]
         NewLine,
-        [LexerCharacter(':')]
+        [LexerCharacter(':', LexerType.Real)]
         Colon,
-        [LexerCharacter(';')]
-        Semicolon,
-        [LexerCharacter(',')]
+        [LexerCharacter(',', LexerType.Both)]
         Comma,
-
-        [LexerCharacter('+')]
+        
         Plus,
-        [LexerCharacter('-')]
+        Append,
+        [LexerCharacter('-', LexerType.Real)]
         Minus,
-        [LexerCharacter('/')]
+        [LexerCharacter('/', LexerType.Real)]
         Slash,
-        [LexerCharacter('*')]
+        [LexerCharacter('*', LexerType.Real)]
         Asterisk,
-        [LexerCharacter('^')]
+        [LexerCharacter('^', LexerType.Real)]
         Caret,
-        [LexerCharacter('=')]
+        [LexerCharacter('=', LexerType.Real)]
         Equal,
         Less,
         More,
@@ -101,17 +103,17 @@ namespace NTERA.Interpreter
         LessEqual,
         MoreEqual,
         [LexerKeyword("OR")]
-        [LexerCharacter('|')]
+        [LexerCharacter('|', LexerType.Real)]
         Or,
         [LexerKeyword("AND")]
-        [LexerCharacter('&')]
+        [LexerCharacter('&', LexerType.Real)]
         And,
-        [LexerCharacter('!')]
+        [LexerCharacter('!', LexerType.Real)]
         Not,
 
-        [LexerCharacter('(')]
+        [LexerCharacter('(', LexerType.Real)]
         LParen,
-        [LexerCharacter(')')]
+        [LexerCharacter(')', LexerType.Real)]
         RParen,
         
         EOF = -1   //End Of File
@@ -142,5 +144,10 @@ namespace NTERA.Interpreter
                 || token == Token.And
                 || token == Token.Not;
         }
+
+        public static bool IsStringOp(this Token token)
+        {
+            return token == Token.Plus;
+        }
     }
 }

+ 5 - 0
NTERA.Interpreter/Value.cs

@@ -71,6 +71,11 @@ namespace NTERA.Interpreter
             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;

+ 1 - 0
NTERA/Console/EraConsoleInstance.cs

@@ -111,6 +111,7 @@ namespace NTERA.Console
 
 		public void ResetStyle()
 		{
+            ForeColor = Color.White;
 		}
 
 		public void OutputLog(string log)