Replace dlr scripting implementation with the new lightweight engine

DLR scripting engine still available in new Orchard.Scripting.Dlr module

--HG--
branch : dev
rename : src/Orchard.Web/Modules/Orchard.Scripting/ScriptingManager.cs => src/Orchard.Web/Modules/Orchard.Scripting/ScriptExpressionEvaluator.cs
This commit is contained in:
Renaud Paquay
2010-11-28 12:03:11 -08:00
parent 14dd1c754f
commit f96abb981a
11 changed files with 159 additions and 107 deletions

View File

@@ -2,30 +2,24 @@
using NUnit.Framework; using NUnit.Framework;
using Orchard.Scripting; using Orchard.Scripting;
using Orchard.Tests.Stubs; using Orchard.Tests.Stubs;
using Orchard.Widgets.Services;
namespace Orchard.Tests.Modules.Scripting { namespace Orchard.Tests.Modules.Scripting {
[TestFixture] [TestFixture]
public class SimpleScriptingTests { public class SimpleScriptingTests {
[Test] [Test]
public void EngineThrowsSyntaxErrors() { public void EngineThrowsSyntaxErrors() {
var engine = new ScriptingEngine(Enumerable.Empty<IGlobalMethodProvider>(), new StubCacheManager()); var engine = new ScriptExpressionEvaluator(new StubCacheManager());
Assert.That(() => engine.Matches("true+"), Throws.Exception); Assert.That(() => engine.Evaluate("true+", Enumerable.Empty<IGlobalMethodProvider>()), Throws.Exception);
}
[Test]
public void EngineThrowsEvalErrors() {
var engine = new ScriptingEngine(Enumerable.Empty<IGlobalMethodProvider>(), new StubCacheManager());
Assert.That(() => engine.Matches("1 + 1"), Throws.Exception);
} }
[Test] [Test]
public void EngineUnderstandsPrimitiveValues() { public void EngineUnderstandsPrimitiveValues() {
var engine = new ScriptingEngine(Enumerable.Empty<IGlobalMethodProvider>(), new StubCacheManager()); var engine = new ScriptExpressionEvaluator(new StubCacheManager());
Assert.That(engine.Matches("true"), Is.True); Assert.That(engine.Evaluate("true", Enumerable.Empty<IGlobalMethodProvider>()), Is.True);
} }
[Test] [Test]
public void EngineUnderstandsPrimitiveValues2() { public void EngineUnderstandsPrimitiveValues2() {
var engine = new ScriptingEngine(Enumerable.Empty<IGlobalMethodProvider>(), new StubCacheManager()); var engine = new ScriptExpressionEvaluator(new StubCacheManager());
Assert.That(engine.Matches("true and true"), Is.True); Assert.That(engine.Evaluate("true and true", Enumerable.Empty<IGlobalMethodProvider>()), Is.True);
} }
} }
} }

View File

@@ -7,5 +7,6 @@ OrchardVersion: 0.8.0
Description: The DLR scripting module enables the possibility to execute scripts using the DLR. Description: The DLR scripting module enables the possibility to execute scripts using the DLR.
Features: Features:
Orchard.Scripting.Dlr: Orchard.Scripting.Dlr:
Description: DLR Scripting support. Description: DLR scripting support.
Dependencies: Orchard.Scripting
Category: Scripting Category: Scripting

View File

@@ -62,6 +62,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\RubyScriptExpressionEvaluator.cs" />
<Compile Include="Services\IScriptingManager.cs" /> <Compile Include="Services\IScriptingManager.cs" />
<Compile Include="Services\IScriptingRuntime.cs" /> <Compile Include="Services\IScriptingRuntime.cs" />
<Compile Include="Services\RubyScriptingRuntime.cs" /> <Compile Include="Services\RubyScriptingRuntime.cs" />
@@ -73,6 +74,10 @@
<Name>Orchard.Framework</Name> <Name>Orchard.Framework</Name>
<Private>True</Private> <Private>True</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Orchard.Scripting\Orchard.Scripting.csproj">
<Project>{99002B65-86F7-415E-BF4A-381AA8AB9CCC}</Project>
<Name>Orchard.Scripting</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Module.txt" /> <Content Include="Module.txt" />

View File

