diff --git a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj
index 04182d1a9..8d33b4f96 100644
--- a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj
+++ b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj
@@ -135,6 +135,9 @@
+
+
+
diff --git a/src/Orchard.Tests.Modules/SimpleScriptingTests/ExpressionLexerTests.cs b/src/Orchard.Tests.Modules/SimpleScriptingTests/ExpressionLexerTests.cs
new file mode 100644
index 000000000..6a2f41052
--- /dev/null
+++ b/src/Orchard.Tests.Modules/SimpleScriptingTests/ExpressionLexerTests.cs
@@ -0,0 +1,76 @@
+using NUnit.Framework;
+using Orchard.Widgets.SimpleScripting;
+
+namespace Orchard.Tests.Modules.SimpleScriptingTests {
+ [TestFixture]
+ public class ExpressionLexerTests {
+
+ [Test]
+ public void LexerShouldProcessSingleQuotedStringLiteral() {
+ TestStringLiteral(@"'toto'", @"toto", TokenKind.SingleQuotedStringLiteral);
+ TestStringLiteral(@"'to\'to'", @"to'to", TokenKind.SingleQuotedStringLiteral);
+ TestStringLiteral(@"'to\\to'", @"to\to", TokenKind.SingleQuotedStringLiteral);
+ TestStringLiteral(@"'to\ato'", @"to\ato", TokenKind.SingleQuotedStringLiteral);
+ }
+
+ [Test]
+ public void LexerShouldProcessStringLiteral() {
+ TestStringLiteral(@"""toto""", @"toto", TokenKind.StringLiteral);
+ TestStringLiteral(@"""to\'to""", @"to'to", TokenKind.StringLiteral);
+ TestStringLiteral(@"""to\\to""", @"to\to", TokenKind.StringLiteral);
+ TestStringLiteral(@"""to\ato""", @"toato", TokenKind.StringLiteral);
+ }
+
+ private void TestStringLiteral(string value, string expected, TokenKind expectedTokenKind) {
+ var lexer = new ExpressionTokenizer(value);
+ var token1 = lexer.NextToken();
+ Assert.That(token1.Kind, Is.EqualTo(expectedTokenKind));
+ Assert.That(token1.Value, Is.EqualTo(expected));
+
+ var token2 = lexer.NextToken();
+ Assert.That(token2.Kind, Is.EqualTo(TokenKind.Eof));
+ }
+
+ [Test]
+ public void LexerShouldProcessReservedWords() {
+ TestReservedWord("true", true, TokenKind.True);
+ TestReservedWord("false", false, TokenKind.False);
+ TestReservedWord("not", "not", TokenKind.Not);
+ TestReservedWord("and", "and", TokenKind.And);
+ TestReservedWord("or", "or", TokenKind.Or);
+ }
+
+ private void TestReservedWord(string expression, object value, TokenKind expectedTokenKind) {
+ var lexer = new ExpressionTokenizer(expression);
+ var token1 = lexer.NextToken();
+ Assert.That(token1.Kind, Is.EqualTo(expectedTokenKind));
+ Assert.That(token1.Value, Is.EqualTo(value));
+
+ var token2 = lexer.NextToken();
+ Assert.That(token2.Kind, Is.EqualTo(TokenKind.Eof));
+ }
+
+ [Test]
+ public void LexerShouldProcesSequenceOfTokens() {
+ CheckTokenSequence("true false", TokenKind.True, TokenKind.False);
+ CheckTokenSequence("true toto false", TokenKind.True, TokenKind.Identifier, TokenKind.False);
+ }
+
+ [Test]
+ public void LexerShouldProcesSequenceOfTokens2() {
+ CheckTokenSequence("1+2*3", TokenKind.NumberLiteral, TokenKind.Plus, TokenKind.NumberLiteral, TokenKind.Mul, TokenKind.NumberLiteral);
+ }
+
+
+ private void CheckTokenSequence(string expression, params TokenKind[] tokenKinds) {
+ var lexer = new ExpressionTokenizer(expression);
+ foreach (var kind in tokenKinds) {
+ var token = lexer.NextToken();
+ Assert.That(token.Kind, Is.EqualTo(kind));
+ }
+
+ var token2 = lexer.NextToken();
+ Assert.That(token2.Kind, Is.EqualTo(TokenKind.Eof));
+ }
+ }
+}
diff --git a/src/Orchard.Tests.Modules/SimpleScriptingTests/ExpressionParserTests.cs b/src/Orchard.Tests.Modules/SimpleScriptingTests/ExpressionParserTests.cs
new file mode 100644
index 000000000..1de65577c
--- /dev/null
+++ b/src/Orchard.Tests.Modules/SimpleScriptingTests/ExpressionParserTests.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Diagnostics;
+using NUnit.Framework;
+using Orchard.Widgets.SimpleScripting;
+
+namespace Orchard.Tests.Modules.SimpleScriptingTests {
+ [TestFixture]
+ public class ExpressionParserTests {
+ [Test]
+ public void ParserShouldUnderstandConstantExpressions() {
+ var tree = new ExpressionParser("true").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.ContantExpression), true,
+ });
+ }
+
+ [Test]
+ public void ParserShouldUnderstandBinaryExpressions() {
+ var tree = new ExpressionParser("true+true").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Plus,
+ typeof(ExpressionTree.ContantExpression), true,
+ typeof(ExpressionTree.ContantExpression), true,
+ });
+ }
+
+ [Test]
+ public void ParserShouldUnderstandOperatorPrecedence() {
+ var tree = new ExpressionParser("1+2*3").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Plus,
+ typeof(ExpressionTree.ContantExpression), 1,
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Mul,
+ typeof(ExpressionTree.ContantExpression), 2,
+ typeof(ExpressionTree.ContantExpression), 3,
+ });
+ }
+
+ [Test]
+ public void ParserShouldUnderstandOperatorPrecedence2() {
+ var tree = new ExpressionParser("1*2+3").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Plus,
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Mul,
+ typeof(ExpressionTree.ContantExpression), 1,
+ typeof(ExpressionTree.ContantExpression), 2,
+ typeof(ExpressionTree.ContantExpression), 3,
+ });
+ }
+
+ [Test]
+ public void ParserShouldUnderstandOperatorPrecedence3() {
+ var tree = new ExpressionParser("not true or true").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Or,
+ typeof(ExpressionTree.UnaryExpression), TokenKind.Not,
+ typeof(ExpressionTree.ContantExpression), true,
+ typeof(ExpressionTree.ContantExpression), true,
+ });
+ }
+
+ [Test]
+ public void ParserShouldUnderstandOperatorPrecedence4() {
+ var tree = new ExpressionParser("not (true or true)").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.UnaryExpression), TokenKind.Not,
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Or,
+ typeof(ExpressionTree.ContantExpression), true,
+ typeof(ExpressionTree.ContantExpression), true,
+ });
+ }
+
+ [Test]
+ public void ParserShouldUnderstandParenthesis() {
+ var tree = new ExpressionParser("1*(2+3)").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Mul,
+ typeof(ExpressionTree.ContantExpression), 1,
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Plus,
+ typeof(ExpressionTree.ContantExpression), 2,
+ typeof(ExpressionTree.ContantExpression), 3,
+ });
+ }
+
+ [Test]
+ public void ParserShouldUnderstandComplexExpressions() {
+ var tree = new ExpressionParser("not 1 * (2 / 4 * 6 + (3))").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.UnaryExpression), TokenKind.Not,
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Mul,
+ typeof(ExpressionTree.ContantExpression), 1,
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Plus,
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Div,
+ typeof(ExpressionTree.ContantExpression), 2,
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Mul,
+ typeof(ExpressionTree.ContantExpression), 4,
+ typeof(ExpressionTree.ContantExpression), 6,
+ typeof(ExpressionTree.ContantExpression), 3,
+ });
+ }
+
+ [Test]
+ public void ParserShouldContainErrorExpressions() {
+ var tree = new ExpressionParser("1 + not 3").Parse();
+ CheckTree(tree, new object[] {
+ typeof(ExpressionTree.BinaryExpression), TokenKind.Plus,
+ typeof(ExpressionTree.ContantExpression), 1,
+ typeof(ExpressionTree.ErrorExpression),
+ });
+ }
+
+ private void CheckTree(ExpressionTree tree, object[] objects) {
+ Assert.That(tree, Is.Not.Null);
+ Assert.That(tree.Root, Is.Not.Null);
+
+ int index = 0;
+ CheckExpression(tree.Root, 0, objects, ref index);
+ Assert.That(index, Is.EqualTo(objects.Length));
+ }
+
+ private void CheckExpression(ExpressionTree.Expression expression, int indent, object[] objects, ref int index) {
+ var type = (Type)objects[index++];
+
+ Trace.WriteLine(string.Format("{0}: {1}{2} (Current: {3})", indent, new string(' ', indent * 2), type.Name, expression));
+
+ Assert.That(expression.GetType(), Is.EqualTo(type));
+
+ if (type == typeof(ExpressionTree.ContantExpression)) {
+ Assert.That((expression as ExpressionTree.ContantExpression).Value, Is.EqualTo(objects[index++]));
+ }
+ else if (type == typeof(ExpressionTree.BinaryExpression)) {
+ Assert.That((expression as ExpressionTree.BinaryExpression).Operator.Kind, Is.EqualTo(objects[index++]));
+ }
+ else if (type == typeof(ExpressionTree.UnaryExpression)) {
+ Assert.That((expression as ExpressionTree.UnaryExpression).Operator.Kind, Is.EqualTo(objects[index++]));
+ }
+
+ foreach(var child in expression.Children) {
+ CheckExpression(child, indent + 1, objects, ref index);
+ }
+ }
+ }
+}
diff --git a/src/Orchard.Tests.Modules/SimpleScriptingTests/SimpleScriptingTests.cs b/src/Orchard.Tests.Modules/SimpleScriptingTests/SimpleScriptingTests.cs
new file mode 100644
index 000000000..91956ad48
--- /dev/null
+++ b/src/Orchard.Tests.Modules/SimpleScriptingTests/SimpleScriptingTests.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Linq;
+using NUnit.Framework;
+using Orchard.Tests.Stubs;
+using Orchard.Widgets.Services;
+using Orchard.Widgets.SimpleScripting;
+
+namespace Orchard.Tests.Modules.SimpleScriptingTests {
+ [TestFixture]
+ public class SimpleScriptingTests {
+ [Test]
+ public void EngineUnderstandsPrimitiveValues() {
+ var engine = new ScriptingEngine(Enumerable.Empty(), new StubCacheManager());
+ Assert.That(engine.Matches("true"), Is.True);
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/Orchard.Widgets.csproj b/src/Orchard.Web/Modules/Orchard.Widgets/Orchard.Widgets.csproj
index d01a15371..a0812cc8a 100644
--- a/src/Orchard.Web/Modules/Orchard.Widgets/Orchard.Widgets.csproj
+++ b/src/Orchard.Web/Modules/Orchard.Widgets/Orchard.Widgets.csproj
@@ -75,6 +75,12 @@
+
+
+
+
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionLexer.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionLexer.cs
new file mode 100644
index 000000000..1bc301d19
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionLexer.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+
+namespace Orchard.Widgets.SimpleScripting {
+ public class ExpressionLexer {
+ private readonly ExpressionTokenizer _tokenizer;
+ private readonly List _tokens= new List();
+ private int _tokenIndex;
+
+ public ExpressionLexer(ExpressionTokenizer tokenizer) {
+ _tokenizer = tokenizer;
+ }
+
+ public ExpressionTokenizer.Token Token() {
+ if (_tokenIndex == _tokens.Count) {
+ _tokens.Add(_tokenizer.NextToken());
+ }
+ return _tokens[_tokenIndex];
+ }
+
+ public void NextToken() {
+ _tokenIndex++;
+ }
+
+ public Marker Mark() {
+ return new Marker(_tokens.Count);
+ }
+
+ public void Mark(Marker marker) {
+ _tokenIndex = marker.TokenIndex;
+ }
+
+ public struct Marker {
+ private readonly int _tokenIndex;
+
+ public Marker(int tokenIndex) {
+ _tokenIndex = tokenIndex;
+ }
+
+ public int TokenIndex {
+ get { return _tokenIndex; }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionParser.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionParser.cs
new file mode 100644
index 000000000..97af5edf8
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionParser.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+
+namespace Orchard.Widgets.SimpleScripting {
+ public class ExpressionParser {
+ private readonly string _expression;
+ private readonly ExpressionLexer _lexer;
+ private readonly List _errors = new List();
+
+ public ExpressionParser(string expression) {
+ _expression = expression;
+ _lexer = new ExpressionLexer(new ExpressionTokenizer(_expression));
+ }
+
+ public ExpressionTree Parse() {
+ var node = ParseExpression();
+ return new ExpressionTree { Root = node };
+ }
+
+ private ExpressionTree.Expression ParseExpression() {
+ return ParseKeywordLogicalExpression();
+ }
+
+ private ExpressionTree.Expression ParseKeywordLogicalExpression() {
+ return ParseKeywordOrExpression();
+ }
+
+ private ExpressionTree.Expression ParseKeywordOrExpression() {
+ var expr = ParseKeywordAndExpression();
+
+ var token = IsMatch(TokenKind.Or);
+ if (token != null)
+ {
+ var right = ParseKeywordOrExpression();
+
+ expr = new ExpressionTree.BinaryExpression(expr, token, right);
+ }
+
+ return expr;
+ }
+
+ private ExpressionTree.Expression ParseKeywordAndExpression() {
+ var expr = ParseKeywordNotExpression();
+
+ var token = IsMatch(TokenKind.And);
+ if (token != null) {
+ var right = ParseKeywordAndExpression();
+
+ expr = new ExpressionTree.BinaryExpression(expr, token, right);
+ }
+
+ return expr;
+ }
+
+ private ExpressionTree.Expression ParseKeywordNotExpression() {
+ var token = IsMatch(TokenKind.Not);
+ if (token != null) {
+ var expr = ParseKeywordNotExpression();
+
+ return new ExpressionTree.UnaryExpression(expr, token);
+ }
+
+ return ParseRelationalExpression();
+ }
+
+ private ExpressionTree.Expression ParseRelationalExpression() {
+ var expr = ParseAdditiveExpression();
+ //TODO
+ //var token = IsMatch(TokenKind.Not);
+ //if (token != null) {
+ return expr;
+ }
+
+ private ExpressionTree.Expression ParseAdditiveExpression() {
+ var expr = ParseMultiplicativeExpression();
+
+ var token = IsMatch(TokenKind.Plus, TokenKind.Minus);
+ if (token != null) {
+ var right = ParseAdditiveExpression();
+
+ expr = new ExpressionTree.BinaryExpression(expr, token, right);
+ }
+
+ return expr;
+ }
+
+ private ExpressionTree.Expression ParseMultiplicativeExpression() {
+ var expr = ParseUnaryExpression();
+
+ var token = IsMatch(TokenKind.Mul, TokenKind.Div);
+ if (token != null) {
+ var right = ParseMultiplicativeExpression();
+
+ expr = new ExpressionTree.BinaryExpression(expr, token, right);
+ }
+
+ return expr;
+ }
+
+ private ExpressionTree.Expression ParseUnaryExpression() {
+ //TODO
+ return ParsePrimaryExpression();
+ }
+
+ private ExpressionTree.Expression ParsePrimaryExpression() {
+ var token = _lexer.Token();
+ switch(_lexer.Token().Kind) {
+ case TokenKind.True:
+ case TokenKind.False:
+ case TokenKind.SingleQuotedStringLiteral:
+ case TokenKind.StringLiteral:
+ case TokenKind.NumberLiteral:
+ _lexer.NextToken();
+ return new ExpressionTree.ContantExpression(token);
+ case TokenKind.OpenParen:
+ return ParseParenthesizedExpression();
+ default:
+ _lexer.NextToken();
+ return new ExpressionTree.ErrorExpression(token,
+ string.Format("Unexptected token in primary expression ({0})", token));
+ }
+ }
+
+ private ExpressionTree.Expression ParseParenthesizedExpression() {
+ Match(TokenKind.OpenParen);
+ var expr = ParseExpression();
+ Match(TokenKind.CloseParen);
+ return expr;
+ }
+
+ private void Match(TokenKind kind) {
+ var token = _lexer.Token();
+ if (token.Kind == kind) {
+ _lexer.NextToken();
+ return;
+ }
+ AddError(token, string.Format("Expected token {0}", kind));
+ }
+
+ private ExpressionTokenizer.Token IsMatch(TokenKind kind) {
+ var token = _lexer.Token();
+ if (token.Kind == kind) {
+ _lexer.NextToken();
+ return token;
+ }
+ return null;
+ }
+
+ private ExpressionTokenizer.Token IsMatch(TokenKind kind, TokenKind kind2) {
+ var token = _lexer.Token();
+ if (token.Kind == kind || token.Kind == kind2) {
+ _lexer.NextToken();
+ return token;
+ }
+ return null;
+ }
+
+ private void AddError(ExpressionTokenizer.Token token, string message) {
+ _errors.Add(message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionTokenizer.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionTokenizer.cs
new file mode 100644
index 000000000..8287e9b3e
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionTokenizer.cs
@@ -0,0 +1,223 @@
+using System;
+using System.Text;
+
+namespace Orchard.Widgets.SimpleScripting {
+ public class ExpressionTokenizer {
+ private readonly string _expression;
+ private readonly StringBuilder _stringBuilder;
+ private int _index;
+
+ public ExpressionTokenizer(string expression) {
+ _expression = expression;
+ _stringBuilder = new StringBuilder();
+ }
+
+ public Token NextToken() {
+ if (Eof())
+ return CreateToken(TokenKind.Eof);
+
+ LexAgain:
+ char ch = Character();
+ switch (ch) {
+ case '(':
+ NextCharacter();
+ return CreateToken(TokenKind.OpenParen);
+ case ')':
+ NextCharacter();
+ return CreateToken(TokenKind.CloseParen);
+ case '+':
+ NextCharacter();
+ return CreateToken(TokenKind.Plus);
+ case '-':
+ NextCharacter();
+ return CreateToken(TokenKind.Minus);
+ case '*':
+ NextCharacter();
+ return CreateToken(TokenKind.Mul);
+ case '/':
+ NextCharacter();
+ return CreateToken(TokenKind.Div);
+ case '"':
+ return LexStringLiteral();
+ case '\'':
+ return LexSingleQuotedStringLiteral();
+ }
+
+ if (IsDigitCharacter(ch)) {
+ return LexInteger();
+ }
+ else if (IsIdentifierCharacter(ch)) {
+ return LexIdentifierOrKeyword();
+ }
+ else if (IsWhitespaceCharacter(ch)) {
+ NextCharacter();
+ goto LexAgain;
+ }
+
+ return CreateToken(TokenKind.Invalid, "Unrecognized character");
+ }
+
+ private Token LexIdentifierOrKeyword() {
+ _stringBuilder.Clear();
+
+ _stringBuilder.Append(Character());
+ while (true) {
+ NextCharacter();
+
+ if (!Eof() && (IsIdentifierCharacter(Character()) || IsDigitCharacter(Character()))) {
+ _stringBuilder.Append(Character());
+ }
+ else {
+ return CreateIdentiferOrKeyword(_stringBuilder.ToString());
+ }
+ }
+ }
+
+ private Token LexInteger() {
+ _stringBuilder.Clear();
+
+ _stringBuilder.Append(Character());
+ while (true) {
+ NextCharacter();
+
+ if (!Eof() && IsDigitCharacter(Character())) {
+ _stringBuilder.Append(Character());
+ }
+ else {
+ return CreateToken(TokenKind.NumberLiteral, Int32.Parse(_stringBuilder.ToString()));
+ }
+ }
+ }
+
+ private Token CreateIdentiferOrKeyword(string identifier) {
+ switch (identifier) {
+ case "true":
+ return CreateToken(TokenKind.True, true);
+ case "false":
+ return CreateToken(TokenKind.False, false);
+ case "or":
+ return CreateToken(TokenKind.Or, identifier);
+ case "and":
+ return CreateToken(TokenKind.And, identifier);
+ case "not":
+ return CreateToken(TokenKind.Not, identifier);
+ default:
+ return CreateToken(TokenKind.Identifier, identifier);
+ }
+ }
+
+ private bool IsWhitespaceCharacter(char character) {
+ return char.IsWhiteSpace(character);
+ }
+
+ private bool IsIdentifierCharacter(char ch) {
+ return
+ (ch >= 'a' && ch <= 'z') ||
+ (ch >= 'A' && ch <= 'Z') ||
+ (ch == '_');
+ }
+
+ private bool IsDigitCharacter(char ch) {
+ return ch >= '0' && ch <= '9';
+ }
+
+ private Token LexSingleQuotedStringLiteral() {
+ _stringBuilder.Clear();
+
+ while (true) {
+ NextCharacter();
+
+ if (Eof())
+ return CreateToken(TokenKind.Invalid, "Unterminated string literal");
+
+ // Termination
+ if (Character() == '\'') {
+ NextCharacter();
+ return CreateToken(TokenKind.SingleQuotedStringLiteral, _stringBuilder.ToString());
+ }
+ // backslash notation
+ else if (Character() == '\\') {
+ NextCharacter();
+
+ if (Eof())
+ return CreateToken(TokenKind.Invalid, "Unterminated string literal");
+
+ if (Character() == '\\') {
+ _stringBuilder.Append('\\');
+ }
+ else if (Character() == '\'') {
+ _stringBuilder.Append('\'');
+ }
+ else {
+ _stringBuilder.Append('\\');
+ _stringBuilder.Append(Character());
+ }
+ }
+ // Regular character in string
+ else {
+ _stringBuilder.Append(Character());
+ }
+ }
+ }
+
+ private Token LexStringLiteral() {
+ _stringBuilder.Clear();
+
+ while (true) {
+ NextCharacter();
+
+ if (Eof())
+ return CreateToken(TokenKind.Invalid, "Unterminated string literal");
+
+ // Termination
+ if (Character() == '"') {
+ NextCharacter();
+ return CreateToken(TokenKind.StringLiteral, _stringBuilder.ToString());
+ }
+ // backslash notation
+ else if (Character() == '\\') {
+ NextCharacter();
+
+ if (Eof())
+ return CreateToken(TokenKind.Invalid, "Unterminated string literal");
+
+ _stringBuilder.Append(Character());
+ }
+ // Regular character in string
+ else {
+ _stringBuilder.Append(Character());
+ }
+ }
+ }
+
+ private void NextCharacter() {
+ _index++;
+ }
+
+ private char Character() {
+ return _expression[_index];
+ }
+
+ private Token CreateToken(TokenKind kind, object value = null) {
+ return new Token {
+ Kind = kind,
+ Position = _index,
+ Value = value
+ };
+ }
+
+ private bool Eof() {
+ return (_index >= _expression.Length);
+ }
+
+ public class Token {
+ public TokenKind Kind { get; set; }
+ public int Position { get; set; }
+ public object Value { get; set; }
+
+ public override string ToString() {
+ return string.Format("{0} at position {1}", Value ?? Kind, Position);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionTree.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionTree.cs
new file mode 100644
index 000000000..d10af8bfb
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ExpressionTree.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Orchard.Widgets.SimpleScripting {
+ public class ExpressionTree {
+ public Expression Root { get; set; }
+
+ public interface IExpressionWithToken {
+ ExpressionTokenizer.Token Token { get; }
+ }
+
+ public class Expression {
+ public virtual IEnumerable Children {
+ get {
+ return Enumerable.Empty();
+ }
+ }
+
+ public override string ToString() {
+ var sb = new StringBuilder();
+ sb.Append(this.GetType().Name);
+ var ewt = (this as IExpressionWithToken);
+ if (ewt != null) {
+ sb.Append(" - ");
+ sb.Append(ewt.Token.Kind);
+ if (ewt.Token.Value != null) {
+ sb.Append(" - ");
+ sb.Append(ewt.Token.Value);
+ }
+ }
+ return sb.ToString();
+ }
+ }
+
+ public class ErrorExpression : Expression, IExpressionWithToken {
+ private readonly ExpressionTokenizer.Token _token;
+ private readonly string _message;
+
+ public ErrorExpression(ExpressionTokenizer.Token token, string message) {
+ _token = token;
+ _message = message;
+ }
+
+ public ExpressionTokenizer.Token Token {
+ get { return _token; }
+ }
+
+ public string Message {
+ get { return _message; }
+ }
+
+ public override string ToString() {
+ return string.Format("{0} - {1}", GetType().Name, Message);
+ }
+ }
+
+
+ public class ContantExpression : Expression, IExpressionWithToken {
+ private readonly ExpressionTokenizer.Token _token;
+
+ public ContantExpression(ExpressionTokenizer.Token token) {
+ _token = token;
+ }
+
+ public ExpressionTokenizer.Token Token {
+ get { return _token; }
+ }
+
+ public object Value { get { return _token.Value; } }
+ }
+
+ public class BinaryExpression : Expression, IExpressionWithToken {
+ private readonly Expression _left;
+ private readonly ExpressionTokenizer.Token _token;
+ private readonly Expression _right;
+
+ public BinaryExpression(Expression left, ExpressionTokenizer.Token token, Expression right) {
+ _left = left;
+ _token = token;
+ _right = right;
+ }
+
+ public ExpressionTokenizer.Token Token {
+ get { return _token; }
+ }
+
+ public ExpressionTokenizer.Token Operator {
+ get { return _token; }
+ }
+
+ public override IEnumerable Children {
+ get {
+ yield return _left;
+ yield return _right;
+ }
+ }
+ }
+
+ public class UnaryExpression : Expression, IExpressionWithToken {
+ private readonly Expression _expr;
+ private readonly ExpressionTokenizer.Token _token;
+
+ public UnaryExpression(Expression expr, ExpressionTokenizer.Token token) {
+ _expr = expr;
+ _token = token;
+ }
+
+ public ExpressionTokenizer.Token Token {
+ get { return _token; }
+ }
+
+
+ public ExpressionTokenizer.Token Operator {
+ get { return _token; }
+ }
+
+ public override IEnumerable Children {
+ get {
+ yield return _expr;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ScriptingManager.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ScriptingManager.cs
new file mode 100644
index 000000000..8bf16b9b4
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/ScriptingManager.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Orchard.Caching;
+using Orchard.Widgets.Services;
+
+namespace Orchard.Widgets.SimpleScripting {
+ public interface IScriptingEngine : IDependency {
+ bool Matches(string expression);
+ }
+
+ public class ScriptingEngine : IScriptingEngine {
+ private readonly IEnumerable _ruleProviders;
+ private readonly ICacheManager _cacheManager;
+
+ public ScriptingEngine(IEnumerable ruleProviders, ICacheManager cacheManager) {
+ _ruleProviders = ruleProviders;
+ _cacheManager = cacheManager;
+ }
+
+ public bool Matches(string expression) {
+
+ var expressionTree = _cacheManager.Get(expression, ctx =>
+ ParseExpression(expression));
+
+ object result = EvaluateExpression(expressionTree.Root);
+
+ return (bool)Convert.ChangeType(result, typeof (bool));
+ }
+
+ private ExpressionTree ParseExpression(string expression) {
+ return new ExpressionParser(expression).Parse();
+ }
+
+ private object EvaluateExpression(ExpressionTree.Expression root) {
+ throw new NotImplementedException();
+ }
+
+ private object Evaluate(string name, IEnumerable