ANTLR (или альтернатива): отделение анализа от оценки - PullRequest
11 голосов
/ 12 октября 2010

У меня есть относительно простой DSL, который я хотел бы обрабатывать более надежно, чем набор java.util.regex.Pattern операторов + логика синтаксического анализа, кодированных вручную.

Наиболее цитируемым инструментом, по-видимому, является ANTLR.Я не знаком с этим и готов попробовать.Однако, когда я смотрю на примеры, мне становится немного подозрительнее (например, пример оценщика выражений ANTLR или HelloAntlr Мартина Фаулера или этого другого Q для stackoverflow ),Причина этого заключается в том, что файлы грамматики выглядят так, как будто они являются мешаниной определений грамматики, вкрапленных фрагментами языка реализации (например, Java), которые являются обязательными по своей природе.

Что я действительно предпочел бы, так это выделитьимператив / оценка часть парсера.Есть ли способ использовать ANTLR (или какой-либо другой инструмент) для определения грамматики и создания набора исходных файлов Java, чтобы он компилировался в классы, которые я могу использовать для анализа ввода в структуру без воздействия на эту структуру?

например, если я хотел использовать вычисление выражений только с операторами + и * и (), и у меня был вход

3 * (4 +7 * 6) * (3 + 7 * (4 + 2))

, тогда я хотел бы написать грамматику для преобразования ее в иерархическую структуру, такую ​​как

Product
  Term(3)
  Sum
     Term(4)
     Product
        Term(7)
        Term(6)
  Sum
     Term(3)
     Product
        Term(7)
        Sum
            Term(4)
            Term(2)

, где я могу использовать классы типа

interface Expression<T> {
    public T evaluate();
}

class Term implements Expression<Double> {
    final private double value;
    @Override public Double evaluate() { return value; }
}

class Product implements Expression<Double> {
    final private List<Expression<Double>> terms;
    @Override public Double evaluate() {
        double result = 1;
        for (Expression<Double> ex : terms)
            result *= ex.evaluate();
        return result;
    }
}

class Sum implements Expression<Double> {
    final private List<Expression<Double>> terms;
    @Override public Double evaluate() {
        double result = 0;
        for (Expression<Double> ex : terms)
            result += ex.evaluate();
        return result;
    }
}

и использовать ANTLR для построения структуры.Есть ли способ сделать это?Я действительно предпочел бы придерживаться этого подхода, поскольку он позволяет мне (и другим разработчикам программного обеспечения) редактировать и визуализировать полные классы Java без необходимости фрагментации этих классов на странные фрагменты в файлах грамматики ANTLR.

Есть ли способсделать это?


уточнение: Я хочу потратить как можно больше своих усилий двумя способами: определением самой грамматики и независимой от ANTLR Java (например, мой продукт/ Сумма / Срок занятий).Я хочу минимизировать количество времени / опыта, которое я должен потратить на изучение синтаксиса ANTLR, причуд и API.Я не знаю, как создавать и управлять AST из грамматики ANTLR.Поскольку это лишь небольшая часть большого Java-проекта, не только я, но и кто-то в моей команде должен проверять или поддерживать мой код.

