2016-06-13 3 views
3

저는 광산 프로젝트에 특정 도메인 특정 언어를 구현하는 파서를 구현하고 있습니다.OperatorPrecedenceParser가 선택적 (opt) 식을 구문 분석하게 만들기

나는 어려움을 겪고있는 한 가지 측면은 전체식이 선택 사항이되도록 식 (FParsec의 OperatorPrecedenceParser를 사용하여 구현 됨)을 만드는 것입니다.

필자는 파서 OPP를 그물 주변의 많은 예제와 거의 같은 방식으로 구현했습니다. 나는 또한 공백이 라인 주석의 끝 부분에 소비되는 곳을 이동하려고 시도했다. 내 시도 중 하나도 봇의 경우에는 작동하지 않는 것 같습니다 (표현식과 끝의 주석이 모두 선택 사항 임)

특히 (아래 예에서 구현 된 것처럼) 다음 구문을 성공적으로 파싱하려고합니다.

KeyValue: expression # comment 
KeyValue: 
KeyValue: # comment 

여기서 expression은 선택 사항이며 표현식 다음에 선택적 주석이 있습니다. "KeyValue :"는이 예제에서는 하드 코딩되었지만, 주 파서 코드에서는 식별자입니다.

N.B. 아래 샘플을보십시오. 나는 2 개의 부동 소수점을 더한 간단한 삽입을 구문 분석하는 최소한의 표현식을 구현했다. 전체 구문 분석기는 훨씬 더 많은 기능을 구현합니다.

N.B.2. 표현식은 :과 표현식 사이에 1 개의 공백이 있어야합니다. # 주석 문자는 표현식에 전혀 표시되지 않습니다.

어떻게 표현식을 '선택적'으로 만들 수 있습니까? 아래의 eKeyValue FParsec Parser 유형을 참조하십시오.

아래 예제에는 6 가지 사례가 들어 있습니다. 모든 경우가 효과가 있습니다. 나는 조건의 끝 부분에 wsBeforeEOL을 추가하려고 시도했다. 표현식을 선택적으로 사용 (opt)했지만 아무 것도 작동하지 않는 것 같다. OPP는 항상 소비되고 결코 실패하지 않는 것처럼 보입니다.

아래의 샘플 프로그램의 출력은 보이는 같은 :

은 샘플 프로그램입니다
Test1 - Failure: 
Error in Test1 - No Expression but comment: Ln: 1 Col: 15 
    KeyName: # No Expression but comment 
      ^
Expecting: floating-point number or '(' 

Test2 - Success: Key (Some (Arithmetic (Number 2.0,Plus,Number 2.0))) 
Test3 - Success: Key (Some (Arithmetic (Number 3.0,Plus,Number 3.0))) 
Test4 - Success: Key (Some (Arithmetic (Number 3.0,Plus,Number 4.0))) 
Test5 - Success: Key (Some (Arithmetic (Number 4.0,Plus,Number 4.0))) 
Test6 - Success: Key null 
Press any key to continue . . . 

