Как построить абстрактные синтаксические деревья из спецификации грамматики в Haskell?

Я работаю над проектом, который включает оптимизацию определенных конструкций в очень небольшом подмножестве Java, формализованном в BNF.

Если бы мне пришлось делать это на Java, я бы использовал комбинацию JTB и JavaCC, которая создает AST. Затем посетители используются для управления деревом. Но, учитывая обширные библиотеки для синтаксического анализа в Haskell (parsec, happy, alex и т. д.), я немного запутался в выборе подходящей библиотеки.

Итак, проще говоря, когда язык указан в BNF, какая библиотека предлагает самые простые средства для создания AST? И как лучше всего изменить это дерево в идиоматическом Haskell?


person Vamshi Surabhi    schedule 10.09.2013    source источник
comment
Существует также интересная, более экспериментальная возможность использования шаблона haskell для создания синтаксических анализаторов из BNF, указанного в исходном коде, называемая bnfc-meta. Однако я не знаю, насколько это применимо для реальных приложений.   -  person phipsgabler    schedule 10.09.2013
comment
(Если вы новичок в Haskell...) Нет реальной необходимости в Haskell или других современных функциональных языках, таких как SML и Caml, для генератора деревьев, такого как JTB. Алгебраические типы столь же лаконичны, как древовидные спецификации для JTB или подобных инструментов, и являются стандартными средствами определения типов данных в функциональных языках. В Haskell вы также можете автоматически получить множество полезных функций для типов данных, таких как отображение, сериализация и структурное равенство.   -  person stephen tetley    schedule 11.09.2013
comment
Существует 2 продвинутых инструмента для обработки и оптимизации AST: uuagc препроцессор грамматик атрибутов и hoopl универсальная библиотека для компонуемой оптимизации на основе решетки фактов.   -  person nponeccop    schedule 11.09.2013


Ответы (5)


Я никогда не использовал bnfc-meta (предложено @phg), но настоятельно рекомендую вам изучить BNFC (на взлома: http://hackage.haskell.org/package/BNFC). Основной подход заключается в том, что вы пишете свою грамматику в аннотированном стиле BNF, и он автоматически генерирует AST, синтаксический анализатор и симпатичный принтер для грамматики.

Насколько подходит BNFC, зависит от сложности вашей грамматики. Если это не зависит от контекста, вам, вероятно, будет трудно добиться какого-либо прогресса (я добился некоторого успеха, взламывая контекстно-зависимые расширения, но этот код, вероятно, уже немного сгнил). Другим недостатком является то, что ваш AST будет напрямую отражать спецификацию грамматики. Но поскольку у вас уже есть спецификация BNF, добавление необходимых аннотаций для BNFC должно быть довольно простым, так что это, вероятно, самый быстрый способ получить пригодный для использования AST. Даже если вы решите пойти другим путем, вы можете взять сгенерированные типы данных в качестве отправной точки для рукописной версии.

person John L    schedule 10.09.2013
comment
Это определенно самый простой способ, если у вас есть BNF! - person Vamshi Surabhi; 12.09.2013

Что ж, в Haskell есть 2 основных способа разбора чего-либо: комбинаторы разбора или генератор парсеров. Поскольку у вас уже есть BNF, я бы предложил последнее.

Хороший вариант — alex. Парсер GHC IIRC написан с использованием этого, так что вы будете в хорошей компании.

Далее у вас будет большой стек деклараций данных для анализа:

data JavaClass = {
    className :: Name,
    interfaces :: [Name],
    contents :: [ClassContents],
    ...
 }
  data ClassContents = M Method
                     | F Field
                     | IC InnerClass

и для выражений и всего, что вам нужно. Наконец, вы объедините их во что-то вроде

data TopLevel = JC JavaClass
              | WhateverOtherForms
              | YouWillParse

Как только вы это сделаете, весь AST будет представлен как один TopLevel или их список в зависимости от того, сколько классов/файлов вы анализируете.

Чтобы продолжить отсюда, зависит от того, что вы хотите сделать. Существует ряд библиотек, таких как syb (выбросьте свой шаблон), которые позволяют вам писать очень краткие обходы дерева и модификации. lens тоже вариант. Как минимум выезд Data.Traversable и Data.Foldable.

Чтобы изменить дерево, вы можете сделать что-то простое, например

ignoreInnerClasses :: JavaClass -> JavaClass
ignoreInnerContents c = c{contents = filter isClass $ contents c}
 --                           ^^^ that is called a record update
    where isClass (IC _) = True
          isClass _      = False

и тогда вы могли бы использовать что-то вроде syb для записи

 everywhere (mkT ignoreInnerClass) toplevel

который будет проходить все и применять ignoreInnerClass ко всем JavaClasses. Это можно сделать и в lens, и во многих других библиотеках, но syb очень легко читается.

person Daniel Gratzer    schedule 10.09.2013
comment
Мне понравился четкий ответ. Хотя я имею дело только с подмножеством, слишком много работы, чтобы начать писать типы данных с нуля. Тем не менее, я планирую использовать syb для преобразований. - person Vamshi Surabhi; 12.09.2013

Алекс + Хэппи.

Существует множество подходов к изменению/исследованию проанализированных терминов (AST). Ключевое слово для поиска - это программирование с общим типом данных. Но будьте осторожны: это сложная тема...

http://people.cs.uu.nl/andres/Rec/MutualRec.pdf

http://www.cs.uu.nl/wiki/GenericProgramming/Multirec

Он имеет общую реализацию молнии, доступную здесь:

http://hackage.haskell.org/packages/archive/zipper/0.3/doc/html/Generics-MultiRec-Zipper.html

Также проверьте https://github.com/pascalh/Astview.

person Bastl    schedule 10.09.2013
comment
Универсальное программирование типов данных действительно выглядит очень интересно. Я попробую. - person Vamshi Surabhi; 12.09.2013

Вы также можете ознакомиться с серией компиляторов Haskell, которая хороша как введение в использование alex и с удовольствием анализирует подмножество Java: http://bjbell.wordpress.com/haskell-compiler-series/.

person ccatalfo    schedule 11.09.2013

Поскольку ваша грамматика может быть выражена в BNF, она относится к классу грамматик, которые эффективно анализируются синтаксическим анализатором сдвига-уменьшения (грамматики LALR). Такие эффективные синтаксические анализаторы могут быть сгенерированы генератором синтаксических анализаторов yacc/bison (C, C++) или его эквивалентом Haskell "Happy".

Вот почему я бы использовал «Happy» в вашем случае. Он принимает правила грамматики в форме БНФ и напрямую генерирует парсер. Результирующий синтаксический анализатор примет язык, описанный вашими грамматическими правилами, и создаст AST (абстрактное синтаксическое дерево). Руководство пользователя Happy очень удобно и поможет вам быстро приступить к работе: http://www.haskell.org/happy/doc/html/

Чтобы преобразовать полученный AST, хорошей идеей будет универсальное программирование. Вот классическое объяснение того, как сделать это в Haskell на практике с нуля: http://research.microsoft.com/en-us/um/people/simonpj/papers/hmap/

Я использовал именно это, чтобы создать компилятор для небольшого предметно-ориентированного языка, и это было простое и лаконичное решение.

person linse    schedule 11.09.2013