FParsec and a delimiter based syntax

风流意气都作罢 提交于 2019-12-08 05:44:05

问题


I'm trying to use fparsec to parse a simple todo list language (the data from TaskPaper actually) as a simple parser combinator example. But I've run into a bug I can't seem to puzzle out. I'm new to parser combinators and FParsec seems to rely on me knowing Parsec, but I'm finding the parsec documentation inscrutable.

The rules of the task paper language are simple (I'm ignoring @tags for now)

  • Projects end with a ':'
  • Tasks are start with '-'
  • Any other line of text is a plain text note on either the project or task

So the string "Project 1:\nSome note\nProject 2:" should return from parseFile as [ProjectName("Project 1");NoteText("Some note");ProjectName("Project 2")], but instead, I get [ProjectName("Project 1");ProjectName("Some note\nProject 2")]

Below is my parser code.

open FParsec.Primitives
open FParsec.CharParsers
type ProjectAst = ProjectName of string
                    | TaskText of string
                    | NoteText of string

let asString (x:char list) :string =
    x
    |> List.map (fun y -> y.ToString())
    |> String.concat ""
let makeNote x = NoteText(asString x)
let parseProject =
    parse { let! s = many (noneOf ":\n\r\c")
            do! skipChar ':'
            return ProjectName( asString s ) }
let parseTask =
    parse { do! skipChar '-'
            let! s = many (noneOf "\n\r\c")
            return TaskText( asString s) }
let parseNote = many (noneOf "\n\r\c") |>> makeNote

let parseLine = parseTask <|> (attempt parseProject) <|> parseNote
let parseFile = sepBy parseLine (many1 whitespace)

Edited

The syntax is taken from Hogbay Software's TaskPaper application TaskPaper website Some examples of the syntax

    Project 1:
    Description of Project One
    -task for project 1
    -another task for project 1
    details for another task
    -final task

    Go to store:
    -buy eggs
    -buy milk

回答1:


I'm not super fluent in FParsec, but this one works:

let newline = pchar '\n'
let notNewLine = noneOf "\n"
let allTillEOL = manyChars notNewLine

let parseProject = 
    let r = manyCharsTill (noneOf ":\n") (pchar ':')
    r |>> ProjectName

let parseTask = 
    let r = skipChar '-' >>. allTillEOL
    r |>> TaskText

let parseNote = allTillEOL |>> NoteText

let parseLine = parseTask <|> attempt parseProject <|> parseNote
let parseFile = sepBy parseLine newline

let a = run parseFile "Project 1:\nSome note\nProject 2:\n-One Task"
match a with
| Success (a,b,c) -> printfn "%A" a
| Failure (a,b,c) -> printfn "failed: %s" a

prints out:

[ProjectName "Project 1"; NoteText "Some note"; ProjectName "Project 2"; TaskText "One Task"]

I'd test it against other examples.

BTW: the few times I've used FParsec I've preferred the combinator style over monadic style.



来源:https://stackoverflow.com/questions/4252441/fparsec-and-a-delimiter-based-syntax

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!