(위의 테스트 케이스는 main() 함수에 있습니다

open FParsec 


    // Placeholder for state... 
    type UserState = 
     { 
      dummy: int    
     } 
     with 
      static member Create() = {dummy = -1} 


    type Operator = 
     | Plus 

    type Expression = 
     | Number of float 
     | Arithmetic of Expression * Operator * Expression // Composes 2 primatives 

    type Statement = 
     | Key of Expression option // Optional expression name 


// very simple parsers which handles a simple string on one line. 
// White space handling 
let isBlank = fun c -> c = ' ' || c = '\t' 
let ws1 = skipMany1SatisfyL isBlank "whitespace" 
let ws = skipManySatisfy isBlank 
let comment = pstring "#" >>. skipRestOfLine false 
let wsBeforeEOL = skipManySatisfy isBlank >>. optional comment 

// Parse a number 
let sNumber = pfloat .>> wsBeforeEOL |>> Number // Test wsExpression ending 

// The expression reference 
let expression, expressionRef = createParserForwardedToRef() 

let expressionFragment = choice [sNumber] //;sBool;sNull;sString;sIdentifier] 
let bracketedExpressionFragment = between (pstring "(" .>> ws) (pstring ")" .>> ws) expression 

// The parser for addition only 
let oppa = new OperatorPrecedenceParser<Expression, unit, UserState>() 

let parithmetic = oppa.ExpressionParser 

//oppa.TermParser <- (expressionFragment .>> wsBeforeEOL) <|> (bracketedExpressionFragment .>> wsBeforeEOL) 
oppa.TermParser <- choice[expressionFragment;bracketedExpressionFragment] 
oppa.AddOperator(InfixOperator("+", ws, 1, Associativity.Left, fun x y -> Arithmetic(x, Plus, y))) 
expressionRef := oppa.ExpressionParser 


// *** HERE: Define the Key, with optional expression, which must have at lease 1 WS,, then followed by wsBeforeEOL, which is multiple blanks and optional comment. 
let eKeyValue = pstringCI "KeyName:" >>. (opt (ws1 >>. expression)) .>> wsBeforeEOL |>> Key 

// Define the parser for the whole string...in this case a single line 
let htmlProgramParser = spaces >>. eKeyValue .>> spaces .>> eof 

// test harnes on a string 
let parseHtmlProgramString programName str = 
    runParserOnString htmlProgramParser (UserState.Create()) programName str //with 

[<EntryPoint>] 
let main argv = 
    printfn "%A" argv 

    let test1 = 
     " KeyName: # No Expression but comment" 
     |> parseHtmlProgramString "Test1 - No Expression but comment" 
     |> printfn "Test1 - %A" 

    let test2 = 
     " KeyName: 2+2 # Expression and Comment" 
     |> parseHtmlProgramString "Test2 - 2+2 # Expression and Comment" 
     |> printfn "Test2 - %A" 

    let test3 = 
     " KeyName: 3 + 3 # # Expression and Comment2" 
     |> parseHtmlProgramString "Test3 - 3 + 3 # Expression and Comment2 (Spaces)" 
     |> printfn "Test3 - %A" 

    let test4 = 
     " KeyName: (3 + 4) # Bracketed Expression and Comment" 
     |> parseHtmlProgramString "Test4 - (3 + 4) # Bracketed Expression and Comment" 
     |> printfn "Test4 - %A" 

    let test5 = 
     " KeyName: (4 + 4) " 
     |> parseHtmlProgramString "Test5 - (4 + 4) # Expression + <no comment>" 
     |> printfn "Test5 - %A" 

    let test6 = 
     " KeyName:" 
     |> parseHtmlProgramString "Test6 - <no expression> <no comment>" 
     |> printfn "Test6 - %A" 

    0 // return an integer exit code 

답변

1

내가 질문을 이해한다면 정확하게, 문제는 공백을 소비 한 후에 eKeyValue에있는 ws1 >>. expression이 실패 할 수있는 것으로 보이므로 연결자가 성공하지 못합니다.

,

당신은

let eKeyValue = pstringCI "KeyName:" >>. (opt (ws1 >>? expression)) .>> wsBeforeEOL |>> Key 

같은 eKeyValue을 정의하거나 작동

let eKeyValue = pstringCI "KeyName:" >>. (ws1 >>. opt expression <|>% None) .>> wsBeforeEOL |>> Key 
+0

처럼 리팩토링에 의해, 예를 들어이 문제를 해결할 수 있습니다. 나는 FParsec (그리고 F # 일반적으로)에 대해 아주 익숙하다. 내 큰 DSL에서 내 문제를 해결할 것입니다. 신속한 도움에 감사드립니다. – Nathan

+0

사실 OPP가 제대로 종료되지 않아서 문제가 더 많았지 만. >>를 지적 해 주셔서 고마워요? 및 조합 자 % <|>. – Nathan