SwordScript - 使用C#开发脚本语言(六)表达式求值

782 阅读3分钟

本章节对应仓库

4.表达式求值 Github

抽象语法树求值

在继上章将算术表达式转换成抽象语法树后,本章开始便可以对抽象语法树进行求值操作。

抽象语法树的求值过程,是一颗树向下遍历调用的过程。因此,可以在抽象语法树的基类上定义如下方法:

public abstract object Evaluate(SwordEnvironment env);

SwordEnvironment是求值过程中的“环境”值,不过目前表达式计算暂时不涉及环境,因此只需要定义一个空的环境类即可。

namespace SwordScript;

public class SwordEnvironment
{
    
}

在定义完抽象方法后,需要逐个编写各个类的具象方法。

叶节点

字面量

字面量的求值方法很简单,直接将当前存储的值进行返回即可。

public override object Evaluate(SwordEnvironment env)
{
    return Value;
}

空类型字面量直接返回null即可。

标识符

标识符目前还没有相应的定义,这里作为空方法即可。到后面相关章节再进行定义:

public override object Evaluate(SwordEnvironment env)
{
    throw new System.NotImplementedException();
}

枝干节点

单目运算符

ASTUnaryExprNegative

public override object Evaluate(SwordEnvironment env)
{
    var value = Expr.Evaluate(env);
    if(value is long l)
    {
        return -l;
    }

    if(value is double d)
    {
        return -d;
    }

    throw new EvaluateException($"Cannot negate {value}, need a long or double value.");
}

ASTUnaryExprNot

public override object Evaluate(SwordEnvironment env)
{
    var value = Expr.Evaluate(env);
    if(value is bool b)
    {
        return !b;
    }

    throw new EvaluateException($"Cannot 'not' {value}, need a boolean value.");
}

对数值取负,对布尔取反即可。

双目运算符

双目运算符大多大同小异,这里只讲述几个有区别的求值。

首先是乘法,大多数的算术运算符与乘法运算符写法类似

public override object Evaluate(SwordEnvironment env)
{
    var left = Left.Evaluate(env);
    var right = Right.Evaluate(env);
    
    if (left is long l1)
    {
        if(right is long l2)
        {
            return l1 * l2;
        }
        if (right is double d2)
        {
            return l1 * d2;
        }
    }
    
    if (left is double d1)
    {
        if(right is long l2)
        {
            return d1 * l2;
        }
        if (right is double d2)
        {
            return d1 * d2;
        }
    }
    
    throw new EvaluateException($"Invalid multiply operation, cannot multiply '{left.GetType()}' and '{right.GetType()}'");
}

只需要判断并转换为数值类型,进行求值后返回即可。

然后是加法,加法相对乘法,多了字符串相加的功能:

public override object Evaluate(SwordEnvironment env)
{
    var left = Left.Evaluate(env);
    var right = Right.Evaluate(env);
    
    if (left is long l1)
    {
        if(right is long l2)
        {
            return l1 + l2;
        }
        if (right is double d2)
        {
            return l1 + d2;
        }
    }
    
    if (left is double d1)
    {
        if(right is long l2)
        {
            return d1 + l2;
        }
        if (right is double d2)
        {
            return d1 + d2;
        }
    }
    
    if(left is string s1)
    {
        if(right is string s2)
        {
            return s1 + s2;
        }
    }
    
    throw new EvaluateException($"Invalid plus operation, cannot plus '{left.GetType()}' and '{right.GetType()}'");
}

关于等号的判断,需要注意的是浮点与整形之间需要做特殊处理,非null的值可以调用Equals方法进行判断

public override object Evaluate(SwordEnvironment env)
{
    var left = Left.Evaluate(env);
    var right = Right.Evaluate(env);

    if (left is bool b1)
    {
        if (right is bool b2)
        {
            return b1 == b2;
        }
    }
    
    if(left is long l1)
    {
        if (right is double d2)
        {
            return l1 == d2;
        }
    }
    
    if(left is double d1)
    {
        if (right is long l2)
        {
            return d1 == l2;
        }
    }

    if (left is not null && right is not null)
    {
        return left.Equals(right);
    }

    return left == right;
}

and和or的判断则基本一致:

public override object Evaluate(SwordEnvironment env)
{
    var left = Left.Evaluate(env);
    var right = Right.Evaluate(env);
    
    if (left is bool b1)
    {
        if (right is bool b2)
        {
            return b1 && b2;
        }
    }
    
    throw new EvaluateException($"Invalid 'and' operation, cannot 'and' '{left.GetType()}' and '{right.GetType()}'");
}

单元测试

在所有求值函数写完后,便可以进行求值的测试。

[Test]
public void ExpressionEvaluateTest()
{
    Assert.AreEqual(1, ScriptParser.Expr.Parse(" 1 ").Evaluate(null));
    Assert.AreEqual(3, ScriptParser.Expr.Parse(" 1 + 2 ").Evaluate(null));
    Assert.AreEqual(3, ScriptParser.Expr.Parse(" 1 + 2 * 3 - 4").Evaluate(null));
    Assert.AreEqual(false, ScriptParser.Expr.Parse(" not true ").Evaluate(null));
    Assert.AreEqual(true, ScriptParser.Expr.Parse(" 1 + 1 == 2 and 2 + 2 == 4 ").Evaluate(null));
    Assert.AreEqual("abc", ScriptParser.Expr.Parse(" "ab" + "c" ").Evaluate(null));
    Assert.AreEqual(true, ScriptParser.Expr.Parse(" "Hello "+"World" == "Hello World" ").Evaluate(null));
    Assert.AreEqual(2.0, (double)ScriptParser.Expr.Parse(" 4 ^ 0.5 ").Evaluate(null), 0.00001);
    Assert.AreEqual(true , ScriptParser.Expr.Parse(" null == null ").Evaluate(null));
    Assert.AreEqual(false , ScriptParser.Expr.Parse(" null != null ").Evaluate(null));
    Assert.AreEqual(true , ScriptParser.Expr.Parse(" 0 == 0.0 ").Evaluate(null));
}

运行单元测试吗,全部通过。

结语

在构建好抽象语法树的情况下,求值便是一件较为简单的事情了。在下一章,将会进行环境的定义,以及赋值语句的解析。