提问人:lamg 提问时间:9/23/2023 最后编辑:lamg 更新时间:9/25/2023 访问量:61
如何避免使用 FParsec 回溯解析 SQL?
How to avoid backtracking parsing SQL with FParsec?
问:
我正在使用 FParsec 解析 SQL (Sqlite),并且在解析像 .由于用于识别标识符的解析器也识别关键字,因此我必须确保首先尝试解析关键字,并且只有在失败时才继续使用标识符。为此,我正在使用解析器组合器,但似乎我仍然无法逃避回溯。START_KEYWORD element_0, …, element_N END_KEYWORD
many1Till
下面是我当前如何解析子句的示例:ORDER BY
let sep1CommaEnd p endP =
let commaOrEnd = symbol S.Comma <|> followedBy endP
many1Till (p .>> commaOrEnd) (followedBy endP)
let orderBy =
parse {
do! keyword K.Order >>. keyword K.By
let! xs = sep1CommaEnd ident (keyword K.Asc <|> keyword K.Desc)
let! asc = (keyword K.Asc >>. preturn true <|> (keyword K.Desc >>. preturn false) <|> preturn true )
return { columns = xs; asc = asc }
}
此实现存在一些问题:
- 它在 中使用两次,这可能会导致回溯。
followedBy
sep1CommaEnd
- 它重复解析用于结束构造的关键字,这是低效的。
为了解决这些问题,我想出了创建一个解析器组合器的想法,它允许我:sepBy1Cont
- 在检测到用于结束的关键字后立即结束。
- 累积所有结果,包括用于结束的关键字的解析器返回的结果。
这是我的实现:
sepBy1Cont
type Either<'a, 'b> =
| Left of 'a
| Right of 'b
let sepBy1Cont (p: Parser<'a, unit>) (sepEnd: Parser<Either<'b, 'c>, unit>) =
let rec loop (acc: 'a list) =
parse {
let! x = p
let! d = sepEnd
match d with
| Left _ -> return! loop (x :: acc)
| Right next ->
let xs = List.rev (x :: acc)
return (xs, next)
}
loop []
我的问题是:
- 有没有更好的方法来解决我缺少的 FParsec 中的这个问题?
- 如果是一个好的解决方案,有没有更好的方法来实施它?我应该了解 FParsec 的内部结构以改进它吗?
sepBy1Cont
答:
1赞
RonaldSchlenker
9/24/2023
#1
回答您的第一个问题(一般而言):FParsec 中有许多内置字符串解析器可以/应该用于清晰度和性能。
给定一个应该解析的伪语法:
START_KEYWORD element_0, …, element_N END_KEYWORD
这里有一个想法:
let sigStart = "START"
let sigEnd = "END"
let pDemo : Parser<_,unit> =
let field = many1Chars2 letter (letter <|> digit <|> pchar '_')
let separator = pchar ',' .>> spaces
let fields = sepBy1 field separator
between (pstring sigStart .>> spaces1) (spaces1 >>. pstring sigEnd) fields
let run p s =
match runParserOnString p () "" s with
| Success (result, _, _) -> printfn "Success: %A" result
| Failure (msg, _, _) -> printfn "Failure: %s" msg
$"{sigStart} field_0, field_1, field_N {sigEnd}"
|> run pDemo
// Success: ["field_0"; "field_1"; "field_N"]
评论
0赞
lamg
9/24/2023
谢谢。我认为在那种特定情况下使用是个好主意。但是,对于更一般的 do 意味着结果不会确切地告诉我它是否以 or 结尾。因此,为了在 的结果之后继续解析,我将不得不再次解析。这意味着我最终会得到我试图解决的相同问题。between
SELECT col0, col1, … (FROM | ORDER BY)
between (keyword "SELECT") (keyword "FROM" <|> (keyword "ORDER" >>. keyword "BY")) columnParser
FROM
ORDER BY
between
FROM | ORDER BY
评论