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 args) { + var ruleContext = new RuleContext { FunctionName = name, Arguments = args.ToArray() }; + + foreach (var ruleProvider in _ruleProviders) { + ruleProvider.Process(ruleContext); + } + + return ruleContext.Result; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/TokenKind.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/TokenKind.cs new file mode 100644 index 000000000..83ddff262 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/TokenKind.cs @@ -0,0 +1,21 @@ +namespace Orchard.Widgets.SimpleScripting { + public enum TokenKind { + Eof, + OpenParen, + CloseParen, + StringLiteral, + SingleQuotedStringLiteral, + NumberLiteral, + Plus, + Minus, + Mul, + Div, + True, + False, + Identifier, + And, + Or, + Not, + Invalid + } +} \ No newline at end of file