在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}中,
“ \\)”:{_在.parensClose}
]
}
}
在这里,我们使用以下正则表达式匹配了四个不同的标记:
- 运算符:“ \ * | \ / | \ + | \-”
- 数字:“ \-?([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上关注我,以获取有关将来教程的通知和讨论。