【发布时间】:2020-10-08 06:59:01
【问题描述】:
作为我自己的练习,我正在使用 FParsec 编写一个函数,该函数可以从(有限的)正则表达式形式的规范生成随机字符串。
例如
Input: ([Hh]ello ){1,3} world!?
Output: Hello hello world!
Input: (\d{1,2}\.){3}(\d{1,2})
Output: 38.12.29.05
我有很多工作,但我对后缀术语的想法有疑问(即解析器可能需要返回并修改输出,而不仅仅是 Char 流中的位置)。例如。 “a”与“a+”
这是我的域类型的缩减版:
type Count =
| ExactCount of int
| MinCount of int
| MaxCount of int
| RangeCount of int * int
type Term =
| CharLiteral of char
| Count of Term * Count
type RandExpSpec = Term list
所以输入ab 应该生成[CharLiteral 'a'; CharLiteral 'b'] 但ab+ 应该生成[CharLiteral 'a'; Count (CharLiteral 'b', MinCount 1)]。因此,这意味着,在流中遇到 Count 术语时,解析器需要回溯输出,以便将最后一个术语包装在另一个对象中。
现在,我不知道该怎么做。这是我当前的解析定义,它(大部分)确实有效,但效率很低:
let parseCharLiteral = choice [ letter; digit ] |>> CharLiteral
let rec parseTerm =
parse.Delay(fun () -> choice [ parseCharLiteral ])
and parseCount =
parseTerm
.>>. choice [ skipChar '*' >>% (MinCount 0)
skipChar '+' >>% (MinCount 1)
skipChar '?' >>% (RangeCount(0, 1)) ]
|>> Count
let parseTerms =
many ((attempt parseCount) <|> parseTerm) .>> eof
可以看到在parseCount我先调用parseTerm,然后解析实际计数信息。然后,在parseTerms 中,我每次都尝试parseCount 解析器,如果它不起作用,则通过 input 回溯。这是非常低效的,因为我实际上是对输入流中的几乎每个字符进行两次传递,以防万一它后面跟着一个计数修饰符。
有没有更有效的方法来做到这一点?我觉得我应该写一些更像:
let parseCharLiteral = choice [ letter; digit ] |>> CharLiteral
let rec parseTerm =
parse.Delay(fun () -> choice [ parseCharLiteral ] .>>. (attempt parseCount))
and parseCount =
choice [ skipChar '*' >>% (MinCount 0)
skipChar '+' >>% (MinCount 1)
skipChar '?' >>% (RangeCount(0, 1)) ]
|>> Count
let parseTerms =
many parseTerm .>> eof
但我无法完成这项工作,因为parseCount 需要包装parseTerm 返回的上一个术语。
【问题讨论】: