本章节对应仓库
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));
}
运行单元测试吗,全部通过。
结语
在构建好抽象语法树的情况下,求值便是一件较为简单的事情了。在下一章,将会进行环境的定义,以及赋值语句的解析。