Implement evaluation of operators: <, <=, >, >=, ==, != and !

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-11-28 18:40:54 -08:00
parent ea049d28d0
commit 32730f0309
7 changed files with 251 additions and 53 deletions

View File

@@ -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",

View File

@@ -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);
}
}
}

View File

@@ -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";
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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" />

View File

@@ -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;