2016-09-12 4 views
2

У меня возникла проблема с анализом однородного json-подобного массива в FParsec. Я разложил проблему на короткий пример, который воспроизводит ее.Как разобрать однородные списки в FParsec?

#r @"..\packages\FParsec.1.0.2\lib\net40-client\FParsecCS.dll" 
#r @"..\packages\FParsec.1.0.2\lib\net40-client\FParsec.dll" 

open System 
open FParsec 

let test p str = 
     match run p str with 
     | Success(result, _, _) -> printfn "Success: %A" result 
     | Failure(errormsg, _, _) -> printfn "Failure: %s" errormsg 


type CValue = CInt of int64 
      | CBool of bool 
      | CList of CValue list 

let P_WHITESPACE = spaces 
let P_COMMA = pstring "," 
let P_L_SBRACE = pstring "[" .>> P_WHITESPACE 
let P_R_SBRACE = P_WHITESPACE >>. pstring "]" 

let P_INT_VALUE = pint64 |>> CInt 

let P_TRUE = stringReturn "true" (CBool true) 
let P_FALSE = stringReturn "false" (CBool false) 
let P_BOOL_VALUE = P_TRUE <|> P_FALSE 


let P_LIST_VALUE = 
    let commaDelimitedList ptype = sepBy (ptype .>> P_WHITESPACE) (P_COMMA .>> P_WHITESPACE) 
    let delimitedList = (commaDelimitedList P_INT_VALUE) <|> (commaDelimitedList P_BOOL_VALUE) 
    let enclosedList = between P_L_SBRACE P_R_SBRACE delimitedList 
    enclosedList |>> CList 

Когда я использую функцию test, чтобы попробовать его, я получаю следующие результаты:

test P_LIST_VALUE "[1,2,3]" 
Success: CList [CInt 1L; CInt 2L; CInt 3L] 

test P_LIST_VALUE "[true,false]" 
Failure: Error in Ln: 1 Col: 2 
[true,false] 
^ 
Expecting: integer number (64-bit, signed) or ']' 

Если я поменять порядок P_INT_VALUE и P_BOOL_VALUE при использовании оператора <|>, затем [true,false] разбирает успешно но [1,2,3] не работает с аналогичной ошибкой. Так что в основном, что когда-либо парсер, который я использую, это то, что он пытается использовать.

Я понимаю, что оператор <|> не будет пытаться использовать парсер RHS, если LHS изменяет состояние пользователя - но я не вижу, как это может произойти. P_BOOL_VALUE и P_INT_VALUE не имеют никаких исходных символов, поэтому оба должны немедленно сбой при попытке проанализировать неправильный тип данных. Ints никогда не начинаются с «false» или «true», а bools никогда не начинаются с числовых цифр.

Что я делаю неправильно?

+1

Я не тестировал ваш код, но быстрый совет - обернуть оба анализатора в 'try'. – bytebuster

+0

«попытка» не требуется, и, как я только что протестировал, проблема не устраняется. Я честно не могу понять, почему это не работает. – Tarmil

ответ

2

А, я понял это. Подсказка в сообщении об ошибке - or ']'. Проблема в том, что sepBy преуспевает на пустом вводе, поэтому, когда он попадает в t, он успешно возвращается с пустым списком, а затем управление переходит к between, который пытается найти и не может найти завершающий ].

Решения переместить пустой список случай из INT/BOOL конкретных анализаторов, например:

let P_LIST_VALUE = 
    let commaDelimitedList ptype = sepBy1 (ptype .>> P_WHITESPACE) (P_COMMA .>> P_WHITESPACE) 
    let delimitedList = (commaDelimitedList P_INT_VALUE) <|> (commaDelimitedList P_BOOL_VALUE) <|> preturn [] 
    let enclosedList = between P_L_SBRACE P_R_SBRACE delimitedList 
    enclosedList |>> CList 

Обратите внимание на использовании sepBy1 вместо sepBy, и добавления <|> preturn [], чтобы справиться с пустой случай только один раз в delimitedList.

В качестве побочного примечания я не знаю вашего точного приложения, но, как правило, не рекомендуется применять типизацию в синтаксическом анализаторе; более распространенным способом реализации этого было бы просто разобрать commaDelimitedList (P_INT_VALUE <|> P_BOOL_VALUE) (с вашим оригиналом commaDelimitedList), а затем проверить написание в последующей фазе анализа.

+1

Успех! Это работает! Поэтому, если я правильно понимаю, как только синтаксический анализатор попадает в «t», что приводит к ошибке P_INT_VALUE. После этого sepBy предполагает, что он получает пустой ввод и преуспевает. Затем между попытками разобрать ']' и не удается? Я действительно думал о принятии гетерогенного списка в синтаксическом анализаторе, а затем проверял его на этапе анализа, как вы сказали. По какой-то причине я подумал, что лучше справляться с этим в парсере. Каков недостаток этого маршрута? –