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

语言: CN / TW / HK

本章节对应仓库

4.表达式求值 Github

抽象语法树求值

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

抽象语法树的求值过程,是一颗树向下遍历调用的过程。因此,可以在抽象语法树的基类上定义如下方法: C# public abstract object Evaluate(SwordEnvironment env);

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

```C# namespace SwordScript;

public class SwordEnvironment {

} ```

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

叶节点

字面量

字面量的求值方法很简单,直接将当前存储的值进行返回即可。 C# public override object Evaluate(SwordEnvironment env) { return Value; }

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

标识符

标识符目前还没有相应的定义,这里作为空方法即可。到后面相关章节再进行定义: C# public override object Evaluate(SwordEnvironment env) { throw new System.NotImplementedException(); }

枝干节点

单目运算符

ASTUnaryExprNegative ```C# 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 ```C# 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.");

} ```

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

双目运算符

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

首先是乘法,大多数的算术运算符与乘法运算符写法类似 ```C# 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()}'");

} ```

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

然后是加法,加法相对乘法,多了字符串相加的功能: ```C# 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的判断则基本一致: ```C# 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()}'");

} ```

单元测试

在所有求值函数写完后,便可以进行求值的测试。 C# [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)); } 运行单元测试吗,全部通过。

结语

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