mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Implement evaluation of operators: <, <=, >, >=, ==, != and !
--HG-- branch : dev
This commit is contained in:
@@ -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",
|
||||
|
@@ -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 "<null>";
|
||||
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class Error {
|
||||
public string Message { get; set; }
|
||||
|
||||
public override string ToString() {
|
||||
return string.Format("Error: {0}", Message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,47 +13,4 @@ namespace Orchard.Scripting.Compiler {
|
||||
public AbstractSyntaxTree Tree { get; set; }
|
||||
public Func<string, IList<object>, 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 "<null>";
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<bool, bool> 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<int, bool> 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<EvaluationResult, EvaluationResult, EvaluationResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -54,6 +54,8 @@
|
||||
<Compile Include="Ast\IAstNodeWithToken.cs" />
|
||||
<Compile Include="Ast\MethodCallAstNode.cs" />
|
||||
<Compile Include="Ast\UnaryAstNode.cs" />
|
||||
<Compile Include="Compiler\EvaluationResult.cs" />
|
||||
<Compile Include="Compiler\PrimitiveType.cs" />
|
||||
<Compile Include="IGlobalMethodProvider.cs" />
|
||||
<Compile Include="IScriptExpressionEvaluator.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.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;
|
||||
|
Reference in New Issue
Block a user