ANTLR получает и разделяет содержимое лексера - PullRequest
1 голос
/ 08 мая 2011

сначала, извините за мой английский, я все еще учусь.

Я пишу модуль Python для моего фреймворка, который разбирает CSS-файлы. Я пробую regex, ply (лексер и парсер python), но я оказался в ANTLR.

Сначала попробуйте, мне нужно разобрать комментарии из файла CSS. Это моя строка CSS для анализа:

/*test*/

/*
test1
/*

/*test2/*nested*/comment/*

Я знаю, что CSS не допускает вложенные комментарии, но мне это нужно в моей среде. Я написал простую грамматику ANTLR:

grammar CSS;

options {
    language = Python;
}

styleSheet
    : comments EOF ;

comments
    : NESTED_ML_COMMENT*
    ;

NESTED_ML_COMMENT
    :   '/*' 
        (options {greedy=false;} : (NESTED_ML_COMMENT | . ) )* 
        '*/' 
    ;

LINEBREAK 
    :  ('\n\r' | '\n')+{$channel=HIDDEN; };

Что я получаю в результате:

enter image description here

Что я ожидаю (покрасочные работы: D):

enter image description here

Обратите внимание, что я не хочу / * и * / в результате.

Есть ли способ сделать это в чистом ANTLR? У меня нет проблем с использованием Python в ANTLR, но если есть какой-либо способ сделать это без Python, я буду благодарен.

Ответы [ 2 ]

2 голосов
/ 09 мая 2011

Нет, простого пути нет. Поскольку NESTED_ML_COMMENT является правилом лексера («простым» токеном), вы не можете позволить правилу синтаксического анализатора создавать больше структуры в источнике, например /*test2/*nested*/comment*/: правила лексера всегда будут оставаться «плоской» последовательностью символов. Конечно, есть (простые) способы переписать эту последовательность символов (например, удалить /* и */), но создать иерархию родительских элементов, нет.

Чтобы создать иерархию, подобную той, которая отображается на вашем изображении 2 и , вам нужно будет "продвинуть" свое правило комментария в синтаксический анализатор (поэтому сделайте его правилом синтаксического анализатора). В этом случае ваш лексер будет иметь правило COMMENT_START : '/*'; и COMMENT_END : '*/';. Но это открывает банку с червями: внутри вашего лексера теперь вам также необходимо учитывать все символы, которые могут находиться между /* и */.

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

Быстрое демо. Грамматика:

grammar T;

parse
  :  comment EOF 
  ;

comment
  :  COMMENT_START (ANY | comment)* COMMENT_END
  ;

COMMENT_START : '/*';
COMMENT_END   : '*/';
ANY           :  . ;

проанализирует источник /*test2/*nested*/comment*/ в следующем дереве разбора:

enter image description here

, который вы можете переписать так, чтобы /* и */ были удалены, конечно.

Внутри вашей CSS грамматики вы затем делаете:

comment
  :  NESTED_ML_COMMENT 
     {
       text = $NESTED_ML_COMMENT.text
       # invoke the TParser (my demo grammar) on `text`
     }
  ;

EDIT

Обратите внимание, что ANTLRWorks создает свое собственное внутреннее дерево разбора, к которому у вас нет доступа. Если вы не скажете ANTLR сгенерировать правильный AST, вы просто получите плоский список токенов (хотя ANTLRWorks предполагает, что это какое-то дерево).

Вот предыдущие вопросы и ответы, которые объясняют, как создать правильный AST: Как вывести AST, построенный с использованием ANTLR?

Теперь вернемся к грамматике «комментарий», которую я разместил выше. Я переименую правило ANY в TEXT. На данный момент это правило соответствует только одному символу за раз. Но удобнее, чтобы оно совпадало до следующего /* или */. Это может быть сделано путем введения простого метода Python в класс лексера, который выполняет эту проверку. Внутри правила TEXT мы будем использовать этот метод внутри предиката, чтобы * сопоставлялось, если за не непосредственно следует /, а / сопоставляется, если оно не , за которым сразу следует *:

grammar Comment;

options {
  output=AST;
  language=Python;
}

tokens {
  COMMENT;
}

@lexer::members {
  def not_part_of_comment(self):
    current = self.input.LA(1)
    next = self.input.LA(2)
    if current == ord('*'): return next != ord('/')
    if current == ord('/'): return next != ord('*')  
    return True
}

parse
  :  comment EOF -> comment
  ;

comment
  :  COMMENT_START atom* COMMENT_END -> ^(COMMENT atom*)
  ;

atom
  :  TEXT
  |  comment
  ;

COMMENT_START : '/*';
COMMENT_END   : '*/';
TEXT          : ({self.not_part_of_comment()}?=> . )+ ;

Узнайте больше о синтаксисе предикатов, { boolean_expression }?=>, в этих вопросах и ответах: Что такое «семантический предикат» в ANTLR?

