当前位置:网站首页>Swift RegexBuilder Vs. Raku Grammar
Swift RegexBuilder Vs. Raku Grammar
2022-07-30 20:31:00 【sxw2k】
Swift 5.7 新增了 RegexBuilder, 它能以一种声明式的方式来写正则表达式。然后, 我发现 RegexBuilder 和 Raku(即 Perl 6)中的 Grammar 在语义表达上有些类似, 这两种语言在构建正则表达式的时候, 都非常有表现力, 可读性也很不错, RegexBuilder 中的某些特性甚至比 Raku 的 Grammar 更好用, 所以花了一点点时间粗浅研究了其中的一部分内容。
匹配整个字符串
Swift 中使用 wholeMatch
匹配整个字符串:
import _StringProcessing
import RegexBuilder
let regex = Regex {
OneOrMore("a")
Capture {
OneOrMore(.digit)
}
}
let input = "aaa12"
if let match = input.wholeMatch(of: regex) {
let (_, digit) = match.output
print(digit)
}
其中 Regex {}
语法是使用 RegexBuilder DSL 构造一个正则表达式, OneOrMore
是一个结构体, 它接收一个或多个 CharacterClass, 相当于传统正则表达式中的量词 +
。上面声明的名为 regex 的正则表达式的意思是: 匹配一个或多个字符 a
, 然后是一个或多个数字, 并捕获这些数字。
Raku Grammar 的等价写法如下:
my $input = "aaa12";
my $match = (grammar :: { token TOP { a+ \d+ }}).parse($input);
say ~$match[0];
因为要解析的文本特别简单, 所以这里使用 grammar :: {}
结构声明了一个匿名的 Grammar。 这个匿名 Grammar 中声明一个一个名为 TOP
的 token, 它匹配一个或多个字符 a, 然后是一个或多个数字。
打印出所有的捕获
import _StringProcessing
import RegexBuilder
let word = OneOrMore(.word)
let space = ZeroOrMore(.whitespace)
let wordPattern = Regex {
space
Capture { word }
}
let input = "The quick brown fox jumps over the lazy dog"
for match in input.matches(of: wordPattern) {
let (_, word) = match.output
print(word)
}
和 Raku 一样, Swift 还可以把正则表达式保存在变量或常量中。上面的正则表达式捕获了所有的单词, 其中 word
常量的意思是匹配一个或多个字符, space
常量的意思是匹配零个或多个空格。wordPattern
也是一个正则表达式, 它由 space
和 word
这两个子正则表达式组合而成, 这和 Raku 的 Grammar 的思想是一样的。
上面的代码会打印出每一个单词:
The
quick
brown
fox
jumps
over
the
lazy
dog
使用 Raku Grammar 的等价写法如下:
grammar WordPattern {
token TOP { <word>+ % \s+ }
token word { (\w+) }
}
my $text = "The quick brown fox jumps over the lazy dog";
my $match = WordPattern.parse($text);
.say for $match<word>;
WordPattern 这个 Grammar 的意思是: 匹配一个或多个单词, 这些单词之间由一个或多个空格分隔。%
是 Raku 中的一个非常好用的正则表达式操作符, 对于匹配由一个或多个分隔符分隔的字符串特别方便:
'1,22,3,44' ~~ /[\d+]+ % ','/
'a,b;c.d;e' ~~ /\w+ % <[,;.]>/
解析行程数据
有一种行程数据, 其格式如下:
Russia
Vladivostok : 43.131621,131.923828 : 4
Ulan Ude : 51.841624,107.608101 : 2
Saint Petersburg : 59.939977,30.315785 : 10
Norway
Oslo : 59.914289,10.738739 : 2
Bergen : 60.388533,5.331856 : 4
Ukraine
Kiev : 50.456001,30.50384 : 3
Switzerland
Wengen : 46.608265,7.922065 : 3
Bern : 46.949076,7.448151 : 1
例如 Norway 表示国家, Oslo 和 Bergen 是目的地, 59.914289,10.738739 是逗号分割的经纬度, 最后的 2 和 4 是售票数。
使用 Swift 的 RegexBuilder 写出来大概是下面这样的(这里是从 Raku Gramamr 翻译成 Swift 的 RegexBuilder 语法):
import _StringProcessing
import RegexBuilder
let word = OneOrMore(.word)
let integer = Regex {
Optionally { "-" }
OneOrMore(.digit)
}
let num = Regex {
Optionally { "-" }
OneOrMore(.digit)
Optionally {
Regex {
"."
OneOrMore(.digit)
}
}
}
let name = Regex {
OneOrMore(.word)
ZeroOrMore {
Regex {
One(.whitespace)
OneOrMore(.word)
}
}
}
let destination = Regex {
OneOrMore(.whitespace)
name
OneOrMore(.whitespace)
":"
OneOrMore(.whitespace)
num
","
num
OneOrMore(.whitespace)
":"
OneOrMore(.whitespace)
integer
.anchorsMatchLineEndings()
}
let country = Regex {
name
.anchorsMatchLineEndings()
OneOrMore { destination }
}
let tripPattern = Regex {
Capture {
OneOrMore { country }
}
}
let text = """
Russia
Vladivostok : 43.131621,131.923828 : 4
Ulan Ude : 51.841624,107.608101 : 2
Saint Petersburg : 59.939977,30.315785 : 10
Norway
Oslo : 59.914289,10.738739 : 2
Bergen : 60.388533,5.331856 : 4
Ukraine
Kiev : 50.456001,30.50384 : 3
Switzerland
Wengen : 46.608265,7.922065 : 3
Bern : 46.949076,7.448151 : 1
"""
for match in text.matches(of: tripPattern) {
let (_, trip) = match.output
print(trip)
}
几乎等价的 Raku Grammar 写法如下:
my $input = q:to/END/;
Russia
Vladivostok : 43.131621,131.923828 : 4
Ulan Ude : 51.841624,107.608101 : 2
Saint Petersburg : 59.939977,30.315785 : 10
Norway
Oslo : 59.914289,10.738739 : 2
Bergen : 60.388533,5.331856 : 4
Ukraine
Kiev : 50.456001,30.50384 : 3
Switzerland
Wengen : 46.608265,7.922065 : 3
Bern : 46.949076,7.448151 : 1
END
grammar SalesExport {
token TOP { ^ <country>+ $ }
token country {
<name> \n
<destination>+
}
token destination {
\s+ <name> \s+ ':' \s+
<lat=.num> ',' <long=.num> \s+ ':' \s+
<sales=.integer> \n
}
token name { \w+ [ \s \w+ ]* }
token num { '-'? \d+ [\.\d+]? }
token integer { '-'? \d+ }
}
my $match = SalesExport.parse($input);
.Str.say for $match;
这个 Grammar 在以前的公众号文章中出现过, 这里不再多说。
解析交易数据
到现在为止, 你可能会说 Swift 的 RegexBuilder 平平无奇, 但是让我感到意外的是它支持 Foundation 框架中的正则表达式解析器。
例如, 在解析字符串中的日期、货币、数字等结构的时候, 就可以使用 Foundation 中的解析器:
import RegexBuilder
import Foundation
let fieldSeparator = /\s{2,}|\t/
let regex = Regex {
Capture { /CREDIT|DEBIT/ }
fieldSeparator
Capture { One(.date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt)) }
fieldSeparator
Capture {
OneOrMore {
NegativeLookahead { fieldSeparator }
CharacterClass.any
}
}
fieldSeparator
Capture { One(.localizedCurrency(code: "USD", locale: Locale(identifier: "en_US"))) }
}
// 这里的 Date 和 NSDecimal 不再是字符串类型了, 而是一个结构化的东西
// Regex<(Substring, Substring, Foundation.Date, Substring, __C.NSDecimal)>
let input = """
CREDIT 03/02/2022 Payroll from employer $200.23
CREDIT 03/03/2022 Suspect A $2,000,000.00
DEBIT 03/03/2022 Ted's Pet Rock Sanctuary $2,000,000.00
DEBIT 03/05/2022 Doug's Dugout Dogs $33.27
"""
for match in input.matches(of: regex) {
let (_, kind, date, description, amount) = match.output
print(kind, date, description, amount)
}
传统正则表达式语法和 RegexBuilder 语法可以混用(仅限于 Xcode, Docker 中这样用会报错)。上面的 .date
语法是 Foundation 中的日期解析器, .localizedCurrency
是 Foundation 中的货币解析器, 这里在 RegexBuilder 中引入 Foundation 框架中已经封装好的日期和货币解析器, 就很高级。 Raku 中还没有这样的封装, 但是我觉得应该也可以在核心 core 中实现。
几乎等价的 Raku Grammar 写法如下:
grammar TransactionGrammar {
token TOP { <transaction>+ %% \n* }
rule transaction { <payment> <date> <description> <cost> }
token payment { 'CREDIT' | 'DEBIT' }
token date { <digit-sequence>+ % '/' }
token description { [<-[\s]>+]+ % \s }
token cost { <currency-sign> <currency-number> }
token digit-sequence { \d+ }
token currency-sign { '$' }
token currency-number { <digit-sequence>+ % <[.,]> }
}
my $input = q:to/END/;
CREDIT 03/02/2022 Payroll from employer $200.23
CREDIT 03/03/2022 Suspect A $2,000,000.00
DEBIT 03/03/2022 Ted's Pet Rock Sanctuary $2,000,000.00
DEBIT 03/05/2022 Doug's Dugout Dogs $33.27
END
my $match = TransactionGrammar.parse($input);
.say for $match;
transform vs. action
当我发现 Swift 的 RegexBuilder 还支持 transform 的时候, 我再次感到高级。
Swift 的 RegexBuilder 还支持对匹配到的字符串进行转换, 其结果就是把字符串转换为结构化的东西。例如, 下面的 transfrom 闭包把捕获到的字符串转换成了枚举:
import Foundation
import RegexBuilder
enum TestStatus: String {
case started = "started"
case passed = "passed"
case failed = "failed"
}
let regex = Regex {
"Test Suite '"
Capture(OneOrMore(.word))
"' "
TryCapture {
ChoiceOf{
"started"
"passed"
"failed"
}
} transform: {
TestStatus(rawValue: String($0))
}
" at "
Capture(.iso8601(
timeZone: .current, includingFractionalSeconds: true, dateTimeSeparator: .space
))
Optionally(".")
} // Regex<(Substring, Substring, TestStatus, Foundation.Date)>
let testSuitTestInputs = [
"Test Suite 'RegexDSLTests' started at 2022-06-06 09:41:00.001",
"Test Suite 'RegexDSLTests' failed at 2022-06-06 09:41:00.001.",
"Test Suite 'RegexDSLTests' passed at 2022-06-06 09:41:00.001.",
]
for line in testSuitTestInputs {
if let match = line.wholeMatch(of: regex) {
let (_, name, status, dateTime) = match.output
print("Matched: \"\(name)\", \"\(status)\", \"\(dateTime)\"")
}
}
Raku Grammar 的对应写法是使用 Action 对象, 咱们下个月再写。
环境
Swift 支持 Linux/Mac/iOS/iPadOS, RegexBuilder 是 Swfit 5.7 引进的新特性, 上面未使用 Foundation 框架的 Swift 代码是跑在 Docker 中的, 使用了 nightly 版本的 Swift 构建:
docker pull swiftlang/swift:nightly-main-centos7
docker run -d swiftlang/swift:nightly-main-centos7
swift --version
Swift version 5.8-dev (LLVM b2416e1165ab97c, Swift 965a54f037cfa76)
Target: x86_64-unknown-linux-gnu
使用了 Foundation 框架的代码是使用 Xcode 中的 Playground 来运行的。
总结
Raku ️ Swift
边栏推荐
- Face-based Common Expression Recognition (2) - Data Acquisition and Arrangement
- [NISACTF 2022]下
- 推荐系统-排序层-模型(一):Embedding + MLP(多层感知机)模型【Deep Crossing模型:经典的Embedding+MLP模型结构】
- vlookup函数匹配不出来只显示公式的解决方法
- SQLyog注释 添加 撤销 快捷键
- MySQL_关于JSON数据的查询
- MySQL 高级(进阶) SQL 语句 (一)
- MySQL8重置root账户密码图文教程
- 7.联合索引(最左前缀原则)
- 【luogu P8031】Kućice(计算几何)
猜你喜欢
随机推荐
awk笔记
线性结构:栈和队列
推荐系统-排序层:排序层架构【用户、物品特征处理步骤】
明解C语言第六章习题
如何解决gedit 深色模式下高亮文本不可见?
To the operation of the int variable assignment is atom?
并发与并行的区别
PHP低代码开发引擎—表单设计
肖特基二极管厂家ASEMI带你认识电路中的三大重要元器件
明解C语言第七章习题
Mysql8创建用户以及赋权操作
MySQL BIGINT 数据类型
MySQL----多表查询
推荐系统:实时性【特征实时性:客户端实时特征(秒级,实时)、流处理平台(分钟级,近实时)、分布式批处理平台(小时/天级,非实时)】【模型实时性:在线学习、增量更新、全量更新】
Difference Between Concurrency and Parallelism
HJ85 最长回文子串
Office365无法打开word文档怎么办?Office365无法打开word文档的解决方法
基于Apache Doris的湖仓分析
@RequestParam使用
第04章 逻辑架构【1.MySQL架构篇】【MySQL高级】