@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Scripting.Hosting;
using Orchard.Caching;
namespace Orchard.Scripting.Dlr.Services {
public class RubyScriptExpressionEvaluator : IScriptExpressionEvaluator {
private readonly IScriptingManager _scriptingManager;
private readonly ICacheManager _cacheManager;
public RubyScriptExpressionEvaluator(IScriptingManager scriptingManager, ICacheManager cacheManager) {
_scriptingManager = scriptingManager;
_cacheManager = cacheManager;
}
public object Evaluate(string expression, IEnumerable<IGlobalMethodProvider> providers) {
object execContextType = _cacheManager.Get("---", ctx => (object)_scriptingManager.ExecuteExpression(@"
class ExecBlock
def initialize(callbacks)
@callbacks = callbacks
end
def method_missing(name, *args, &block)
@callbacks.send(name, args, &block);
end
end
class ExecContext
class << self
def alloc(thing)
instance_eval 'self.new {' + thing + '}'
end
end
def initialize(&block)
@block = block
end
def evaluate(callbacks)
ExecBlock.new(callbacks).instance_eval(&@block)
end
end
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 result;
}
public class CallbackApi {
private readonly RubyScriptExpressionEvaluator _ruleManager;
private readonly IEnumerable<IGlobalMethodProvider> _providers;
public CallbackApi(RubyScriptExpressionEvaluator ruleManager, IEnumerable<IGlobalMethodProvider> providers) {
_ruleManager = ruleManager;
_providers = providers;
}
public object send(string name, IList<object> args) {
return _ruleManager.Evaluate(_providers, name, args);
}
}
private object Evaluate(IEnumerable<IGlobalMethodProvider> providers, string name, IList<object> args) {
GlobalMethodContext ruleContext = new GlobalMethodContext { FunctionName = name, Arguments = args.ToArray() };
foreach (var ruleProvider in providers) {
ruleProvider.Process(ruleContext);
}
return ruleContext.Result;
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace Orchard.Scripting {
public interface IGlobalMethodProvider {
void Process(GlobalMethodContext context);
}
public class GlobalMethodContext {
public string FunctionName { get; set; }
public IList<object> Arguments { get; set; }
public object Result { get; set; }
}
}

View File

@@ -1,5 +1,7 @@
 using System.Collections.Generic;
namespace Orchard.Scripting { namespace Orchard.Scripting {
public interface IScriptExpressionEvaluator : ISingletonDependency { public interface IScriptExpressionEvaluator : ISingletonDependency {
object Evaluate(string expression, IEnumerable<IGlobalMethodProvider> providers);
} }
} }

View File

@@ -6,6 +6,11 @@ Version: 0.8.0
OrchardVersion: 0.8.0 OrchardVersion: 0.8.0
Description: The scripting module enables the possibility to execute scripts using a simple custom scripting language. Description: The scripting module enables the possibility to execute scripts using a simple custom scripting language.
Features: Features:
Orchard.Scripting.: Orchard.Scripting:
Description: Simple scripting support. Description: Scripting support.
Category: Scripting
Orchard.Scripting.Lightweight:
Name: Lightweight scripting
Description: Custom lightweight and simple scripting language.
Dependencies: Orchard.Scripting
Category: Scripting Category: Scripting

View File

@@ -54,6 +54,7 @@
<Compile Include="Ast\IAstNodeWithToken.cs" /> <Compile Include="Ast\IAstNodeWithToken.cs" />
<Compile Include="Ast\MethodCallAstNode.cs" /> <Compile Include="Ast\MethodCallAstNode.cs" />
<Compile Include="Ast\UnaryAstNode.cs" /> <Compile Include="Ast\UnaryAstNode.cs" />
<Compile Include="IGlobalMethodProvider.cs" />
<Compile Include="IScriptExpressionEvaluator.cs" /> <Compile Include="IScriptExpressionEvaluator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Compiler\Interpreter.cs" /> <Compile Include="Compiler\Interpreter.cs" />
@@ -63,7 +64,7 @@
<Compile Include="Compiler\Token.cs" /> <Compile Include="Compiler\Token.cs" />
<Compile Include="Compiler\Tokenizer.cs" /> <Compile Include="Compiler\Tokenizer.cs" />
<Compile Include="Compiler\TokenKind.cs" /> <Compile Include="Compiler\TokenKind.cs" />
<Compile Include="ScriptingManager.cs" /> <Compile Include="ScriptExpressionEvaluator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj"> <ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">

View File

@@ -2,38 +2,24 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Scripting.Ast; using Orchard.Scripting.Ast;
using Orchard.Scripting.Compiler; using Orchard.Scripting.Compiler;
namespace Orchard.Scripting { namespace Orchard.Scripting {
public class GlobalMethodContext { [OrchardFeature("Orchard.Scripting.Lightweight")]
public string FunctionName { get; set; } public class ScriptExpressionEvaluator : IScriptExpressionEvaluator {
public IList<object> Arguments { get; set; }
public object Result { get; set; }
}
public interface IGlobalMethodProvider {
object Process(GlobalMethodContext context);
}
public interface IScriptingEngine : IDependency {
bool Matches(string expression);
}
public class ScriptingEngine : IScriptingEngine {
private readonly IEnumerable<IGlobalMethodProvider> _ruleProviders;
private readonly ICacheManager _cacheManager; private readonly ICacheManager _cacheManager;
public ScriptingEngine(IEnumerable<IGlobalMethodProvider> ruleProviders, ICacheManager cacheManager) { public ScriptExpressionEvaluator(ICacheManager cacheManager) {
_ruleProviders = ruleProviders;
_cacheManager = cacheManager; _cacheManager = cacheManager;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
} }
public Localizer T { get; set; } public Localizer T { get; set; }
public bool Matches(string expression) { public object Evaluate(string expression, IEnumerable<IGlobalMethodProvider> providers) {
var expr = _cacheManager.Get(expression, ctx => { var expr = _cacheManager.Get(expression, ctx => {
var ast = ParseExpression(expression); var ast = ParseExpression(expression);
return new { Tree = ast, Errors = ast.GetErrors().ToList() }; return new { Tree = ast, Errors = ast.GetErrors().ToList() };
@@ -44,38 +30,38 @@ namespace Orchard.Scripting {
throw new OrchardException(T("Syntax error: {0}", expr.Errors.First().Message)); throw new OrchardException(T("Syntax error: {0}", expr.Errors.First().Message));
} }
var result = EvaluateExpression(expr.Tree); var result = EvaluateExpression(expr.Tree, providers);
if (result.IsError) { if (result.IsError) {
throw new ApplicationException(result.Error.Message); throw new ApplicationException(result.Error.Message);
} }
if (!result.IsBool) { return result.Value;
throw new OrchardException(T("Expression is not a boolean value"));
}
return result.BoolValue;
} }
private AbstractSyntaxTree ParseExpression(string expression) { private AbstractSyntaxTree ParseExpression(string expression) {
return new Parser(expression).Parse(); return new Parser(expression).Parse();
} }
private EvaluationResult EvaluateExpression(AbstractSyntaxTree tree) { private EvaluationResult EvaluateExpression(AbstractSyntaxTree tree, IEnumerable<IGlobalMethodProvider> providers) {
var context = new EvaluationContext { var context = new EvaluationContext {
Tree = tree, Tree = tree,
MethodInvocationCallback = (m, args) => Evaluate(m, args) MethodInvocationCallback = (m, args) => Evaluate(providers, m, args)
}; };
return new Interpreter().Evalutate(context); return new Interpreter().Evalutate(context);
} }
private object Evaluate(string name, IEnumerable<object> args) { private object Evaluate(IEnumerable<IGlobalMethodProvider> globalMethodProviders, string name, IEnumerable<object> args) {
var ruleContext = new GlobalMethodContext() { FunctionName = name, Arguments = args.ToArray() }; var globalMethodContext = new GlobalMethodContext {
FunctionName = name,
Arguments = args.ToArray(),
Result = null
};
foreach (var ruleProvider in _ruleProviders) { foreach (var globalMethodProvider in globalMethodProviders) {
ruleProvider.Process(ruleContext); globalMethodProvider.Process(globalMethodContext);
} }
return ruleContext.Result; return globalMethodContext.Result;
} }
} }
} }

View File

@@ -35,10 +35,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Scripting, Version=1.1.0.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\dlr\Microsoft.Scripting.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@@ -100,10 +96,6 @@
<Name>Orchard.Framework</Name> <Name>Orchard.Framework</Name>
<Private>True</Private> <Private>True</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Orchard.Scripting.Dlr\Orchard.Scripting.Dlr.csproj">
<Project>{2AD6973D-C7BB-416E-89FE-EEE34664E05F}</Project>
<Name>Orchard.Scripting.Dlr</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Scripting\Orchard.Scripting.csproj"> <ProjectReference Include="..\Orchard.Scripting\Orchard.Scripting.csproj">
<Project>{99002B65-86F7-415E-BF4A-381AA8AB9CCC}</Project> <Project>{99002B65-86F7-415E-BF4A-381AA8AB9CCC}</Project>
<Name>Orchard.Scripting</Name> <Name>Orchard.Scripting</Name>

View File

@@ -1,73 +1,55 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Orchard.Scripting.Dlr.Services; using Orchard.Localization;
using Orchard.Scripting;
using Orchard.Widgets.Services; using Orchard.Widgets.Services;
using Microsoft.Scripting.Hosting;
using Orchard.Caching;
namespace Orchard.Widgets.RuleEngine { namespace Orchard.Widgets.RuleEngine {
public class RuleManager : IRuleManager { public class RuleManager : IRuleManager {
private readonly IEnumerable<IRuleProvider> _ruleProviders; private readonly IEnumerable<IRuleProvider> _ruleProviders;
private readonly IScriptingManager _scriptingManager; private readonly IEnumerable<IScriptExpressionEvaluator> _evaluators;
private readonly ICacheManager _cacheManager;
public RuleManager(IEnumerable<IRuleProvider> ruleProviders, IScriptingManager scriptingManager, ICacheManager cacheManager) { public RuleManager(IEnumerable<IRuleProvider> ruleProviders, IEnumerable<IScriptExpressionEvaluator> evaluators) {
_ruleProviders = ruleProviders; _ruleProviders = ruleProviders;
_scriptingManager = scriptingManager; _evaluators = evaluators;
_cacheManager = cacheManager; T = NullLocalizer.Instance;
} }
public Localizer T { get; set; }
public bool Matches(string expression) { public bool Matches(string expression) {
object execContextType = _cacheManager.Get("---", ctx => (object)_scriptingManager.ExecuteExpression(@" var evaluator = _evaluators.FirstOrDefault();
class ExecBlock if (evaluator == null) {
def initialize(callbacks) throw new OrchardException(T("There are currently not scripting engine enabled"));
@callbacks = callbacks
end
def method_missing(name, *args, &block)
@callbacks.send(name, args, &block);
end
end
class ExecContext
class << self
def alloc(thing)
instance_eval 'self.new {' + thing + '}'
end
end
def initialize(&block)
@block = block
end
def evaluate(callbacks)
ExecBlock.new(callbacks).instance_eval(&@block)
end
end
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));
return result;
} }
public class CallbackApi { var result = evaluator.Evaluate(expression, new List<IGlobalMethodProvider> { new GlobalMethodProvider(this) });
if (!(result is bool)) {
throw new OrchardException(T("Expression is not a boolean value"));
}
return (bool)result;
}
private class GlobalMethodProvider : IGlobalMethodProvider {
private readonly RuleManager _ruleManager; private readonly RuleManager _ruleManager;
public CallbackApi(RuleManager ruleManager) { public GlobalMethodProvider(RuleManager ruleManager) {
_ruleManager = ruleManager; _ruleManager = ruleManager;
} }
public object send(string name, IList<object> args) { public void Process(GlobalMethodContext context) {
return _ruleManager.Evaluate(name, args); var ruleContext = new RuleContext {
} FunctionName = context.FunctionName,
} Arguments = context.Arguments.ToArray(),
Result = context.Result
};
private object Evaluate(string name, IList<object> args) { foreach(var ruleProvider in _ruleManager._ruleProviders) {
RuleContext ruleContext = new RuleContext { FunctionName = name, Arguments = args.ToArray() };
foreach (var ruleProvider in _ruleProviders) {
ruleProvider.Process(ruleContext); ruleProvider.Process(ruleContext);
} }
return ruleContext.Result; context.Result = ruleContext.Result;
}
} }
} }
} }