let terminators = [ ['\r'; '\n']; ['\r']; ['\n'] ]
顺序是重要的-如果我们先找到“\r\n”,那么我们希望跳过2个字符(这样我们就不会将下一个“\n”字符作为下一个终止符)。不幸的是
“跳过2个字符”
mapi
函数,它为每个元素调用函数。
使用递归函数的直接实现可以如下所示:
let input = "aaa\nbbb\r\nccc" |> List.ofSeq
// Returns Some() if input starts with token (and the returned
// value is the rest of the input with the starting token removed)
let rec equalStart input token =
match input, token with
| _, [] -> Some(input) // End of recursion
| a::input, b::token when a = b -> equalStart input token // Recursive call
| _ -> None // Mismatch
// Counts the number of lines...
let rec countLines count input =
// Pick first terminator that matches with the beginning of the input (if any)
let term = terminators |> List.tryPick (equalStart input)
match term, input with
| None, _::input -> countLines count input // Nothing found - continue
| None, [] -> count + 1 // At the end, add the last line & return
| Some(rest), _ -> countLines (count + 1) rest // Found - add line & continue
如果您使用的是某个解析器组合器库(例如
FParsec
),然后您可以使用内置的解析器来处理大多数事情。实际上我没有尝试这个,但这里有一个粗略的草图-您可以将终止符列表存储为字符串列表,并为每个字符串生成解析器:
let terminators = [ "\r\n"; "\r"; "\n" ]
let parsers = [ for t in terminators ->
parser { let! _ = pstring t
return 1 } ] // Return '1' because we found next line
这将为您提供一个解析器列表,在结尾有终止符时返回1—现在您可以使用
<|>
(或combinator),然后运行组合解析器。如果失败,您可以跳过第一个字符(将其与另一个解析器结合起来)并递归地继续。唯一的问题是解析器组合符通常返回所有可能的派生(“\r\n”可以解释为两个换行符..),因此您需要只得到第一个结果。。。
(从你的问题来看,不清楚你是否真的想使用解析器组合器库,所以我没有详细说明这个主题-如果你感兴趣,你可以问更多细节…)