Почему мой рекурсивный синтаксический анализатор FParsec выдает исключение, когда он анализирует вложенный массив?

Я пытаюсь использовать FParsec для анализа массива TOML. Я работал с несколькими парсерами для различных частей спецификации 0.5, включая массивы. Однако, когда я пытаюсь поддерживать вложенные массивы, у меня возникали небольшие проблемы. Вот что у меня есть:

let pArrayOf<'a> (parser:Parser<'a,_>) : Parser<'a list, unit> =
    pchar '[' >>. (sepBy parser (spaces >>. pchar ',' .>> spaces)) .>> pchar ']'
let pBasicStringArray = pArrayOf pBasicString
let pLiteralStringArray = pArrayOf pLiteralString
let pMultilineLiteralStringArray = pArrayOf pMultilineLiteralString
let pMultilineStringArray = pArrayOf pMultilineString
let pIntegerArray = pArrayOf pInteger
let pFloatArray = pArrayOf pFloat
let pBoolArray = pArrayOf pBool
let pOffsetDateTimeArray = pArrayOf pOffsetDateTime
let pLocalDateTimeArray = pArrayOf pLocalDateTime
let pLocalDateArray = pArrayOf pDate
let pLocalTimeArray = pArrayOf pTime

let pStringArray = (attempt pBasicStringArray) <|> (attempt pLiteralStringArray) <|> (attempt pMultilineLiteralStringArray) <|> (attempt pMultilineStringArray)

let mapObj (l:'a list) = List.map box l
let pArray,pArrayRef = createParserForwardedToRef()
pArrayRef :=
    choice [
        attempt pStringArray |>> mapObj;
        attempt pIntegerArray |>> mapObj;
        attempt pFloatArray |>> mapObj;
        attempt pBoolArray |>> mapObj;
        attempt pOffsetDateTimeArray |>> mapObj;
        attempt pLocalDateTimeArray |>> mapObj;
        attempt pLocalDateArray |>> mapObj;
        attempt pLocalTimeArray |>> mapObj;
        attempt pArray
    ]

Очевидно, здесь есть больше кода, который не показан; в частности, парсеры значений (pBasicString, pIntegerи т. д.) не показаны. Я предполагаю, что они работают правильно, но любой может посмотреть их здесь: https://github.com/aggieben/FPConfig/blob/d4dc081dcefcee57fc1b45da69ac2178a1e10b2a/src/FPConfig.Toml/Parsers.fsx

Проблема возникает, когда я пытался использовать createParserForwardedToRef техника. Когда я тестирую этот парсер, я получаю сообщение об ошибке:

> test pArray "[1,2,3]";; 
Ok: [1; 2; 3] <null> (Ln: 1, Col: 8) val it : unit = ()

> test pArray "[ [1,2], [3,4] ]";;


error FS0193: internal error: Object reference not set to an instance
of an object

>

Как вы видете, pArray работает нормально для обычного массива, но вложенный массив взрывает его.

Что может быть причиной этого?

1 ответ

Решение

Это по-прежнему не будет полным ответом, но остановлюсь на моем предыдущем комментарии: подумайте, как pArrayRef разбирает префикс строки [ [, Он идет вниз pStringArray, pIntegerArray, pFloatArrayи т. д., все из которых выйдет из строя на втором [ и вернуться к первому [, Затем в конце вы нажмете рекурсивный вызов attempt pArray, На данный момент парсер еще ничего не потребляет (все те attemptОткат до первого [), поэтому вы делаете рекурсивный вызов pArrayRef (с помощью pArray) и начните цикл снова. И снова, и снова... То, что вы написали здесь, является бесконечно-рекурсивным циклом. Тот факт, что это происходит с ошибкой нулевой ссылки вместо ошибки переполнения стека, возможно, объясняется некоторыми деталями внутренней реализации FParsec.

Я думаю, что вам нужно сделать следующее:

let pArray,pArrayRef = createParserForwardedToRef()
let pNestedArray = pArrayOf pArray
pArrayRef :=
    choice [
        attempt pStringArray |>> mapObj;
        attempt pIntegerArray |>> mapObj;
        attempt pFloatArray |>> mapObj;
        attempt pBoolArray |>> mapObj;
        attempt pOffsetDateTimeArray |>> mapObj;
        attempt pLocalDateTimeArray |>> mapObj;
        attempt pLocalDateArray |>> mapObj;
        attempt pLocalTimeArray |>> mapObj;
        attempt pNestedArray |>> mapObj
    ]

У меня нет времени, чтобы проверить это в данный момент, но я считаю, что это должно сработать для вас.

Другие вопросы по тегам