Handle variables

This commit is contained in:
Deukhoofd 2018-11-10 17:00:39 +01:00
parent 0693698f28
commit ab61a01573
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
16 changed files with 266 additions and 23 deletions

View File

@ -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);
}
}
}

View File

@ -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<string, object> 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<T>()
{
return (T)Evaluator.Evaluate(Parsed);
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
namespace Upsilon.Evaluator
{
public class VariableScope
{
private VariableScope _parentScope;
public readonly Dictionary<string, object> Variables = new Dictionary<string, object>();
public VariableScope(VariableScope parentScope = null, Dictionary<string, object> 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);
}
}
}

View File

@ -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<SyntaxNode> ChildNodes()
{
yield return Identifier;
yield return Expression;
}
}
}

View File

@ -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<SyntaxNode> ChildNodes()
{
yield return Identifier;
}
}
}

View File

@ -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<SyntaxNode> ChildNodes()
{
yield break;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -16,6 +16,8 @@ namespace Upsilon.Parser
return SyntaxKind.AndKeyword;
case "or":
return SyntaxKind.OrKeyword;
case "local":
return SyntaxKind.LocalKeyword;
default:
return SyntaxKind.Identifier;
}

View File

@ -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,

View File

@ -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;

View File

@ -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)
{

View File

@ -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");

View File

@ -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<double>();
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<double>();
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<double>();
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<double>();
Assert.Equal(expectedOutput, actual, 8);
}

View File

@ -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<double>();
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<double>();
Assert.Equal(expectedOutput, actual, 8);
}

View File

@ -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<string, object> variables = new Dictionary<string, object>();
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;
}
}
}