Правила разбора - как заставить их хорошо играть вместе - PullRequest
1 голос
/ 02 октября 2009

Итак, я делаю парсер, где я предпочитаю гибкость, а не скорость, и я хочу, чтобы было легко писать грамматики, например нет хитрых правил обхода (фальшивые правила для разрешения конфликтов и т. д., как вы должны делать в yacc / bison и т. д.)

Существует Lexer с ручным кодированием с фиксированным набором токенов (например, PLUS, DECIMAL, STRING_LIT, NAME и т. Д.). Сейчас существует три типа правил:

  • TokenRule: соответствует определенному токену
  • SequenceRule: соответствует упорядоченному списку правил
  • GroupRule: соответствует любому правилу из списка

Например, допустим, у нас есть TokenRule 'varAccess', который соответствует токену NAME (примерно / [A-Za-z] [A-Za-z0-9 _] * /), и SequenceRule 'assignment', который соответствует [выражение, TokenRule (PLUS), выражение].

Выражение - это GroupRule, совпадающее либо с «назначением», либо с «varAccess» (фактический набор правил, с которым я тестирую, немного более полон, но для примера это подходит)

Но теперь допустим, я хочу разобрать

var1 = var2

И скажем, парсер начинается с правила Expression (порядок, в котором они определены, не должен иметь значения - приоритеты будут решены позже). И скажем, выражение GroupRule сначала попробует «назначение». Затем, поскольку «выражение» - это первое правило, которое должно быть найдено в «присваивании», оно будет пытаться снова проанализировать выражение и т. Д., Пока стек не будет заполнен, и компьютер - как и ожидалось - просто сдается в искрящем segfault.

Так что я сделал - SequenceRules добавляют себя в качестве «листьев» к своему первому правилу и становятся нерутовыми правилами. Корневые правила - это правила, которые парсер сначала попробует. Когда один из них применяется и совпадает, он пытается применить каждый из своих листьев один за другим, пока один не совпадет. Затем он пробует листы совпадающего листа и так далее, пока ничто больше не совпадает.

Так что он может анализировать выражения вроде

var1 = var2 = var3 = var4

В самый раз =) Теперь интересные вещи. Этот код:

var1 = (var2 + var3)

Не будет разбирать. Что происходит, так это то, что var1 анализируется (varAccess), назначается подприложение, он ищет выражение, пробует «скобки», начинает, ищет выражение после «(», находит var2, а затем нажимает на «+» «потому что ожидал»).

Почему он не совпадает с 'var2 + var3'? (и да, есть «добавить» SequenceRule, прежде чем вы спросите). Поскольку 'add' не является корневым правилом (чтобы избежать бесконечной рекурсии с помощью parse-expresssion-begin-with-expression-etc.) И что листы не проверяются в SequenceRules, в противном случае он будет анализировать такие вещи, как

reader readLine() println()

в

reader (readLine() println())

(например, '1 = 3' - ожидаемое выражение от add, лист varAccess a)

тогда как мы хотели бы, чтобы оно было левоассоциативным, например парсинг как

(reader readLine()) println()

Так или иначе, теперь у нас есть такая проблема, что мы должны иметь возможность анализировать выражения, такие как '1 + 2' в SequenceRules. Что делать? Добавьте особый случай, когда SequenceRules начинаются с TokenRule, а содержащиеся в нем GroupRules проверяются на наличие листьев? Будет ли это иметь смысл вне этого конкретного примера? Или нужно уметь указывать в каждом элементе SequenceRule, должен ли он быть проверен на наличие листьев или нет? Скажите мне, что вы думаете (кроме как отбросить всю систему - это, вероятно, произойдет через несколько месяцев)

П.С .: Пожалуйста, довольно, пожалуйста, не отвечайте на что-то вроде «иди и прочитай эту книгу на 400 страницах, или ты даже не заслуживаешь нашего времени». Если вы чувствуете необходимость - просто воздержитесь и идите на Reddit. Хорошо? Заранее спасибо.

Ответы [ 2 ]

2 голосов
/ 03 октября 2009

LL (k) парсеры (рекурсивные сверху вниз, автоматизированные или написанные вручную) требуют рефакторинга вашей грамматики, чтобы избежать левой рекурсии, и часто требуют специальных спецификаций просмотра (например, ANTLR), чтобы иметь возможность обрабатывать просмотр k-токена,Поскольку грамматики сложны, вы можете обнаружить k, экспериментируя, а это именно то, чего вы хотите избежать.

YACC / LALR (1) грамматики позволяют избежать проблемы левой рекурсии, что является большим шагом вперед.Плохая новость в том, что нет реальных языков программирования (кроме оригинального PASCAL Вирта), которые бы были LALR (1).Поэтому вы можете взломать свою грамматику, чтобы изменить ее с LR (k) на LALR (1), снова заставляя вас переживать эксперименты, которые выявляют странные случаи, и взламывая логику сокращения грамматики, чтобы попытаться обработать K-lookaheads, когда анализаторгенераторы (YACC, BISON, ... вы называете это) производят парсеры 1-го взгляда.

парсеры GLR (http://en.wikipedia.org/wiki/GLR_parser) позволяют вам избежать почти всей этой чепухи. Если вы можете написать контекстбесплатный анализатор, в большинстве практических случаев анализатор GLR будет анализировать его без дополнительных усилий. Это огромное облегчение, когда вы пытаетесь написать произвольные грамматики. И действительно хороший анализатор GLR будет непосредственно создавать дерево.

BISON имеетбыл расширен для выполнения анализа GLR, вроде того. Вам все еще нужно написать сложную логику для получения желаемого AST, и вам нужно беспокоиться о том, как обрабатывать сбойные парсеры и очищать / удалять их соответствующие (сбойные) деревья. DMS Software Reengineering Tookit предоставляет стандартные анализаторы GLR для любого контекста безграмматика, и автоматически создает AST без каких-либо дополнительных усилий с вашей стороны;неоднозначные деревья создаются автоматически и могут быть очищены с помощью семантического анализа после анализа.Мы использовали это для определения более 30 языковых грамматик, включая C, включая C ++ (который, как принято считать, трудно анализировать [и практически невозможно проанализировать с YACC], но прост с реальной GLR);см. синтаксический анализатор внешнего интерфейса C +++ и построитель AST на основе DMS.

Итог: если вы хотите написать грамматические правила простым способом и получить синтаксический анализатор для их обработки, используйте анализ GLRтехнология.Бизон почти работает.DM действительно работает.

0 голосов
/ 15 октября 2009

Мой любимый метод разбора - создание парсера с рекурсивным спуском (RD) из спецификации грамматики PEG. Они обычно очень быстрые, простые и гибкие. Одним приятным преимуществом является то, что вам не нужно беспокоиться об отдельных проходах токенизации, а беспокоиться о том, чтобы втиснуть грамматику в какую-то форму LALR, не существует. Некоторые библиотеки PEG перечислены [здесь] [1].

Извините, я знаю, что это сводится к выбрасыванию системы, но вы едва вышли из-под контроля своей проблемы, и переключение на анализатор PEG RD просто устранит ваши головные боли.

...