diff --git a/Minsk.Tests/CodeAnalysis/EvaluationTest.cs b/Minsk.Tests/CodeAnalysis/EvaluationTest.cs new file mode 100644 index 0000000..7317a6b --- /dev/null +++ b/Minsk.Tests/CodeAnalysis/EvaluationTest.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using Minsk.CodeAnalysis; +using Minsk.CodeAnalysis.Syntax; +using Xunit; + +namespace Minsk.Tests.CodeAnalysis +{ + public class EvaluationTests + { + [Theory] + [InlineData("1", 1)] + [InlineData("+1", 1)] + [InlineData("-1", -1)] + [InlineData("14 + 12", 26)] + [InlineData("12 - 3", 9)] + [InlineData("4 * 2", 8)] + [InlineData("9 / 3", 3)] + [InlineData("(10)", 10)] + [InlineData("12 == 3", false)] + [InlineData("3 == 3", true)] + [InlineData("12 != 3", true)] + [InlineData("3 != 3", false)] + [InlineData("false == false", true)] + [InlineData("true == false", false)] + [InlineData("false != false", false)] + [InlineData("true != false", true)] + [InlineData("true", true)] + [InlineData("false", false)] + [InlineData("!true", false)] + [InlineData("!false", true)] + [InlineData("(a = 10) * a", 100)] + public void SyntaxFact_GetText_RoundTrips(string text, object expectedValue) + { + var syntaxTree = SyntaxTree.Parse(text); + var compilation = new Compilation(syntaxTree); + var variables = new Dictionary(); + var result = compilation.evaluate(variables); + + Assert.Empty(result.Diagnostics); + Assert.Equal(expectedValue, result.Value); + } + } +} \ No newline at end of file diff --git a/Minsk.Tests/CodeAnalysis/Syntax/SyntaxFctTest.cs b/Minsk.Tests/CodeAnalysis/Syntax/SyntaxFactTest.cs similarity index 62% rename from Minsk.Tests/CodeAnalysis/Syntax/SyntaxFctTest.cs rename to Minsk.Tests/CodeAnalysis/Syntax/SyntaxFactTest.cs index f41e0cc..eb7b95f 100644 --- a/Minsk.Tests/CodeAnalysis/Syntax/SyntaxFctTest.cs +++ b/Minsk.Tests/CodeAnalysis/Syntax/SyntaxFactTest.cs @@ -5,13 +5,20 @@ namespace Minsk.Tests.CodeAnalysis.Syntax { - public class SyntaxFctTest + public class SyntaxFactTests { [Theory] [MemberData(nameof(GetSyntaxKindData))] public void SyntaxFact_GetText_RoundTrips(SyntaxKind kind) { - var text = SyntaxFct.GetText(kind); + var text = SyntaxFacts.GetText(kind); + if (text == null) + return; + + var tokens = SyntaxTree.ParseTokens(text); + var token = Assert.Single(tokens); + Assert.Equal(kind, token.Kind); + Assert.Equal(text, token.Text); } public static IEnumerable GetSyntaxKindData() diff --git a/Minsk.Tests/Minsk.Tests.csproj b/Minsk.Tests/Minsk.Tests.csproj index a4c6c41..5330e14 100644 --- a/Minsk.Tests/Minsk.Tests.csproj +++ b/Minsk.Tests/Minsk.Tests.csproj @@ -1,4 +1,4 @@ - + + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Binding/Binder.cs b/Minsk/CodeAnalysis/Binding/Binder.cs index fbd51bc..7378445 100644 --- a/Minsk/CodeAnalysis/Binding/Binder.cs +++ b/Minsk/CodeAnalysis/Binding/Binder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Minsk.CodeAnalysis.Syntax; namespace Minsk.CodeAnalysis.Binding @@ -9,9 +10,9 @@ internal sealed class Binder { private readonly DiagnosticBag _diagnostics = new DiagnosticBag(); - private readonly Dictionary _variables; + private readonly Dictionary _variables; - public Binder(Dictionary variables) + public Binder(Dictionary variables) { _variables = variables; } @@ -46,33 +47,56 @@ private BoundExpression BindParenthesizedExpression(ParenthesizedExpressionSynta private BoundExpression BindAssignmentExpression(AssignmentExpressionSyntax syntax) { + // var name = syntax.IdentifierToken.Text; + // var boundExpression = BindExpression(syntax.Expression); + // var defaultValue = + // boundExpression.Type == typeof(int) + // ? (object)0 + // : boundExpression.Type == typeof(bool) + // ? (object)false + // : null; + + // if (defaultValue == null) + // throw new Exception($"Unsupported variable type: {boundExpression.Type}"); + + // _variables[name] = defaultValue; + // return new BoundAssignmentExpression(name, boundExpression); + // a=1 var name = syntax.IdentifierToken.Text; var boundExpression = BindExpression(syntax.Expression); - var defaultValue = - boundExpression.Type == typeof(int) - ? (object)0 - : boundExpression.Type == typeof(bool) - ? (object)false - : null; - - if (defaultValue == null) - throw new Exception($"Unsupported variable type: {boundExpression.Type}"); - - _variables[name] = defaultValue; - return new BoundAssignmentExpression(name, boundExpression); + + var existingVariable = _variables.Keys.FirstOrDefault(v => v.Name == name); + if (existingVariable != null) + _variables.Remove(existingVariable); + + var variable = new VariableSymbol(name, boundExpression.Type); + _variables[variable] = null; + + return new BoundAssignmentExpression(variable, boundExpression); + } private BoundExpression BindNameExpression(NameExpressionSyntax syntax) { + // var name = syntax.IdentifierToken.Text; + // if (!_variables.TryGetValue(name, out var value)) + // { + // _diagnostics.ReportUndefinedName(syntax.IdentifierToken.Span, name); + // return new BoundLiteralExpression(0); + // } + + // var type = value.GetType(); + // return new BoundVariableExpression(name, type); var name = syntax.IdentifierToken.Text; - if (!_variables.TryGetValue(name, out var value)) + var variable = _variables.Keys.FirstOrDefault(v => v.Name == name); + + if (variable == null) { _diagnostics.ReportUndefinedName(syntax.IdentifierToken.Span, name); return new BoundLiteralExpression(0); } - var type = value.GetType(); - return new BoundVariableExpression(name, type); + return new BoundVariableExpression(variable); } private BoundExpression BindLiteralExpression(LiteralExpressionSyntax syntax) diff --git a/Minsk/CodeAnalysis/Binding/BoundAssignmentExpression.cs b/Minsk/CodeAnalysis/Binding/BoundAssignmentExpression.cs index 017e4a2..ea49f6a 100644 --- a/Minsk/CodeAnalysis/Binding/BoundAssignmentExpression.cs +++ b/Minsk/CodeAnalysis/Binding/BoundAssignmentExpression.cs @@ -4,15 +4,15 @@ namespace Minsk.CodeAnalysis.Binding { internal sealed class BoundAssignmentExpression : BoundExpression { - public BoundAssignmentExpression(string name, BoundExpression expression) + public BoundAssignmentExpression(VariableSymbol variable, BoundExpression expression) { - Name = name; + Variable = variable; Expression = expression; } - public override BoundNodeKind Kind => BoundNodeKind.AssignmentExpression; + public override BoundNodeKind Kind => BoundNodeKind.BoundAssignmentExpression; public override Type Type => Expression.Type; - public string Name { get; } + public VariableSymbol Variable { get; } public BoundExpression Expression { get; } } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Binding/BoundBinaryExpression.cs b/Minsk/CodeAnalysis/Binding/BoundBinaryExpression.cs index c5455e9..ba7acb5 100644 --- a/Minsk/CodeAnalysis/Binding/BoundBinaryExpression.cs +++ b/Minsk/CodeAnalysis/Binding/BoundBinaryExpression.cs @@ -11,7 +11,7 @@ public BoundBinaryExpression(BoundExpression left, BoundBinaryOperator op, Bound Right = right; } - public override BoundNodeKind Kind => BoundNodeKind.BinaryExpression; + public override BoundNodeKind Kind => BoundNodeKind.BoundBinaryExpression; public override Type Type => Op.LeftType; public BoundExpression Left { get; } public BoundBinaryOperator Op { get; } diff --git a/Minsk/CodeAnalysis/Binding/BoundLiteralExpression.cs b/Minsk/CodeAnalysis/Binding/BoundLiteralExpression.cs index b0ece14..5936c6e 100644 --- a/Minsk/CodeAnalysis/Binding/BoundLiteralExpression.cs +++ b/Minsk/CodeAnalysis/Binding/BoundLiteralExpression.cs @@ -9,7 +9,7 @@ public BoundLiteralExpression(object value) Value = value; } - public override BoundNodeKind Kind => BoundNodeKind.LiteralExpression; + public override BoundNodeKind Kind => BoundNodeKind.BoundLiteralExpression; public override Type Type => Value.GetType(); public object Value { get; } } diff --git a/Minsk/CodeAnalysis/Binding/BoundNodeKind.cs b/Minsk/CodeAnalysis/Binding/BoundNodeKind.cs index a818f71..c246777 100644 --- a/Minsk/CodeAnalysis/Binding/BoundNodeKind.cs +++ b/Minsk/CodeAnalysis/Binding/BoundNodeKind.cs @@ -10,10 +10,10 @@ namespace Minsk.CodeAnalysis.Binding { internal enum BoundNodeKind { - LiteralExpression, - VariableExpression, - AssignmentExpression, - UnaryExpression, - BinaryExpression, + BoundLiteralExpression, + BoundVariableExpression, + BoundAssignmentExpression, + BoundUnaryExpression, + BoundBinaryExpression, } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Binding/BoundUnaryExpression.cs b/Minsk/CodeAnalysis/Binding/BoundUnaryExpression.cs index 8d32aec..a64f319 100644 --- a/Minsk/CodeAnalysis/Binding/BoundUnaryExpression.cs +++ b/Minsk/CodeAnalysis/Binding/BoundUnaryExpression.cs @@ -10,7 +10,7 @@ public BoundUnaryExpression(BoundUnaryOperator op, BoundExpression operand) Op = op; } - public override BoundNodeKind Kind => BoundNodeKind.UnaryExpression; + public override BoundNodeKind Kind => BoundNodeKind.BoundUnaryExpression; public override Type Type => Op.OperandType; public BoundUnaryOperator Op { get; } public BoundExpression Operand { get; } diff --git a/Minsk/CodeAnalysis/Binding/BoundVariableExpression.cs b/Minsk/CodeAnalysis/Binding/BoundVariableExpression.cs index c14f2f3..c58e1d0 100644 --- a/Minsk/CodeAnalysis/Binding/BoundVariableExpression.cs +++ b/Minsk/CodeAnalysis/Binding/BoundVariableExpression.cs @@ -4,14 +4,13 @@ namespace Minsk.CodeAnalysis.Binding { internal sealed class BoundVariableExpression: BoundExpression { - public BoundVariableExpression(string name, Type type) + public BoundVariableExpression(VariableSymbol variable) { - Name = name; - Type = type; + Variable = variable; } - public override BoundNodeKind Kind => BoundNodeKind.VariableExpression; - public string Name { get; } - public override Type Type { get; } + public override BoundNodeKind Kind => BoundNodeKind.BoundVariableExpression; + public override Type Type => Variable.Type; + public VariableSymbol Variable { get; } } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Compilation.cs b/Minsk/CodeAnalysis/Compilation.cs new file mode 100644 index 0000000..e7488c8 --- /dev/null +++ b/Minsk/CodeAnalysis/Compilation.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Minsk.CodeAnalysis.Binding; +using Minsk.CodeAnalysis.Syntax; + +namespace Minsk.CodeAnalysis +{ + public sealed class Compilation + { + public Compilation(SyntaxTree syntaxTree) + { + SyntaxTree = syntaxTree; + } + + public SyntaxTree SyntaxTree { get; } + + public EvaluateResult evaluate(Dictionary variables){ + // var binder = new Binder(variables); + // var boundExpression = binder.BindExpression(SyntaxTree.Root); + // var diagnostics = SyntaxTree.Diagnostics.Concat(binder.Diagnostics).ToArray(); + + // if (diagnostics.Any()) + // { + // return new EvaluateResult(diagnostics,null); + // } + + // var value = new Evaluator(boundExpression,variables).Evaluate(); + // return new EvaluateResult(Array.Empty(),value); + var binder = new Binder(variables); + var boundExpression = binder.BindExpression(SyntaxTree.Root); + + var diagnostics = SyntaxTree.Diagnostics.Concat(binder.Diagnostics).ToImmutableArray(); + if (diagnostics.Any()) + return new EvaluateResult(diagnostics, null); + + var evaluator = new Evaluator(boundExpression, variables); + var value = evaluator.Evaluate(); + return new EvaluateResult(ImmutableArray.Empty, value); + } + } +} + diff --git a/Minsk/CodeAnalysis/Complication.cs b/Minsk/CodeAnalysis/Complication.cs deleted file mode 100644 index 062f7d8..0000000 --- a/Minsk/CodeAnalysis/Complication.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Minsk.CodeAnalysis.Binding; -using Minsk.CodeAnalysis.Syntax; - -namespace Minsk.CodeAnalysis -{ - public sealed class Complication - { - public Complication(SyntaxTree syntaxTree) - { - SyntaxTree = syntaxTree; - } - - public SyntaxTree SyntaxTree { get; } - - public EvaluateResult evaluate(Dictionary variables){ - var binder = new Binder(variables); - var boundExpression = binder.BindExpression(SyntaxTree.Root); - var diagnostics = SyntaxTree.Diagnostics.Concat(binder.Diagnostics).ToArray(); - - if (diagnostics.Any()) - { - return new EvaluateResult(diagnostics,null); - } - - var value = new Evaluator(boundExpression,variables).Evaluate(); - return new EvaluateResult(Array.Empty(),value); - } - } -} - diff --git a/Minsk/CodeAnalysis/Diagnostic.cs b/Minsk/CodeAnalysis/Diagnostic.cs index 1b19282..8eb36c9 100644 --- a/Minsk/CodeAnalysis/Diagnostic.cs +++ b/Minsk/CodeAnalysis/Diagnostic.cs @@ -1,4 +1,4 @@ - +using Minsk.CodeAnalysis.Text; /** diagnostic目的是当输入的代码与语法规则不合的时候指出错误, 之前的diagnostic都是大致提示,比如` operator xxx is not defined for xtype and ytype` diff --git a/Minsk/CodeAnalysis/DiagnosticBag.cs b/Minsk/CodeAnalysis/DiagnosticBag.cs index 2c33385..c1906ce 100644 --- a/Minsk/CodeAnalysis/DiagnosticBag.cs +++ b/Minsk/CodeAnalysis/DiagnosticBag.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Collections.Generic; using Minsk.CodeAnalysis.Syntax; - +using Minsk.CodeAnalysis.Text; namespace Minsk.CodeAnalysis { @@ -57,6 +57,13 @@ public void ReportUndefinedBinaryOperator(TextSpan span, string operatorText, Ty Report(span, message); } + public void ReportBadCharacter(int position, char character) + { + var span = new TextSpan(position, 1); + var message = $"Bad character input: '{character}'."; + Report(span, message); + } + public void ReportUndefinedName(TextSpan span, string name) { var message = $"Variable '{name}' doesn't exist."; diff --git a/Minsk/CodeAnalysis/EvaluateResult.cs b/Minsk/CodeAnalysis/EvaluateResult.cs index 57d2804..53ba570 100644 --- a/Minsk/CodeAnalysis/EvaluateResult.cs +++ b/Minsk/CodeAnalysis/EvaluateResult.cs @@ -1,17 +1,18 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace Minsk.CodeAnalysis { public sealed class EvaluateResult { - public EvaluateResult(IEnumerable diagnostics, object value) + public EvaluateResult(ImmutableArray diagnostics, object value) { - Diagnostics = diagnostics.ToArray(); + Diagnostics = diagnostics; Value = value; } - public IReadOnlyList Diagnostics { get; } + public ImmutableArray Diagnostics { get; } public object Value { get; } } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Evaluator.cs b/Minsk/CodeAnalysis/Evaluator.cs index 075d34f..7811eea 100644 --- a/Minsk/CodeAnalysis/Evaluator.cs +++ b/Minsk/CodeAnalysis/Evaluator.cs @@ -1,23 +1,20 @@ using System; using System.Collections.Generic; using Minsk.CodeAnalysis.Binding; -using Minsk.CodeAnalysis.Syntax; namespace Minsk.CodeAnalysis { - internal sealed class Evaluator { private readonly BoundExpression _root; - private readonly Dictionary _variables; + private readonly Dictionary _variables; - public Evaluator(BoundExpression root, Dictionary variables) + public Evaluator(BoundExpression root, Dictionary variables) { _root = root; _variables = variables; } - public object Evaluate() { return EvaluateExpression(_root); @@ -25,66 +22,86 @@ public object Evaluate() private object EvaluateExpression(BoundExpression node) { - if (node is BoundLiteralExpression n) - return n.Value; - if (node is BoundVariableExpression v) + switch(node.Kind) { - return _variables[v.Name]; - } + case BoundNodeKind.BoundLiteralExpression: + return EvaluateLiteralExpression((BoundLiteralExpression)node); - if (node is BoundAssignmentExpression a) - { - var value = EvaluateExpression(a.Expression); - _variables[a.Name] = value; - return value; + case BoundNodeKind.BoundVariableExpression: + return EvaluateVariableExpression((BoundVariableExpression)node); + case BoundNodeKind.BoundAssignmentExpression: + return EvaluateAssignmentExpression((BoundAssignmentExpression)node); + case BoundNodeKind.BoundUnaryExpression: + return EvaluateUnaryExpression((BoundUnaryExpression)node); + case BoundNodeKind.BoundBinaryExpression: + return EvaluateBinaryExpression((BoundBinaryExpression)node); + default: + throw new Exception($"Unexpected node {node.Kind}"); } - if (node is BoundUnaryExpression u) - { - var operand = EvaluateExpression(u.Operand); + + } - switch (u.Op.Kind) - { - case BoundUnaryOperatorKind.Identity: - return (int)operand; - case BoundUnaryOperatorKind.Negation: - return -(int)operand; - case BoundUnaryOperatorKind.LogicalNegation: - return !(bool)operand; - default: - throw new Exception($"Unexpected unary operator {u.Op}"); - } - } + private object EvaluateAssignmentExpression(BoundAssignmentExpression a) + { + var value = EvaluateExpression(a.Expression); + _variables[a.Variable] = value; + return value; + } - if (node is BoundBinaryExpression b) - { - var left = EvaluateExpression(b.Left); - var right = EvaluateExpression(b.Right); + private object EvaluateVariableExpression(BoundVariableExpression v) + { + return _variables[v.Variable]; + } - switch (b.Op.Kind) - { - case BoundBinaryOperatorKind.Addition: - return (int)left + (int)right; - case BoundBinaryOperatorKind.Subtraction: - return (int)left - (int)right; - case BoundBinaryOperatorKind.Multiplication: - return (int)left * (int)right; - case BoundBinaryOperatorKind.Division: - return (int)left / (int)right; - case BoundBinaryOperatorKind.LogicalAnd: - return (bool) left && (bool) right; - case BoundBinaryOperatorKind.LogicalOr: - return (bool) left || (bool) right; - case BoundBinaryOperatorKind.Equals: - return Equals(left, right); - case BoundBinaryOperatorKind.NotEquals: - return !Equals(left, right); - default: - throw new Exception($"Unexpected binary operator {b.Op}"); - } + private static object EvaluateLiteralExpression(BoundLiteralExpression n) + { + return n.Value; + } + + private object EvaluateBinaryExpression(BoundBinaryExpression b) + { + var left = EvaluateExpression(b.Left); + var right = EvaluateExpression(b.Right); + + switch (b.Op.Kind) + { + case BoundBinaryOperatorKind.Addition: + return (int)left + (int)right; + case BoundBinaryOperatorKind.Subtraction: + return (int)left - (int)right; + case BoundBinaryOperatorKind.Multiplication: + return (int)left * (int)right; + case BoundBinaryOperatorKind.Division: + return (int)left / (int)right; + case BoundBinaryOperatorKind.LogicalAnd: + return (bool)left && (bool)right; + case BoundBinaryOperatorKind.LogicalOr: + return (bool)left || (bool)right; + case BoundBinaryOperatorKind.Equals: + return Equals(left, right); + case BoundBinaryOperatorKind.NotEquals: + return !Equals(left, right); + default: + throw new Exception($"Unexpected binary operator {b.Op}"); } + } + + private object EvaluateUnaryExpression(BoundUnaryExpression u) + { + var operand = EvaluateExpression(u.Operand); - throw new Exception($"Unexpected node {node.Kind}"); + switch (u.Op.Kind) + { + case BoundUnaryOperatorKind.Identity: + return (int)operand; + case BoundUnaryOperatorKind.Negation: + return -(int)operand; + case BoundUnaryOperatorKind.LogicalNegation: + return !(bool)operand; + default: + throw new Exception($"Unexpected unary operator {u.Op}"); + } } } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Syntax/AssignmentExpressionSyntax.cs b/Minsk/CodeAnalysis/Syntax/AssignmentExpressionSyntax.cs index 6235c95..33cfa41 100644 --- a/Minsk/CodeAnalysis/Syntax/AssignmentExpressionSyntax.cs +++ b/Minsk/CodeAnalysis/Syntax/AssignmentExpressionSyntax.cs @@ -18,13 +18,7 @@ public AssignmentExpressionSyntax(SyntaxToken identifierToken, SyntaxToken equal public SyntaxToken EqualsToken { get; } public ExpressionSyntax Expression { get; } - public override IEnumerable GetChildren() - { - yield return IdentifierToken; - yield return EqualsToken; - yield return Expression; - } - + } } diff --git a/Minsk/CodeAnalysis/Syntax/BinaryExpressionSyntax.cs b/Minsk/CodeAnalysis/Syntax/BinaryExpressionSyntax.cs index 7f56eaa..300cf43 100644 --- a/Minsk/CodeAnalysis/Syntax/BinaryExpressionSyntax.cs +++ b/Minsk/CodeAnalysis/Syntax/BinaryExpressionSyntax.cs @@ -16,11 +16,6 @@ public BinaryExpressionSyntax(ExpressionSyntax left, SyntaxToken operatorToken, public SyntaxToken OperatorToken { get; } public ExpressionSyntax Right { get; } - public override IEnumerable GetChildren() - { - yield return Left; - yield return OperatorToken; - yield return Right; - } + } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Syntax/Lexer.cs b/Minsk/CodeAnalysis/Syntax/Lexer.cs index f38687d..dc37cbc 100644 --- a/Minsk/CodeAnalysis/Syntax/Lexer.cs +++ b/Minsk/CodeAnalysis/Syntax/Lexer.cs @@ -1,14 +1,21 @@ using System.Collections.Generic; +using Minsk.CodeAnalysis.Text; + namespace Minsk.CodeAnalysis.Syntax { internal sealed class Lexer { - private readonly string _text; + private readonly SourceText _text; + private readonly DiagnosticBag _diagnostics = new DiagnosticBag(); + private int _position; - private DiagnosticBag _diagnostics = new DiagnosticBag(); - public Lexer(string text) + private int _start; + private SyntaxKind _kind; + private object _value; + + public Lexer(SourceText text) { _text = text; } @@ -33,103 +40,155 @@ private void Next() } public SyntaxToken Lex() - { - if (_position >= _text.Length) - return new SyntaxToken(SyntaxKind.EndOfFileToken, _position, "\0", null); - - var start = _position; - if (char.IsDigit(Current)) - { - - - while (char.IsDigit(Current)) - Next(); - - var length = _position - start; - var text = _text.Substring(start, length); - if (!int.TryParse(text, out var value)) - _diagnostics.ReportInvalidNumber(new TextSpan(start, length),_text, typeof(int)); - - return new SyntaxToken(SyntaxKind.NumberToken, start, text, value); - } - - if (char.IsWhiteSpace(Current)) - { - - while (char.IsWhiteSpace(Current)) - Next(); - - var length = _position - start; - var text = _text.Substring(start, length); - return new SyntaxToken(SyntaxKind.WhitespaceToken, start, text, null); - } - - if (char.IsLetter(Current)) - { - - while (char.IsLetter(Current)) - Next(); + { + _start = _position; + _kind = SyntaxKind.BadToken; + _value = null; - var length = _position - start; - var text = _text.Substring(start, length); - var kind = SyntaxFacts.GetKeywordKind(text); - return new SyntaxToken(kind, start, text, null); - } switch (Current) { + case '\0': + _kind = SyntaxKind.EndOfFileToken; + break; case '+': - return new SyntaxToken(SyntaxKind.PlusToken, _position++, "+", null); + _kind = SyntaxKind.PlusToken; + _position++; + break; case '-': - return new SyntaxToken(SyntaxKind.MinusToken, _position++, "-", null); + _kind = SyntaxKind.MinusToken; + _position++; + break; case '*': - return new SyntaxToken(SyntaxKind.StarToken, _position++, "*", null); + _kind = SyntaxKind.StarToken; + _position++; + break; case '/': - return new SyntaxToken(SyntaxKind.SlashToken, _position++, "/", null); + _kind = SyntaxKind.SlashToken; + _position++; + break; case '(': - return new SyntaxToken(SyntaxKind.OpenParenthesisToken, _position++, "(", null); + _kind = SyntaxKind.OpenParenthesisToken; + _position++; + break; + case ')': - return new SyntaxToken(SyntaxKind.CloseParenthesisToken, _position++, ")", null); + _kind = SyntaxKind.CloseParenthesisToken; + _position++; + break; + case '&': if (Lookahead == '&') { + _kind = SyntaxKind.AmpersandAmpersandToken; _position += 2; - return new SyntaxToken(SyntaxKind.AmpersandAmpersandToken, start, "&&", null); + break; } break; case '|': if (Lookahead == '|') { + _kind = SyntaxKind.PipePipeToken; _position += 2; - return new SyntaxToken(SyntaxKind.PipePipeToken, start, "||", null); + break; } break; case '=': - if (Lookahead == '=') + _position++; + if(Current != '=') { - _position += 2; - return new SyntaxToken(SyntaxKind.EqualsEqualsToken, start, "==", null); - } else + _kind = SyntaxKind.EqualsToken; + } + else { - _position += 1; - return new SyntaxToken(SyntaxKind.EqualsToken, start, "=", null); + _position++; + _kind = SyntaxKind.EqualsEqualsToken; } - + break; + case '!': - if (Lookahead == '='){ - _position += 2; - return new SyntaxToken(SyntaxKind.BangEqualsToken, start, "!=", null); - + _position++; + if(Current != '=') + { + _kind = SyntaxKind.BangToken; + } + else { + _position++; + _kind = SyntaxKind.BangEqualsToken; + } + break; + case '0': case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + ReadNumberToken(); + break; + + case ' ': + case '\t': + case '\r': + case '\n': + ReadWhiteSpace(); + break; + + default: + if (char.IsLetter(Current)) + { + ReadIdentifierOrKeyword(); + } + else if(char.IsWhiteSpace(Current)) + { + ReadWhiteSpace(); } - else + else { - _position += 1; - return new SyntaxToken(SyntaxKind.BangToken, start,"!", null); + _diagnostics.ReportBadCharacter(_position, Current); + _position++; } + break; } - _diagnostics.ReportBadToken(_position, Current); - return new SyntaxToken(SyntaxKind.BadToken, _position++, _text.Substring(_position - 1, 1), null); + var length =_position -_start; + var text = SyntaxFacts.GetText(_kind); + if(text == null) + text = _text.ToString(_start, length); + + return new SyntaxToken(_kind, _start, text, _value); + } + + private void ReadNumberToken() + { + while (char.IsDigit(Current)) + Next(); + + var length = _position - _start; + var text = _text.ToString(_start, length); + if (!int.TryParse(text, out var value)) + _diagnostics.ReportInvalidNumber(new TextSpan(_start, length), text, typeof(int)); + + _value = value; + _kind = SyntaxKind.NumberToken; + } + + private void ReadWhiteSpace() + { + while (char.IsWhiteSpace(Current)) + _position++; + + _kind = SyntaxKind.WhitespaceToken; + } + + private void ReadIdentifierOrKeyword() + { + while (char.IsLetter(Current)) + _position++; + + var length = _position - _start; + var text = _text.ToString(_start, length); + _kind = SyntaxFacts.GetKeywordKind(text); + + // var length = _position - _start; + // var text = _text.Substring(_start, length); + // var kind = SyntaxFacts.GetKeywordKind(text); + // _kind = SyntaxFacts.GetKeywordKind(text); } } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Syntax/LiteralExpressionSyntax.cs b/Minsk/CodeAnalysis/Syntax/LiteralExpressionSyntax.cs index aaea511..0765d60 100644 --- a/Minsk/CodeAnalysis/Syntax/LiteralExpressionSyntax.cs +++ b/Minsk/CodeAnalysis/Syntax/LiteralExpressionSyntax.cs @@ -19,9 +19,6 @@ public LiteralExpressionSyntax(SyntaxToken literalToken, object value) public SyntaxToken LiteralToken { get; } public object Value { get; } - public override IEnumerable GetChildren() - { - yield return LiteralToken; - } + } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Syntax/NameExpressionSyntax.cs b/Minsk/CodeAnalysis/Syntax/NameExpressionSyntax.cs index cc8658a..1aa089b 100644 --- a/Minsk/CodeAnalysis/Syntax/NameExpressionSyntax.cs +++ b/Minsk/CodeAnalysis/Syntax/NameExpressionSyntax.cs @@ -13,10 +13,7 @@ public NameExpressionSyntax(SyntaxToken identifierToken) public override SyntaxKind Kind => SyntaxKind.NameExpression; public SyntaxToken IdentifierToken { get; } - public override IEnumerable GetChildren() - { - yield return IdentifierToken; - } + } } diff --git a/Minsk/CodeAnalysis/Syntax/ParenthesizedExpressionSyntax.cs b/Minsk/CodeAnalysis/Syntax/ParenthesizedExpressionSyntax.cs index 0339442..68d2a62 100644 --- a/Minsk/CodeAnalysis/Syntax/ParenthesizedExpressionSyntax.cs +++ b/Minsk/CodeAnalysis/Syntax/ParenthesizedExpressionSyntax.cs @@ -16,11 +16,6 @@ public ParenthesizedExpressionSyntax(SyntaxToken openParenthesisToken, Expressio public ExpressionSyntax Expression { get; } public SyntaxToken CloseParenthesisToken { get; } - public override IEnumerable GetChildren() - { - yield return OpenParenthesisToken; - yield return Expression; - yield return CloseParenthesisToken; - } + } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Syntax/Parser.cs b/Minsk/CodeAnalysis/Syntax/Parser.cs index 3ce7a4a..9313e28 100644 --- a/Minsk/CodeAnalysis/Syntax/Parser.cs +++ b/Minsk/CodeAnalysis/Syntax/Parser.cs @@ -1,4 +1,7 @@ +using System; using System.Collections.Generic; +using System.Collections.Immutable; +using Minsk.CodeAnalysis.Text; namespace Minsk.CodeAnalysis.Syntax { @@ -6,12 +9,12 @@ namespace Minsk.CodeAnalysis.Syntax internal sealed class Parser { - private readonly SyntaxToken[] _tokens; + private readonly ImmutableArray _tokens; - private DiagnosticBag _diagnostics = new DiagnosticBag(); + private readonly DiagnosticBag _diagnostics = new DiagnosticBag(); private int _position; - public Parser(string text) + public Parser(SourceText text) { var tokens = new List(); @@ -28,7 +31,7 @@ public Parser(string text) } } while (token.Kind != SyntaxKind.EndOfFileToken); - _tokens = tokens.ToArray(); + _tokens = tokens.ToImmutableArray(); _diagnostics.AddRange(lexer.Diagnostics); } @@ -129,32 +132,49 @@ private ExpressionSyntax ParsePrimaryExpression() switch (Current.Kind) { case SyntaxKind.OpenParenthesisToken: - { - var left = NextToken(); - var expression = ParseExpression(); - var right = MatchToken(SyntaxKind.CloseParenthesisToken); - return new ParenthesizedExpressionSyntax(left, expression, right); - } - + return ParseParenthesizedExpression(); + case SyntaxKind.FalseKeyword: case SyntaxKind.TrueKeyword: - { - var keywordToken = NextToken(); - var value = keywordToken.Kind == SyntaxKind.TrueKeyword; - return new LiteralExpressionSyntax(keywordToken, value); - } - case SyntaxKind.IdentifierToken: - { - var variable = NextToken(); - return new NameExpressionSyntax(variable); - } + return ParseBooleanLiteral(); + + case SyntaxKind.NumberToken: + return ParseNumberLiteral(); + case SyntaxKind.IdentifierToken: default: - { - var numberToken = MatchToken(SyntaxKind.NumberToken); - return new LiteralExpressionSyntax(numberToken); - } + return ParseNameExpression(); } + + } + + private ExpressionSyntax ParseNumberLiteral() + { + var numberToken = MatchToken(SyntaxKind.NumberToken); + return new LiteralExpressionSyntax(numberToken); + } + + private ExpressionSyntax ParseParenthesizedExpression() + { + var left = MatchToken(SyntaxKind.OpenParenthesisToken); + var expression = ParseExpression(); + var right = MatchToken(SyntaxKind.CloseParenthesisToken); + return new ParenthesizedExpressionSyntax(left, expression, right); + } + + private ExpressionSyntax ParseBooleanLiteral() + { + // var keywordToken = NextToken(); + // var value = keywordToken.Kind == SyntaxKind.TrueKeyword; + var isValue = Current.Kind == SyntaxKind.TrueKeyword; + var keywordToken = isValue ? MatchToken(SyntaxKind.TrueKeyword) : MatchToken(SyntaxKind.FalseKeyword); + return new LiteralExpressionSyntax(keywordToken, isValue); + } + + private ExpressionSyntax ParseNameExpression() + { + var variable = MatchToken(SyntaxKind.IdentifierToken); + return new NameExpressionSyntax(variable); } } } diff --git a/Minsk/CodeAnalysis/Syntax/SyntaxFacts.cs b/Minsk/CodeAnalysis/Syntax/SyntaxFacts.cs index b530f4b..e19a4bc 100644 --- a/Minsk/CodeAnalysis/Syntax/SyntaxFacts.cs +++ b/Minsk/CodeAnalysis/Syntax/SyntaxFacts.cs @@ -1,8 +1,9 @@ using System; +using System.Collections.Generic; namespace Minsk.CodeAnalysis.Syntax { - internal static class SyntaxFacts + public static class SyntaxFacts { public static int GetUnaryOperatorPrecedence(this SyntaxKind kind) { diff --git a/Minsk/CodeAnalysis/Syntax/SyntaxNode.cs b/Minsk/CodeAnalysis/Syntax/SyntaxNode.cs index 5e8a0b4..b6e660d 100644 --- a/Minsk/CodeAnalysis/Syntax/SyntaxNode.cs +++ b/Minsk/CodeAnalysis/Syntax/SyntaxNode.cs @@ -1,4 +1,9 @@ +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Minsk.CodeAnalysis.Text; namespace Minsk.CodeAnalysis.Syntax { @@ -6,6 +11,89 @@ public abstract class SyntaxNode { public abstract SyntaxKind Kind { get; } - public abstract IEnumerable GetChildren(); + public virtual TextSpan Span + { + get + { + var first = GetChildren().First().Span; + var last = GetChildren().Last().Span; + return TextSpan.FromBounds(first.Start,last.Start); + } + } + + public IEnumerable GetChildren() + { + var properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var property in properties) + { + if (typeof(SyntaxNode).IsAssignableFrom(property.PropertyType)) + { + var child = (SyntaxNode)property.GetValue(this); + if (child != null) + { + yield return child; + } + } + else if(typeof(IEnumerable).IsAssignableFrom(property.PropertyType)) + { + var children = (IEnumerable)property.GetValue(this); + foreach (var child in children) + { + if (child != null) + yield return child; + } + } + } + } + public void WriteTo(TextWriter writer) + { + PrettyPrint(writer, this); + } + + private static void PrettyPrint(TextWriter writer, SyntaxNode node, string indent = "", bool isLast = true) + { + var isToConsole = writer == Console.Out; + var marker = isLast ? "└──" : "├──"; + + if (isToConsole) + Console.ForegroundColor = ConsoleColor.DarkGray; + + writer.Write(indent); + writer.Write(marker); + + if (isToConsole) + Console.ForegroundColor = node is SyntaxToken ? ConsoleColor.Blue : ConsoleColor.Cyan; + + writer.Write(node.Kind); + + if (node is SyntaxToken t && t.Value != null) + { + writer.Write(" "); + writer.Write(t.Value); + } + + if (isToConsole) + Console.ResetColor(); + + writer.WriteLine(); + + indent += isLast ? " " : "│ "; + + var lastChild = node.GetChildren().LastOrDefault(); + + foreach (var child in node.GetChildren()) + PrettyPrint(writer, child, indent, child == lastChild); + } + + public override string ToString() + { + using (var writer = new StringWriter()) + { + WriteTo(writer); + return writer.ToString(); + } + } + } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Syntax/SyntaxToken.cs b/Minsk/CodeAnalysis/Syntax/SyntaxToken.cs index b0c730b..83098f0 100644 --- a/Minsk/CodeAnalysis/Syntax/SyntaxToken.cs +++ b/Minsk/CodeAnalysis/Syntax/SyntaxToken.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Minsk.CodeAnalysis.Text; namespace Minsk.CodeAnalysis.Syntax { @@ -18,11 +19,8 @@ public SyntaxToken(SyntaxKind kind, int position, string text, object value) public string Text { get; } public object Value { get; } - public TextSpan Span => new TextSpan(Position, Text.Length); + public override TextSpan Span => new TextSpan(Position, Text?.Length ?? 0); - public override IEnumerable GetChildren() - { - return Enumerable.Empty(); - } + } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Syntax/SyntaxTree.cs b/Minsk/CodeAnalysis/Syntax/SyntaxTree.cs index 8425d39..5cf87af 100644 --- a/Minsk/CodeAnalysis/Syntax/SyntaxTree.cs +++ b/Minsk/CodeAnalysis/Syntax/SyntaxTree.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Minsk.CodeAnalysis.Text; namespace Minsk.CodeAnalysis.Syntax { @@ -17,12 +18,25 @@ public SyntaxTree(IEnumerable diagnostics, ExpressionSyntax root, Sy public SyntaxToken EndOfFileToken { get; } public static SyntaxTree Parse(string text) + { + // var parser = new Parser(text); + // return parser.Parse(); + var sourcetext = SourceText.From(text); + return Parse(sourcetext); + } + + public static SyntaxTree Parse(SourceText text) { var parser = new Parser(text); return parser.Parse(); } public static IEnumerable ParseTokens(string text) + { + var sourceText = SourceText.From(text); + return ParseTokens(sourceText); + } + public static IEnumerable ParseTokens(SourceText text) { var lexer = new Lexer(text); diff --git a/Minsk/CodeAnalysis/Syntax/UnaryExpressionSyntax.cs b/Minsk/CodeAnalysis/Syntax/UnaryExpressionSyntax.cs index 4e784ce..de418cc 100644 --- a/Minsk/CodeAnalysis/Syntax/UnaryExpressionSyntax.cs +++ b/Minsk/CodeAnalysis/Syntax/UnaryExpressionSyntax.cs @@ -14,11 +14,7 @@ public UnaryExpressionSyntax(SyntaxToken operatorToken, ExpressionSyntax operand public SyntaxToken OperatorToken { get; } public ExpressionSyntax Operand { get; } - public override IEnumerable GetChildren() - { - yield return OperatorToken; - yield return Operand; - } + } } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Text/ImmutableArray.cs b/Minsk/CodeAnalysis/Text/ImmutableArray.cs new file mode 100644 index 0000000..e69de29 diff --git a/Minsk/CodeAnalysis/Text/SourceText.cs b/Minsk/CodeAnalysis/Text/SourceText.cs new file mode 100644 index 0000000..7799adf --- /dev/null +++ b/Minsk/CodeAnalysis/Text/SourceText.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Immutable; + +namespace Minsk.CodeAnalysis.Text +{ + // 把输入的文本类型string转换成SourceText + public sealed class SourceText + { + private readonly string _text; + + public SourceText(string text) + { + _text = text; + Lines = ParseLines(this, text); + } + + public static SourceText From(string text) + { + return new SourceText(text); + } + + private static ImmutableArray ParseLines(SourceText sourceText, string text) + { + var result = ImmutableArray.CreateBuilder(); + var position = 0; + var linestart = 0; + while(position< text.Length) + { + var lineBreak = GetLineBreakWidth(text,position); + if(lineBreak == 0) + { + position++; + } + else + { + AddLine(result,sourceText,position,linestart,lineBreak); + position += lineBreak; + linestart =position; + } + + } + + if (position > linestart) + AddLine(result,sourceText,position,linestart,0); + + return result.ToImmutable(); + + } + + private static void AddLine(ImmutableArray.Builder result, SourceText sourceText, + int position, int lineStart, int lineBreakWidth) + { + var length = position -lineStart; + var lengthIncludingBreak = lineBreakWidth+length -lineStart; + var line = new TextLine(sourceText,lineStart,length,lengthIncludingBreak); + result.Add(line); + } + private static int GetLineBreakWidth(string text, int position) + { + var c = text[position]; + var l = position+1 >=text.Length ? '\0' : text[position+1]; + + if (c == '\r' && l=='\n') + return 2; + + if (c=='\r' || l=='\n') + return 1; + + return 0; + } + + + public int GetLineIndex(int position) + { + var lower = 0; + var upper = Lines.Length-1; + + // 使用二分法求值 + while (lower <= upper) + { + var index = lower + (upper - lower) / 2; + var start = Lines[index].Start; + + if (position == start) + return index; + + if (start > position) + { + upper = index - 1; + } + else + { + lower = index + 1; + } + } + + return lower - 1; + + } + + public ImmutableArray Lines {get;} + public int Length => _text.Length; + public char this[int index] => _text[index]; + + public override string ToString() => _text; + + public string ToString(int start, int length) => _text.Substring(start, length); + + public string ToString(TextSpan span) => ToString(span.Start, span.Length); + + } +} + +// using System; +// using System.Collections.Immutable; + +// namespace Minsk.CodeAnalysis.Text +// { +// public sealed class SourceText +// { +// private readonly string _text; + +// private SourceText(string text) +// { +// _text = text; +// Lines = ParseLines(this, text); +// } + +// public static SourceText From(string text) +// { +// return new SourceText(text); +// } + +// private static ImmutableArray ParseLines(SourceText sourceText, string text) +// { +// var result = ImmutableArray.CreateBuilder(); + +// var position = 0; +// var lineStart = 0; + +// while (position < text.Length) +// { +// var lineBreakWidth = GetLineBreakWidth(text, position); + +// if (lineBreakWidth == 0) +// { +// position++; +// } +// else +// { +// AddLine(result, sourceText, position, lineStart, lineBreakWidth); + +// position += lineBreakWidth; +// lineStart = position; +// } +// } + +// if (position > lineStart) +// AddLine(result, sourceText, position, lineStart, 0); + +// return result.ToImmutable(); +// } + +// private static void AddLine(ImmutableArray.Builder result, SourceText sourceText, int position, int lineStart, int lineBreakWidth) +// { +// var lineLength = position - lineStart; +// var lineLengthIncludingLineBreak = lineLength + lineBreakWidth; +// var line = new TextLine(sourceText, lineStart, lineLength, lineLengthIncludingLineBreak); +// result.Add(line); +// } + +// private static int GetLineBreakWidth(string text, int position) +// { +// var c = text[position]; +// var l = position + 1 >= text.Length ? '\0' : text[position + 1]; + +// if (c == '\r' && l == '\n') +// return 2; + +// if (c == '\r' || c == '\n') +// return 1; + +// return 0; +// } + +// public ImmutableArray Lines { get; } + +// public char this[int index] => _text[index]; + +// public int Length => _text.Length; + +// public int GetLineIndex(int position) +// { +// var lower = 0; +// var upper = Lines.Length - 1; + +// while (lower <= upper) +// { +// var index = lower + (upper - lower) / 2; +// var start = Lines[index].Start; + +// if (position == start) +// return index; + +// if (start > position) +// { +// upper = index - 1; +// } +// else +// { +// lower = index + 1; +// } +// } + +// return lower - 1; +// } + +// public override string ToString() => _text; + +// public string ToString(int start, int length) => _text.Substring(start, length); + +// public string ToString(TextSpan span) => ToString(span.Start, span.Length); +// } +// } \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Text/TextLine.cs b/Minsk/CodeAnalysis/Text/TextLine.cs new file mode 100644 index 0000000..b0e86cb --- /dev/null +++ b/Minsk/CodeAnalysis/Text/TextLine.cs @@ -0,0 +1,24 @@ +namespace Minsk.CodeAnalysis.Text +{ + public sealed class TextLine + { + public TextLine(SourceText text,int start,int length,int lengthIncludingBreak) + { + Text = text; + Start = start; + Length = length; + LengthIncludingBreak = lengthIncludingBreak; + } + + public SourceText Text { get; } + public int Start { get; } + public int Length { get; } + public int LengthIncludingBreak { get; } + + public int End => Start+Length; + public TextSpan Span => new TextSpan(Start,Length); + public TextSpan SpanIncludingLineBreak => new TextSpan(Start, LengthIncludingBreak); + + public override string ToString() => Text.ToString(Span); + } +} \ No newline at end of file diff --git a/Minsk/CodeAnalysis/Text/TextSpan.cs b/Minsk/CodeAnalysis/Text/TextSpan.cs new file mode 100644 index 0000000..bcd54bf --- /dev/null +++ b/Minsk/CodeAnalysis/Text/TextSpan.cs @@ -0,0 +1,31 @@ +using System; + +namespace Minsk.CodeAnalysis.Text +{ + + public struct TextSpan + { + public TextSpan(int start, int length) + { + Start = start; + Length = length; + } + + public int Start { get; } + public int Length { get; } + + public int End => Start + Length; + + + public static TextSpan FromBounds(int start, int end) + { + var length = end-start; + return new TextSpan(start, length); + } + + + public override string ToString() => $"{Start}..{End}"; + + } + +} \ No newline at end of file diff --git a/Minsk/CodeAnalysis/TextSpan.cs b/Minsk/CodeAnalysis/TextSpan.cs deleted file mode 100644 index 85458da..0000000 --- a/Minsk/CodeAnalysis/TextSpan.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Minsk.CodeAnalysis -{ - public struct TextSpan - { - public TextSpan(int start, int length) - { - Start = start; - Length = length; - } - - public int Start { get; } - public int Length { get; } - - public int End => Start + Length; - } -} - diff --git a/Minsk/CodeAnalysis/VariableSymbol.cs b/Minsk/CodeAnalysis/VariableSymbol.cs new file mode 100644 index 0000000..0121157 --- /dev/null +++ b/Minsk/CodeAnalysis/VariableSymbol.cs @@ -0,0 +1,16 @@ +using System; + +namespace Minsk.CodeAnalysis +{ + public sealed class VariableSymbol + { + internal VariableSymbol(string name, Type type) + { + Name = name; + Type = type; + } + + public string Name { get; } + public Type Type { get; } + } +} \ No newline at end of file diff --git a/Minsk/Minsk.csproj b/Minsk/Minsk.csproj index 9f5c4f4..d83fd0c 100644 --- a/Minsk/Minsk.csproj +++ b/Minsk/Minsk.csproj @@ -4,4 +4,8 @@ netstandard2.0 + + + + diff --git a/mc/Program.cs b/mc/Program.cs index dbbc8ed..37a022a 100644 --- a/mc/Program.cs +++ b/mc/Program.cs @@ -5,6 +5,7 @@ using Minsk.CodeAnalysis.Binding; using Minsk.CodeAnalysis; using Minsk.CodeAnalysis.Syntax; +using Minsk.CodeAnalysis.Text; namespace Minsk { @@ -13,36 +14,52 @@ internal static class Program static void Main(string[] args) { bool showTree = false; - var _variables = new Dictionary(); + var _variables = new Dictionary(); + var textBuilder = new StringBuilder(); while (true) - { + { + if (textBuilder.Length == 0) + { Console.Write("> "); - var line = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(line)) - return; + } + var input = Console.ReadLine(); + var isBlank = string.IsNullOrWhiteSpace(input); - if (line == "#showTree") + if (textBuilder.Length == 0) { - showTree = !showTree; - Console.WriteLine(showTree ? "Showing parse trees." : "Not showing parse trees"); - continue; + if (isBlank) + { + break; + } + else if (line == "#showTree") + { + showTree = !showTree; + Console.WriteLine(showTree ? "Showing parse trees." : "Not showing parse trees"); + continue; + } + else if (line == "#cls") + { + Console.Clear(); + continue; + } } - else if (line == "#cls") - { - Console.Clear(); + + textBuilder.Append(input); + var text = textBuilder.toString(); + var syntaxTree = SyntaxTree.Parse(text); + + if (!isBlank && syntaxTree.Diagnostics.Any()) continue; - } - var syntaxTree = SyntaxTree.Parse(line); - var comp = new Complication(syntaxTree); + var comp = new Compilation(syntaxTree); var result = comp.evaluate(_variables); if (showTree) { var color = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.DarkGray; - PrettyPrint(syntaxTree.Root); + syntaxTree.Root.WriteTo(Console.Out); Console.ResetColor(); } @@ -54,15 +71,28 @@ static void Main(string[] args) { foreach (var diagnostic in result.Diagnostics) { + var lineIndex = syntaxTree.Text.GetLineIndex(diagnostic.Span.Start); + var line = syntaxTree.Text.Lines[lineIndex]; + var lineNumber = lineIndex + 1; + var character = diagnostic.Span.Start - line.Start + 1; + + Console.WriteLine(); Console.ForegroundColor = ConsoleColor.DarkRed; Console.WriteLine(diagnostic); Console.ResetColor(); - var prefix = line.Substring(0, diagnostic.Span.Start); - var error = line.Substring(diagnostic.Span.Start, diagnostic.Span.Length); - var suffix = line.Substring(diagnostic.Span.End); + // var prefix = line.Substring(0, diagnostic.Span.Start); + // var error = line.Substring(diagnostic.Span.Start, diagnostic.Span.Length); + // var suffix = line.Substring(diagnostic.Span.End); + var prefixSpan = TextSpan.FromBounds(line.Start, diagnostic.Span.Start); + var suffixSpan = TextSpan.FromBounds(diagnostic.Span.End, line.End); + + var prefix = syntaxTree.Text.ToString(prefixSpan); + var error = syntaxTree.Text.ToString(diagnostic.Span); + var suffix = syntaxTree.Text.ToString(suffixSpan); + Console.Write(" "); Console.Write(prefix); @@ -83,34 +113,12 @@ static void Main(string[] args) // Console.ResetColor(); } + textBuilder.Clear(); } } - - static void PrettyPrint(SyntaxNode node, string indent = "", bool isLast = true) - { - var marker = isLast ? "└──" : "├──"; - - Console.Write(indent); - Console.Write(marker); - Console.Write(node.Kind); - - if (node is SyntaxToken t && t.Value != null) - { - Console.Write(" "); - Console.Write(t.Value); - } - - Console.WriteLine(); - - indent += isLast ? " " : "│ "; - - var lastChild = node.GetChildren().LastOrDefault(); - - foreach (var child in node.GetChildren()) - PrettyPrint(child, indent, child == lastChild); - } + } } diff --git a/mc/mc.csproj b/mc/mc.csproj index bf00071..475f6e1 100644 --- a/mc/mc.csproj +++ b/mc/mc.csproj @@ -1,3 +1,17 @@ + + + @@ -9,4 +23,4 @@ netcoreapp2.1 - + \ No newline at end of file