5

FParsec を使用して、独自の形式を記述する入力を解析しています。たとえば、次の入力を検討してください。

int,str,int:4,'hello',3

入力の最初の部分 (コロンの前) は、入力の 2 番目の部分の形式を記述します。この場合、形式はint, str,ですint。これは、実際のデータが指定された型の 3 つのカンマ区切りの値で構成されることを意味するため、結果は4, "hello", になり3ます。

FParsec でこのようなものを解析する最良の方法は何ですか?

以下に最善を尽くしましたが、満足していません。parseよりクリーンで、ステートフルでなく、モナドへの依存度が低い、これを行うためのより良い方法はありますか? これは UserState のよりスマートな管理にかかっていると思いますが、その方法がわかりません。ありがとう。

open FParsec

type State = { Formats : string[]; Index : int32 }
    with static member Default = { Formats = [||]; Index = 0 }

type Value =
    | Integer of int
    | String of string

let parseFormat : Parser<_, State> =
    parse {
        let! formats =
            sepBy
                (pstring "int" <|> pstring "str")
                (skipString ",")
                |>> Array.ofList
        do! updateUserState (fun state -> { state with Formats = formats })
    }

let parseValue format =
    match format with
        | "int" -> pint32 |>> Integer
        | "str" ->
            between
                (skipString "'")
                (skipString "'")
                (manySatisfy (fun c -> c <> '\''))
                    |>> String
        | _ -> failwith "Unexpected"

let parseValueByState =
    parse {
        let! state = getUserState
        let format = state.Formats.[state.Index]
        do! setUserState { state with Index = state.Index + 1}
        return! parseValue format
    }

let parseData =
    sepBy
        parseValueByState
        (skipString ",")

let parse =
    parseFormat
        >>. skipString ":"
        >>. parseData

[<EntryPoint>]
let main argv =
    let result = runParserOnString parse State.Default "" "int,str,int:4,'hello',3"
    printfn "%A" result
    0
4

3 に答える 3

3

@bytebusterは私を打ち負かしましたが、私はまだ解決策を投稿しています。このテクニックは @bytebuster に似ています。

興味深い質問をありがとう。

コンパイラでは、テキストを解析して AST にし、その上で型チェッカーを実行することが推奨される手法だと思います。この例では、型定義を解析して値のパーサーのセットを返すという、より単純な手法が考えられます。これらのパーサーは、文字列の残りの部分に適用されます。

open FParsec

type Value = 
  | Integer of int
  | String  of string

type ValueParser = Parser<Value, unit>

let parseIntValue : Parser<Value, unit> =
  pint32 |>> Integer

let parseStringValue : Parser<Value, unit> =
  between
    (skipChar '\'')
    (skipChar '\'')
    (manySatisfy (fun c -> c <> '\''))
    <?> "string"
    |>> String

let parseValueParser : Parser<ValueParser, unit> =
  choice 
    [
      skipString "int"  >>% parseIntValue
      skipString "str"  >>% parseStringValue
    ]

let parseValueParsers : Parser<ValueParser list, unit> =
    sepBy1
      parseValueParser
      (skipChar ',')

// Runs a list of parsers 'ps' separated by 'sep' parser
let sepByList (ps : Parser<'T, unit> list) (sep : Parser<unit, unit>) : Parser<'T list, unit> =
  let rec loop adjust ps =
    match ps with
    | []    -> preturn []
    | h::t  ->
      adjust h >>= fun v -> loop (fun pp -> sep >>. pp) t >>= fun vs -> preturn (v::vs)
  loop id ps

let parseLine : Parser<Value list, unit> =
  parseValueParsers .>> skipChar ':' >>= (fun vps -> sepByList vps (skipChar ',')) .>> eof

[<EntryPoint>]
let main argv = 
    let s = "int,str,int:4,'hello',3"

    let r = run parseLine s

    printfn "%A" r

    0

解析int,str,int:4,'hello',3するとSuccess: [Integer 4; String "hello";Integer 3].

解析int,str,str:4,'hello',3(正しくない) の結果:

Failure:
Error in Ln: 1 Col: 23
int,str,str:4,'hello',3
                      ^
Expecting: string
于 2015-04-19T17:17:24.320 に答える
1

@FuleSnabel の sepByList を次のように書き直して、理解を深めました。これは正しく見えますか?

let sepByList (parsers : Parser<'T, unit> list) (sep : Parser<unit, unit>) : Parser<'T list, unit> = 
    let rec loop adjust parsers =
        parse {
            match parsers with
                | [] -> return []
                | parser :: tail ->
                    let! value = adjust parser
                    let! values = loop (fun parser -> sep >>. parser) tail
                    return value :: values
        }
    loop id parsers
于 2015-04-20T04:10:55.623 に答える