Исправление плохой грамматики JSON

Я только начал изучать синтаксический анализ и написал этот простой синтаксический анализатор на Haskell (используя parsec) для чтения JSON и построить для него простое дерево. Я использую грамматику из RFC 4627.

Однако, когда я пытаюсь разобрать строку {"x":1 }, я получаю вывод:

parse error at (line 1, column 8):
unexpected "}"
expecting whitespace character or ","

Кажется, это происходит только тогда, когда у меня есть пробелы перед закрывающей фигурной скобкой (]) или усами (}).

Что я сделал не так? Если я избегаю пробелов перед закрывающим символом, это работает отлично.


person Clark Gaebel    schedule 02.01.2012    source источник
comment
Немного не связанно: подсветка синтаксиса в pastebin не блестящая. На самом деле существует версия pastebin для haskell: hpaste.org   -  person Tikhon Jelvis    schedule 02.01.2012


Ответы (2)


Parsec не выполняет автоматическую перемотку назад и назад. Когда вы пишете sepBy member valueSeparator, valueSeparator занимает пробел, поэтому синтаксический анализатор будет анализировать ваше значение следующим образом:

{"x":1 }
[------- object
%        beginObject
 [-]     name
    %    nameSeparator
     %   jvalue
      [- valueSeparator
       X In valueSeparator: unexpected "}"

Legend:
[--]     full match
%        full char match
[--      incomplete match
X        incomplete char match

Когда valueSeparator терпит неудачу, Parsec не будет возвращаться и пробовать другую комбинацию синтаксических анализов, потому что один символ уже совпал в valueSeparator.

У вас есть два варианта решения вашей проблемы:

  1. Поскольку пробелы в JSON несущественны, всегда используйте пробелы после важного токена, а не раньше. Таким образом, tok должен использовать только пробел после char, поэтому его определение tok c = char c *> ws ((*>) из Control.Applicative); применить то же правило ко всем остальным парсерам. Поскольку вы никогда не будете использовать пустое пространство после входа в «неправильный синтаксический анализатор», вам не придется возвращаться назад.
  2. Используйте отслеживание с возвратом в Parsec, добавляя try перед парсерами, которые могут потреблять более одного символа и должны перематывать ввод в случае сбоя.

EDIT: обновлена ​​графика ASCII, чтобы сделать ее более понятной.

person dflemstr    schedule 02.01.2012
comment
Что было бы лучшим способом (в коде) исправить это? Должен ли я отказаться от sepBy в этом случае? - person Clark Gaebel; 02.01.2012
comment
Кроме того, причина, по которой у меня есть чтение пробелов до и после токена, заключается в том, что это сказано в RFC =/ - person Clark Gaebel; 02.01.2012
comment
sepBy подойдет, если вы выберете один из двух вариантов, которые я предложил. В частности, member 'sepBy' try valueSeparator должен работать как положено (замените ' на '). - person dflemstr; 02.01.2012
comment
Ура! Оно работает! Большое спасибо. У меня сейчас самая нелепая улыбка на лице. :) Любые советы о том, где использовать или не использовать try? Мне никогда не удавалось полностью уложить это в голове. - person Clark Gaebel; 02.01.2012
comment
@Clark Gaebel - попробуйте использовать try только для анализаторов уровня символов. Если вы последуете совету Тихона Джелвиса по использованию модуля ParsecToken, необходимость в try отпадет. Тем не менее, вам все еще может понадобиться try для CharParsers - это Parsec, эквивалентный токенам сканера. - person stephen tetley; 02.01.2012

Общее решение состоит в том, чтобы все ваши синтаксические анализаторы пропускали завершающие пробелы. Посмотрите lexemeParsecToken) в документации Parsec, чтобы узнать, как это сделать, или просто придумайте простую версию самостоятельно:

 lexeme parser = do result <- parser
                    spaces
                    return result

Затем используйте эту функцию для всех ваших токенов (например, числовых литералов). Таким образом, вам придется беспокоиться только о пробелах в самом начале выражения.

Дополнительные сведения о ParsecToken и его друзьях см. в разделе «Лексический анализ» документа документы Parsec.

Имеет смысл пропускать пробелы только после маркера, за исключением самого начала, где вы можете пропустить его вручную. Вы должны использовать этот подход, даже если в конечном итоге вы не используете модуль ParsecToken.

Кажется, у вас уже есть tok, который действует как мой lexeme, за исключением того, что он использует пробелы с обеих сторон. Измените его, чтобы использовать только пробелы после токена и просто игнорировать пробелы в самом начале ввода вручную. Это должно (в идеале :)) решить проблему.

person Tikhon Jelvis    schedule 02.01.2012
comment
Мой код находится по адресу pastebin.com/ewGH7QMh. Нажмите на этот простой парсер в моем первоначальном вопросе. - person Clark Gaebel; 02.01.2012
comment
Да, я пропустил ссылку, когда впервые прочитал вопрос. Я, вероятно, собираюсь много пересмотреть свой ответ, как только закончу его читать. - person Tikhon Jelvis; 02.01.2012
comment
Ах. Раньше у меня была более или менее такая же проблема, и в основном я ее решил, но мой фактический код был совершенно другим. - person Tikhon Jelvis; 02.01.2012