F# 创建对第一个解析器缩进敏感的sepBy解析器组合器

F# 创建对第一个解析器缩进敏感的sepBy解析器组合器,f#,indentation,fparsec,F#,Indentation,Fparsec,通过FParsec并使用它(它的代码很短,已经复制到问题的末尾),我试图设计一个类似于sebby的解析器,它对传入参数的第一个解析器的缩进很敏感。通常,如果我给出:indentedsebby(pstring“Example”)(pchar.),我希望这种类型的程序是可以接受的: Example .Example .Example.Example .Example.Example.Example .Example 但不是这个: Example .Example .Example.

通过FParsec并使用它(它的代码很短,已经复制到问题的末尾),我试图设计一个类似于sebby的解析器,它对传入参数的第一个解析器的缩进很敏感。通常,如果我给出:
indentedsebby(pstring“Example”)(pchar.)
,我希望这种类型的程序是可以接受的:

Example
  .Example
  .Example.Example
  .Example.Example.Example
  .Example
但不是这个:

Example
.Example
.Example.Example
.Example
因此,第一个位置(以及第一个解析器)设置其余部分的缩进。为此,我尝试了使用FParsec的默认sebby解析器对缩进库进行简单重写,得到如下结果:

open FParsec
open IndentParsec

let indentSepBy p sep =
    parse {
        let! pos = getPosition
        return! sepBy (greater pos p) (greater pos sep)
    }

let test = indentSepBy (pstring "Example") (pchar '.')
let text = "Example.Example" (*Simple for start*)
应用此方法,我得到以下错误消息(来自FParsec):

如果我移除缩进相关的解析器

