Использование PEG Parser для разбора BBCode: pegjs или что?

У меня есть конвертер bbcode -> html, который реагирует на событие изменения в текстовом поле. В настоящее время это делается с помощью ряда регулярных выражений, и есть ряд патологических случаев. Я всегда хотел поточить карандаш в этой грамматике, но не хотел влезать в бритье яка. Но... недавно я узнал о pegjs, который кажется довольно полной реализацией генерации синтаксического анализатора PEG. У меня есть большая часть грамматики, но теперь я задаюсь вопросом, является ли это подходящим использованием полнофункционального синтаксического анализатора.

Мои конкретные вопросы:

  1. Поскольку мое приложение основано на переводе того, что я могу, в HTML и оставлении остального в виде необработанного текста, имеет ли смысл реализация bbcode с использованием синтаксического анализатора, который может дать сбой из-за синтаксической ошибки? Например: [url=/foo/bar]click me![/url], безусловно, будет успешным, как только будет введена закрывающая скобка закрывающего тега. Но что пользователь увидит в это время? С регулярным выражением я могу просто игнорировать несоответствующие вещи и рассматривать их как обычный текст для целей предварительного просмотра. С формальной грамматикой я не знаю, возможно ли это, потому что я полагаюсь на создание HTML из дерева синтаксического анализа, а синтаксический анализ не удается... что?

  2. Я не понимаю, где преобразования должны быть сделаны. В формальном синтаксическом анализаторе на основе lex/yacc у меня были бы заголовочные файлы и символы, обозначающие тип узла. В pegjs я получаю вложенные массивы с текстом узла. Я могу сгенерировать переведенный код как действие парсера, сгенерированного pegjs, но это похоже на запах кода, чтобы объединить парсер и эмиттер. Однако, если я вызову PEG.parse.parse(), я верну что-то вроде этого:

[
       [
          "[",
          "img",
          "",
          [
             "/",
             "f",
             "o",
             "o",
             "/",
             "b",
             "a",
             "r"
          ],
          "",
          "]"
       ],
       [
          "[/",
          "img",
          "]"
       ]
    ]

учитывая грамматику вроде:

document
   = (open_tag / close_tag / new_line / text)*

open_tag
   = ("[" tag_name "="? tag_data? tag_attributes? "]")


close_tag
   = ("[/" tag_name "]") 

text
   = non_tag+

non_tag
   = [\n\[\]]

new_line
   = ("\r\n" / "\n")

Я, конечно, сокращаю грамматику, но вы поняли. Так что, если вы заметили, в массиве массивов нет контекстной информации, которая подсказывает мне, какой у меня узел, и мне остается снова сравнивать строки, даже если синтаксический анализатор уже сделал это. сделал это. Я полагаю, что можно определить обратные вызовы и использовать действия для их запуска во время синтаксического анализа, но в Интернете мало информации о том, как это можно сделать.

Я лаю не на то дерево? Должен ли я вернуться к сканированию регулярных выражений и забыть о синтаксическом анализе?

Спасибо


person Steve Ross    schedule 29.06.2012    source источник
comment
Стив, ваш вопрос очень интересен (+1), я просто хочу сделать то же самое в расширении: разобрать BBCode в текстовом поле (к сожалению, это формат, который все еще используется на форуме) и создать предварительный просмотр в реальном времени из напечатанного текст с использованием PEG.js или чего-либо еще, кроме регулярных выражений. Удалось ли вам создать грамматику для парсера BBCode? Не могли бы вы поделиться своим решением через GitHub или что-то еще? Это бы мне очень помогло. Большое спасибо заранее!   -  person Sk8erPeter    schedule 28.05.2015
comment
Я использовал парсер bbcode от patorjk. Отлично работает и может быть настроен в соответствии с вашими потребностями, если у вас есть специальные теги.   -  person Steve Ross    schedule 28.05.2015
comment
Спасибо, я уже видел эту библиотеку, но она использует регулярные выражения, чего я хотел избежать, так как теоретически парсинг BBCode с использованием регулярных выражений не может быть выполнен без ошибок (»»ссылка) в некоторых случаях, например при вложении их друг в друга и т. д. Вот почему я хотел сделать это, используя формализм грамматики синтаксического анализа. Так ты не пытался улучшить начатую грамматику? :) Не могли бы вы поделиться основой? :)   -  person Sk8erPeter    schedule 28.05.2015


Ответы (3)


Что касается вашего первого вопроса, я должен сказать, что предварительный просмотр в прямом эфире будет трудным. Проблемы, которые вы указали в отношении того, что синтаксический анализатор не поймет, что ввод находится в стадии разработки, верны. Peg.js сообщает вам, в какой момент возникла ошибка, поэтому, возможно, вы могли бы взять эту информацию и вернуться на несколько слов назад и снова проанализировать или, если отсутствует конечный тег, попробуйте добавить его в конец.

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

text
   = text:non_tag+ {
     // we captured the text in an array and can manipulate it now
     return text.join("");
   }

На данный момент вы должны вписать эти обратные вызовы в свою грамматику. Я сейчас много чего делаю на работе, поэтому я могу сделать запрос на включение в peg.js, чтобы исправить это. Но я не уверен, когда я найду время, чтобы сделать это.

person dignifiedquire    schedule 23.07.2012

Первый вопрос (грамматика для неполных текстов):

Можете добавить

incomplete_tag = ("[" tag_name "="? tag_data? tag_attributes?)
//                         the closing bracket is omitted ---^

после open_tag и измените document, добавив в конце неполный тег. Хитрость заключается в том, что вы предоставляете синтаксическому анализатору все необходимые продукты для всегда синтаксического анализа, но действительные идут первыми. Затем вы можете игнорировать incomplete_tag во время предварительного просмотра в реальном времени.

Второй вопрос (как включить действия):

Вы пишете так называемые действия после выражений. Действие представляет собой код Javascript, заключенный в фигурные скобки, и допускается после выражения pegjs, т.е. е. также в середине производства!

На практике такие действия, как { return result.join("") }, почти всегда необходимы, потому что pegjs разбивается на отдельные символы. Также могут быть возвращены сложные вложенные массивы. Поэтому я обычно пишу вспомогательные функции в инициализаторе pegjs в начале грамматики, чтобы действия были небольшими. Если вы тщательно выбираете имена функций, действие самодокументируется.

Для примера см. PEG для отступов в стиле Python. Отказ от ответственности: это мой ответ.

person nalply    schedule 24.07.2012

Попробуйте что-то вроде этого правила замены. Вы на правильном пути; вам просто нужно сказать это, чтобы собрать результаты.

текст = результат: non_tag+ { return result.join(''); }

person John Haugeland    schedule 24.07.2012