在Swift中实现编程语言—第3部分:Lexer

这是“用Swift编写编程语言”教程系列的第三部分。请务必阅读 第2部分

Lexer的核心工作是分析我们解释器的文本输入并将其简化为Token的集合,这些Token仅仅是代表来自我们输入的特定子字符串的简单结构。

每个标记表示文本输入中的文本模式。 通常通过使用正则表达式来识别这种模式。 如果您不熟悉正则表达式,建议您继续阅读Paul Hudson的介绍,并在继续本教程之前在RegExr操场上玩些正则表达式。

我们首先为令牌声明一个枚举:

 枚举令牌{ 
  typealias Generator =(String)->令牌? 
 大小写op(String) 
 案例号(浮点数) 
 案例解析 
 案例解析 
 静态var生成器:[String:Generator] { 
 返回[ 
  “ \\ * | \\ / | \\ + | \\-| times | divided \\ sby | plus | minus”:{.op($ 0)}, 
“ \\-?([0-9] * \\。[0-9] + | [0-9] +)”:{.number(Float($ 0)!)},
  “ \\(”:{_在.parensOpen}中, 
  “ \\)”:{_在.parensClo​​se} 
  ] 
  } 
  } 

在这里,我们使用以下正则表达式匹配了四个不同的标记:

  • 运算符:“ \ * | \ / | \ + | \-”
  • 数字:“ \-?([0–9] * \。[0–9] + | [0–9] +)”
  • 空心括号:“ \(”
  • 右括号:“ \)”

如您所见,在Swift中实现时,正则表达式中的每个反斜杠都需要使用另一个反斜杠进行转义。 肯定会令人烦恼,但我们无法处理,对吗?

要观察的另一件有趣的事情是,我们采用了假设所有数字均为浮点数的方法。 这仅仅是保持我们的语言简单性的一种折衷。

现在,在我们继续实施Lexer之前,为String提供一些帮助器功能将有所帮助:

 公共扩展字符串{ 
 公共功能getPrefix(regex:String)->字符串?  { 
 让表达式=尝试!  NSRegularExpression(模式:“ ^ \(regex)”,选项:[]) 
 让范围= expression.rangeOfFirstMatch(在:自我,选项:[],范围:NSRange(位置:0,长度:self.utf16.count)) 
 如果range.location == 0 { 
  return(本身为NSString).substring(with:range) 
  } 
 返回零 
  } 
 公共变异函数trimLeadingWhitespace(){ 
 让我= startIndex 
 而我<endIndex { 
 保护CharacterSet.whitespacesAndNewlines.contains(self [i] .unicodeScalars.first!)else { 
 返回 
  } 
  self.remove(at:i) 
  } 
  } 
  } 

在这里,函数: func trimLeadingWhitespace()确实执行了您可能已经猜到的事情,它从String中删除了所有前导空格。 另一个, func getPrefix(regex: String) -> String? 返回与给定正则表达式匹配的子字符串,但前提是该子字符串是字符串( self )的前缀。

现在,我们终于可以为我们的语言实施词法分析了:

  Lexer类{ 
 让令牌:[令牌] 
 私有静态函数getNextPrefix(code:String)->(regex:String,prefix:String)?  { 
 让keyValue = Token.generators 
.first(其中:{正则表达式,生成器
  code.getPrefix(regex:regex)!=无 
  }) 
 保护正则表达式= keyValue?.key, 
  keyValue?.value!=无其他{ 
 返回零 
  } 
 返回(regex,code.getPrefix(regex:regex)!) 
  } 
 初始化(代码:字符串){ 
  var code =代码 
  code.trimLeadingWhitespace() 
  var令牌:[令牌] = [] 
 而让next = Lexer.getNextPrefix(code:code){ 
  let(正则表达式,前缀)= next 
 代码=字符串(代码[prefix.endIndex ...]) 
  code.trimLeadingWhitespace() 
 保护let generator = Token.generators [regex], 
让令牌=生成器(前缀)其他{
 致命错误() 
  } 
  tokens.append(令牌) 
  } 
  self.tokens =代币 
  } 
  } 

这就是为我们的语言编写Lexer的全部内容。 这就是我们正在做的所有事情:

首先,我们初始化一个空的令牌数组。

然后尝试将已知的正则表达式与工作代码字符串的前缀匹配,然后将该前缀的令牌表示形式添加到令牌列表中。

如果找到匹配项,我们将从工作代码中删除添加的前缀,否则,我们完成了,可以简单地将标记列表作为属性存储在lexer实例上。

在下一个教程中,我们将进入下一个阶段,即分析。

敬请关注!

PS别忘了鼓掌并记住,您可以在twitter(valdi101)上关注我,也可以在Medium上关注我,以获取有关将来教程的通知和讨论。