From 6ba3860e84acd8758e1401004adeb1d5e70fd974 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Mon, 3 Dec 2018 16:05:14 +0100 Subject: [PATCH] Implement number comparison operators --- Upsilon/BaseTypes/Number/ScriptNumber.cs | 35 ++++++++++++++++++ Upsilon/Binder/BoundBinaryOperator.cs | 24 ++++++++++++- Upsilon/Evaluator/Evaluator.cs | 38 ++++++++++++++++++++ Upsilon/Exceptions/ScriptRuntimeException.cs | 27 ++++++++++++++ Upsilon/Parser/Lexer.cs | 14 ++++++++ Upsilon/Parser/SyntaxKind.cs | 4 +++ Upsilon/Parser/SyntaxKindPrecedence.cs | 6 +++- 7 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 Upsilon/Exceptions/ScriptRuntimeException.cs diff --git a/Upsilon/BaseTypes/Number/ScriptNumber.cs b/Upsilon/BaseTypes/Number/ScriptNumber.cs index 63447bb..97014a0 100644 --- a/Upsilon/BaseTypes/Number/ScriptNumber.cs +++ b/Upsilon/BaseTypes/Number/ScriptNumber.cs @@ -60,6 +60,7 @@ namespace Upsilon.BaseTypes.Number return new ScriptNumberLong(-((ScriptNumberLong)n).Value); } + #region Equality private bool Equals(ScriptNumber other) { @@ -82,6 +83,40 @@ namespace Upsilon.BaseTypes.Number #endregion + public static ScriptBoolean operator < (ScriptNumber a, ScriptNumber b) + { + if (!a.IsFloat && !b.IsFloat) + return new ScriptBoolean(((ScriptNumberLong) a).Value < ((ScriptNumberLong) b).Value); + if (a.IsFloat && b.IsFloat) + return new ScriptBoolean(((ScriptNumberDouble) a).Value < ((ScriptNumberDouble) b).Value); + if (a.IsFloat) + return new ScriptBoolean(((ScriptNumberDouble) a).Value < ((ScriptNumberLong) b).Value); + return new ScriptBoolean(((ScriptNumberLong) a).Value < ((ScriptNumberDouble) b).Value); + } + + public static ScriptBoolean operator > (ScriptNumber a, ScriptNumber b) + { + if (!a.IsFloat && !b.IsFloat) + return new ScriptBoolean(((ScriptNumberLong) a).Value > ((ScriptNumberLong) b).Value); + if (a.IsFloat && b.IsFloat) + return new ScriptBoolean(((ScriptNumberDouble) a).Value > ((ScriptNumberDouble) b).Value); + if (a.IsFloat) + return new ScriptBoolean(((ScriptNumberDouble) a).Value > ((ScriptNumberLong) b).Value); + return new ScriptBoolean(((ScriptNumberLong) a).Value > ((ScriptNumberDouble) b).Value); + } + + public static ScriptBoolean operator <= (ScriptNumber a, ScriptNumber b) + { + return (a < b) || a.Equals(b); + } + + public static ScriptBoolean operator >= (ScriptNumber a, ScriptNumber b) + { + return !(a < b) || a.Equals(b); + } + + + public static explicit operator double(ScriptNumber n) { if (n.IsFloat) diff --git a/Upsilon/Binder/BoundBinaryOperator.cs b/Upsilon/Binder/BoundBinaryOperator.cs index 64fdf4a..599279b 100644 --- a/Upsilon/Binder/BoundBinaryOperator.cs +++ b/Upsilon/Binder/BoundBinaryOperator.cs @@ -9,7 +9,11 @@ namespace Upsilon.Binder { public enum OperatorKind { - Addition, Subtraction, Multiplication, Division, Equality, Inequality + Addition, Subtraction, Multiplication, Division, Equality, Inequality, + GreaterEquals, + Greater, + LessEquals, + Less } private Type LeftType { get; } @@ -45,6 +49,12 @@ namespace Upsilon.Binder new BoundBinaryOperator(OperatorKind.Equality, Type.Number, Type.Number, Type.Boolean), new BoundBinaryOperator(OperatorKind.Inequality, Type.Number, Type.Number, Type.Boolean), + // Number comparison + new BoundBinaryOperator(OperatorKind.Less, Type.Number, Type.Number, Type.Boolean), + new BoundBinaryOperator(OperatorKind.LessEquals, Type.Number, Type.Number, Type.Boolean), + new BoundBinaryOperator(OperatorKind.Greater, Type.Number, Type.Number, Type.Boolean), + new BoundBinaryOperator(OperatorKind.GreaterEquals, Type.Number, Type.Number, Type.Boolean), + // Boolean equality new BoundBinaryOperator(OperatorKind.Equality, Type.Boolean), new BoundBinaryOperator(OperatorKind.Inequality, Type.Boolean), @@ -82,6 +92,18 @@ namespace Upsilon.Binder case SyntaxKind.TildeEquals: kind = OperatorKind.Inequality; break; + case SyntaxKind.Less: + kind = OperatorKind.Less; + break; + case SyntaxKind.LessEquals: + kind = OperatorKind.LessEquals; + break; + case SyntaxKind.Greater: + kind = OperatorKind.Greater; + break; + case SyntaxKind.GreaterEquals: + kind = OperatorKind.GreaterEquals; + break; default: throw new Exception("Unknown binary operator token: " + operatorToken); } diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index 62ce0e5..0ab8ad9 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -7,6 +7,8 @@ using Upsilon.BaseTypes.ScriptTypeInterfaces; using Upsilon.BaseTypes.UserData; using Upsilon.Binder; using Upsilon.Binder.VariableSymbols; +using Upsilon.Exceptions; +using Upsilon.Text; using Type = Upsilon.BaseTypes.Type; namespace Upsilon.Evaluator @@ -328,11 +330,47 @@ namespace Upsilon.Evaluator return new ScriptBoolean(Equals(left, right)); case BoundBinaryOperator.OperatorKind.Inequality: return new ScriptBoolean(!Equals(left, right)); + case BoundBinaryOperator.OperatorKind.Less: + if (left.Type == Type.Number && right.Type == Type.Number) + { + return ((ScriptNumber)left) < ((ScriptNumber)right); + } + ThrowException($"Can't find operator for types '{left.Type}' and '{right.Type}'", e.Span); + return new ScriptNull(); + case BoundBinaryOperator.OperatorKind.LessEquals: + if (left.Type == Type.Number && right.Type == Type.Number) + { + return ((ScriptNumber)left) <= ((ScriptNumber)right); + } + ThrowException($"Can't find operator for types '{left.Type}' and '{right.Type}'", e.Span); + return new ScriptNull(); + case BoundBinaryOperator.OperatorKind.Greater: + if (left.Type == Type.Number && right.Type == Type.Number) + { + return ((ScriptNumber)left) > ((ScriptNumber)right); + } + ThrowException($"Can't find operator for types '{left.Type}' and '{right.Type}'", e.Span); + return new ScriptNull(); + case BoundBinaryOperator.OperatorKind.GreaterEquals: + if (left.Type == Type.Number && right.Type == Type.Number) + { + return ((ScriptNumber)left) >= ((ScriptNumber)right); + } + ThrowException($"Can't find operator for types '{left.Type}' and '{right.Type}'", e.Span); + return new ScriptNull(); + default: throw new Exception("Invalid Binary Operator: " + e.Operator); } } + private void ThrowException(string message, TextSpan location) + { + var (i, pos) = _script.ScriptString.GetLinePosition(location.Start); + var line = _script.ScriptString.GetLine(i); + throw new ScriptRuntimeException(message, i, pos, line); + } + private void EvaluateAssignmentStatement(BoundVariableAssignment e) { var val = EvaluateExpression(e.BoundExpression); diff --git a/Upsilon/Exceptions/ScriptRuntimeException.cs b/Upsilon/Exceptions/ScriptRuntimeException.cs new file mode 100644 index 0000000..883ddf6 --- /dev/null +++ b/Upsilon/Exceptions/ScriptRuntimeException.cs @@ -0,0 +1,27 @@ +using System; + +namespace Upsilon.Exceptions +{ + public class ScriptRuntimeException : Exception + { + public string ErrorMessage { get; } + public int Line { get; } + public int Character { get; } + public string ErrorLine { get; } + + public ScriptRuntimeException(string errorMessage, int line, int character, string errorLine) + { + ErrorMessage = errorMessage; + Line = line; + Character = character; + ErrorLine = errorLine; + } + + public override string ToString() + { + return $"{ErrorMessage} at ({Line}, {Character})\n{ErrorLine}"; + } + + public override string Message => ToString(); + } +} \ No newline at end of file diff --git a/Upsilon/Parser/Lexer.cs b/Upsilon/Parser/Lexer.cs index 819daac..9eebd3b 100644 --- a/Upsilon/Parser/Lexer.cs +++ b/Upsilon/Parser/Lexer.cs @@ -118,6 +118,20 @@ namespace Upsilon.Parser return new SyntaxToken(SyntaxKind.TildeEquals, _position - 1, "~=", null); } return new SyntaxToken(SyntaxKind.Tilde, _position, "~", null); + case '<': + if (Next == '=') + { + _position++; + return new SyntaxToken(SyntaxKind.LessEquals, _position - 1, "<=", null); + } + return new SyntaxToken(SyntaxKind.Less, _position, "<", null); + case '>': + if (Next == '=') + { + _position++; + return new SyntaxToken(SyntaxKind.GreaterEquals, _position - 1, ">=", null); + } + return new SyntaxToken(SyntaxKind.Greater, _position, ">", null); default: if (char.IsLetter(Current) || Current == '_') return LexIdentifierOrKeyword(); diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index 784c875..14b8941 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -29,6 +29,10 @@ namespace Upsilon.Parser OpenBracket, CloseBracket, PoundSign, + Less, + LessEquals, + Greater, + GreaterEquals, // key words TrueKeyword, diff --git a/Upsilon/Parser/SyntaxKindPrecedence.cs b/Upsilon/Parser/SyntaxKindPrecedence.cs index 534bae9..e39f692 100644 --- a/Upsilon/Parser/SyntaxKindPrecedence.cs +++ b/Upsilon/Parser/SyntaxKindPrecedence.cs @@ -34,8 +34,12 @@ namespace Upsilon.Parser { // equality operators case SyntaxKind.EqualsEquals: - return Precedence.Equality; case SyntaxKind.TildeEquals: + + case SyntaxKind.Greater: + case SyntaxKind.GreaterEquals: + case SyntaxKind.Less: + case SyntaxKind.LessEquals: return Precedence.Equality; // logical operators