Чтобы проверить все это, убедитесь, что у вас установлены правильные библиотеки времени выполнения Python (см. ANTLR Wiki ). И обязательно используйте ANTLR версии 3.1.3 с этой средой выполнения.

Сгенерируйте лексер и парсер следующим образом:

java -cp antlr-3.1.3.jar org.antlr.Tool Comment.g 

и протестируйте лексер и парсер с помощью следующего скрипта Python:

#!/usr/bin/env python

import antlr3
from antlr3 import *
from antlr3.tree import *
from CommentLexer import *
from CommentParser import *

# http://www.antlr.org/wiki/display/ANTLR3/Python+runtime
# http://www.antlr.org/download/antlr-3.1.3.jar

def print_level_order(tree, indent):
  print '{0}{1}'.format('   '*indent, tree.text)
  for child in tree.getChildren():
    print_level_order(child, indent+1)

input = '/*aaa1/*bbb/*ccc*/*/aaa2*/'
char_stream = antlr3.ANTLRStringStream(input)
lexer = CommentLexer(char_stream)
tokens = antlr3.CommonTokenStream(lexer)
parser = CommentParser(tokens)
tree = parser.parse().tree 
print_level_order(tree, 0)

Как видно из источника "/*aaa1/*bbb/*ccc*/*/aaa2*/", создается следующий AST:

COMMENT
   aaa1
   COMMENT
      bbb
      COMMENT
         ccc
   aaa2

РЕДАКТИРОВАТЬ II

Я также не против показать, как вы можете вызывать анализатор комментариев из вашей грамматики CSS. Вот короткая демонстрация:

grammar CSS;

options {
  output=AST;
  language=Python;
}

tokens {
  CSS_FILE;
  RULE;
  BLOCK;
  DECLARATION;
}

@parser::header {
import antlr3
from antlr3 import *
from antlr3.tree import *
from CommentLexer import *
from CommentParser import *
}

@parser::members {
  def parse_comment(self, text):
    lexer = CommentLexer(antlr3.ANTLRStringStream(text))
    parser = CommentParser(antlr3.CommonTokenStream(lexer))
    return parser.parse().tree 
}

parse
  :  atom+ EOF -> ^(CSS_FILE atom+)
  ;

atom
  :  rule
  |  Comment -> {self.parse_comment($Comment.text)}
  ;

rule
  :  Identifier declarationBlock -> ^(RULE Identifier declarationBlock)
  ;

declarationBlock
  :  '{' declaration+ '}' -> ^(BLOCK declaration+)
  ;

declaration
  :  a=Identifier ':' b=Identifier ';' -> ^(DECLARATION $a $b)
  ;

Identifier
  :  ('a'..'z' | 'A'..'Z') ('a'..'z' | 'A'..'Z' | '0'..'9')*
  ;

Comment
  :  '/*' (options {greedy=false;} : Comment | . )* '*/'
  ;

Space
  :  (' ' | '\t' | '\r' | '\n') {$channel=HIDDEN;}
  ;

Если вы анализируете источник:

h1 {  a: b;  c: d;}

/*aaa1/*bbb/*ccc*/*/aaa2*/

p {x  :  y;}

с помощью CSSParser вы получите следующее дерево:

CSS_FILE
   RULE
      h1
      BLOCK
         DECLARATION
            a
            b
         DECLARATION
            c
            d
   COMMENT
      aaa1
      COMMENT
         bbb
         COMMENT
            ccc
      aaa2
   RULE
      p
      BLOCK
         DECLARATION
            x
            y

как вы видите, запустив этот тестовый скрипт:

#!/usr/bin/env python

import antlr3
from antlr3 import *
from antlr3.tree import *
from CSSLexer import *
from CSSParser import *

def print_level_order(tree, indent):
  print '{0}{1}'.format('   '*indent, tree.text)
  for child in tree.getChildren():
    print_level_order(child, indent+1)

input = 'h1 {  a: b;  c: d;}\n\n/*aaa1/*bbb/*ccc*/*/aaa2*/\n\np {x  :  y;}'
char_stream = antlr3.ANTLRStringStream(input)
lexer = CSSLexer(char_stream)
tokens = antlr3.CommonTokenStream(lexer)
parser = CSSParser(tokens)
tree = parser.parse().tree 
print_level_order(tree, 0)
0 голосов
/ 08 мая 2011

Вы должны использовать подсказки ! и ^ AST.Чтобы /* не появлялся в вашем AST, поставьте ! после него.Чтобы определить, какие элементы становятся корнями поддеревьев AST, добавьте ^.Это может выглядеть примерно так:

NESTED_ML_COMMENT
:   COMMENT_START!
    (options {greedy=false;} : (NESTED_ML_COMMENT^ | . ) )* 
    COMMENT_END!
;

Вот вопрос конкретно об этих операторах, которые теперь, когда вы знаете, существуют, надеюсь, будут полезны: Что значит ^ и!обозначать грамматику ANTLR

...