let indentSepBy p sep = (*so it's just trivially equivalent to the sepBy parser*)
    parse {
        let! pos = getPosition
        return! sepBy p sep
    }
问题不再出现,结果是我们所期望的。因此,我不明白是哪个参数导致了这个错误。这似乎很可能是缩进库中的一个问题,但我无法解决它。。。以下是我所讨论的lib,我将其缩短为要点:

open FParsec

module IndentParser =
    type Indentation =
        | Fail
        | Any
        | Greater of Position
        | Exact of Position
        | AtLeast of Position
        | StartIndent of Position
        member this.Position =
            match this with
            | Any
            | Fail -> None
            | Greater p -> Some p
            | Exact p -> Some p
            | AtLeast p -> Some p
            | StartIndent p -> Some p

    type IndentState<'T> = { Indent: Indentation; UserState: 'T }
    type IndentParser<'T, 'UserState> = Parser<'T, IndentState<'UserState>>

    let indentState u = { Indent = Any; UserState = u }
    let runParser p u s = runParserOnString p (indentState u) "" s

    let runParserOnFile p u path =
        runParserOnFile p (indentState u) path System.Text.Encoding.UTF8

    let getIndentation: IndentParser<_, _> =
        fun stream ->
            match stream.UserState with
            | { Indent = i } -> Reply i

    let putIndentation newi: IndentParser<unit, _> =
        fun stream ->
            stream.UserState <- { stream.UserState with Indent = newi }
            Reply(Unchecked.defaultof<unit>)

    let failf fmt = fail << sprintf fmt

    let acceptable i (pos: Position) =
        match i with
        | Any _ -> true
        | Fail -> false
        | Greater bp -> bp.Column < pos.Column
        | Exact ep -> ep.Column = pos.Column
        | AtLeast ap -> ap.Column <= pos.Column
        | StartIndent _ -> true

    let nestableIn i o =
        match i, o with
        | Greater i, Greater o -> o.Column < i.Column
        | Greater i, Exact o -> o.Column < i.Column
        | Exact i, Exact o -> o.Column = i.Column
        | Exact i, Greater o -> o.Column <= i.Column
        | _, _ -> true

    let tokeniser p =
        parse {
            let! pos = getPosition
            let! i = getIndentation

            if acceptable i pos
            then return! p
            else return! failf "incorrect indentation at %A" pos
        }

    let nestP i o p =
        parse {
            do! putIndentation i
            let! x = p

            do! notFollowedBy (tokeniser anyChar)
                <?> (sprintf "unterminated %A" i)

            do! putIndentation o

            return x
        }

    let indented<'a, 'u> i (p: Parser<'a, _>): IndentParser<_, 'u> =
        parse {
            do! putIndentation i
            do! spaces
            return! tokeniser p
        }

    let exact<'a, 'u> pos p: IndentParser<'a, 'u> = indented (Exact pos) p
    let greater<'a, 'u> pos p: IndentParser<'a, 'u> = indented (Greater pos) p
    let atLeast<'a, 'u> pos p: IndentParser<'a, 'u> = indented (AtLeast pos) p
    let any<'a, 'u> pos p: IndentParser<'a, 'u> = indented Any p
打开FParsec
模块缩进分析器=
类型缩进=
|失败
|任何
|更大的位置
|精确位置
|至少在位置上
|起始位置凹痕
议员:这是我的立场=
与此匹配
|任何
|失败->无
|大p->一些p
|精确p->Some p
|至少p->一些p
|StartIndent p->Some p
类型缩进状态
设indentState u={Indent=Any;UserState=u}
让runParser p u s=runParserOnString p(indentState u)“”s
让我们运行parseron文件pu路径=
runparseronp文件(indentState u)路径System.Text.Encoding.UTF8
让getIndentation:IndentParser=
趣味流->
将stream.UserState与
|{Indent=i}->回复i
让我们来看看newi:IndentParser=
趣味流->
stream.UserState错误
|较大bp->bp.列<位置列
|精确ep->ep柱=位置柱
|至少ap->ap.列为真
让NestableinI o=
将i、o与
|大i,大o->o.列o.列o.列=i.列
|精确i,大o->o.列为真
让标记器p=
解析{
let!pos=getPosition
让我来!i=Get
如果可以的话,我会购买
然后回来
否则返回!失败“在%A位置的缩进不正确”
}
让nestP i o p=
解析{
做吧!普蒂
设!x=p
do!notfollowerby(标记器anyChar)
(sprintf“未终止%A”i)
做!普托
返回x
}
设缩进i(p:Parser)=
解析{
做吧!普蒂
做!空格
返回!标记器p
}
设精确位置p:IndentParser=缩进(精确位置)p
设更大位置p:IndentParser=缩进(更大位置)p
让至少位置p:IndentParser=indented(至少位置)p
让任意位置p:IndentParser=缩进任意p

我认为这里有两个问题:

  • 您需要将第一个
    “示例”
    缩进到自身之外,这是不可能的。您应该让第一个解析器成功,而不管当前位置如何
  • greater
    不是原子的,因此当它失败时,您的解析器将处于无效状态。这可能被视为库中的错误,也可能不被视为库中的错误。在任何情况下,您都可以通过
    尝试将其原子化
  • 考虑到这一点,我认为下面的解析器大致满足了您的要求:

    let indentSepBy p sep =
        parse {
            let! pos = getPosition
            let! head = p
            let! tail =
                let p' = attempt (greater pos p)
                let sep' = attempt (greater pos sep)
                many (sep' >>. p')
            return head :: tail
        }
    
    您可以按如下方式进行测试:

    let test =
        indentSepBy (pstring "Example") (pchar '.')
    
    let run text =
        printfn "***"
        runParser (test .>> eof) () text
            |> printfn "%A"
    
    [<EntryPoint>]
    let main argv =
        run "Example.Example"      // success
        run "Example\n.Example"    // failure
        run "Example\n .Example"   // success
        0
    
    let测试=
    indentseby(pstring“示例”)(pchar.”)
    让我们运行文本=
    printfn“***”
    runParser(test.>>eof)()文本
    |>printfn“%A”
    []
    让主argv=
    运行“Example.Example”//success
    运行“示例\n.Example”//failure
    运行“示例\n.Example”//success
    0
    
    请注意,我已经强制
    测试
    解析器通过
    eof
    使用整个输入。否则,当它实际上无法解析整个字符串时,它将错误地报告成功

    let test =
        indentSepBy (pstring "Example") (pchar '.')
    
    let run text =
        printfn "***"
        runParser (test .>> eof) () text
            |> printfn "%A"
    
    [<EntryPoint>]
    let main argv =
        run "Example.Example"      // success
        run "Example\n.Example"    // failure
        run "Example\n .Example"   // success
        0