mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Refactor/simplify interpreter
--HG-- branch : dev
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user