From ab61a01573fea21a4e3272fbc70b0a83db7bdccc Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 10 Nov 2018 17:00:39 +0100 Subject: [PATCH] Handle variables --- Upsilon/Evaluator/Evaluator.cs | 52 +++++++++++++++--- Upsilon/Evaluator/Script.cs | 37 +++++++++++++ Upsilon/Evaluator/VariableScope.cs | 53 +++++++++++++++++++ .../AssignmentExpressionSyntax.cs | 29 ++++++++++ .../VariableExpressionSyntax.cs | 20 +++++++ Upsilon/Parser/IdentifierToken.cs | 19 +++++++ Upsilon/Parser/Lexer.cs | 15 ++++-- Upsilon/Parser/Parser.cs | 24 +++++++++ Upsilon/Parser/SyntaxKeyWords.cs | 2 + Upsilon/Parser/SyntaxKind.cs | 3 ++ Upsilon/Parser/SyntaxKindPrecedence.cs | 6 +++ Upsilon/Parser/SyntaxToken.cs | 2 +- Upsilon/Utilities/NodeStringFormatter.cs | 4 ++ UpsilonTests/BasicMathExpressions.cs | 8 +-- UpsilonTests/MathPrecedence.cs | 4 +- Yc/Program.cs | 11 ++-- 16 files changed, 266 insertions(+), 23 deletions(-) create mode 100644 Upsilon/Evaluator/Script.cs create mode 100644 Upsilon/Evaluator/VariableScope.cs create mode 100644 Upsilon/Parser/ExpressionSyntax/AssignmentExpressionSyntax.cs create mode 100644 Upsilon/Parser/ExpressionSyntax/VariableExpressionSyntax.cs create mode 100644 Upsilon/Parser/IdentifierToken.cs diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index bda0ad7..20443d4 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -3,19 +3,28 @@ using Upsilon.Parser; namespace Upsilon.Evaluator { - public static class Evaluator + public class Evaluator { - public static object Evaluate(this ScriptSyntax e) + public Script Script { get; } + public VariableScope Scope { get; } + + public Evaluator(Script script, VariableScope scope) + { + Script = script; + Scope = scope; + } + + public object Evaluate(ScriptSyntax e) { return EvaluateExpression(e.Statement); } - public static object Evaluate(this ExpressionSyntax e) + public object Evaluate(ExpressionSyntax e) { return EvaluateExpression(e); } - private static object EvaluateExpression(ExpressionSyntax e) + private object EvaluateExpression(ExpressionSyntax e) { switch (e.Kind) { @@ -26,13 +35,17 @@ namespace Upsilon.Evaluator case SyntaxKind.LiteralExpression: return ((LiteralExpressionSyntax) e).Value; case SyntaxKind.ParenthesizedExpression: - return ((ParenthesizedExpressionSyntax) e).Expression.Evaluate(); + return Evaluate(((ParenthesizedExpressionSyntax)e).Expression); + case SyntaxKind.AssignmentExpression: + return EvaluateAssignmentExpression((AssignmentExpressionSyntax)e); + case SyntaxKind.VariableExpression: + return EvaluateVariableExpression((VariableExpressionSyntax) e); default: throw new Exception("Invalid expression: " + e.Kind); } } - private static object EvaluateUnaryExpression(UnaryExpressionSyntax e) + private object EvaluateUnaryExpression(UnaryExpressionSyntax e) { var operand = EvaluateExpression(e.Expression); switch (e.Operator.Kind) @@ -48,7 +61,7 @@ namespace Upsilon.Evaluator } } - private static object EvaluateBinaryExpression(BinaryExpressionSyntax e) + private object EvaluateBinaryExpression(BinaryExpressionSyntax e) { var left = EvaluateExpression(e.Left); var right = EvaluateExpression(e.Right); @@ -75,5 +88,30 @@ namespace Upsilon.Evaluator } } + private object EvaluateAssignmentExpression(AssignmentExpressionSyntax e) + { + var variableName = e.Identifier.Name; + var val = EvaluateExpression(e.Expression); + if (e.LocalToken == null) + { + Scope.SetGlobalVariable(variableName, val); + } + else + { + Scope.SetVariable(variableName, val); + } + return val; + } + + private object EvaluateVariableExpression(VariableExpressionSyntax e) + { + var varName = e.Identifier.Name; + if (Scope.TryGetVariable(varName, out var value)) + { + return value; + } + throw new Exception("Unknown variable: " + varName); + } + } } \ No newline at end of file diff --git a/Upsilon/Evaluator/Script.cs b/Upsilon/Evaluator/Script.cs new file mode 100644 index 0000000..f1e333a --- /dev/null +++ b/Upsilon/Evaluator/Script.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Upsilon.Parser; + +namespace Upsilon.Evaluator +{ + public class Script : VariableScope + { + private string ScriptString { get; } + private Evaluator Evaluator { get; } + public readonly ScriptSyntax Parsed; + + public Script(string scriptString) + { + ScriptString = scriptString; + Evaluator = new Evaluator(this, this); + Parsed = Parser.Parser.Parse(scriptString); + } + + public Script(string scriptString, Dictionary variables = null) + :base(variables: variables) + { + ScriptString = scriptString; + Evaluator = new Evaluator(this, this); + Parsed = Parser.Parser.Parse(scriptString); + } + + public object Evaluate() + { + return Evaluator.Evaluate(Parsed); + } + + public T Evaluate() + { + return (T)Evaluator.Evaluate(Parsed); + } + } +} \ No newline at end of file diff --git a/Upsilon/Evaluator/VariableScope.cs b/Upsilon/Evaluator/VariableScope.cs new file mode 100644 index 0000000..88595f7 --- /dev/null +++ b/Upsilon/Evaluator/VariableScope.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +namespace Upsilon.Evaluator +{ + public class VariableScope + { + private VariableScope _parentScope; + public readonly Dictionary Variables = new Dictionary(); + + public VariableScope(VariableScope parentScope = null, Dictionary variables = null) + { + _parentScope = parentScope; + if (variables != null) + Variables = variables; + } + + + public void SetVariable(string key, object value) + { + if (Variables.ContainsKey(key)) + Variables[key] = value; + else + Variables.Add(key, value); + } + + public void SetGlobalVariable(string key, object value) + { + if (_parentScope == null) + { + SetVariable(key, value); + } + else + { + _parentScope.SetGlobalVariable(key, value); + } + } + + + public bool TryGetVariable(string key, out object result) + { + if (Variables.TryGetValue(key, out result)) + { + return true; + } + if (_parentScope != null) + { + return _parentScope.TryGetVariable(key, out result); + } + throw new Exception("Variable not found: " + key); + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/AssignmentExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/AssignmentExpressionSyntax.cs new file mode 100644 index 0000000..f0d67bc --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/AssignmentExpressionSyntax.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class AssignmentExpressionSyntax : ExpressionSyntax + { + public AssignmentExpressionSyntax(SyntaxToken localToken, IdentifierToken identifier, SyntaxToken equalsToken, + ExpressionSyntax expression) + { + LocalToken = localToken; + Identifier = identifier; + EqualsToken = equalsToken; + Expression = expression; + } + + public override SyntaxKind Kind => SyntaxKind.AssignmentExpression; + + public SyntaxToken LocalToken { get; } + public IdentifierToken Identifier { get; } + public SyntaxToken EqualsToken { get; } + public ExpressionSyntax Expression { get; } + + public override IEnumerable ChildNodes() + { + yield return Identifier; + yield return Expression; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/VariableExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/VariableExpressionSyntax.cs new file mode 100644 index 0000000..2e1e401 --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/VariableExpressionSyntax.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class VariableExpressionSyntax : ExpressionSyntax + { + public IdentifierToken Identifier { get; } + + public VariableExpressionSyntax(IdentifierToken identifier) + { + Identifier = identifier; + } + + public override SyntaxKind Kind => SyntaxKind.VariableExpression; + public override IEnumerable ChildNodes() + { + yield return Identifier; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/IdentifierToken.cs b/Upsilon/Parser/IdentifierToken.cs new file mode 100644 index 0000000..7e306c9 --- /dev/null +++ b/Upsilon/Parser/IdentifierToken.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class IdentifierToken : SyntaxToken + { + public IdentifierToken(string name, int position, int length) : base(SyntaxKind.Identifier, position, name, null) + { + Name = name; + } + + public string Name { get; } + public override SyntaxKind Kind => SyntaxKind.Identifier; + public override IEnumerable ChildNodes() + { + yield break; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/Lexer.cs b/Upsilon/Parser/Lexer.cs index 2fa89c1..8f9c73b 100644 --- a/Upsilon/Parser/Lexer.cs +++ b/Upsilon/Parser/Lexer.cs @@ -127,16 +127,21 @@ namespace Upsilon.Parser private SyntaxToken LexIdentifierOrKeyword() { var start = _position; - var numStr = new StringBuilder(); - numStr.Append(Current); + var stringBuilder = new StringBuilder(); + stringBuilder.Append(Current); while (char.IsLetterOrDigit(Next) || Next == '_') { - numStr.Append(Next); + stringBuilder.Append(Next); _position++; } - var kind = SyntaxKeyWords.GetSyntaxKind(numStr.ToString()); - return new SyntaxToken(kind, start, numStr.ToString(), null); + var kind = SyntaxKeyWords.GetSyntaxKind(stringBuilder.ToString()); + var str = stringBuilder.ToString(); + if (kind == SyntaxKind.Identifier) + { + return new IdentifierToken(str, start, str.Length); + } + return new SyntaxToken(kind, start, str, null); } } } \ No newline at end of file diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index 9e4b828..2c6c561 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -53,9 +53,30 @@ namespace Upsilon.Parser public ExpressionSyntax ParseExpression() { + if (Current.Kind == SyntaxKind.Identifier && Next.Kind == SyntaxKind.Equals) + { + return AssignmentExpression(); + } + if (Current.Kind == SyntaxKind.LocalKeyword && Next.Kind == SyntaxKind.Identifier) + { + return AssignmentExpression(); + } return ParseBinaryExpression(); } + private AssignmentExpressionSyntax AssignmentExpression() + { + SyntaxToken localKeyword = null; + if (Current.Kind == SyntaxKind.LocalKeyword) + { + localKeyword = MatchToken(SyntaxKind.LocalKeyword); + } + var identifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier); + var assignmentToken = MatchToken(SyntaxKind.Equals); + var expression = ParseExpression(); + return new AssignmentExpressionSyntax(localKeyword, identifier, assignmentToken, expression); + } + private ExpressionSyntax ParseBinaryExpression(SyntaxKindPrecedence.Precedence parentPrecedence = SyntaxKindPrecedence.Precedence.None) { ExpressionSyntax left; @@ -96,6 +117,9 @@ namespace Upsilon.Parser case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: return ParseBoolean(); + case SyntaxKind.Identifier: + var token = MatchToken(SyntaxKind.Identifier); + return new VariableExpressionSyntax((IdentifierToken) token); default: throw new Exception("Unknown primary expression type: " + Current.Kind); } diff --git a/Upsilon/Parser/SyntaxKeyWords.cs b/Upsilon/Parser/SyntaxKeyWords.cs index b23c933..987a629 100644 --- a/Upsilon/Parser/SyntaxKeyWords.cs +++ b/Upsilon/Parser/SyntaxKeyWords.cs @@ -16,6 +16,8 @@ namespace Upsilon.Parser return SyntaxKind.AndKeyword; case "or": return SyntaxKind.OrKeyword; + case "local": + return SyntaxKind.LocalKeyword; default: return SyntaxKind.Identifier; } diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index cf016f3..a3bb0f3 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -24,6 +24,7 @@ namespace Upsilon.Parser NotKeyword, AndKeyword, OrKeyword, + LocalKeyword, Identifier, @@ -32,6 +33,8 @@ namespace Upsilon.Parser BinaryExpression, LiteralExpression, ParenthesizedExpression, + AssignmentExpression, + VariableExpression, // script unit ScriptUnit, diff --git a/Upsilon/Parser/SyntaxKindPrecedence.cs b/Upsilon/Parser/SyntaxKindPrecedence.cs index d323f7b..9074166 100644 --- a/Upsilon/Parser/SyntaxKindPrecedence.cs +++ b/Upsilon/Parser/SyntaxKindPrecedence.cs @@ -30,17 +30,23 @@ namespace Upsilon.Parser { switch (kind) { + // equality operators case SyntaxKind.EqualsEquals: return Precedence.Equality; case SyntaxKind.TildeEquals: return Precedence.Equality; + + // logical operators case SyntaxKind.AndKeyword: return Precedence.And; case SyntaxKind.OrKeyword: return Precedence.Or; + + // math operators case SyntaxKind.Plus: case SyntaxKind.Minus: return Precedence.PlusMinus; + case SyntaxKind.Star: case SyntaxKind.Slash: return Precedence.StarSlash; diff --git a/Upsilon/Parser/SyntaxToken.cs b/Upsilon/Parser/SyntaxToken.cs index b676a72..f350dfd 100644 --- a/Upsilon/Parser/SyntaxToken.cs +++ b/Upsilon/Parser/SyntaxToken.cs @@ -3,7 +3,7 @@ using Upsilon.Text; namespace Upsilon.Parser { - public sealed class SyntaxToken : SyntaxNode + public class SyntaxToken : SyntaxNode { public SyntaxToken(SyntaxKind kind, int position, string text, object value) { diff --git a/Upsilon/Utilities/NodeStringFormatter.cs b/Upsilon/Utilities/NodeStringFormatter.cs index da6be95..0c26e99 100644 --- a/Upsilon/Utilities/NodeStringFormatter.cs +++ b/Upsilon/Utilities/NodeStringFormatter.cs @@ -16,6 +16,10 @@ namespace Upsilon.Utilities sb.Append("|-- "); } sb.Append(token.Kind); + if (token is SyntaxToken node && node.Value != null) + { + sb.Append($" - {node.Value}"); + } foreach (var syntaxNode in token.ChildNodes()) { sb.Append("\n"); diff --git a/UpsilonTests/BasicMathExpressions.cs b/UpsilonTests/BasicMathExpressions.cs index a8fc6ea..e9ed4a5 100644 --- a/UpsilonTests/BasicMathExpressions.cs +++ b/UpsilonTests/BasicMathExpressions.cs @@ -16,7 +16,7 @@ namespace UpsilonTests [InlineData("0.005 + 2.2", 2.205)] public void Addition(string input, double expectedOutput) { - var actual = (double)Parser.Parse(input).Evaluate(); + var actual = new Script(input).Evaluate(); Assert.Equal(expectedOutput, actual, 8); } @@ -29,7 +29,7 @@ namespace UpsilonTests [InlineData("10.256-2.8546", 7.4014)] public void Subtraction(string input, double expectedOutput) { - var actual = (double)Parser.Parse(input).Evaluate(); + var actual = new Script(input).Evaluate(); Assert.Equal(expectedOutput, actual, 8); } @@ -39,7 +39,7 @@ namespace UpsilonTests [InlineData("21312 * 41684", 888369408)] public void Multiplication(string input, double expectedOutput) { - var actual = (double)Parser.Parse(input).Evaluate(); + var actual = new Script(input).Evaluate(); Assert.Equal(expectedOutput, actual, 8); } @@ -49,7 +49,7 @@ namespace UpsilonTests [InlineData("656486 / 5146", 127.57209483)] public void Divison(string input, double expectedOutput) { - var actual = (double)Parser.Parse(input).Evaluate(); + var actual = new Script(input).Evaluate(); Assert.Equal(expectedOutput, actual, 8); } diff --git a/UpsilonTests/MathPrecedence.cs b/UpsilonTests/MathPrecedence.cs index 716c720..32b970d 100644 --- a/UpsilonTests/MathPrecedence.cs +++ b/UpsilonTests/MathPrecedence.cs @@ -11,7 +11,7 @@ namespace UpsilonTests [InlineData("(10 + 5) * 5", 75)] public void Parenthesis(string input, double expectedOutput) { - var actual = (double)Parser.Parse(input).Evaluate(); + var actual = new Script(input).Evaluate(); Assert.Equal(expectedOutput, actual, 8); } @@ -20,7 +20,7 @@ namespace UpsilonTests [InlineData("5 + 10 * 5", 55)] public void MultiplicationBeforeAddition(string input, double expectedOutput) { - var actual = (double)Parser.Parse(input).Evaluate(); + var actual = new Script(input).Evaluate(); Assert.Equal(expectedOutput, actual, 8); } diff --git a/Yc/Program.cs b/Yc/Program.cs index 8679282..2f958f3 100644 --- a/Yc/Program.cs +++ b/Yc/Program.cs @@ -1,6 +1,7 @@ using System; +using System.Collections.Generic; using Upsilon.Evaluator; -using Upsilon.Parser; +using Upsilon.Utilities; namespace Yc { @@ -9,6 +10,7 @@ namespace Yc static void Main(string[] args) { Console.WriteLine("Upsilon REPL"); + Dictionary variables = new Dictionary(); while (true) { Console.Write("» "); @@ -18,9 +20,10 @@ namespace Yc return; } - var parser = Parser.Parse(input); - //Console.WriteLine(parser.Print()); - Console.WriteLine(parser.Evaluate()); + var parsed = new Script(input, variables); + //Console.WriteLine(parsed.Parsed.Print()); + Console.WriteLine(parsed.Evaluate()); + variables = parsed.Variables; } } }