如何使用FParsec进行基于缩进的语句(如在python中)?

时间:2015-08-18 01:13:14

标签: parsing f# indentation fparsec

所以,根据Is possible to parse "off-side" (indentation-based) languages with fparsec?

,我的语言有一个基本的解析器

我想为它添加基于缩进的语法,就像在python中一样。

但是,我很难看到如何将indentationParser与我的相结合,它们会与类型不匹配发生碰撞。

用我的语言," do"是新的范围,并要求标识:

type ExprC =
  | BoolC of bool
  | DecC of decimal
  | MathC of MathOp * array<ExprC>
  | VarC of string
  | BlockC of BlockCodeC
  | IfC of LogiOpC * BlockCodeC * BlockCodeC
  | LoopC of ExprC * BlockCodeC

module Parser2

open System
open System.Collections.Generic

open FParsec
open TablaM

let tabStopDistance = 8 // must be a power of 2

module IndentationParser =
    type LastParsedIndentation() =
        [<DefaultValue>]
        val mutable Value: int32
        [<DefaultValue>]
        val mutable EndIndex: int64

    type UserState = 
        {Indentation: int
         // We put LastParsedIndentation into the UserState so that we 
         // can conveniently use a separate instance for each stream.
         // The members of the LastParsedIndentation instance will be mutated
         // directly and hence won't be affected by any stream backtracking. 
         LastParsedIndentation: LastParsedIndentation}
        with
           static member Create() = {Indentation = -1
                                     LastParsedIndentation = LastParsedIndentation(EndIndex = -1L)}

    type CharStream = CharStream<UserState>
    type Parser<'t> = Parser<'t, UserState>

    // If this function is called at the same index in the stream
    // where the function previously stopped, then the previously
    // returned indentation will be returned again. 
    // This way we can avoid backtracking at the end of indented blocks.
    let skipIndentation (stream: CharStream) =    
        let lastParsedIndentation = stream.UserState.LastParsedIndentation
        if lastParsedIndentation.EndIndex = stream.Index then
            lastParsedIndentation.Value
        else
            let mutable indentation = stream.SkipNewlineThenWhitespace(tabStopDistance, false)
            while stream.Peek() = '#' do
                stream.SkipRestOfLine(false) // skip comment
                indentation <- stream.SkipNewlineThenWhitespace(tabStopDistance, false)
            lastParsedIndentation.EndIndex <- stream.Index
            lastParsedIndentation.Value <- indentation
            indentation

    let indentedMany1 (p: Parser<'t>) label : Parser<'t list> =
        fun stream ->
            let oldIndentation = stream.UserState.Indentation
            let indentation = skipIndentation stream
            if indentation <= oldIndentation then 
                Reply(Error, expected (if indentation < 0 then "newline" else "indented " + label))
            else
                stream.UserState <- {stream.UserState with Indentation = indentation}            
                let results = ResizeArray()
                let mutable stateTag = stream.StateTag
                let mutable reply = p stream // parse the first element
                let mutable newIndentation = 0
                while reply.Status = Ok 
                      && (results.Add(reply.Result)
                          newIndentation <- skipIndentation stream
                          newIndentation = indentation)
                   do
                     stateTag <- stream.StateTag
                     reply <- p stream
                if reply.Status = Ok 
                   || (stream.IsEndOfStream && results.Count > 0 && stream.StateTag = stateTag) 
                then
                    if newIndentation < indentation || stream.IsEndOfStream then
                        stream.UserState <- {stream.UserState with Indentation = oldIndentation}
                        Reply(List.ofSeq results)
                    else
                        Reply(Error, messageError "wrong indentation")
                else // p failed
                    Reply(reply.Status, reply.Error) 


open IndentationParser

let reserved = ["for";"do"; "while";"if";"case";"type"]

let DecimalParser : Parser<_, unit> =
    // note: doesn't parse a float exponent suffix
    numberLiteral NumberLiteralOptions.AllowFraction "number" 
    |>> fun num -> 
            decimal num.String

let isBlank = fun c -> c = ' ' || c = '\t'

let ws = skipMany1SatisfyL isBlank "whitespace"
let str_ws s = pstring s .>> ws
let comment = pstring "#" >>. skipRestOfLine false
let wsBeforeEOL = skipManySatisfy isBlank >>. optional comment

let pidentifierraw =
    let isIdentifierFirstChar c = isLetter c || c = '_'
    let isIdentifierChar c = isLetter c || isDigit c || c = '_'
    many1Satisfy2L isIdentifierFirstChar isIdentifierChar "identifier"
let pidentifier =
    pidentifierraw 
    >>= fun s -> 
        if reserved |> List.exists ((=) s) then fail "keyword" 
        else preturn s

let keyword str = pstring str >>? nextCharSatisfiesNot (fun c -> isLetter c || isDigit c) <?> str
let pvar = pidentifier |>> VarC

let booleans = choice[stringReturn "true" true <|> stringReturn "false" false] .>> spaces |>> BoolC
let decimals = DecimalParser |>> DecC

let pliteral = decimals <|> booleans //<|> pstringliteral
let expression1 = spaces >>? choice[pliteral;pvar] 

let between a b p = pstring a >>. p .>> pstring b

let expr, exprImpl = createParserForwardedToRef()

let parens = expr |> between "(" ")"

let lhExpression = choice[pliteral; parens; pvar]

do exprImpl := spaces >>. choice[attempt parens; 
                                   expression1]

(* Rules of associations *)
type Assoc = Associativity

let opp = new OperatorPrecedenceParser<ExprC,unit,unit>()
let parithmetic = opp.ExpressionParser

let terma = (lhExpression .>> spaces) <|> parithmetic .>> parens

opp.TermParser <- terma
let mathOps = [
    "+", Add, 1;
    "-", Sub, 1;
    "*", Mul, 2;
    "/", Div, 2
]

for str:String, op:MathOp, precedence:int in mathOps do
    opp.AddOperator(InfixOperator(str, spaces, precedence, Assoc.Left, fun x y -> MathC(op, [| x; y |])))


let indentedStatements, indentedStatementsRef = createParserForwardedToRef()

let doBlock = keyword "do" >>. (pipe2 (ws .>> wsBeforeEOL) 
                                        indentedStatements
                               (fun a stmts -> 
                                   let lines = stmts |> List.toArray
                                   BlockC(lines)))

let ifBlock = keyword "if" >>. doBlock
let forBlock = keyword "for" >>. doBlock
let funBlock = keyword "fun" >>. doBlock

let statement = ifBlock <|> forBlock <|> funBlock <|> lhExpression <|> parithmetic

do indentedStatementsRef := indentedMany1 statement "statement"

let document = indentedStatements .>> spaces .>> eof

let parse str =
    runParserOnString document (UserState.Create()) "" str

错误在于:

let statement = ifBlock <|> lhExpression <|> parithmetic

Error FS0001: Type mismatch. Expecting a    Parser<ExprC list,unit>    but given a    Parser<'a list>    The type 'unit' does not match the type 'UserState' (FS0001) (TablaM)

0 个答案:

没有答案