【问题标题】:How to parse a list of tokens with FParsec如何使用 FParsec 解析令牌列表
【发布时间】:2014-05-16 23:28:51
【问题描述】:

我正在尝试使用 FParsec 解析令牌列表,其中每个令牌是文本块或标签 - 例如:

这是一个{type of test}测试,它{成功或失败}

这是解析器:

type Parser<'t> = Parser<'t, unit>

type Token =
| Text of string
| Tag of string

let escape fromString toString : Parser<_> =
    pstring fromString |>> (fun c -> toString)

let content : Parser<_> =
    let contentNormal = many1Satisfy (fun c -> c <> '{' && c <> '}')
    let openBraceEscaped = escape "{{" "{"
    let closeBraceEscaped = escape "}}" "}"
    let contentEscaped = openBraceEscaped <|> closeBraceEscaped
    stringsSepBy contentNormal contentEscaped

let ident : Parser<_> =
    let isIdentifierFirstChar c = isLetter c || c = '_'
    let isIdentifierChar c = isLetter c || isDigit c || c = '_'
    spaces >>. many1Satisfy2L isIdentifierFirstChar isIdentifierChar "identifier" .>> spaces

let text = content |>> Text

let tag = 
    ident |> between (skipString "{") (skipString "}")
    |>> Tag

let token = text <|> tag
let tokens = many token .>>. eof   

以下测试有效:

> run token "abc def" ;;
val it : ParserResult<Token,unit> = Success: Text "abc def"

> run token "{abc def}" ;;
val it : ParserResult<Token,unit> = Success: Tag "abc def"

但尝试运行令牌会导致异常:

> run tokens "{abc} def" ;;
System.InvalidOperationException: (Ln: 1, Col: 10): The combinator 'many' was 
    applied to a parser that succeeds without consuming input and without
    changing the parser state in any other way. (If no exception had been raised,
    the combinator likely would have entered an infinite loop.)

我已经检查过this stackoverflow question,但我没有尝试过任何工作。我什至添加了以下内容,但我得到了同样的例外:

let tokenFwd, tokenRef = createParserForwardedToRef<Token, unit>()
do tokenRef := choice [tag; text]
let readEndOfInput : Parser<unit, unit> = spaces >>. eof
let readExprs = many tokenFwd
let readExprsTillEnd = readExprs .>> readEndOfInput

run readExprsTillEnd "{abc} def"  // System.InvalidOperationException ... The combinator 'many' was applied  ...

我认为问题在于内容中的 stringsSepBy,但我想不出任何其他方法来获取带有转义项的字符串

任何帮助将不胜感激 - 我已经经历了几天,但无法弄清楚。

【问题讨论】:

    标签: f# fparsec


    【解决方案1】:

    stringsSepBy 接受零字符串,导致 token 接受空字符串,导致 many 抱怨。

    我将其更改为以下内容,以验证这是您需要处理的行。

    many1 (contentNormal <|> contentEscaped) |>> fun l -> String.concat "" l
    

    我也摆脱了 stringsSepBy contentNormal contentEscaped,因为这表示您需要在它们之间匹配 contentNormalscontentEscapeds。所以 a{{b}}c 是可以的,但是 {{b}}、{{b}}c 和 a{{b}} 会失败。

    【讨论】:

    • 谢谢!只是管道到 String.concat 工作完美(many1 (contentNormal &lt;|&gt; contentEscaped) |&gt;&gt; String.Concat),但我想看看我是否能让 notEmpty 也能正常工作
    【解决方案2】:

    notEmpty 可用于消耗输入。如果您不使用任何输入但让解析器成功,则解析器的“当前位置”不会向前移动,因此当执行此操作的语句位于 many 内时,它将进入无限循环,而不会出现该异常。 stringsSepBy 正在成功并解析零元素,如果它获得零元素,您可以使用 notEmpty 使其失败:

    stringsSepBy contentNormal contentEscaped |> notEmpty
    

    另外,我试图让您的完整示例进行解析,标签可以包含空格,因此您需要允许 ident 包含空格以匹配:

    let isIdentifierChar c = isLetter c || isDigit c || c = '_' || c = ' '
    

    另一个小的调整是只返回一个Token list 而不是Token list * unit 元组(uniteof 的结果):

    let tokens = many token .>> eof  
    

    【讨论】:

    • 感谢您的帮助!我想知道我是否正确编码了大括号转义 - 当我通过管道连接到 notEmpty 时,转义大括号仅在它不在字符串的开头或结尾时才有效。例如,"a{{b}}c" 会解析,但如果我删除 a 或 c,则会出现错误。有什么建议吗?
    • stringsSepBy 在这种情况下没有意义。如果您考虑一下,您实际上是在寻找许多可以是转义大括号或正常内容的字符串,而不是由转义大括号分隔的正常内容。所以jyoung的回答就是解决这个问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-04
    相关资源
    最近更新 更多