Почему мой рекурсивный синтаксический анализатор 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
]
У меня нет времени, чтобы проверить это в данный момент, но я считаю, что это должно сработать для вас.