From 886b897a4c61a3d485c70e54dd4ec71f51b9a08a Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Tue, 14 Jul 2020 18:48:00 +0200 Subject: [PATCH] Evaluation in REPL --- Parser/CompilationContext.cs | 6 +- Parser/Evaluator.cs | 116 +++++++++++++++++++++++++++--- Parser/Internal/DiagnosticsBag.cs | 12 +++- Parser/Internal/GreenNode.cs | 5 ++ Parser/Internal/MLexerGreen.cs | 35 ++++++++- Parser/Internal/SyntaxToken.cs | 11 ++- Parser/MFunctions/MOperations.cs | 50 +++++++++++++ Parser/Objects/MDoubleNumber.cs | 20 +++++- Parser/Objects/MObject.cs | 4 ++ Parser/SyntaxToken.cs | 2 + Repl/MRepl.cs | 4 +- 11 files changed, 248 insertions(+), 17 deletions(-) create mode 100644 Parser/MFunctions/MOperations.cs diff --git a/Parser/CompilationContext.cs b/Parser/CompilationContext.cs index df3c6fc..b716356 100644 --- a/Parser/CompilationContext.cs +++ b/Parser/CompilationContext.cs @@ -1,7 +1,11 @@ -namespace Parser +using Parser.Objects; +using System.Collections.Generic; + +namespace Parser { public class CompilationContext { + public Dictionary Variables { get; } = new Dictionary(); public static CompilationContext Empty => new CompilationContext(); } } \ No newline at end of file diff --git a/Parser/Evaluator.cs b/Parser/Evaluator.cs index 15ac7f5..8aeaa9a 100644 --- a/Parser/Evaluator.cs +++ b/Parser/Evaluator.cs @@ -1,21 +1,31 @@ using Parser.Internal; +using Parser.MFunctions; using Parser.Objects; using System; +using System.Collections.Generic; using System.Collections.Immutable; namespace Parser { + internal class EvaluationScope + { + public Dictionary Variables { get; } = new Dictionary(); + } + internal class Evaluator { - private SyntaxTree _syntaxTree; - private CompilationContext _context; - private DiagnosticsBag _diagnostics; + private readonly SyntaxTree _syntaxTree; + private readonly CompilationContext _context; + private readonly DiagnosticsBag _diagnostics = new DiagnosticsBag(); + private bool _insideFunction = false; + private readonly Stack _scopeStack = new Stack(); public Evaluator(SyntaxTree syntaxTree, CompilationContext context) { _syntaxTree = syntaxTree; _context = context; - _diagnostics = new DiagnosticsBag(); + var outerScope = new EvaluationScope(); + _scopeStack.Push(outerScope); } internal EvaluationResult Evaluate() @@ -178,7 +188,7 @@ namespace Parser private MObject? EvaluateParenthesizedExpression(ParenthesizedExpressionSyntaxNode expression) { - throw new NotImplementedException(); + return EvaluateExpression(expression.Expression); } private MObject? EvaluateClassInvokation(BaseClassInvokationSyntaxNode expression) @@ -243,17 +253,47 @@ namespace Parser private MObject? EvaluateNumberLiteralExpression(NumberLiteralSyntaxNode expression) { - throw new NotImplementedException(); + return expression.Number.Value is double value + ? MObject.CreateDoubleNumber(value) + : null; } private MObject? EvaluateIdentifierNameExpression(IdentifierNameExpressionSyntaxNode expression) { - throw new NotImplementedException(); + var variableName = expression.Name.Text; + var maybeValue = GetVariableValue(variableName); + if (maybeValue is null) + { + _diagnostics.ReportVariableNotFound( + new TextSpan(expression.Name.Position, expression.Name.Text.Length), + variableName); + } + + return maybeValue; } private MObject? EvaluateBinaryOperation(BinaryOperationExpressionSyntaxNode expression) { - throw new NotImplementedException(); + var left = EvaluateExpression(expression.Lhs); + if (left is null) + { + return null; + } + + var right = EvaluateExpression(expression.Rhs); + if (right is null) + { + return null; + } + + return expression.Operation.Kind switch + { + TokenKind.PlusToken => MOperations.Plus(left, right), + TokenKind.MinusToken => MOperations.Minus(left, right), + TokenKind.StarToken => MOperations.Star(left, right), + TokenKind.SlashToken => MOperations.Slash(left, right), + _ => throw new NotImplementedException($"Binary operation {expression.Operation.Kind} is not implemented."), + }; } private MObject? EvaluateCompoundName(CompoundNameExpressionSyntaxNode expression) @@ -273,9 +313,69 @@ namespace Parser private MObject? EvaluateAssignmentExpression(AssignmentExpressionSyntaxNode expression) { + var rightValue = EvaluateExpression(expression.Rhs); + if (rightValue is null) + { + _diagnostics.ReportCannotEvaluateExpression( + new TextSpan(expression.Rhs.Position, expression.Rhs.Position + expression.Rhs.FullWidth)); + return null; + } + + var left = expression.Lhs; + if (left.Kind == TokenKind.IdentifierNameExpression) + { + var leftIdentifier = (IdentifierNameExpressionSyntaxNode)left; + var variableName = leftIdentifier.Name.Text; + SetVariableValue(variableName, rightValue); + return rightValue; + } + throw new NotImplementedException(); } + private MObject? GetVariableValue(string name) + { + if (_insideFunction) + { + if (_context.Variables.TryGetValue(name, out var globalValue)) + { + return globalValue; + } + + var currentScope = _scopeStack.Peek(); + return currentScope.Variables.TryGetValue(name, out var localValue) ? globalValue : null; + } + else + { + if (_context.Variables.TryGetValue(name, out var globalValue)) + { + return globalValue; + } + + return null; + } + } + + private void SetVariableValue(string name, MObject value) + { + if (_insideFunction) + { + if (_context.Variables.ContainsKey(name)) + { + _context.Variables[name] = value; + } + else + { + var currentScope = _scopeStack.Peek(); + currentScope.Variables[name] = value; + } + } + else + { + _context.Variables[name] = value; + } + } + private MObject? EvaluateLambdaExpression(LambdaExpressionSyntaxNode expression) { throw new NotImplementedException(); diff --git a/Parser/Internal/DiagnosticsBag.cs b/Parser/Internal/DiagnosticsBag.cs index 1dadf31..76f1540 100644 --- a/Parser/Internal/DiagnosticsBag.cs +++ b/Parser/Internal/DiagnosticsBag.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -67,6 +68,11 @@ namespace Parser.Internal Report(span, "Unmatched open parenthesis by the end of file."); } + internal void ReportCannotEvaluateExpression(TextSpan span) + { + Report(span, $"Cannot evaluate expression."); + } + public IEnumerator GetEnumerator() { return _diagnostics.GetEnumerator(); @@ -77,5 +83,9 @@ namespace Parser.Internal return GetEnumerator(); } + internal void ReportVariableNotFound(TextSpan span, string variableName) + { + Report(span, $"Variable '{variableName}' not found."); + } } } \ No newline at end of file diff --git a/Parser/Internal/GreenNode.cs b/Parser/Internal/GreenNode.cs index dfda086..1ee5b19 100644 --- a/Parser/Internal/GreenNode.cs +++ b/Parser/Internal/GreenNode.cs @@ -43,6 +43,11 @@ namespace Parser.Internal internal abstract Parser.SyntaxNode CreateRed(Parser.SyntaxNode parent, int position); + public virtual object? GetValue() + { + return null; + } + protected int _fullWidth; public int FullWidth => _fullWidth; diff --git a/Parser/Internal/MLexerGreen.cs b/Parser/Internal/MLexerGreen.cs index 1537d84..5c8a170 100644 --- a/Parser/Internal/MLexerGreen.cs +++ b/Parser/Internal/MLexerGreen.cs @@ -13,6 +13,7 @@ namespace Parser.Internal public string Text { get; set; } = ""; public string StringValue { get; set; } = ""; public double DoubleValue { get; set; } + public bool ImaginaryFlag { get; set; } } private ITextWindow Window { get; } @@ -358,20 +359,48 @@ namespace Parser.Internal if (success) { + tokenInfo.Kind = TokenKind.NumberLiteralToken; + Range rangeToParse; if (Window.PeekChar(n) == 'i' || Window.PeekChar(n) == 'j') { + tokenInfo.ImaginaryFlag = true; n++; + rangeToParse = ..^1; + } + else + { + rangeToParse = ..; } - var s = Window.GetAndConsumeChars(n); - tokenInfo.Kind = TokenKind.NumberLiteralToken; + var s = Window.GetAndConsumeChars(n); tokenInfo.Text = s; - return true; + var maybeValue = ParseDoubleValue(s[rangeToParse]); + if (maybeValue is double value) + { + tokenInfo.DoubleValue = value; + return true; + } + else + { + tokenInfo.DoubleValue = double.NaN; + return true; + } } return false; } + private double? ParseDoubleValue(string s) + { + if (double.TryParse(s, out var doubleValue)) + { + return doubleValue; + } else + { + return null; + } + } + private bool ContinueLexingGeneralStringLiteral(ref TokenInfo tokenInfo, char quote) { var status = 0; // no errors diff --git a/Parser/Internal/SyntaxToken.cs b/Parser/Internal/SyntaxToken.cs index 4c5ff63..22ff7ac 100644 --- a/Parser/Internal/SyntaxToken.cs +++ b/Parser/Internal/SyntaxToken.cs @@ -116,6 +116,7 @@ namespace Parser.Internal _fullWidth = text?.Length ?? 0; } + public override void WriteTokenTo(TextWriter writer, bool leading, bool trailing) { writer.Write(_text); @@ -126,7 +127,9 @@ namespace Parser.Internal return new SyntaxTokenWithValue(Kind, _text, _value, diagnostics); } - public T Value => _value; + public T TypedValue => _value; + + public override object? Value => TypedValue; } internal class SyntaxTokenWithValueAndTrivia : SyntaxTokenWithValue @@ -181,7 +184,7 @@ namespace Parser.Internal public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics) { - return new SyntaxTokenWithValueAndTrivia(Kind, _text, Value, LeadingTrivia, TrailingTrivia, diagnostics); + return new SyntaxTokenWithValueAndTrivia(Kind, _text, TypedValue, LeadingTrivia, TrailingTrivia, diagnostics); } } @@ -311,6 +314,10 @@ namespace Parser.Internal internal static SyntaxToken NoneToken => new MissingTokenWithTrivia(TokenKind.None, s_EmptySyntaxTriviaList, s_EmptySyntaxTriviaList); + public virtual object? Value => null; + + public override object? GetValue() => Value; + public virtual int Width => Text.Length; public override IReadOnlyList LeadingTriviaCore => s_EmptySyntaxTriviaList; diff --git a/Parser/MFunctions/MOperations.cs b/Parser/MFunctions/MOperations.cs new file mode 100644 index 0000000..7761f19 --- /dev/null +++ b/Parser/MFunctions/MOperations.cs @@ -0,0 +1,50 @@ +using Parser.Objects; + +namespace Parser.MFunctions +{ + public static class MOperations + { + public static MObject? Plus(MObject left, MObject right) + { + if (left is MDoubleNumber { Value: var lValue } + && right is MDoubleNumber { Value: var rValue }) + { + return MObject.CreateDoubleNumber(lValue + rValue); + } + + return null; + } + + public static MObject? Minus(MObject left, MObject right) + { + if (left is MDoubleNumber { Value: var lValue } + && right is MDoubleNumber { Value: var rValue }) + { + return MObject.CreateDoubleNumber(lValue - rValue); + } + + return null; + } + + public static MObject? Star(MObject left, MObject right) + { + if (left is MDoubleNumber { Value: var lValue } + && right is MDoubleNumber { Value: var rValue }) + { + return MObject.CreateDoubleNumber(lValue * rValue); + } + + return null; + } + public static MObject? Slash(MObject left, MObject right) + { + if (left is MDoubleNumber { Value: var lValue } + && right is MDoubleNumber { Value: var rValue }) + { + return MObject.CreateDoubleNumber(lValue / rValue); + } + + return null; + } + } +} diff --git a/Parser/Objects/MDoubleNumber.cs b/Parser/Objects/MDoubleNumber.cs index b745a64..bb7f76f 100644 --- a/Parser/Objects/MDoubleNumber.cs +++ b/Parser/Objects/MDoubleNumber.cs @@ -1,6 +1,24 @@ -namespace Parser.Objects +using System.Globalization; + +namespace Parser.Objects { public class MDoubleNumber : MObject { + private MDoubleNumber(double value) + { + Value = value; + } + + public double Value { get; } + + public static MDoubleNumber Create(double value) + { + return new MDoubleNumber(value); + } + + public override string ToString() + { + return Value.ToString(CultureInfo.InvariantCulture); + } } } \ No newline at end of file diff --git a/Parser/Objects/MObject.cs b/Parser/Objects/MObject.cs index f84b55d..cda575b 100644 --- a/Parser/Objects/MObject.cs +++ b/Parser/Objects/MObject.cs @@ -2,5 +2,9 @@ { public abstract class MObject { + public static MDoubleNumber CreateDoubleNumber(double value) + { + return MDoubleNumber.Create(value); + } } } \ No newline at end of file diff --git a/Parser/SyntaxToken.cs b/Parser/SyntaxToken.cs index e0c27f1..2b68968 100644 --- a/Parser/SyntaxToken.cs +++ b/Parser/SyntaxToken.cs @@ -29,6 +29,8 @@ namespace Parser public int Position { get; } + public object? Value => _token.GetValue(); + public bool Equals(SyntaxToken other) { return Equals(_parent, other._parent) && Equals(_token, other._token); diff --git a/Repl/MRepl.cs b/Repl/MRepl.cs index c43e3e8..3edfe38 100644 --- a/Repl/MRepl.cs +++ b/Repl/MRepl.cs @@ -36,7 +36,9 @@ namespace Repl private void Print(string result) { - Console.Write(result); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(result); + Console.ResetColor(); } private string Read()