(я не хочу показаться дерзким: я 'Я готов потратить время и силы на использование инструмента, но только если инструмент становится полезным инструментом и не становится камнем преткновения.)

Ответы [ 3 ]

11 голосов
/ 12 октября 2010

Джейсон С писал:

Есть ли способ сделать это?

Да.

Сначала определите свойграмматика (я взял ваш пример синтаксического анализатора выражений только с операторами + и * и ()):

grammar Exp;

// parser rules
parse
  :  additionExp
  ;

additionExp
  :  multiplyExp (Add multiplyExp)*
  ;

multiplyExp
  :  atomExp (Mult atomExp)* 
  ;

atomExp
  :  Number
  |  LParen additionExp RParen
  ;

// lexer rules
Add    : '+' ;
Mult   : '*' ;
LParen : '(' ;
RParen : ')' ;   
Number : ('0'..'9')+ ('.' ('0'..'9')+)? ;
Spaces : (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;} ;

Если вы хотите, чтобы ANTLR генерировал правильный AST из приведенной выше грамматикиВы должны поместить следующее в верхнюю часть вашей грамматики (под объявлением грамматики):

options { 
  output=AST; 
}

, и вы должны указать, каким должен быть корень каждого из ваших правил синтаксического анализа.Это можно сделать двумя способами:

  1. , используя правила перезаписи ;
  2. или поместив один из «встроенных операторов дерева» ^ и! после токенов:
    • ^ означает: сделать этот токен корнем ;
    • ! означает: исключить этот токен из AST.

Теперь ваша грамматика будет выглядеть следующим образом:

grammar Exp;

options { 
  output=AST; 
}

// parser rules
parse
  :  additionExp
  ;

additionExp
  :  multiplyExp (Add^ multiplyExp)*
  ;

multiplyExp
  :  atomExp (Mult^ atomExp)* 
  ;

atomExp
  :  Number
  |  LParen! additionExp RParen!
  ;

// lexer rules
Add    : '+' ;
Mult   : '*' ;
LParen : '(' ;
RParen : ')' ;   
Number : ('0'..'9')+ ('.' ('0'..'9')+)? ;
Spaces : (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;} ;

Как видите, я сделал Add и Mult корни и исключили скобки.

Теперь сгенерируйте лексер и парсер из грамматики:

java -cp antlr-3.2.jar org.antlr.Tool Exp.g 

создайте небольшой тестовый набор:

import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import java.util.*;

public class Main {

    private static void preOrder(CommonTree tree, int depth) {
        for(int i = 0; i < depth; i++) {
            System.out.print("- ");
        }
        System.out.println("> "+tree + " :: " + ExpParser.tokenNames[tree.getType()]);
        List children = tree.getChildren();
        if(children == null) return;
        for(Object o : children) {
            preOrder((CommonTree)o, depth+1);
        }
    }

    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("3 * (4 + 7 * 6) * (3 + 7 * (4 + 2))");
        ExpLexer lexer = new ExpLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpParser parser = new ExpParser(tokens);
        CommonTree tree = (CommonTree)parser.parse().getTree();
        preOrder(tree, 0);
    }
}

скомпилируйте все:

javac -cp antlr-3.2.jar *.java

и запустите класс Main:

// *nix/Mac OS
java -cp .:antlr-3.2.jar Main

// Windows
java -cp .;antlr-3.2.jar Main

, который выдает следующее:

> * :: Mult
- > * :: Mult
- - > 3 :: Number
- - > + :: Add
- - - > 4 :: Number
- - - > * :: Mult
- - - - > 7 :: Number
- - - - > 6 :: Number
- > + :: Add
- - > 3 :: Number
- - > * :: Mult
- - - > 7 :: Number
- - - > + :: Add
- - - - > 4 :: Number
- - - - > 2 :: Number

Как видите, parseПравило (метод) возвращает объект CommonTree, который можно использовать для создания собственного посетителя / посетителя, оставляя грамматику такой, как .

HTH

3 голосов
/ 12 октября 2010

Как насчет использования ANTLR AST (абстрактного синтаксического дерева) и построения зеркального дерева с вашими классами, посещая каждый узел дерева.

@ Джузеппе Кардоне добавил несколько замечательных ссылок, которые я публикую здесь:

http://www.antlr.org/article/1100569809276/use.tree.grammars.tml

http://www.antlr.org/article/1170602723163/treewalkers.html

Пример можно найти по адресу:

http://sagarsunkle.spaces.live.com/blog/cns!E07F3B561597E4EE!664.entry?sa=97619042

2 голосов
/ 12 октября 2010

Примеры, о которых вы упоминаете, для краткости встраивают действия парсера прямо в грамматику. Это отлично работает для небольших проектов. Для более крупных вы предпочитаете сначала сделать AST, а затем делать с ним все, что захотите. Вы можете сделать это, хе-хе, встраивая действия, которые создают дерево, но antlr обеспечивает более хороший декларативный способ:

http://www.antlr.org/wiki/display/ANTLR3/Tree+construction

Затем вы можете использовать древовидную грамматику для генерации кода, например, с помощью StringTemplate. Я использовал этот набор инструментов для своей диссертации, и он работал как шарм. Но держу пари, я бы сильно пострадал, не имея справочника Anlr3 (http://pragprog.com/titles/tpantlr/the-definitive-antlr-reference)

Мне также показались полезными примечания к лекциям на странице antlr: http://www.antlr.org/wiki/display/CS652/CS652+Home

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

...