Fixing widget layer rule issue

Ensuring that the layer rule scripting engine supports more complex rules
such as (authenticated) and (url "~/notes").

Add a bunch of unit tests to ensure consistency between DLR based
engine and custom engine.

Work Item: 17186

--HG--
branch : 1.x
This commit is contained in:
Renaud Paquay
2011-01-07 21:44:50 -08:00
parent c3b2b87ed1
commit 9a4c5d6c52
13 changed files with 904 additions and 217 deletions

View File

@@ -138,6 +138,8 @@
<Compile Include="Comments\Services\CommentServiceTests.cs" />
<Compile Include="Indexing\LuceneIndexProviderTests.cs" />
<Compile Include="Indexing\LuceneSearchBuilderTests.cs" />
<Compile Include="Scripting.Dlr\EvaluatorTests.cs" />
<Compile Include="Scripting\EvaluatorTestsBase.cs" />
<Compile Include="Scripting\EvaluatorTests.cs" />
<Compile Include="Scripting\ParserTests.cs" />
<Compile Include="Scripting\TokenizerTests.cs" />

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Autofac;
using NUnit.Framework;
using Orchard.Scripting;
using Orchard.Scripting.Compiler;
using Orchard.Scripting.Dlr.Services;
using Orchard.Tests.Stubs;
namespace Orchard.Tests.Modules.Scripting.Dlr {
[TestFixture]
public class EvaluatorTests : EvaluatorTestsBase {
private IContainer _container;
private IScriptingManager _scriptingManager;
[SetUp]
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<RubyScriptingRuntime>().As<IScriptingRuntime>();
builder.RegisterType<ScriptingManager>().As<IScriptingManager>();
_container = builder.Build();
_scriptingManager = _container.Resolve<IScriptingManager>();
}
protected override EvaluationResult EvaluateSimpleExpression(string expression, Func<string, IList<object>, object> methodInvocationCallback) {
var evaluator = new RubyScriptExpressionEvaluator(_scriptingManager, new StubCacheManager());
try {
var value = evaluator.Evaluate(expression, new[] { new GlobalMethodProvider(methodInvocationCallback) });
return new EvaluationResult(value);
}
catch (Exception e) {
Trace.WriteLine(string.Format("Error during evaluation of '{0}': {1}", expression, e.Message));
return new EvaluationResult(new Error { Message = e.Message, Exception = e });
}
}
private class GlobalMethodProvider : IGlobalMethodProvider {
private readonly Func<string, IList<object>, object> _methodInvocationCallback;
public GlobalMethodProvider(Func<string, IList<object>, object> methodInvocationCallback) {
_methodInvocationCallback = methodInvocationCallback;
}
public void Process(GlobalMethodContext context) {
context.Result = _methodInvocationCallback(context.FunctionName, context.Arguments);
}
}
}
}

View File

