Refactor/simplify interpreter

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-11-27 22:18:56 -08:00
parent 8fa1af1939
commit 98abc18658
4 changed files with 131 additions and 60 deletions

View File

@@ -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));
}

View File

@@ -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<AstNode> Children {
get {
yield return _expr;
}
get { yield return _operand; }
}
public override object Accept(AstVisitor visitor) {

View File

@@ -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<object, string, IList<object>> MethodInvocationCallback { get; set; }
}
public class EvaluationResult<T> {
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 "<null>";
return Value.ToString();
}
}
public class EvaluationResult : EvaluationResult<object> {
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";
}
}
}

View File

@@ -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<int, int, int> operation) {
public override object VisitError(ErrorAstNode node) {
return Error(node.Message);
}
private static EvaluationResult EvaluateArithmetic(EvaluationResult left, EvaluationResult right,
Func<EvaluationResult, EvaluationResult, EvaluationResult> 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<bool, bool, bool> operation) {
//TODO: Proper type conversion
private static EvaluationResult EvaluateLogical(EvaluationResult left, EvaluationResult right,
Func<EvaluationResult, EvaluationResult, EvaluationResult> 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<int> ConvertToInt(EvaluationResult value) {
if (value.Value is int)
return new EvaluationResult<int> { Value = (int)value.Value };
return new EvaluationResult<int>() { HasErrors = true, Value = 0 };
return Error(string.Format("Value '{0}' is not convertible to an integer.", value));
}
private EvaluationResult<bool> ConvertToBool(EvaluationResult value) {
if (value.Value is bool)
return new EvaluationResult<bool>() { 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<bool>() { Value = ((int)value.Value) != 0 };
return Error(string.Format("Value '{0}' is not convertible to a boolean.", value));
}
return new EvaluationResult<bool>() { 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 });
}
}
}