debug console transpiles GDScript to GD Expression
							parent
							
								
									ad29c9cd29
								
							
						
					
					
						commit
						b173a02912
					
				|  | @ -1,6 +1,6 @@ | |||
| namespace SupaLidlGame.Debug; | ||||
| 
 | ||||
| internal sealed class CharIterator : Iterator<char> | ||||
| public class CharIterator : Iterator<char> | ||||
| { | ||||
|     public CharIterator(string str) : base(str.ToCharArray()) | ||||
|     { | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| using Godot; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace SupaLidlGame.Debug; | ||||
| 
 | ||||
|  | @ -55,11 +56,21 @@ public sealed partial class DebugConsole : Control | |||
|         Property | ||||
|     }; | ||||
| 
 | ||||
|     public Variant From(NodePath path) | ||||
|     public IEnumerable<NodePathToken> SplitPath(NodePath path) | ||||
|     { | ||||
|         CharIterator iterator = new(path); | ||||
|         return NodePathParser.ParseNodePath(iterator); | ||||
|     } | ||||
| 
 | ||||
|     public NodePath ToNodePath(string path) | ||||
|     { | ||||
|         return Variant.From(path).AsNodePath(); | ||||
|     } | ||||
| 
 | ||||
|     public Variant From(NodePath path) | ||||
|     { | ||||
|         Variant variant = Context ?? this; | ||||
|         foreach (var subpath in NodePathParser.ParseNodePath(iterator)) | ||||
|         foreach (var subpath in SplitPath(path)) | ||||
|         { | ||||
|             if (variant.VariantType == Variant.Type.Object) | ||||
|             { | ||||
|  | @ -83,14 +94,44 @@ public sealed partial class DebugConsole : Control | |||
|         return variant; | ||||
|     } | ||||
| 
 | ||||
|     public void SetProp(NodePath path, Variant value) | ||||
|     public void SetProp(Variant prop, Variant value) | ||||
|     { | ||||
|         var node = GetNode(path.GetAsPropertyPath()); | ||||
|         //var ent = CurrentMap.Entities.GetNodeOrNull(entityName); | ||||
|         //if (ent is not null) | ||||
|         //{ | ||||
|         //    ent.Set(property, value); | ||||
|         //} | ||||
|         if (prop.VariantType == Variant.Type.NodePath) | ||||
|         { | ||||
|             var tokens = new List<NodePathToken>(SplitPath(prop.AsNodePath())); | ||||
|             Node variant = Context ?? this; | ||||
|             for (int i = 0; i < tokens.Count; i++) | ||||
|             { | ||||
|                 var subpath = tokens[i]; | ||||
|                 GD.Print(subpath); | ||||
|                 if (i == tokens.Count - 1) | ||||
|                 { | ||||
|                     if (subpath.Type == NodePathTokenType.Property) | ||||
|                     { | ||||
|                         variant.SetIndexed(":" + subpath.Path, value); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (subpath.Type == NodePathTokenType.Node) | ||||
|                     { | ||||
|                         if (subpath.Path != "") | ||||
|                         { | ||||
|                             variant = variant.GetNode(subpath.Path); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         variant = variant.GetIndexed(subpath.Path) | ||||
|                             .AsGodotObject() as Node; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public string CallMethod( | ||||
|  | @ -115,18 +156,21 @@ public sealed partial class DebugConsole : Control | |||
| 
 | ||||
|     public void Execute(string str) | ||||
|     { | ||||
|         str = Sanitizer.Sanitize(str); | ||||
|         //str = Sanitizer.Sanitize(str); | ||||
|         str = Transpiler.Transpiler.Transpile(str); | ||||
|         string inputMirror = $"[b]{Context.GetPath()}:[/b] {str}"; | ||||
|         _output.Text += inputMirror + "\n"; | ||||
|         var context = Context; | ||||
| 
 | ||||
|         Godot.Expression exp = new(); | ||||
| 
 | ||||
|         string[] reserved = { "from", "set_context", "context" }; | ||||
|         string[] reserved = { "from", "set_context", "context", "set_prop", "to_node_path" }; | ||||
|         Godot.Collections.Array reservedMap = new(); | ||||
|         reservedMap.Add(new Callable(this, MethodName.From)); | ||||
|         reservedMap.Add(new Callable(this, MethodName.SetContext)); | ||||
|         reservedMap.Add(Context); | ||||
|         reservedMap.Add(new Callable(this, MethodName.SetProp)); | ||||
|         reservedMap.Add(new Callable(this, MethodName.ToNodePath)); | ||||
| 
 | ||||
|         var err = exp.Parse(str, reserved); | ||||
|         if (err != Error.Ok) | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ using Godot; | |||
| 
 | ||||
| namespace SupaLidlGame.Debug; | ||||
| 
 | ||||
| public partial class Entry : LineEdit | ||||
| public partial class Entry : CodeEdit | ||||
| { | ||||
|     [Signal] | ||||
|     public delegate void ConsoleInputEventHandler(string input); | ||||
|  | @ -12,17 +12,35 @@ public partial class Entry : LineEdit | |||
|         GuiInput += OnGuiInput; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|     public override void _Input(InputEvent @event) | ||||
|     { | ||||
|         if (HasFocus()) | ||||
|         { | ||||
|             if (@event is InputEventKey && @event.IsPressed()) | ||||
|             { | ||||
|                 AcceptEvent(); | ||||
|                 OnGuiInput(@event); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     */ | ||||
| 
 | ||||
|     public void OnGuiInput(InputEvent @event) | ||||
|     { | ||||
|         if (@event is InputEventKey key) | ||||
|         { | ||||
|             if (key.KeyLabel == Key.Enter && !key.Pressed) | ||||
|             if (key.KeyLabel == Key.Enter) | ||||
|             { | ||||
|                 EmitSignal(SignalName.ConsoleInput, Text); | ||||
| 
 | ||||
|                 if (!key.CtrlPressed) | ||||
|                 AcceptEvent(); | ||||
|                 if (!key.Pressed) | ||||
|                 { | ||||
|                     Text = ""; | ||||
|                     EmitSignal(SignalName.ConsoleInput, Text); | ||||
| 
 | ||||
|                     if (!key.IsCommandOrControlPressed()) | ||||
|                     { | ||||
|                         Text = ""; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| using Godot; | ||||
| using System.Text.RegularExpressions; | ||||
| 
 | ||||
| namespace SupaLidlGame.Debug; | ||||
|  | @ -7,7 +6,7 @@ public static class Sanitizer | |||
| { | ||||
|     private static Regex _nonAlphanum = new("[^a-zA-Z0-9_]"); | ||||
| 
 | ||||
|     private static Regex _nonNodeName = new("[^a-zA-Z0-9_\\-\\/]"); | ||||
|     private static Regex _nonNodeName = new("[^a-zA-Z0-9_\\-\\/\\.\\:]"); | ||||
| 
 | ||||
|     private static string ScanString(CharIterator iterator) | ||||
|     { | ||||
|  | @ -69,12 +68,12 @@ public static class Sanitizer | |||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     private static string ScanUntilOrEOL(CharIterator iterator, char delim) | ||||
|     private static string ScanUntilOrEOL(CharIterator iterator, char? delim) | ||||
|     { | ||||
|         string ret = ""; | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char c = iterator.GetNext(); | ||||
|             char c = iterator.MoveNext(); | ||||
|             if (c == delim) | ||||
|             { | ||||
|                 return ret; | ||||
|  | @ -125,6 +124,15 @@ public static class Sanitizer | |||
|                 string command = ScanGlobalCommand(iterator); | ||||
|                 ret += $"{command}.call"; | ||||
|             } | ||||
|             else if (c == '=') | ||||
|             { | ||||
|                 if (iterator.GetNext(-2) != '!') | ||||
|                 { | ||||
|                     var val = ScanUntilOrEOL(iterator, null); | ||||
|                     ret = ret.Replace("from.call", "to_node_path.call"); | ||||
|                     ret = $"set_prop.call({ret}, {val})"; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 ret += c; | ||||
|  |  | |||
|  | @ -1,49 +0,0 @@ | |||
| namespace SupaLidlGame.Debug; | ||||
| 
 | ||||
| public enum TokenType | ||||
| { | ||||
|     None, | ||||
|     Identifier, | ||||
|     String, | ||||
|     GodotExpression, | ||||
|     Command, | ||||
|     End | ||||
| }; | ||||
| 
 | ||||
| public struct Token | ||||
| { | ||||
|     public TokenType Type { get; set; } | ||||
| 
 | ||||
|     public string Value { get; set; } | ||||
| 
 | ||||
|     public int Line { get; set; } | ||||
| 
 | ||||
|     public int Column { get; set; } | ||||
| 
 | ||||
|     public Token(TokenType type, string value, int line, int col) | ||||
|     { | ||||
|         Type = type; | ||||
|         Value = value; | ||||
|         Line = line; | ||||
|         Column = col; | ||||
|     } | ||||
| 
 | ||||
|     public bool CompareTypeValue(Token token) | ||||
|     { | ||||
|         return Type == token.Type && Value == token.Value; | ||||
|     } | ||||
| 
 | ||||
|     public override bool Equals(object obj) | ||||
|     { | ||||
|         return base.Equals(obj); | ||||
|     } | ||||
| 
 | ||||
|     public override int GetHashCode() | ||||
|     { | ||||
|         return base.GetHashCode(); | ||||
|     } | ||||
| 
 | ||||
|     public static bool operator ==(Token left, Token right) => left.Equals(right); | ||||
| 
 | ||||
|     public static bool operator !=(Token left, Token right) => !left.Equals(right); | ||||
| } | ||||
|  | @ -1,185 +0,0 @@ | |||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace SupaLidlGame.Debug; | ||||
| 
 | ||||
| internal sealed class Tokenizer | ||||
| { | ||||
|     private static readonly HashSet<char> WHITESPACE = new HashSet<char> { ' ', '\n' }; | ||||
| 
 | ||||
|     private static string ScanString(CharIterator iterator) | ||||
|     { | ||||
|         string ret = ""; | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char c = iterator.MoveNext(); | ||||
| 
 | ||||
|             if (c == '"') | ||||
|             { | ||||
|                 return ret; | ||||
|             } | ||||
|             else if (c == '\\') | ||||
|             { | ||||
|                 char escape = iterator.MoveNext(); | ||||
| 
 | ||||
|                 switch (escape) | ||||
|                 { | ||||
|                     case 'n': | ||||
|                         ret += '\n'; | ||||
|                         break; | ||||
|                     case 't': | ||||
|                         ret += '\t'; | ||||
|                         break; | ||||
|                     case '\0': | ||||
|                         throw new InterpreterException("Unexpected EOL", | ||||
|                             iterator.Line, iterator.Column); | ||||
|                     default: | ||||
|                         ret += escape; | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             ret += c; | ||||
|         } | ||||
|         throw new InterpreterException("Unexpected EOL, expected '\"'", | ||||
|             iterator.Line, iterator.Column); | ||||
|     } | ||||
| 
 | ||||
|     private static string ScanNodePath(CharIterator iterator) | ||||
|     { | ||||
|         string ret = ""; | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char c = iterator.GetNext(); | ||||
| 
 | ||||
|             if (c == '"') | ||||
|             { | ||||
|                 ret += ScanString(iterator); | ||||
|             } | ||||
|             else if (WHITESPACE.Contains(c)) | ||||
|             { | ||||
|                 return ret; | ||||
|             } | ||||
| 
 | ||||
|             ret += c; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|     private static string ScanUntil(CharIterator iterator) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
|     */ | ||||
| 
 | ||||
|     private static string ScanExpression(CharIterator iterator) | ||||
|     { | ||||
|         int level = 0; | ||||
|         string exp = ""; | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char c = iterator.GetNext(); | ||||
| 
 | ||||
|             if (c == '(') | ||||
|             { | ||||
|                 level++; | ||||
|             } | ||||
|             else if (c == ')') | ||||
|             { | ||||
|                 level--; | ||||
|             } | ||||
| 
 | ||||
|             if (level < 0) | ||||
|             { | ||||
|                 return exp; | ||||
|             } | ||||
| 
 | ||||
|             exp += c; | ||||
|         } | ||||
|         return exp; | ||||
|     } | ||||
| 
 | ||||
|     private static string ScanUntilOrEOL(CharIterator iterator, char delim) | ||||
|     { | ||||
|         string ret = ""; | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char c = iterator.GetNext(); | ||||
|             if (c == delim) | ||||
|             { | ||||
|                 return ret; | ||||
|             } | ||||
|             ret += c; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     public static IEnumerable<Token> Tokenize(CharIterator iterator) | ||||
|     { | ||||
|         System.Diagnostics.Debug.Print("hi"); | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char curChar = iterator.MoveNext(); | ||||
|             System.Diagnostics.Debug.Print(curChar.ToString()); | ||||
| 
 | ||||
|             int line = iterator.Line; | ||||
|             int col = iterator.Column; | ||||
| 
 | ||||
|             if (WHITESPACE.Contains(curChar)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|             else if (curChar == '\\') | ||||
|             { | ||||
|                 string command = ScanUntilOrEOL(iterator, ' '); | ||||
|                 if (command == "") | ||||
|                 { | ||||
|                     throw new InterpreterException( | ||||
|                         "Expected a command name", | ||||
|                         iterator.Line, | ||||
|                         iterator.Column); | ||||
|                 } | ||||
|                 yield return new Token(TokenType.Command, | ||||
|                     command, | ||||
|                     line, | ||||
|                     col); | ||||
|             } | ||||
|             else if (curChar == '(') | ||||
|             { | ||||
|                 string exp = ScanExpression(iterator); | ||||
|                 yield return new Token(TokenType.GodotExpression, | ||||
|                     exp, | ||||
|                     line, | ||||
|                     col); | ||||
|             } | ||||
|             else if (curChar == '"') | ||||
|             { | ||||
|                 yield return new Token(TokenType.String, | ||||
|                     ScanString(iterator), | ||||
|                     line, | ||||
|                     col); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // parse this as expression | ||||
|                 string exp = ScanUntilOrEOL(iterator, ' '); | ||||
|                 yield return new Token(TokenType.GodotExpression, | ||||
|                     exp, | ||||
|                     line, | ||||
|                     col); | ||||
|             } | ||||
|             /* | ||||
|             else if (curChar == '$') | ||||
|             { | ||||
|                 yield return new Token(TokenType.NodePath, | ||||
|                     ScanNodePath(iterator), | ||||
|                     line, | ||||
|                     col); | ||||
|             } | ||||
|             */ | ||||
|         } | ||||
|         yield return new Token(TokenType.End, "", | ||||
|             iterator.Line, iterator.Column); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -0,0 +1,25 @@ | |||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public class AssignmentExpression : Expression | ||||
| { | ||||
|     public LiteralExpression Left { get; set; } | ||||
| 
 | ||||
|     public Expression Expression { get; set; } | ||||
| 
 | ||||
|     public AssignmentExpression(LiteralExpression left, Expression expression, | ||||
|         int line, int col) : base(line, col) | ||||
|     { | ||||
|         Left = left; | ||||
|         Expression = expression; | ||||
|     } | ||||
| 
 | ||||
|     public override string Transpile() | ||||
|     { | ||||
|         var right = Expression.Transpile(); | ||||
|         if (Left.Literal.Type == TokenType.NodePath) | ||||
|         { | ||||
|             return $"set_prop.call({Left.TranspileNodePath()}, {right})"; | ||||
|         } | ||||
|         return $"set(\"{Left.Transpile()}\", {right})"; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,25 @@ | |||
| using System.Linq; | ||||
| 
 | ||||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public class CallExpression : Expression | ||||
| { | ||||
|     public Expression Identifier { get; set; } | ||||
| 
 | ||||
|     public Expression[] Arguments { get; set; } | ||||
| 
 | ||||
|     public CallExpression(LiteralExpression identifier, Expression[] args, | ||||
|         int line, int col) : base(line, col) | ||||
|     { | ||||
|         Identifier = identifier; | ||||
|         Arguments = args; | ||||
|     } | ||||
| 
 | ||||
|     public override string Transpile() | ||||
|     { | ||||
|         var args = Arguments | ||||
|             .Select((ex) => ex.Transpile()) | ||||
|             .Aggregate((a, b) => a + ", " + b); | ||||
|         return $"{Identifier.Transpile()}({args})"; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,16 @@ | |||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public abstract class Expression | ||||
| { | ||||
|     public int Line { get; set; } | ||||
| 
 | ||||
|     public int Column { get; set; } | ||||
| 
 | ||||
|     public Expression(int line, int col) | ||||
|     { | ||||
|         Line = line; | ||||
|         Column = col; | ||||
|     } | ||||
| 
 | ||||
|     public abstract string Transpile(); | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public class GroupedExpression : Expression | ||||
| { | ||||
|     public Expression Root { get; set; } | ||||
| 
 | ||||
|     public GroupedExpression(Expression root, int line, int col) | ||||
|         : base(line, col) | ||||
|     { | ||||
|         Root = root; | ||||
|     } | ||||
| 
 | ||||
|     public override string Transpile() | ||||
|     { | ||||
|         return $"({Root.Transpile()})"; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| using System.Text.RegularExpressions; | ||||
| 
 | ||||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public class LiteralExpression : Expression | ||||
| { | ||||
|     public Token Literal { get; set; } | ||||
| 
 | ||||
|     public LiteralExpression(Token literal, int line, int col) | ||||
|         : base(line, col) | ||||
|     { | ||||
|         Literal = literal; | ||||
|     } | ||||
| 
 | ||||
|     public override string Transpile() | ||||
|     { | ||||
|         if (Literal.Type == TokenType.NodePath) | ||||
|         { | ||||
|             var val = Regex.Escape(Literal.Value); | ||||
|             return $"from.call({val})"; | ||||
|         } | ||||
|         else if (Literal.Type == TokenType.String) | ||||
|         { | ||||
|             return $"\"{Literal.Value}\""; | ||||
|         } | ||||
|         return Literal.Value; | ||||
|     } | ||||
| 
 | ||||
|     public string TranspileNodePath() | ||||
|     { | ||||
|         var val = Regex.Escape(Literal.Value); | ||||
|         return $"to_node_path.call(\"{val}\")"; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,40 @@ | |||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public class OperationExpression : Expression | ||||
| { | ||||
|     public Expression Left { get; set; } | ||||
| 
 | ||||
|     public Expression Right { get; set; } | ||||
| 
 | ||||
|     public Token Operator { get; set; } | ||||
| 
 | ||||
|     public OperationExpression(Expression left, Token token, Expression right, | ||||
|         int line, int col) : base(line, col) | ||||
|     { | ||||
|         if (token.Type != TokenType.Operator) | ||||
|         { | ||||
|             throw new InterpreterException( | ||||
|                 $"Expected operator, got {token.Value}", | ||||
|                 token.Line, | ||||
|                 token.Column); | ||||
|         } | ||||
|         Left = left; | ||||
|         Operator = token; | ||||
|         Right = right; | ||||
|     } | ||||
| 
 | ||||
|     public override string Transpile() | ||||
|     { | ||||
|         var left = Left.Transpile(); | ||||
|         var right = Right.Transpile(); | ||||
|         var op = Operator.Value; | ||||
|         if (op == ".") | ||||
|         { | ||||
|             return $"{left}{op}{right}"; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return $"{left} {op} {right}"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,145 @@ | |||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public class Parser | ||||
| { | ||||
|     private HashSet<Token> _endTokens = null; | ||||
| 
 | ||||
|     private Iterator<Token> _iterator; | ||||
| 
 | ||||
|     private static readonly Token ARGS_DELIM = new Token(TokenType.Operator, ",", 0, 0); | ||||
| 
 | ||||
|     private static readonly Token CLOSE_DELIM = new Token(TokenType.Grouping, ")", 0, 0); | ||||
| 
 | ||||
|     private Parser(Token[] tokens) | ||||
|     { | ||||
|         _iterator = new(tokens); | ||||
|         _endTokens = new HashSet<Token> { default }; | ||||
|     } | ||||
| 
 | ||||
|     private Parser(Iterator<Token> iterator, HashSet<Token> endTokens) | ||||
|     { | ||||
|         _iterator = iterator; | ||||
|         _endTokens = endTokens; | ||||
|     } | ||||
| 
 | ||||
|     public GroupedExpression GroupedExpression() | ||||
|     { | ||||
|         Parser p = new Parser(_iterator, new HashSet<Token> { CLOSE_DELIM }); | ||||
|         var next = p.NextExpression(null); | ||||
|         Expect(CLOSE_DELIM); | ||||
|         _iterator.MoveNext(); | ||||
|         return new GroupedExpression(next, next.Line, next.Column); | ||||
|     } | ||||
| 
 | ||||
|     public IEnumerable<Expression> DelimitedExpressions(Token delim, Token end) | ||||
|     { | ||||
|         Expect(end); | ||||
| 
 | ||||
|         var endTokens = new HashSet<Token> { delim, end }; | ||||
| 
 | ||||
|         Parser p = new Parser(_iterator, endTokens); | ||||
|         var next = _iterator.GetNext(); | ||||
|         while (next != end) | ||||
|         { | ||||
|             var expr = p.NextExpression(null); | ||||
|             if (expr is not null) | ||||
|             { | ||||
|                 yield return expr; | ||||
|             } | ||||
|             next = _iterator.MoveNext(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public Expression NextExpression(Expression prev) | ||||
|     { | ||||
|         foreach (var end in _endTokens) | ||||
|         { | ||||
|             if (end == _iterator.GetNext()) | ||||
|             { | ||||
|                 return prev; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         var token = _iterator.MoveNext(); | ||||
| 
 | ||||
|         if (prev is null && token.IsSymbol) | ||||
|         { | ||||
|             var exp = new LiteralExpression(token, token.Line, token.Column); | ||||
|             return NextExpression(exp); | ||||
|         } | ||||
|         else if (token.Type == TokenType.Operator) | ||||
|         { | ||||
|             Expression right = NextExpression(null); | ||||
|             if (token.Value == "=") | ||||
|             { | ||||
|                 if (prev is not LiteralExpression l) | ||||
|                 { | ||||
|                     throw new InterpreterException("Invalid assignment", | ||||
|                         prev.Line, prev.Column); | ||||
|                 } | ||||
|                 var assignment = new AssignmentExpression(l, right, | ||||
|                     token.Line, token.Column); | ||||
|                 return NextExpression(assignment); | ||||
|             } | ||||
| 
 | ||||
|             var exp = new OperationExpression( | ||||
|                 prev, token, right, token.Line, token.Column); | ||||
|             return NextExpression(exp); | ||||
|         } | ||||
|         else if (token.Type == TokenType.Grouping) | ||||
|         { | ||||
|             if (token.Value == ")") | ||||
|             { | ||||
|                 throw new InterpreterException("Unexpected )", | ||||
|                     token.Line, token.Column); | ||||
|             } | ||||
|             if (prev is LiteralExpression l) | ||||
|             { | ||||
|                 // this is a function call | ||||
|                 Expression[] args = | ||||
|                     DelimitedExpressions(ARGS_DELIM, CLOSE_DELIM) | ||||
|                     .ToArray(); | ||||
|                 var c = new CallExpression(l, args, token.Line, token.Column); | ||||
|                 return NextExpression(c); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // otherwise it's just a grouping | ||||
|                 return NextExpression(GroupedExpression()); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         throw new InterpreterException($"Unexpected token {token.Value}", | ||||
|             token.Line, token.Column); | ||||
|     } | ||||
| 
 | ||||
|     public void Expect(Token token) | ||||
|     { | ||||
|         var next = _iterator.GetNext(); | ||||
|         if (next == default) | ||||
|         { | ||||
|             var cur = _iterator.GetNext(-1); | ||||
|             throw new InterpreterException($"Expected {token.Value}", | ||||
|                 cur.Line, cur.Column); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static IEnumerable<Expression> Parse(Token[] tokens) | ||||
|     { | ||||
|         var parser = new Parser(tokens); | ||||
| 
 | ||||
|         var iterator = parser._iterator; | ||||
|         while (iterator.GetNext() != default) | ||||
|         { | ||||
|             var expr = parser.NextExpression(null); | ||||
|             if (expr is not null) | ||||
|             { | ||||
|                 yield return expr; | ||||
|             } | ||||
|             iterator.MoveNext(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,64 @@ | |||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public enum TokenType | ||||
| { | ||||
|     None, | ||||
|     Identifier, | ||||
|     Grouping, | ||||
|     Operator, | ||||
|     String, | ||||
|     Number, | ||||
|     NodePath, | ||||
| } | ||||
| 
 | ||||
| public struct Token | ||||
| { | ||||
|     //public static Token EndToken => new Token(TokenType.End, ";", -1, -1); | ||||
|     public TokenType Type { get; set; } | ||||
|     public string Value { get; set; } | ||||
|     public int Line { get; set; } | ||||
|     public int Column { get; set; } | ||||
| 
 | ||||
|     public Token(TokenType type, string value, int line, int col) | ||||
|     { | ||||
|         Type = type; | ||||
|         Value = value; | ||||
|         Line = line; | ||||
|         Column = col; | ||||
|     } | ||||
| 
 | ||||
| #if DEBUG | ||||
|     public override string ToString() | ||||
|     { | ||||
|         return $"{Type} - {Value}\t\t@{Line}:{Column}"; | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     public bool CompareTypeValue(Token token) | ||||
|     { | ||||
|         return Type == token.Type && Value == token.Value; | ||||
|     } | ||||
| 
 | ||||
|     public override bool Equals(object o) | ||||
|     { | ||||
|         return base.Equals(o); | ||||
|     } | ||||
| 
 | ||||
|     public override int GetHashCode() | ||||
|     { | ||||
|         return base.GetHashCode(); | ||||
|     } | ||||
| 
 | ||||
|     public bool IsLiteral => Type == TokenType.String || | ||||
|         Type == TokenType.Number || | ||||
|         Type == TokenType.NodePath; | ||||
| 
 | ||||
|     public bool IsSymbol => IsLiteral || Type == TokenType.Identifier; | ||||
| 
 | ||||
|     public static bool operator ==(Token left, Token right) | ||||
|     { | ||||
|         return left.Type == right.Type && left.Value == right.Value; | ||||
|     } | ||||
| 
 | ||||
|     public static bool operator !=(Token left, Token right) => !(left == right); | ||||
| } | ||||
|  | @ -0,0 +1,182 @@ | |||
| using System.Collections.Generic; | ||||
| using System.Text.RegularExpressions; | ||||
| 
 | ||||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public sealed class Tokenizer | ||||
| { | ||||
|     public readonly char DECIMAL_POINT = '.'; | ||||
|     public readonly char DECIMAL_SUBSEPARATOR = ','; | ||||
|     public readonly char NODE_PATH_PREFIX = '$'; | ||||
| 
 | ||||
|     private readonly HashSet<char> WHITESPACE = new HashSet<char> | ||||
|     { | ||||
|         ' ', | ||||
|         '\n' | ||||
|     }; | ||||
| 
 | ||||
|     private readonly HashSet<char> OPERATOR = new HashSet<char> | ||||
|     { | ||||
|         '+', | ||||
|         '-', | ||||
|         '*', | ||||
|         '/', | ||||
|         '.', | ||||
|         ',', | ||||
|         '=', | ||||
|         '!', | ||||
|     }; | ||||
| 
 | ||||
|     private readonly HashSet<char> GROUPING = new HashSet<char> | ||||
|     { | ||||
|         '(', | ||||
|         ')', | ||||
|     }; | ||||
| 
 | ||||
|     private readonly HashSet<char> STRING_DELIM = new HashSet<char> | ||||
|     { | ||||
|         '"', | ||||
|         '\'', | ||||
|     }; | ||||
| 
 | ||||
|     private readonly Regex REGEX_NUMBER = new Regex("[.0-9]"); | ||||
|     private readonly Regex REGEX_IDENTIFIER_START = new Regex("[_a-zA-Z]"); | ||||
|     private readonly Regex REGEX_IDENTIFIER = new Regex("[_a-zA-Z0-9]"); | ||||
|     private Regex NON_NODE_PATH = new("[^a-zA-Z0-9_\\-\\/\\.\\:]"); | ||||
| 
 | ||||
|     private static string ScanString(CharIterator iterator, char delim = '"') | ||||
|     { | ||||
|         string ret = ""; | ||||
| 
 | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char c = iterator.MoveNext(); | ||||
| 
 | ||||
|             if (c == delim) | ||||
|             { | ||||
|                 return ret; | ||||
|             } | ||||
|             else if (c == '\\') | ||||
|             { | ||||
|                 char escape = iterator.MoveNext(); | ||||
| 
 | ||||
|                 switch (escape) | ||||
|                 { | ||||
|                     case 'n': | ||||
|                         ret += '\n'; | ||||
|                         break; | ||||
|                     case 't': | ||||
|                         ret += '\t'; | ||||
|                         break; | ||||
|                     case '\0': | ||||
|                         throw new InterpreterException("Unexpected EOL, " + | ||||
|                             "expected proper string termination", | ||||
|                             iterator.Line, iterator.Column); | ||||
|                     default: | ||||
|                         ret += escape; | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 ret += c; | ||||
|             } | ||||
|         } | ||||
|         throw new InterpreterException($"Unexpected EOL, expected: {delim}", | ||||
|             iterator.Line, iterator.Column); | ||||
|     } | ||||
| 
 | ||||
|     private string ScanNodePath(CharIterator iterator) | ||||
|     { | ||||
|         string ret = ""; | ||||
|         bool isAtStart = true; | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char c = iterator.MoveNext(); | ||||
| 
 | ||||
|             if (isAtStart && STRING_DELIM.Contains(c)) | ||||
|             { | ||||
|                 isAtStart = false; | ||||
|                 return ScanString(iterator, c); | ||||
|             } | ||||
|             else if (NON_NODE_PATH.IsMatch(c.ToString())) | ||||
|             { | ||||
|                 iterator.MoveBack(); | ||||
|                 return ret; | ||||
|             } | ||||
| 
 | ||||
|             isAtStart = false; | ||||
|             ret += c; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     private string ScanRegex(CharIterator iterator, Regex regex) | ||||
|     { | ||||
|         string ret = ""; | ||||
|         while (iterator.GetNext() != '\0') | ||||
|         { | ||||
|             char c = iterator.MoveNext(); | ||||
| 
 | ||||
|             if (!regex.IsMatch(c.ToString())) | ||||
|             { | ||||
|                 iterator.MoveBack(); | ||||
|                 return ret; | ||||
|             } | ||||
| 
 | ||||
|             ret += c; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     public IEnumerable<Token> Lex(CharIterator iterator) | ||||
|     { | ||||
|         //Token curToken = new Token(TokenType.Any, ); | ||||
|         while (iterator.GetNext() != default) | ||||
|         { | ||||
|             char c = iterator.MoveNext(); | ||||
|             int line = iterator.Line; | ||||
|             int col = iterator.Column; | ||||
| 
 | ||||
|             if (GROUPING.Contains(c)) | ||||
|             { | ||||
|                 yield return new Token(TokenType.Grouping, | ||||
|                     c.ToString(), line, col); | ||||
|             } | ||||
|             else if (OPERATOR.Contains(c)) | ||||
|             { | ||||
|                 yield return new Token(TokenType.Operator, | ||||
|                     c.ToString(), line, col); | ||||
|             } | ||||
|             else if (c == NODE_PATH_PREFIX) | ||||
|             { | ||||
|                 yield return new Token(TokenType.NodePath, | ||||
|                     ScanNodePath(iterator), line, col); | ||||
|             } | ||||
|             else if (STRING_DELIM.Contains(c)) | ||||
|             { | ||||
|                 yield return new Token(TokenType.String, | ||||
|                     ScanString(iterator, c), line, col); | ||||
|             } | ||||
|             else if (REGEX_IDENTIFIER_START.IsMatch(c.ToString())) | ||||
|             { | ||||
|                 yield return new Token(TokenType.Identifier, | ||||
|                     c + ScanRegex(iterator, REGEX_IDENTIFIER), line, col); | ||||
|             } | ||||
|             else if (REGEX_NUMBER.IsMatch(c.ToString())) | ||||
|             { | ||||
|                 yield return new Token(TokenType.Number, | ||||
|                     c + ScanRegex(iterator, REGEX_NUMBER), line, col); | ||||
|             } | ||||
|             else if (WHITESPACE.Contains(c)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 throw new InterpreterException($"Unknown symbol {c}", | ||||
|                     line, col); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,15 @@ | |||
| using System.Linq; | ||||
| 
 | ||||
| namespace SupaLidlGame.Debug.Transpiler; | ||||
| 
 | ||||
| public static class Transpiler | ||||
| { | ||||
|     public static string Transpile(string source) | ||||
|     { | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(source); | ||||
|         var tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         var exprs = Parser.Parse(tokens).ToArray(); | ||||
|         return exprs[0].Transpile(); | ||||
|     } | ||||
| } | ||||
|  | @ -4,6 +4,9 @@ | |||
|     <EnableDynamicLoading>true</EnableDynamicLoading> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <Content Remove="UnitTests/**/*" /> | ||||
|     <Compile Remove="UnitTests/**/*" /> | ||||
|     <None Remove="UnitTests/**/*" /> | ||||
|     <PackageReference Include="Firebelley.GodotUtilities" Version="4.0.4" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
| </Project> | ||||
|  |  | |||
|  | @ -2,11 +2,13 @@ | |||
| # Visual Studio 2012 | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SupaLidlGame", "SupaLidlGame.csproj", "{BC071CA6-9462-4CEC-AA20-B0CA618321E5}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{5D279483-BBEE-46A7-B5B9-68F335BDD6ED}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 	Debug|Any CPU = Debug|Any CPU | ||||
| 	ExportDebug|Any CPU = ExportDebug|Any CPU | ||||
| 	ExportRelease|Any CPU = ExportRelease|Any CPU | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		ExportDebug|Any CPU = ExportDebug|Any CPU | ||||
| 		ExportRelease|Any CPU = ExportRelease|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{BC071CA6-9462-4CEC-AA20-B0CA618321E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
|  | @ -15,5 +17,11 @@ Global | |||
| 		{BC071CA6-9462-4CEC-AA20-B0CA618321E5}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU | ||||
| 		{BC071CA6-9462-4CEC-AA20-B0CA618321E5}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU | ||||
| 		{BC071CA6-9462-4CEC-AA20-B0CA618321E5}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU | ||||
| 		{5D279483-BBEE-46A7-B5B9-68F335BDD6ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{5D279483-BBEE-46A7-B5B9-68F335BDD6ED}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{5D279483-BBEE-46A7-B5B9-68F335BDD6ED}.ExportDebug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{5D279483-BBEE-46A7-B5B9-68F335BDD6ED}.ExportDebug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{5D279483-BBEE-46A7-B5B9-68F335BDD6ED}.ExportRelease|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{5D279483-BBEE-46A7-B5B9-68F335BDD6ED}.ExportRelease|Any CPU.Build.0 = Debug|Any CPU | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
|  |  | |||
|  | @ -48,10 +48,14 @@ text = "[b]/root/World:[/b] \\echo :CurrentPlayer:Health | |||
| " | ||||
| scroll_following = true | ||||
| 
 | ||||
| [node name="Entry" type="LineEdit" parent="Window/DebugConsole/VBoxContainer"] | ||||
| [node name="Entry" type="CodeEdit" parent="Window/DebugConsole/VBoxContainer"] | ||||
| unique_name_in_owner = true | ||||
| custom_minimum_size = Vector2(0, 48) | ||||
| layout_mode = 2 | ||||
| theme_override_font_sizes/font_size = 24 | ||||
| placeholder_text = "Enter a GDScript expression or \\command..." | ||||
| draw_control_chars = true | ||||
| code_completion_enabled = true | ||||
| auto_brace_completion_enabled = true | ||||
| auto_brace_completion_highlight_matching = true | ||||
| script = ExtResource("2_kdlsh") | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| # output | ||||
| bin/ | ||||
| obj/ | ||||
|  | @ -0,0 +1,194 @@ | |||
| using SupaLidlGame.Debug.Transpiler; | ||||
| using Xunit.Abstractions; | ||||
| 
 | ||||
| namespace SupaLidlGame.UnitTests; | ||||
| 
 | ||||
| public class GodotTranspilerTest | ||||
| {    | ||||
|     private readonly ITestOutputHelper output; | ||||
| 
 | ||||
|     public GodotTranspilerTest(ITestOutputHelper output) | ||||
|     { | ||||
|         this.output = output; | ||||
|     } | ||||
| 
 | ||||
|     [Theory] | ||||
|     [InlineData("abc", TokenType.Identifier)] | ||||
|     [InlineData("123", TokenType.Number)] | ||||
|     [InlineData("$Node/Path:Property", TokenType.NodePath)] | ||||
|     public void Tokenize(string str, TokenType expectedType) | ||||
|     { | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(str); | ||||
|         Token[] tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         Assert.Equal(1, tokens.Length); | ||||
|         Assert.Equal(expectedType, tokens[0].Type); | ||||
|     } | ||||
| 
 | ||||
|     [Theory] | ||||
|     [InlineData("abc", "abc")] | ||||
|     [InlineData("ABC_DEF", "ABC_DEF")] | ||||
|     [InlineData("\"str\"", "str")] | ||||
|     public void TokenizeIdentifier(string str, string expectedValue) | ||||
|     { | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(str); | ||||
|         Token[] tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         Assert.Equal(1, tokens.Length); | ||||
|         Assert.Equal(expectedValue, tokens[0].Value); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void TokenizeSource() | ||||
|     { | ||||
|         string source = "abc $NodePath + operator="; | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(source); | ||||
|         Token[] tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         TokenType[] expectedTypes = | ||||
|         { | ||||
|             TokenType.Identifier, | ||||
|             TokenType.NodePath, | ||||
|             TokenType.Operator, | ||||
|             TokenType.Identifier, | ||||
|             TokenType.Operator, | ||||
|         }; | ||||
|         Assert.Equal(tokens.Length, expectedTypes.Length); | ||||
|         for (int i = 0; i < tokens.Length; i++) | ||||
|         { | ||||
|             Assert.Equal(expectedTypes[i], tokens[i].Type); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     [Theory] | ||||
|     [InlineData("$Some/Nested", "Some/Nested")] | ||||
|     [InlineData("$'Some/Nested'", "Some/Nested")] | ||||
|     [InlineData("$'With A/Path and:Property'", "With A/Path and:Property")] | ||||
|     [InlineData("$'broken as'hell", "broken as", 2)] | ||||
|     public void TestNodePath(string source, string expectedValue, int count = 1) | ||||
|     { | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(source); | ||||
|         Token[] tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         Assert.Equal(count, tokens.Length); | ||||
|         Assert.Equal(expectedValue, tokens[0].Value); | ||||
|     } | ||||
| 
 | ||||
|     [Theory] | ||||
|     [InlineData("\"this is a string\"", "this is a string")] | ||||
|     [InlineData("\"escape\\\\\"", "escape\\")] | ||||
|     public void TestStrings(string source, string expectedValue) | ||||
|     { | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(source); | ||||
|         Token[] tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         Assert.Equal(expectedValue, tokens[0].Value); | ||||
|     } | ||||
| 
 | ||||
|     [Theory] | ||||
|     [InlineData("()", 2)] | ||||
|     [InlineData("\"escape\\\\\"", 1)] | ||||
|     public void TestCount(string source, int expectedValue) | ||||
|     { | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(source); | ||||
|         Token[] tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         Assert.Equal(expectedValue, tokens.Length); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void TestAssignmentParsing() | ||||
|     { | ||||
|         Token[] tokens = | ||||
|         { | ||||
|             new(TokenType.Identifier, "x", 0, 0), | ||||
|             new(TokenType.Operator, "=", 0, 0), | ||||
|             new(TokenType.Identifier, "val", 0, 0), | ||||
|         }; | ||||
|         Expression[] expr = Parser.Parse(tokens).ToArray(); | ||||
|         Assert.Equal(1, expr.Length); | ||||
|         Assert.IsType<AssignmentExpression>(expr[0]); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void TestAssignmentGrouping() | ||||
|     { | ||||
|         Token[] tokens = | ||||
|         { | ||||
|             new(TokenType.Identifier, "x", 0, 0), | ||||
|             new(TokenType.Grouping, "(", 0, 0), | ||||
|             new(TokenType.Identifier, "val", 0, 0), | ||||
|             new(TokenType.Operator, ",", 0, 0), | ||||
|             new(TokenType.Identifier, "val", 0, 0), | ||||
|             new(TokenType.Grouping, ")", 0, 0), | ||||
|             new(TokenType.Operator, "+", 0, 0), | ||||
|             new(TokenType.Grouping, "(", 0, 0), | ||||
|             new(TokenType.Number, "2", 0, 0), | ||||
|             new(TokenType.Grouping, ")", 0, 0), | ||||
|         }; | ||||
|         Expression[] expr = Parser.Parse(tokens).ToArray(); | ||||
|         Assert.Equal(1, expr.Length); | ||||
|         Assert.IsType<OperationExpression>(expr[0]); | ||||
|         var op = expr[0] as OperationExpression; | ||||
|         Assert.NotNull(op); | ||||
|         var call = op.Left as CallExpression; | ||||
|         Assert.NotNull(call); | ||||
|         Assert.Equal(2, call.Arguments.Length); | ||||
|         Assert.IsType<GroupedExpression>(op.Right); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void TestExpressionTypes() | ||||
|     { | ||||
|         string source = "print(\"test\")"; | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(source); | ||||
|         var tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         var exprs = Parser.Parse(tokens).ToArray(); | ||||
|         Assert.IsType<CallExpression>(exprs[0]); | ||||
|         var call = exprs[0] as CallExpression; | ||||
|         Assert.IsType<LiteralExpression>(call.Arguments[0]); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void TestExpressionTypes2() | ||||
|     { | ||||
|         string source = "x = \"val\""; | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(source); | ||||
|         var tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         var exprs = Parser.Parse(tokens).ToArray(); | ||||
|         Assert.IsType<AssignmentExpression>(exprs[0]); | ||||
|         var call = exprs[0] as AssignmentExpression; | ||||
|         Assert.NotEqual(null, call.Left); | ||||
|         Assert.IsType<LiteralExpression>(call.Expression); | ||||
|         Assert.Equal("x", call.Left.Literal.Value); | ||||
|         Assert.Equal("x", call.Left.Transpile()); | ||||
|         Assert.Equal("\"val\"", call.Expression.Transpile()); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void TestIndividualTranspilation() | ||||
|     { | ||||
|         var left = new LiteralExpression(new Token(TokenType.Identifier, "x", 0, 0), 0, 0); | ||||
|         var right = new LiteralExpression(new Token(TokenType.String, "lol", 0, 0), 0, 0); | ||||
|         var exp = new AssignmentExpression(left, right, 0, 0); | ||||
|         Assert.Equal("set(\"x\", \"lol\")", exp.Transpile()); | ||||
|     } | ||||
| 
 | ||||
|     [Theory] | ||||
|     [InlineData("x = 2", "set(\"x\", 2)")] | ||||
|     [InlineData("call(arg1, arg2)", "call(arg1, arg2)")] | ||||
|     [InlineData("call(arg1,arg2)", "call(arg1, arg2)")] | ||||
|     [InlineData("x = (a + b) *c+d + f(\"str\")", "set(\"x\", (a + b) * c + d + f(\"str\"))")] | ||||
|     [InlineData("3 + My_Func(x * 2, x*5)", "3 + My_Func(x * 2, x * 5)")] | ||||
|     public void TestTranspilation(string source, string transpiled) | ||||
|     { | ||||
|         var tokenizer = new Debug.Transpiler.Tokenizer(); | ||||
|         SupaLidlGame.Debug.CharIterator iterator = new(source); | ||||
|         var tokens = tokenizer.Lex(iterator).ToArray(); | ||||
|         var exprs = Parser.Parse(tokens).ToArray(); | ||||
|         Assert.Equal(1, exprs.Length); | ||||
|         Assert.Equal(transpiled, exprs[0].Transpile()); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,10 @@ | |||
| namespace SupaLidlGame.UnitTests; | ||||
| 
 | ||||
| public class UnitTest1 | ||||
| { | ||||
|     [Fact] | ||||
|     public void Test1() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,28 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net7.0</TargetFramework> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <Nullable>enable</Nullable> | ||||
| 
 | ||||
|     <IsPackable>false</IsPackable> | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> | ||||
|     <PackageReference Include="xunit" Version="2.4.2" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|     </PackageReference> | ||||
|     <PackageReference Include="coverlet.collector" Version="3.1.2"> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|     </PackageReference> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\SupaLidlGame.csproj" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
|  | @ -0,0 +1 @@ | |||
| global using Xunit; | ||||
		Loading…
	
		Reference in New Issue