本章节对应仓库
5.变量与环境 Github
环境
在开始定义变量前,脚本需要有一个存放变量的环境。上一章之中,我们定义了SwordEnvironment作为环境类,本章将对该类进行进一步的扩展。
存储变量
脚本语言中的变量与变量存储的值,基本可以看做 string - object 的键值对。因此,我们暂时先用字典来存储变量。
public class SwordEnvironment
{
private Dictionary<string, object> _variables = new Dictionary<string, object>();
public void SetVariable(string name, object value)
{
_variables[name] = value;
}
public object GetVariable(string name)
{
if(_variables.ContainsKey(name))
return _variables[name];
return null;
}
}
变量赋值语句
在有了存放变量的环境后,就可以开始定义变量赋值语句了。
用BNF表示变量赋值语句的语法如下:
assignment ::= <identifier> <"="> <expr> [";"]
赋值语句需要有代表变量的标识符,用于赋值的表达式,以及句末可选的分号。
先定义代表赋值语句的抽象语法树节点:
ASTBinaryExprAssignment
public class ASTBinaryExprAssignment : ASTBinaryExpr
{
public ASTBinaryExprAssignment(ASTNode left, ASTNode right) : base(left, right)
{
}
public override string ToString()
{
return $"{Left} = {Right};";
}
public override object Evaluate(SwordEnvironment env)
{
string variableName = ((ASTIdentifier)Left).Name;
env.SetVariable(variableName, Right.Evaluate(env));
return null;
}
}
由于我们的变量语法在设计上不允许连续赋值(如a = b = 1)
因此语句求值中不需要返回值。
接下来,补充对标识符节点的求值方法:
ASTIdentifier
public override object Evaluate(SwordEnvironment env)
{
return env.GetVariable(Name);
}
在Lexer中,加上分号与等号的词法解析
public static readonly Parser<string> Assign = PunctuationSymbol("=");
public static readonly Parser<string> Semicolon = PunctuationSymbol(";");
在ScriptParser中,加上变量赋值语句的语法解析式:
public static readonly Parser<ASTNode> Assignment =
from left in Identifier
from assign in Lexer.Assign
from right in Expr
from _ in Lexer.Semicolon.Optional()
select new ASTBinaryExprAssignment(left, right);
单元测试
[Test]
public void AssignmentTest()
{
Assert.AreEqual("a = 1;", ScriptParser.Assignment.Parse(" a = 1 ").ToString());
Assert.AreEqual("a = (1 + 1);", ScriptParser.Assignment.Parse(" a = 1 + 1; ").ToString());
Assert.AreEqual("a = (2 == b);", ScriptParser.Assignment.Parse(" a = 2 == b ").ToString());
Assert.Catch<ParseException>(() => ScriptParser.Assignment.Parse(" a + 1 = 1 "));
}
[Test]
public void AssignmentEvaluateTest()
{
var env = new SwordEnvironment();
ScriptParser.Assignment.Parse(" a = 1 ").Evaluate(env);
Assert.AreEqual(1, env.GetVariable("a"));
ScriptParser.Assignment.Parse(" a = 1 + 1 ").Evaluate(env);
Assert.AreEqual(2, env.GetVariable("a"));
Assert.AreEqual(null, env.GetVariable("b"));
ScriptParser.Assignment.Parse(" b = 2 ").Evaluate(env);
Assert.AreEqual(2, env.GetVariable("b"));
ScriptParser.Assignment.Parse(" c = a == b ").Evaluate(env);
Assert.AreEqual(true, env.GetVariable("c"));
运行单元测试,全部通过。
结语
变量的解析完成后,下一章将要进行流程语句的解析,包括if、else、while等。 本章的变量存储较为简单,其中直接存为object的方式可能会导致拆箱装箱等问题,这些问题留待后续章节进行优化。