From 32730f03094d14ed41573db9dfb411b0bcb4a0e7 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sun, 28 Nov 2010 18:40:54 -0800 Subject: [PATCH] Implement evaluation of operators: <, <=, >, >=, ==, != and ! --HG-- branch : dev --- .../Scripting/EvaluatorTests.cs | 56 ++++++++++ .../Compiler/EvaluationResult.cs | 50 +++++++++ .../Orchard.Scripting/Compiler/Interpreter.cs | 43 -------- .../Compiler/InterpreterVisitor.cs | 51 +++++++-- .../Compiler/PrimitiveType.cs | 100 ++++++++++++++++++ .../Orchard.Scripting.csproj | 2 + .../ScriptExpressionEvaluator.cs | 2 +- 7 files changed, 251 insertions(+), 53 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.Scripting/Compiler/EvaluationResult.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Scripting/Compiler/PrimitiveType.cs diff --git a/src/Orchard.Tests.Modules/Scripting/EvaluatorTests.cs b/src/Orchard.Tests.Modules/Scripting/EvaluatorTests.cs index ba8f11835..4f84c496c 100644 --- a/src/Orchard.Tests.Modules/Scripting/EvaluatorTests.cs +++ b/src/Orchard.Tests.Modules/Scripting/EvaluatorTests.cs @@ -35,6 +35,62 @@ namespace Orchard.Tests.Modules.Scripting { Assert.That(result.Value, Is.EqualTo(4)); } + [Test] + public void EvaluateRelationalOperators() { + var result = EvaluateSimpleExpression("1 < 2"); + Assert.That(result.IsError, Is.False); + Assert.That(result.Value, Is.EqualTo(true)); + } + + [Test] + public void EvaluateRelationalOperators2() { + var result = EvaluateSimpleExpression("2 <= 2"); + Assert.That(result.IsError, Is.False); + Assert.That(result.Value, Is.EqualTo(true)); + } + + [Test] + public void EvaluateRelationalOperators3() { + var result = EvaluateSimpleExpression("1 < 2 or 2 > 3 and !false"); + Assert.That(result.IsError, Is.False); + Assert.That(result.Value, Is.EqualTo(true)); + } + + [Test] + public void EvaluateRelationalOperators4() { + var result = EvaluateSimpleExpression("1 > 2 or 2 > 3 and !false"); + Assert.That(result.IsError, Is.False); + Assert.That(result.Value, Is.EqualTo(false)); + } + + [Test] + public void EvaluateRelationalOperators5() { + var result = EvaluateSimpleExpression("1 > 2 or 4 > 3 and !false"); + Assert.That(result.IsError, Is.False); + Assert.That(result.Value, Is.EqualTo(true)); + } + + [Test] + public void EvaluateRelationalOperators6() { + var result = EvaluateSimpleExpression("!false"); + Assert.That(result.IsError, Is.False); + Assert.That(result.Value, Is.EqualTo(true)); + } + + [Test] + public void EvaluateEqualityOperators() { + var result = EvaluateSimpleExpression("1 == 2"); + Assert.That(result.IsError, Is.False); + Assert.That(result.Value, Is.EqualTo(false)); + } + + [Test] + public void EvaluateEqualityOperators2() { + var result = EvaluateSimpleExpression("1 != 2"); + Assert.That(result.IsError, Is.False); + Assert.That(result.Value, Is.EqualTo(true)); + } + [Test] public void EvaluateSimpleMethodCall() { var result = EvaluateSimpleExpression("print 1 + 2 * 3 - 6 / 2", diff --git a/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/EvaluationResult.cs b/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/EvaluationResult.cs new file mode 100644 index 000000000..af0b82c64 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/EvaluationResult.cs @@ -0,0 +1,50 @@ +using System; + +namespace Orchard.Scripting.Compiler { + public class EvaluationResult { + private readonly object _value; + + public EvaluationResult(object value) { + _value = value; + } + + public static EvaluationResult Result(object value) { + if (value is EvaluationResult) + throw new InvalidOperationException("Internal error: value cannot be an evaluation result."); + return new EvaluationResult(value); + } + + public static EvaluationResult Error(string message) { + return new EvaluationResult(new Error { Message = message }); + } + + public object Value { get { return _value; } } + + public bool IsError { get { return Value is Error; } } + public bool IsNil { get { return IsNull; } } + public bool IsNull { get { return Value == null; } } + public bool IsBool { get { return Value is bool; } } + public bool IsInt32 { get { return Value is int; } } + public bool IsString { get { return Value is string; } } + + public Error ErrorValue { get { return (Error)Value; } } + public bool BoolValue { get { return (bool)Value; } } + public int Int32Value { get { return (int)Value; } } + public string StringValue { get { return (string)Value; } } + + public override string ToString() { + if (IsNull) + return ""; + + return Value.ToString(); + } + } + + public class Error { + public string Message { get; set; } + + public override string ToString() { + return string.Format("Error: {0}", Message); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/Interpreter.cs b/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/Interpreter.cs index 54a05aa8c..9b7efadb0 100644 --- a/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/Interpreter.cs +++ b/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/Interpreter.cs @@ -13,47 +13,4 @@ namespace Orchard.Scripting.Compiler { public AbstractSyntaxTree Tree { get; set; } public Func, object> MethodInvocationCallback { get; set; } } - - public class EvaluationResult { - private readonly object _value; - - public EvaluationResult(object value) { - _value = value; - } - - public object Value { get { return _value; } } - - public bool IsError { get { return Value is Error; } } - public bool IsNil { get { return Value is Nil; } } - public bool IsNull { get { return Value == null; } } - public bool IsBool { get { return Value is bool; } } - public bool IsInt32 { get { return Value is int; } } - public bool IsString { get { return Value is string; } } - - public Error Error { get { return (Error)Value; } } - public bool BoolValue { get { return (bool)Value; } } - public int Int32Value { get { return (int)Value; } } - public string StringValue { get { return (string)Value; } } - - public override string ToString() { - if (IsNull) - return ""; - - return Value.ToString(); - } - } - - public class Error { - public string Message { get; set; } - - public override string ToString() { - return string.Format("Error: {0}", Message); - } - } - - public class Nil { - public override string ToString() { - return "nil"; - } - } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/InterpreterVisitor.cs b/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/InterpreterVisitor.cs index 760f38e7a..a297541aa 100644 --- a/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/InterpreterVisitor.cs +++ b/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/InterpreterVisitor.cs @@ -27,11 +27,17 @@ namespace Orchard.Scripting.Compiler { if (operandValue.IsError) return operandValue; - var operandBoolValue = ConvertToBool(operandValue); - if (operandBoolValue.IsError) - return operandBoolValue; + switch (node.Operator.Kind) { + case TokenKind.Not: + case TokenKind.NotSign: + var operandBoolValue = ConvertToBool(operandValue); + if (operandBoolValue.IsError) + return operandBoolValue; - return Result(!operandBoolValue.BoolValue); + return Result(!operandBoolValue.BoolValue); + default: + throw new InvalidOperationException(string.Format("Internal error: unary operator '{0}' is not supported.", node.Token.Kind)); + } } public override object VisitBinary(BinaryAstNode node) { @@ -56,8 +62,21 @@ namespace Orchard.Scripting.Compiler { return EvaluateLogical(left, right, (a, b) => Result(a.BoolValue && b.BoolValue)); case TokenKind.Or: return EvaluateLogical(left, right, (a, b) => Result(a.BoolValue || b.BoolValue)); + case TokenKind.EqualEqual: + return EvaluateEquality(left, right, v => v); + case TokenKind.NotEqual: + return EvaluateEquality(left, right, v => !v); + case TokenKind.LessThan: + return EvaluateComparison(left, right, v => v < 0); + case TokenKind.LessThanEqual: + return EvaluateComparison(left, right, v => v <= 0); + case TokenKind.GreaterThan: + return EvaluateComparison(left, right, v => v > 0); + case TokenKind.GreaterThanEqual: + return EvaluateComparison(left, right, v => v >= 0); + default: - throw new InvalidOperationException(string.Format("Internal error: binary expression {0} is not supported.", node.Token)); + throw new InvalidOperationException(string.Format("Internal error: binary operator '{0}' is not supported.", node.Token.Kind)); } } @@ -73,6 +92,22 @@ namespace Orchard.Scripting.Compiler { return Error(node.Message); } + private EvaluationResult EvaluateEquality(EvaluationResult left, EvaluationResult right, Func operation) { + var type = PrimitiveType.InstanceFor(left.Value); + var result = type.EqualityOperator(left, right); + if (result.IsBool) + return Result(operation(result.BoolValue)); + return result; + } + + private EvaluationResult EvaluateComparison(EvaluationResult left, EvaluationResult right, Func operation) { + var type = PrimitiveType.InstanceFor(left.Value); + var result = type.ComparisonOperator(left, right); + if (result.IsInt32) + return Result(operation(result.Int32Value)); + return result; + } + private static EvaluationResult EvaluateArithmetic(EvaluationResult left, EvaluationResult right, Func operation) { //TODO: Proper type conversion @@ -117,13 +152,11 @@ namespace Orchard.Scripting.Compiler { } private static EvaluationResult Result(object value) { - if (value is EvaluationResult) - throw new InvalidOperationException("Internal error: value cannot be an evaluation result."); - return new EvaluationResult(value); + return EvaluationResult.Result(value); } private static EvaluationResult Error(string message) { - return new EvaluationResult(new Error { Message = message }); + return EvaluationResult.Error(message); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/PrimitiveType.cs b/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/PrimitiveType.cs new file mode 100644 index 000000000..cff704695 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Scripting/Compiler/PrimitiveType.cs @@ -0,0 +1,100 @@ +using System; + +namespace Orchard.Scripting.Compiler { + public abstract class PrimitiveType { + public static PrimitiveType InstanceFor(object value) { + if (value == null) + return NilPrimitiveType.Instance; + if (value is bool) + return BooleanPrimitiveType.Instance; + if (value is int) + return IntegerPrimitiveType.Instance; + if (value is string) + return StringPrimitiveType.Instance; + throw new InvalidOperationException(string.Format("Scripting engine internal error: no primitive type for value '{0}'", value)); + } + + public abstract EvaluationResult EqualityOperator(EvaluationResult value, EvaluationResult other); + public abstract EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other); + + protected EvaluationResult Result(object value) { + return EvaluationResult.Result(value); + } + + protected EvaluationResult Error(string message) { + return EvaluationResult.Error(message); + } + } + + public class BooleanPrimitiveType : PrimitiveType { + private static BooleanPrimitiveType _instance; + + public static BooleanPrimitiveType Instance { + get { return _instance ?? (_instance = new BooleanPrimitiveType()); } + } + + public override EvaluationResult EqualityOperator(EvaluationResult value, EvaluationResult other) { + if (value.IsBool && other.IsBool) + return Result(value.BoolValue == other.BoolValue); + return Error("Boolean values can only be compared to other boolean values"); + } + + public override EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other) { + return Error("Boolean values can only be compared to other boolean values"); + } + } + + public class IntegerPrimitiveType : PrimitiveType { + private static IntegerPrimitiveType _instance; + + public static IntegerPrimitiveType Instance { + get { return _instance ?? (_instance = new IntegerPrimitiveType()); } + } + + public override EvaluationResult EqualityOperator(EvaluationResult value, EvaluationResult other) { + if (value.IsInt32 && other.IsInt32) + return Result(value.Int32Value == other.Int32Value); + return Error("Integer values can only be compared to other integer values"); + } + + public override EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other) { + if (value.IsInt32 && other.IsInt32) + return Result(value.Int32Value.CompareTo(other.Int32Value)); + return Error("Integer values can only be compared to other integer values"); + } + } + + public class StringPrimitiveType : PrimitiveType { + private static StringPrimitiveType _instance; + + public static StringPrimitiveType Instance { + get { return _instance ?? (_instance = new StringPrimitiveType()); } + } + + public override EvaluationResult EqualityOperator(EvaluationResult value, EvaluationResult other) { + if (value.IsString && other.IsString) + return Result(value.StringValue == other.StringValue); + return Result(false); + } + + public override EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other) { + return Error("String values can not be compared"); + } + } + + public class NilPrimitiveType : PrimitiveType { + private static NilPrimitiveType _instance; + + public static NilPrimitiveType Instance { + get { return _instance ?? (_instance = new NilPrimitiveType()); } + } + + public override EvaluationResult EqualityOperator(EvaluationResult value, EvaluationResult other) { + return Result(value.IsNull && other.IsNull); + } + + public override EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other) { + return Error("'null' values can not be compared"); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Scripting/Orchard.Scripting.csproj b/src/Orchard.Web/Modules/Orchard.Scripting/Orchard.Scripting.csproj index 266f7ac92..64cd6740f 100644 --- a/src/Orchard.Web/Modules/Orchard.Scripting/Orchard.Scripting.csproj +++ b/src/Orchard.Web/Modules/Orchard.Scripting/Orchard.Scripting.csproj @@ -54,6 +54,8 @@ + + diff --git a/src/Orchard.Web/Modules/Orchard.Scripting/ScriptExpressionEvaluator.cs b/src/Orchard.Web/Modules/Orchard.Scripting/ScriptExpressionEvaluator.cs index a7e4a615b..b6ebf4635 100644 --- a/src/Orchard.Web/Modules/Orchard.Scripting/ScriptExpressionEvaluator.cs +++ b/src/Orchard.Web/Modules/Orchard.Scripting/ScriptExpressionEvaluator.cs @@ -32,7 +32,7 @@ namespace Orchard.Scripting { var result = EvaluateExpression(expr.Tree, providers); if (result.IsError) { - throw new ApplicationException(result.Error.Message); + throw new ApplicationException(result.ErrorValue.Message); } return result.Value;