@@ -7,146 +7,17 @@ using Orchard.Scripting.Compiler;
namespace Orchard.Tests.Modules.Scripting {
[TestFixture]
public class EvaluatorTests {
[Test]
public void EvaluateSimpleConstant() {
var result = EvaluateSimpleExpression("true and true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateInvalidBooleanExpression() {
var result = EvaluateSimpleExpression("true and 1");
Assert.That(result.IsError, Is.True);
}
[Test]
public void EvaluateBooleanExpression() {
var result = EvaluateSimpleExpression("not true");
Assert.That(result.IsError, Is.False);
Assert.That(result.BoolValue, Is.EqualTo(false));
}
[Test]
public void EvaluateSimpleArithmetic() {
var result = EvaluateSimpleExpression("1 + 2 * 3 - 6 / 2");
Assert.That(result.IsError, Is.False);
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",
(m, args) => (m == "print") ? (int)args[0] * 2 : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(4 * 2));
}
[Test]
public void EvaluateSimpleMethodCall2() {
var result = EvaluateSimpleExpression("foo 1 + bar 3",
(m, args) =>
(m == "foo") ? (int)args[0] * 2 :
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(2 * (1 + 3)));
}
[Test]
public void EvaluateSimpleMethodCall3() {
var result = EvaluateSimpleExpression("foo(1) + bar(3)",
(m, args) =>
(m == "foo") ? (int)args[0] * 2 :
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(2 + 3));
}
[Test]
public void EvaluateSimpleMethodCall4() {
var result = EvaluateSimpleExpression("foo",
(m, args) => (m == "foo") ? true : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateSimpleMethodCall5() {
var result = EvaluateSimpleExpression("foo()",
(m, args) => (m == "foo") ? true : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
private EvaluationResult EvaluateSimpleExpression(string expression) {
return EvaluateSimpleExpression(expression, (m, args) => null);
}
private EvaluationResult EvaluateSimpleExpression(
string expression, Func<string, IList<object>, object> methodInvocationCallback) {
public class EvaluatorTests : EvaluatorTestsBase {
protected override EvaluationResult EvaluateSimpleExpression(string expression, Func<string, IList<object>, object> methodInvocationCallback) {
var ast = new Parser(expression).Parse();
foreach (var error in ast.GetErrors()) {
Trace.WriteLine(string.Format("Error during parsing of '{0}': {1}", expression, error.Message));
}
Assert.That(ast.GetErrors().Any(), Is.False);
if (ast.GetErrors().Any()) {
return new EvaluationResult(new Error { Message = ast.GetErrors().First().Message });
}
var result = new Interpreter().Evalutate(new EvaluationContext {
Tree = ast,
MethodInvocationCallback = methodInvocationCallback

View File

@@ -0,0 +1,527 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Orchard.Scripting.Compiler;
namespace Orchard.Tests.Modules.Scripting {
[TestFixture]
public abstract class EvaluatorTestsBase {
[Test]
public void EvaluateSimpleConstant() {
var result = EvaluateSimpleExpression("true and true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateSimpleConstant0() {
var result = EvaluateSimpleExpression("true && true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression() {
var result = EvaluateSimpleExpression("true and 1");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(1));
}
[Test]
public void EvaluateConvertingBooleanExpression1() {
var result = EvaluateSimpleExpression("true && 1");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(1));
}
[Test]
public void EvaluateConvertingBooleanExpression2() {
var result = EvaluateSimpleExpression("true and 0");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(0));
}
[Test]
public void EvaluateConvertingBooleanExpression3() {
var result = EvaluateSimpleExpression("true && 0");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(0));
}
[Test]
public void EvaluateConvertingBooleanExpression4() {
var result = EvaluateSimpleExpression("1 and true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression5() {
var result = EvaluateSimpleExpression("0 and true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression6() {
var result = EvaluateSimpleExpression("1 && true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression7() {
var result = EvaluateSimpleExpression("true and 'boo'");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo("boo"));
}
[Test]
public void EvaluateConvertingBooleanExpression8() {
var result = EvaluateSimpleExpression("true && 'boo'");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo("boo"));
}
[Test]
public void EvaluateConvertingBooleanExpression9() {
var result = EvaluateSimpleExpression("'boo' and true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression10() {
var result = EvaluateSimpleExpression("'boo' && true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression11() {
var result = EvaluateSimpleExpression("true or 1");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression12() {
var result = EvaluateSimpleExpression("true || 1");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression13() {
var result = EvaluateSimpleExpression("1 or true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(1));
}
[Test]
public void EvaluateConvertingBooleanExpression14() {
var result = EvaluateSimpleExpression("1 || true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(1));
}
[Test]
public void EvaluateConvertingBooleanExpression15() {
var result = EvaluateSimpleExpression("true or 'boo'");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression16() {
var result = EvaluateSimpleExpression("false or 'boo'");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo("boo"));
}
[Test]
public void EvaluateConvertingBooleanExpression17() {
var result = EvaluateSimpleExpression("nil or 'boo'");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo("boo"));
}
[Test]
public void EvaluateConvertingBooleanExpression18() {
var result = EvaluateSimpleExpression("'boo' or nil");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo("boo"));
}
[Test]
public void EvaluateConvertingBooleanExpression19() {
var result = EvaluateSimpleExpression("true || 'boo'");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateConvertingBooleanExpression20() {
var result = EvaluateSimpleExpression("'boo' or true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo("boo"));
}
[Test]
public void EvaluateConvertingBooleanExpression21() {
var result = EvaluateSimpleExpression("'boo' || true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo("boo"));
}
[Test]
public void EvaluateConvertingBooleanExpression22() {
var result = EvaluateSimpleExpression("1 and 2");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(2));
}
[Test]
public void EvaluateConvertingBooleanExpression23() {
var result = EvaluateSimpleExpression("false and 2");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(false));
}
[Test]
public void EvaluateConvertingBooleanExpression24() {
var result = EvaluateSimpleExpression("nil and 2");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(null));
}
[Test]
public void EvaluateConvertingBooleanExpression25() {
var result = EvaluateSimpleExpression("nil and false");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(null));
}
[Test]
public void EvaluateConvertingBooleanExpression26() {
var result = EvaluateSimpleExpression("nil and true");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(null));
}
[Test]
public void EvaluateBooleanExpression() {
var result = EvaluateSimpleExpression("not true");
Assert.That(result.IsError, Is.False);
Assert.That(result.BoolValue, Is.EqualTo(false));
}
[Test]
public void EvaluateBooleanExpression0() {
var result = EvaluateSimpleExpression("!true");
Assert.That(result.IsError, Is.False);
Assert.That(result.BoolValue, Is.EqualTo(false));
}
[Test]
public void EvaluateSimpleArithmetic() {
var result = EvaluateSimpleExpression("1 + 2 * 3 - 6 / 2");
Assert.That(result.IsError, Is.False);
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 EvaluateRelationalOperators7() {
var result = EvaluateSimpleExpression("5 || 10 && nil");
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(5));
}
[Test]
public void EvaluateRelationalOperators8() {
var result = EvaluateSimpleExpression("true or false and nil");
Assert.That(result.IsError, Is.False);
Assert.That(result.IsNull, Is.True);
}
[Test]
public void EvaluateRelationalOperators9() {
var result = EvaluateSimpleExpression("true and nil");
Assert.That(result.IsError, Is.False);
Assert.That(result.IsNull, Is.True);
}
[Test]
public void EvaluateRelationalOperators10() {
var result = EvaluateSimpleExpression("5 and nil");
Assert.That(result.IsError, Is.False);
Assert.That(result.IsNull, Is.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("printtoto 1 + 2 * 3 - 6 / 2",
(m, args) => (m == "printtoto") ? (int)args[0] * 2 : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(4 * 2));
}
[Test]
public void EvaluateSimpleMethodCall2() {
var result = EvaluateSimpleExpression("printtoto(1 + 2 * 3 - 6 / 2)",
(m, args) => (m == "printtoto") ? (int)args[0] * 2 : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(4 * 2));
}
[Test]
public void EvaluateSimpleMethodCall3() {
var result = EvaluateSimpleExpression("foo 1 + bar 3",
(m, args) =>
(m == "foo") ? (int)args[0] * 2 :
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.True);
}
[Test]
public void EvaluateSimpleMethodCall4() {
var result = EvaluateSimpleExpression("foo(1 + bar 3)",
(m, args) =>
(m == "foo") ? (int)args[0] * 2 :
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.True);
}
[Test]
public void EvaluateSimpleMethodCall5() {
var result = EvaluateSimpleExpression("foo 1 + bar(3)",
(m, args) =>
(m == "foo") ? (int)args[0] * 2 :
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(2 * (1 + 3)));
}
[Test]
public void EvaluateSimpleMethodCall6() {
var result = EvaluateSimpleExpression("foo(1) + bar(3)",
(m, args) =>
(m == "foo") ? (int)args[0] * 2 :
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(2 + 3));
}
[Test]
public void EvaluateSimpleMethodCall7() {
var result = EvaluateSimpleExpression("foo",
(m, args) => (m == "foo") ? true : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateSimpleMethodCall8() {
var result = EvaluateSimpleExpression("foo()",
(m, args) => (m == "foo") ? true : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateSimpleMethodCall9() {
#if false
var result = EvaluateSimpleExpression("1 + bar 3",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.True);
#endif
}
[Test]
public void EvaluateSimpleMethodCall10() {
#if false
var result = EvaluateSimpleExpression("1 || bar 3",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.True);
#endif
}
[Test]
public void EvaluateSimpleMethodCall11() {
#if false
var result = EvaluateSimpleExpression("1 * bar 3",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.True);
#endif
}
[Test]
public void EvaluateSimpleMethodCall12() {
#if false
var result = EvaluateSimpleExpression("1 && bar 3",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.True);
#endif
}
[Test]
public void EvaluateSimpleMethodCall13() {
#if false
var result = EvaluateSimpleExpression("(1 + bar 3)",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.True);
#endif
}
[Test]
public void EvaluateSimpleMethodCall14() {
var result = EvaluateSimpleExpression("1 + bar(3)",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(1 + 3));
}
[Test]
public void EvaluateSimpleMethodCall15() {
var result = EvaluateSimpleExpression("1 + (bar 3)",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(1 + 3));
}
[Test]
public void EvaluateSimpleMethodCall16() {
var result = EvaluateSimpleExpression("1 and bar 3",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(3));
}
[Test]
public void EvaluateSimpleMethodCall17() {
var result = EvaluateSimpleExpression("1 or bar 3",
(m, args) =>
(m == "bar") ? (int)args[0] : 0);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(1));
}
[Test]
public void EvaluateComplexMethodCall() {
var result = EvaluateSimpleExpression("authenticated and url \"~/boo*\"",
(m, args) => (m == "authenticated") ? true : (m == "url") ? (string)args[0] == "~/boo*" : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateComplexMethodCall2() {
var result = EvaluateSimpleExpression("(authenticated) and (url \"~/boo*\")",
(m, args) => (m == "authenticated") ? true : (m == "url") ? (string)args[0] == "~/boo*" : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateComplexMethodCall3() {
var result = EvaluateSimpleExpression("(authenticated and url \"~/boo*\")",
(m, args) => (m == "authenticated") ? true : (m == "url") ? (string)args[0] == "~/boo*" : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateComplexMethodCall4() {
var result = EvaluateSimpleExpression("(authenticated) and url \"~/boo*\"",
(m, args) => (m == "authenticated") ? true : (m == "url") ? (string)args[0] == "~/boo*" : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateComplexMethodCall5() {
var result = EvaluateSimpleExpression("(authenticated()) and (url \"~/boo*\")",
(m, args) => (m == "authenticated") ? true : (m == "url") ? (string)args[0] == "~/boo*" : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
[Test]
public void EvaluateComplexMethodCall6() {
var result = EvaluateSimpleExpression("authenticated() and url(\"~/boo*\")",
(m, args) => (m == "authenticated") ? true : (m == "url") ? (string)args[0] == "~/boo*" : false);
Assert.That(result.IsError, Is.False);
Assert.That(result.Value, Is.EqualTo(true));
}
private EvaluationResult EvaluateSimpleExpression(string expression) {
return EvaluateSimpleExpression(expression, (m, args) => null);
}
protected abstract EvaluationResult EvaluateSimpleExpression(string expression, Func<string, IList<object>, object> methodInvocationCallback);
}
}

View File

@@ -110,6 +110,30 @@ namespace Orchard.Tests.Modules.Scripting {
});
}
[Test]
public void ParserShouldUnderstandOperatorPrecedence5() {
var tree = new Parser("1+2+3").Parse();
CheckTree(tree, new object[] {
"binop", TokenKind.Plus,
"binop", TokenKind.Plus,
"const", 1,
"const", 2,
"const", 3,
});
}
[Test]
public void ParserShouldUnderstandOperatorPrecedence6() {
var tree = new Parser("1+2-3").Parse();
CheckTree(tree, new object[] {
"binop", TokenKind.Minus,
"binop", TokenKind.Plus,
"const", 1,
"const", 2,
"const", 3,
});
}
[Test]
public void ParserShouldUnderstandRelationalOperators() {
var tree = new Parser("true == true").Parse();
@@ -184,11 +208,11 @@ namespace Orchard.Tests.Modules.Scripting {
public void ParserShouldUnderstandRelationalOperatorPrecedence() {
var tree = new Parser("1 < 2 or 2 > 3 and !false").Parse();
CheckTree(tree, new object[] {
"binop", TokenKind.And,
"binop", TokenKind.Or,
"binop", TokenKind.LessThan,
"const", 1,
"const", 2,
"binop", TokenKind.And,
"binop", TokenKind.GreaterThan,
"const", 2,
"const", 3,
@@ -201,11 +225,11 @@ namespace Orchard.Tests.Modules.Scripting {
public void ParserShouldUnderstandRelationalOperatorPrecedence2() {
var tree = new Parser("1 < 2 and 2 > 3 or !false").Parse();
CheckTree(tree, new object[] {
"binop", TokenKind.Or,
"binop", TokenKind.And,
"binop", TokenKind.LessThan,
"const", 1,
"const", 2,
"binop", TokenKind.Or,
"binop", TokenKind.GreaterThan,
"const", 2,
"const", 3,
@@ -234,9 +258,9 @@ namespace Orchard.Tests.Modules.Scripting {
"binop", TokenKind.Mul,
"const", 1,
"binop", TokenKind.Plus,
"binop", TokenKind.Mul,
"binop", TokenKind.Div,
"const", 2,
"binop", TokenKind.Mul,
"const", 4,
"const", 6,
"const", 3,
@@ -246,6 +270,14 @@ namespace Orchard.Tests.Modules.Scripting {
[Test]
public void ParserShouldContainErrorExpressions() {
var tree = new Parser("1 + not 3").Parse();
CheckTree(tree, new object[] {
"error",
});
}
[Test]
public void ParserShouldContainErrorExpressions2() {
var tree = new Parser("1 +").Parse();
CheckTree(tree, new object[] {
"binop", TokenKind.Plus,
"const", 1,

View File

@@ -41,6 +41,12 @@ ExecContext
var ops = _cacheManager.Get("----", ctx => (ObjectOperations)_scriptingManager.ExecuteOperation(x => x));
object execContext = _cacheManager.Get(expression, ctx => (object)ops.InvokeMember(execContextType, "alloc", expression));
dynamic result = ops.InvokeMember(execContext, "evaluate", new CallbackApi(this, providers));
return ConvertRubyValue(result);
}
private object ConvertRubyValue(object result) {
if (result is IronRuby.Builtins.MutableString)
return result.ToString();
return result;
}
@@ -58,8 +64,11 @@ ExecContext
}
}
private object Evaluate(IEnumerable<IGlobalMethodProvider> providers, string name, IList<object> args) {
GlobalMethodContext ruleContext = new GlobalMethodContext { FunctionName = name, Arguments = args.ToArray() };
private object Evaluate(IEnumerable<IGlobalMethodProvider> providers, string name, IEnumerable<object> args) {
var ruleContext = new GlobalMethodContext {
FunctionName = name,
Arguments = args.Select(v => ConvertRubyValue(v)).ToArray()
};
foreach (var ruleProvider in providers) {
ruleProvider.Process(ruleContext);

View File

@@ -39,6 +39,7 @@ namespace Orchard.Scripting.Compiler {
public class Error {
public string Message { get; set; }
public Exception Exception { get; set; }
public override string ToString() {
return string.Format("Error: {0}", Message);

View File

@@ -59,9 +59,11 @@ namespace Orchard.Scripting.Compiler {
case TokenKind.Div:
return EvaluateArithmetic(left, right, (a, b) => b.Int32Value == 0 ? Error("Attempted to divide by zero.") : Result(a.Int32Value / b.Int32Value));
case TokenKind.And:
return EvaluateLogical(left, right, (a, b) => Result(a.BoolValue && b.BoolValue));
case TokenKind.AndSign:
return EvaluateLogicalAnd(left, right);
case TokenKind.Or:
return EvaluateLogical(left, right, (a, b) => Result(a.BoolValue || b.BoolValue));
case TokenKind.OrSign:
return EvaluateLogicalOr(left, right);
case TokenKind.EqualEqual:
return EvaluateEquality(left, right, v => v);
case TokenKind.NotEqual:
@@ -122,17 +124,14 @@ namespace Orchard.Scripting.Compiler {
return operation(leftValue, rightValue);
}
private static EvaluationResult EvaluateLogical(EvaluationResult left, EvaluationResult right,
Func<EvaluationResult, EvaluationResult, EvaluationResult> operation) {
var leftValue = ConvertToBool(left);
if (leftValue.IsError)
return leftValue;
private EvaluationResult EvaluateLogicalAnd(EvaluationResult left, EvaluationResult right) {
var type = PrimitiveType.InstanceFor(left.Value);
return type.LogicalAnd(left, right);
}
var rightValue = ConvertToBool(right);
if (rightValue.IsError)
return rightValue;
return operation(leftValue, rightValue);
private EvaluationResult EvaluateLogicalOr(EvaluationResult left, EvaluationResult right) {
var type = PrimitiveType.InstanceFor(left.Value);
return type.LogicalOr(left, right);
}
private static EvaluationResult ConvertToInt(EvaluationResult value) {

View File

@@ -1,11 +1,12 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Orchard.Scripting.Ast;
namespace Orchard.Scripting.Compiler {
public class Parser {
private readonly string _expression;
private readonly Lexer _lexer;
private readonly List<string> _errors = new List<string>();
private bool _parsingMethodCall = false;
public Parser(string expression) {
_expression = expression;
@@ -14,6 +15,9 @@ namespace Orchard.Scripting.Compiler {
public AbstractSyntaxTree Parse() {
var node = ParseExpression();
if (_lexer.Token().Kind != TokenKind.Eof) {
node = UnexpectedTokenError();
}
return new AbstractSyntaxTree { Root = node };
}
@@ -24,11 +28,13 @@ namespace Orchard.Scripting.Compiler {
private AstNode ParseKeywordLogicalExpression() {
var expr = ParseKeywordNotExpression();
again:
var token = IsMatch(TokenKind.Or, TokenKind.And);
if (token != null) {
var right = ParseKeywordLogicalExpression();
var right = ParseKeywordNotExpression();
expr = new BinaryAstNode(expr, token, right);
goto again;
}
return expr;
@@ -42,11 +48,42 @@ namespace Orchard.Scripting.Compiler {
return new UnaryAstNode(token, expr);
}
return ParseEqualityExpression();
return ParseLogicalOrExpression();
}
private AstNode ParseLogicalOrExpression() {
var expr = ParseLogicalAndExpression();
again:
var token = IsMatch(TokenKind.OrSign);
if (token != null) {
var right = ParseLogicalAndExpression();
expr = new BinaryAstNode(expr, token, right);
goto again;
}
return expr;
}
private AstNode ParseLogicalAndExpression() {
var expr = ParseEqualityExpression();
again:
var token = IsMatch(TokenKind.AndSign);
if (token != null) {
var right = ParseEqualityExpression();
expr = new BinaryAstNode(expr, token, right);
goto again;
}
return expr;
}
private AstNode ParseEqualityExpression() {
var expr = ParseRelationalExpression();
var expr = ParseComparisonExpression();
var token = IsMatch(TokenKind.EqualEqual, TokenKind.NotEqual);
if (token != null) {
@@ -58,14 +95,14 @@ namespace Orchard.Scripting.Compiler {
return expr;
}
private AstNode ParseRelationalExpression() {
private AstNode ParseComparisonExpression() {
var expr = ParseAdditiveExpression();
var token =
IsMatch(TokenKind.LessThan, TokenKind.LessThanEqual) ??
IsMatch(TokenKind.GreaterThan, TokenKind.GreaterThanEqual);
if (token != null) {
var right = ParseRelationalExpression();
var right = ParseComparisonExpression();
expr = new BinaryAstNode(expr, token, right);
}
@@ -76,11 +113,13 @@ namespace Orchard.Scripting.Compiler {
private AstNode ParseAdditiveExpression() {
var expr = ParseMultiplicativeExpression();
again:
var token = IsMatch(TokenKind.Plus, TokenKind.Minus);
if (token != null) {
var right = ParseAdditiveExpression();
var right = ParseMultiplicativeExpression();
expr = new BinaryAstNode(expr, token, right);
goto again;
}
return expr;
@@ -89,11 +128,13 @@ namespace Orchard.Scripting.Compiler {
private AstNode ParseMultiplicativeExpression() {
var expr = ParseUnaryExpression();
again:
var token = IsMatch(TokenKind.Mul, TokenKind.Div);
if (token != null) {
var right = ParseMultiplicativeExpression();
var right = ParseUnaryExpression();
expr = new BinaryAstNode(expr, token, right);
goto again;
}
return expr;
@@ -125,14 +166,25 @@ namespace Orchard.Scripting.Compiler {
case TokenKind.Identifier:
return ParseMethodCallExpression();
default:
return ProduceError(token);
return UnexpectedTokenError();
}
}
private AstNode ParseIndentifier(Token identifier) {
return new MethodCallAstNode(identifier, new List<AstNode>());
}
private AstNode ParseParenthesizedExpression() {
Match(TokenKind.OpenParen);
// '('
_lexer.NextToken();
var expr = ParseExpression();
Match(TokenKind.CloseParen);
// ')'
if (IsMatch(TokenKind.CloseParen) == null) {
return ExpectedTokenError(TokenKind.CloseParen);
}
return expr;
}
@@ -142,6 +194,20 @@ namespace Orchard.Scripting.Compiler {
bool isParenthesizedCall = (IsMatch(TokenKind.OpenParen) != null);
// This is to avoid parsing method calls within method calls that have no
// parenthesis (language ambiguity)
if (!isParenthesizedCall && _parsingMethodCall) {
return ParseIndentifier(target);
}
// Detect tokens that can't be a function argument start token
if (!IsValidMethodArgumentToken(isParenthesizedCall)) {
return ParseIndentifier(target);
}
_parsingMethodCall = true;
try {
var arguments = new List<AstNode>();
while (true) {
// Special case: we might reach the end of the token stream
@@ -152,6 +218,20 @@ namespace Orchard.Scripting.Compiler {
if (isParenthesizedCall && _lexer.Token().Kind == TokenKind.CloseParen)
break;
// Special case: for non parenthized calls, some tokens mark the end of the call
if (!isParenthesizedCall) {
bool endOfMethodCall = false;
switch (_lexer.Token().Kind) {
case TokenKind.And:
case TokenKind.Or:
case TokenKind.Not:
endOfMethodCall = true;
break;
}
if (endOfMethodCall)
break;
}
var argument = ParseExpression();
arguments.Add(argument);
@@ -159,29 +239,55 @@ namespace Orchard.Scripting.Compiler {
break;
}
if (isParenthesizedCall)
Match(TokenKind.CloseParen);
if (isParenthesizedCall) {
// ')'
if (IsMatch(TokenKind.CloseParen) == null) {
return ExpectedTokenError(TokenKind.CloseParen);
}
}
return new MethodCallAstNode(target, arguments);
}
finally {
_parsingMethodCall = false;
}
}
private bool IsValidMethodArgumentToken(bool isParenthesizedCall) {
switch(_lexer.Token().Kind) {
case TokenKind.OpenParen:
case TokenKind.StringLiteral:
case TokenKind.SingleQuotedStringLiteral:
case TokenKind.Identifier:
case TokenKind.Integer:
case TokenKind.NotSign:
case TokenKind.NullLiteral:
case TokenKind.Minus:
case TokenKind.Plus:
case TokenKind.True:
case TokenKind.False:
return true;
case TokenKind.CloseParen:
return isParenthesizedCall;
default:
return false;
}
}
private AstNode ProduceConstant(Token token) {
_lexer.NextToken();
return new ConstantAstNode(token);
}
private AstNode ProduceError(Token token) {
private AstNode UnexpectedTokenError() {
var token = _lexer.Token();
_lexer.NextToken();
return new ErrorAstNode(token, string.Format("Unexptected Token in primary expression ({0})", token));
return new ErrorAstNode(token, string.Format("Unexpected token in primary expression ({0})", token));
}
private void Match(TokenKind kind) {
private AstNode ExpectedTokenError(TokenKind tokenKind) {
var token = _lexer.Token();
if (token.Kind == kind) {
_lexer.NextToken();
return;
}
AddError(token, string.Format("Expected Token {0}", kind));
return new ErrorAstNode(token, string.Format("Expected token {0}", tokenKind));
}
private Token IsMatch(TokenKind kind) {
@@ -201,9 +307,5 @@ namespace Orchard.Scripting.Compiler {
}
return null;
}
private void AddError(Token token, string message) {
_errors.Add(message);
}
}
}

View File

@@ -11,11 +11,15 @@ namespace Orchard.Scripting.Compiler {
return IntegerPrimitiveType.Instance;
if (value is string)
return StringPrimitiveType.Instance;
if (value is Error)
return ErrorPrimitiveType.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);
public abstract EvaluationResult LogicalAnd(EvaluationResult value, EvaluationResult other);
public abstract EvaluationResult LogicalOr(EvaluationResult value, EvaluationResult other);
protected EvaluationResult Result(object value) {
return EvaluationResult.Result(value);
@@ -42,6 +46,18 @@ namespace Orchard.Scripting.Compiler {
public override EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other) {
return Error("Boolean values can only be compared to other boolean values");
}
public override EvaluationResult LogicalAnd(EvaluationResult value, EvaluationResult other) {
if (!value.BoolValue)
return value;
return other;
}
public override EvaluationResult LogicalOr(EvaluationResult value, EvaluationResult other) {
if (value.BoolValue)
return value;
return other;
}
}
public class IntegerPrimitiveType : PrimitiveType {
@@ -62,6 +78,14 @@ namespace Orchard.Scripting.Compiler {
return Result(value.Int32Value.CompareTo(other.Int32Value));
return Error("Integer values can only be compared to other integer values");
}
public override EvaluationResult LogicalAnd(EvaluationResult value, EvaluationResult other) {
return other;
}
public override EvaluationResult LogicalOr(EvaluationResult value, EvaluationResult other) {
return value;
}
}
public class StringPrimitiveType : PrimitiveType {
@@ -80,6 +104,14 @@ namespace Orchard.Scripting.Compiler {
public override EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other) {
return Error("String values can not be compared");
}
public override EvaluationResult LogicalAnd(EvaluationResult value, EvaluationResult other) {
return other;
}
public override EvaluationResult LogicalOr(EvaluationResult value, EvaluationResult other) {
return value;
}
}
public class NullPrimitiveType : PrimitiveType {
@@ -96,5 +128,37 @@ namespace Orchard.Scripting.Compiler {
public override EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other) {
return Error("'null' values can not be compared");
}
public override EvaluationResult LogicalAnd(EvaluationResult value, EvaluationResult other) {
return value;
}
public override EvaluationResult LogicalOr(EvaluationResult value, EvaluationResult other) {
return other;
}
}
public class ErrorPrimitiveType : PrimitiveType {
private static ErrorPrimitiveType _instance;
public static ErrorPrimitiveType Instance {
get { return _instance ?? (_instance = new ErrorPrimitiveType()); }
}
public override EvaluationResult EqualityOperator(EvaluationResult value, EvaluationResult other) {
return value;
}
public override EvaluationResult ComparisonOperator(EvaluationResult value, EvaluationResult other) {
return value;
}
public override EvaluationResult LogicalAnd(EvaluationResult value, EvaluationResult other) {
return value;
}
public override EvaluationResult LogicalOr(EvaluationResult value, EvaluationResult other) {
return value;
}
}
}

View File

@@ -7,7 +7,7 @@ namespace Orchard.Scripting.Compiler {
public object Value { get; set; }
public override string ToString() {
return Value == null ? String.Format("Token {0} at position {1}", Kind, Position) : String.Format("Token {0} ({1}) at position {2}", Kind, Value, Position);
return Value == null ? String.Format("Token '{0}' at position {1}", Kind, Position) : String.Format("Token '{0}' ({1}) at position {2}", Kind, Value, Position);
}
}
}

View File

@@ -16,11 +16,13 @@
True,
False,
And,
AndSign,
Or,
OrSign,
Not,
NotSign,
Equal,
EqualEqual,
NotSign,
NotEqual,
LessThan,
LessThanEqual,

View File

@@ -48,6 +48,10 @@ namespace Orchard.Scripting.Compiler {
return LexSingleQuotedStringLiteral();
case '!':
return LexNotSign();
case '|':
return LexOrSign();
case '&':
return LexAndSign();
case '=':
return LexEqual();
case '<':
@@ -67,10 +71,14 @@ namespace Orchard.Scripting.Compiler {
continue;
}
return CreateToken(TokenKind.Invalid, "Unrecognized character");
return InvalidToken();
}
}
private Token InvalidToken() {
return CreateToken(TokenKind.Invalid, "Unrecognized character");
}
private Token LexNotSign() {
NextCharacter();
char ch = Character();
@@ -81,6 +89,26 @@ namespace Orchard.Scripting.Compiler {
return CreateToken(TokenKind.NotSign);
}
private Token LexOrSign() {
NextCharacter();
char ch = Character();
if (ch == '|') {
NextCharacter();
return CreateToken(TokenKind.OrSign);
}
return InvalidToken();
}
private Token LexAndSign() {
NextCharacter();
char ch = Character();
if (ch == '&') {
NextCharacter();
return CreateToken(TokenKind.AndSign);
}
return InvalidToken();
}
private Token LexGreaterThan() {
NextCharacter();
char ch = Character();