在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() ,我们必须首先做两件事

  1. 为新的关键字var添加TokenGenerators,该关键字代表变量声明的开始以及=符号,用于声明变量声明的表达式的开始。
  2. 为变量声明添加一个名为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}中, 
  “ \\)”:{_在.parensClo​​se}中, 

  “ \\ =”:{_等于。} 

  ] 
  } 
  } 

第二个同样是微不足道的。 我们要做的就是添加一个简单的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)) 
  } 

接下来,我们开始讨论添加功能!

敬请关注!