From 98abc1865838029dd92c8983bc50e04f8097cc7b Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Sat, 27 Nov 2010 22:18:56 -0800 Subject: [PATCH] Refactor/simplify interpreter --HG-- branch : dev --- .../SimpleScripting/EvaluatorTests.cs | 19 +++- .../SimpleScripting/Ast/UnaryAstNode.cs | 21 ++-- .../SimpleScripting/Compiler/Interpreter.cs | 48 +++++++- .../Compiler/InterpreterVisitor.cs | 103 +++++++++++------- 4 files changed, 131 insertions(+), 60 deletions(-) diff --git a/src/Orchard.Tests.Modules/SimpleScripting/EvaluatorTests.cs b/src/Orchard.Tests.Modules/SimpleScripting/EvaluatorTests.cs index abc162ed4..e7da077a0 100644 --- a/src/Orchard.Tests.Modules/SimpleScripting/EvaluatorTests.cs +++ b/src/Orchard.Tests.Modules/SimpleScripting/EvaluatorTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NUnit.Framework; using Orchard.Widgets.SimpleScripting; using Orchard.Widgets.SimpleScripting.Compiler; @@ -9,14 +10,28 @@ namespace Orchard.Tests.Modules.SimpleScriptingTests { [Test] public void EvaluateSimpleConstant() { var result = EvaluateSimpleExpression("true and true"); - Assert.That(result.HasErrors, Is.False); + 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); + Trace.WriteLine(string.Format("Evaluation error: {0}", result.Error.Message)); + } + + [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.HasErrors, Is.False); + Assert.That(result.IsError, Is.False); Assert.That(result.Value, Is.EqualTo(4)); } diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Ast/UnaryAstNode.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Ast/UnaryAstNode.cs index 0de0f57b7..7f9f4b756 100644 --- a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Ast/UnaryAstNode.cs +++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Ast/UnaryAstNode.cs @@ -3,27 +3,20 @@ using Orchard.Widgets.SimpleScripting.Compiler; namespace Orchard.Widgets.SimpleScripting.Ast { public class UnaryAstNode : AstNode, IAstNodeWithToken { - private readonly AstNode _expr; + private readonly AstNode _operand; private readonly Token _token; - public UnaryAstNode(AstNode expr, Token token) { - _expr = expr; + public UnaryAstNode(AstNode operand, Token token) { + _operand = operand; _token = token; } - public Token Token { - get { return _token; } - } - - - public Token Operator { - get { return _token; } - } + public Token Token { get { return _token; } } + public Token Operator { get { return _token; } } + public AstNode Operand { get { return _operand; } } public override IEnumerable Children { - get { - yield return _expr; - } + get { yield return _operand; } } public override object Accept(AstVisitor visitor) { diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Compiler/Interpreter.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Compiler/Interpreter.cs index 0ffd901bc..2f1cb40df 100644 --- a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Compiler/Interpreter.cs +++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Compiler/Interpreter.cs @@ -4,22 +4,58 @@ using Orchard.Widgets.SimpleScripting.Ast; namespace Orchard.Widgets.SimpleScripting.Compiler { public class Interpreter { + private readonly InterpreterVisitor _interpreterVisitor = new InterpreterVisitor(); + public EvaluationResult Evalutate(EvaluationContext context) { - return new InterpreterVisitor(context).Evaluate(); + return _interpreterVisitor.Evaluate(context); } } public class EvaluationContext { public AbstractSyntaxTree Tree { get; set; } public Func> MethodInvocationCallback { get; set; } - } - public class EvaluationResult { - public bool HasErrors { get; set; } - public T Value { get; set; } + public class EvaluationResult { + private readonly object _value; + + public EvaluationResult(object value) { + _value = value; + } + + public object Value { get { return _value; } } + + public bool IsError { get { return Value is Error; } } + public bool IsNil { get { return Value is Nil; } } + public bool IsNull { get { return Value == null; } } + public bool IsBool { get { return Value is bool; } } + public bool IsInt32 { get { return Value is int; } } + public bool IsString { get { return Value is string; } } + + public Error Error { get { return (Error)Value; } } + public bool BoolValue { get { return (bool)Value; } } + public int Int32Value { get { return (int)Value; } } + public string StringValue { get { return (string)Value; } } + + public override string ToString() { + if (IsNull) + return ""; + + return Value.ToString(); + } } - public class EvaluationResult : EvaluationResult { + public class Error { + public string Message { get; set; } + + public override string ToString() { + return string.Format("Error: {0}", Message); + } + } + + public class Nil { + public override string ToString() { + return "nil"; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Compiler/InterpreterVisitor.cs b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Compiler/InterpreterVisitor.cs index 828fb93c7..0a12d61ff 100644 --- a/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Compiler/InterpreterVisitor.cs +++ b/src/Orchard.Web/Modules/Orchard.Widgets/SimpleScripting/Compiler/InterpreterVisitor.cs @@ -3,85 +3,112 @@ using Orchard.Widgets.SimpleScripting.Ast; namespace Orchard.Widgets.SimpleScripting.Compiler { public class InterpreterVisitor : AstVisitor { - private readonly EvaluationContext _context; - - public InterpreterVisitor(EvaluationContext context) { - _context = context; + public EvaluationResult Evaluate(EvaluationContext context) { + return Evaluate(context.Tree.Root); } - public EvaluationResult Evaluate() { - return Evaluate(_context.Tree.Root); - } - - public EvaluationResult Evaluate(AstNode node) { + private EvaluationResult Evaluate(AstNode node) { return (EvaluationResult)this.Visit(node); } public override object VisitConstant(ConstantAstNode node) { - return new EvaluationResult { Value = node.Value }; + return Result(node.Value); + } + + public override object VisitUnary(UnaryAstNode node) { + var operandValue = Evaluate(node.Operand); + if (operandValue.IsError) + return operandValue; + + var operandBoolValue = ConvertToBool(operandValue); + if (operandBoolValue.IsError) + return operandBoolValue; + + return Result(!operandBoolValue.BoolValue); } public override object VisitBinary(BinaryAstNode node) { var left = Evaluate(node.Left); - if (left.HasErrors) + if (left.IsError) return left; var right = Evaluate(node.Right); - if (right.HasErrors) + if (right.IsError) return right; switch (node.Token.Kind) { case TokenKind.Plus: - return EvaluateArithmetic(left, right, (a, b) => a + b); + return EvaluateArithmetic(left, right, (a, b) => Result(a.Int32Value + b.Int32Value)); case TokenKind.Minus: - return EvaluateArithmetic(left, right, (a, b) => a - b); + return EvaluateArithmetic(left, right, (a, b) => Result(a.Int32Value - b.Int32Value)); case TokenKind.Mul: - return EvaluateArithmetic(left, right, (a, b) => a * b); + return EvaluateArithmetic(left, right, (a, b) => Result(a.Int32Value * b.Int32Value)); case TokenKind.Div: - //TODO: divide by zero? - return EvaluateArithmetic(left, right, (a, b) => a / b); + 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) => a && b); + return EvaluateLogical(left, right, (a, b) => Result(a.BoolValue && b.BoolValue)); case TokenKind.Or: - return EvaluateLogical(left, right, (a, b) => a || b); - + return EvaluateLogical(left, right, (a, b) => Result(a.BoolValue || b.BoolValue)); + default: + throw new InvalidOperationException(string.Format("Internal error: binary expression {0} is not supported.", node.Token)); } - - return new EvaluationResult {HasErrors = true}; } - private EvaluationResult EvaluateArithmetic(EvaluationResult left, EvaluationResult right, Func operation) { + public override object VisitError(ErrorAstNode node) { + return Error(node.Message); + } + + private static EvaluationResult EvaluateArithmetic(EvaluationResult left, EvaluationResult right, + Func operation) { //TODO: Proper type conversion var leftValue = ConvertToInt(left); + if (leftValue.IsError) + return leftValue; + var rightValue = ConvertToInt(right); + if (rightValue.IsError) + return rightValue; - return new EvaluationResult { Value = operation(leftValue.Value, rightValue.Value) }; + return operation(leftValue, rightValue); } - private EvaluationResult EvaluateLogical(EvaluationResult left, EvaluationResult right, Func operation) { - //TODO: Proper type conversion + private static EvaluationResult EvaluateLogical(EvaluationResult left, EvaluationResult right, + Func operation) { var leftValue = ConvertToBool(left); + if (leftValue.IsError) + return leftValue; + var rightValue = ConvertToBool(right); + if (rightValue.IsError) + return rightValue; - return new EvaluationResult { Value = operation(leftValue.Value, rightValue.Value) }; + return operation(leftValue, rightValue); } + private static EvaluationResult ConvertToInt(EvaluationResult value) { + //TODO: Proper type conversion + if (value.IsInt32) + return value; - private EvaluationResult ConvertToInt(EvaluationResult value) { - if (value.Value is int) - return new EvaluationResult { Value = (int)value.Value }; - - return new EvaluationResult() { HasErrors = true, Value = 0 }; + return Error(string.Format("Value '{0}' is not convertible to an integer.", value)); } - private EvaluationResult ConvertToBool(EvaluationResult value) { - if (value.Value is bool) - return new EvaluationResult() { Value = (bool)value.Value }; + private static EvaluationResult ConvertToBool(EvaluationResult value) { + //TODO: Proper type conversion + if (value.IsBool) + return value; - if (value.Value is int) - return new EvaluationResult() { Value = ((int)value.Value) != 0 }; + return Error(string.Format("Value '{0}' is not convertible to a boolean.", value)); + } - return new EvaluationResult() { HasErrors = true, Value = false }; + private static EvaluationResult Result(object value) { + if (value is EvaluationResult) + throw new InvalidOperationException("Internal error: value cannot be an evaluation result."); + return new EvaluationResult(value); + } + + private static EvaluationResult Error(string message) { + return new EvaluationResult(new Error { Message = message }); } } } \ No newline at end of file