在Swift中实现编程语言—第7部分:声明变量
注意:这是“用Swift编写编程语言”教程系列的第七部分。一定要检查 以前的内容 。
上一次我们完成编写用于解析变量名称的功能,并使用了一个预先用变量初始化的全局字典来测试我们的代码。 因此,实现我们的语言自然而然的下一步就是增加声明变量的能力。
到目前为止,我们仅实现了表达式解析,例如,我们可以在单个语句中解析标识符,运算符和数字( x + 5 * 6
),并且缺乏解析多个表达式的能力。
表达式解析是从解析器的parse()
方法开始的。 我们想要将该方法的名称更改为parseExpression()
,因为我们仍然希望parse()
代表解释器的入口点。
因此,在解析器中,我们要替换几行:
func parseExpression()throws- > Node { //是:func parse() throws- > Node
...
}
和:
func parseParens()引发->节点{
...
let expressionNode = try parseExpression()//是:try parse()
...
}
这个想法是让新的parse方法支持两种语句类型,分别由parseExpresssion()
方法和parseVariableDeclaration()
方法表示。 后者我们尚未实施。
在准备实现parseVariableDeclaration()
,我们必须首先做两件事
- 为新的关键字
var
添加TokenGenerators,该关键字代表变量声明的开始以及=
符号,用于声明变量声明的表达式的开始。 - 为变量声明添加一个名为VariableDeclaration的节点
首先,我们导航到Token枚举并添加以下内容:
枚举令牌{
typealias Generator =(String)->令牌?
案例操作员(操作员)
案例号(浮点数)
案例标识符(字符串)
案例解析
案例解析
案例 `var`
大小写 相等
静态var生成器:[String:Generator] {
返回[
“ \\ * | \\ / | \\ + | \\-”:{.op(Operator(rawValue:$ 0)!)},
“ \\-?([0-9] * \\。[0-9] + | [0-9] +)”:{.number(Float($ 0)!)},
“ [[a-zA-Z _ $] [a-zA-Z_ $ 0-9] *”:{
Guard $ 0!=“ var” else {return .var}
返回.identifier($ 0)
},
“ \\(”:{_在.parensOpen}中,
“ \\)”:{_在.parensClose}中,
“ \\ =”:{_等于。}
]
}
}
第二个同样是微不足道的。 我们要做的就是添加一个简单的struct
:
struct VariableDeclaration:节点{
命名:字符串
let值:节点
func interpret()throws-> Float {
让val =试试value.interpret()
标识符[名称] = val
返回值
}
}
这里的interpret()
方法很简单,我们要做的就是将=
符号右侧的值提取为val
将其添加到我们的全局变量dict, identifiers
,最后返回提取的值。
有了VariableDeclaration结构后,实现parseVariableDeclaration()
相当简单,在Parser类中,我们只需添加:
func parseVariableDeclaration()引发->节点{
警卫队.var = popToken()else {
引发Error.expected(“ \” var \“在变量声明中”)
}
警卫队让.identifier(name)= popToken()else {
抛出Error.expectedIdentifier
}
警卫队.equals = popToken()else {
抛出Error.expected(“ =”)
}
让expression =试试parseExpression()
返回VariableDeclaration(name:名称,value:表达式)
}
有了parseVariableDeclaration()
方法,我们现在可以开始实现新的parse()
方法(记住,我们parseExpression()
旧的parse()
重命名为parseExpression()
)。
请注意,通过增加对新语句类型(如声明变量)的支持,可以更改一些内容。 一方面,我们的代码现在开始看起来更像是一个节点列表,而不是一棵带有单个根节点的树。 就像变量声明列表和表达式一样。
但是我们仍然希望我们的parse()
方法返回单个Node。 为了弥补这一点,我们将围绕多个节点创建一个包装器节点,我们将其称为块:
结构块:节点{
让节点:[节点]
func interpret()throws-> Float {
用于节点[0 .. <(nodes.endIndex-1)]中的行{
尝试line.interpret()
}
守卫let last = nodes.last else {
抛出Parser.Error.expectedExpression
}
返回尝试last.interpret()
}
}
注意,我们块的interpret()
方法的返回值,我们正在将整个块解释为与其最后一个节点相同的值。 这是有道理的,因为在返回此值之前,请确保我们解释了所有先前的Node。
我们的实现的一个有趣的副作用是,如果我们以一个变量声明结尾一个Block,它将被解释为变量声明值的值。 这当然是出乎意料的,但我发现Block节点实现的简单性弥补了这一点。
现在我们可以使用parse()
方法了!
func parse()抛出->节点{
var节点:[Node] = []
而canPop {
让令牌= peek()
切换令牌{
案例.var:
让声明=尝试parseVariableDeclaration()
nodes.append(声明)
默认:
让expression =试试parseExpression()
nodes.append(表达式)
}
}
返回块(节点:节点)
}
这非常简单,如果一条语句以我们的新关键字var
开头,我们将尝试解析一个variableDeclaration,否则,我们尝试解析一个表达式。
而已! 现在,我们可以使用我们的语言声明变量了。
var code =“””
var x = 5
var y = 3
var z = x * y
50 + z
“””
let令牌= Lexer(代码:代码).tokens
做{
让节点=尝试解析器(令牌:令牌).parse()
print(node.interpret()== 65)//是
} {
打印((错误为?Parser.Error))
}
接下来,我们开始讨论添加功能!
敬请关注!