2017-02-07 25 views
1

У меня есть странный формат входного сигнала вдоль линий:Синтаксических фиксированная ширина ввода с использованием ANTLR4

ACOMAND   1.0  1.0 
ACOMAND 
ACOMAND   1.0 
ACOMAND   1.0  1.0 1300.2     .9  1.0 
ACOMAND   1.0  1.0 1300.2     .9 
ACOMAND   OKK  1.0 1300.2     .9  1.0  WOW 
ACOMAND   1.0  1.0 1300.2 

Каждая из них является командой в своих собственных правах, где отсутствует или пустые столбцы неявно нуль. В основном первая строка выравнивается по левому краю, а все остальные выровнены по правому краю на 20, 30, 40, ..., 80-й столбец. Первый столбец всегда является идентификатором. Все остальные столбцы - это идентификаторы или поплавки. Пустые столбцы (заполненные пробелами или вообще ничего) неявно равны нулю.

Как я могу разобрать это?

Я думал:

grammar WeirdGrammar; 
comm: KEYWORD NEWLINE 
    | KEYWORD COLUMN NEWLINE 
    | KEYWORD COLUMN COLUMN NEWLINE 
    | KEYWORD COLUMN COLUMN COLUMN NEWLINE 
    | KEYWORD COLUMN COLUMN COLUMN COLUMN NEWLINE 
    | KEYWORD COLUMN COLUMN COLUMN COLUMN COLUMN NEWLINE 
    | KEYWORD COLUMN COLUMN COLUMN COLUMN COLUMN COLUMN NEWLINE 
    | KEYWORD COLUMN COLUMN COLUMN COLUMN COLUMN COLUMN COLUMN NEWLINE 
    ; 

KEYWORD: [A-Z] {getCharPositionInLine() == 1}? ([A-Z]|'-')* WS*? {getCharPositionInLine() == 10}? ; 
COLUMN: .+? {(getCharPositionInLine() % 10) == 0}? ; 
NEWLINE : '\r'? '\n' ; 
WS : [ \t] ; 

В принципе идея заключается в том, чтобы справиться с любой комбинацией KEYWORD и COLUMN весь путь от просто KEYWORD к KEYWORD с последующим 7 COLUMN с. Ограничение ширины в COLUMN составляет 10, каждый из них принудительно согласуется с чем угодно, пока не будет равен нулю по модулю CharPosition с 10. Ключевое слово должно начинаться в начале строки, следовательно, первое правило для этого токена, тогда оно должно простираться не дальше, чем на 10-й столбец, следовательно, на второй предикат. Однако в настоящее время это не работает, вместо возвращения:

line 1:0 mismatched input 'ACOMAND   1' expecting KEYWORD 

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

+0

1) Имеет ли значение, чтобы проверить, что ACOMAND начинается в колонке 1 и другие значения выровненный по фиксированное положение, иначе почему бы не просто «ID VALUE *?» 2) Пожалуйста, дайте всю необходимую грамматику, чтобы мы могли ее выполнить. У меня отсутствует WS и «неявное определение токена» – BernardK

+0

1) Да, это имеет значение, к сожалению, поэтому я думаю, что я должен использовать предикаты для обеспечения правильного выравнивания. 2) Я добавил отсутствующую грамматику WS, извиняясь за ее отказ. – rooms

ответ

0

1) Использование ANTLR 4.6 и данная грамматика и вход, у меня есть следующее сообщение:

line 3:0 no viable alternative at input 'ACOMAND 1.0 1.0\nACOMAND\nACOMAND ' 

При отладке грамматики, очень полезно перечислить маркеры, замеченные лексером:

$ echo $CLASSPATH 
.:/usr/local/lib/antlr-4.6-complete.jar 
$ alias grun 
alias grun='java org.antlr.v4.gui.TestRig' 
$ grun Question question -tokens data.txt 
[@0,0:9='ACOMAND ',<KEYWORD>,1:0] 
[@1,10:19='  1.0',<COLUMN>,1:10] 
[@2,20:29='  1.0',<COLUMN>,1:20] 
[@3,30:30='\n',<COLUMN>,1:30] 
[@4,31:38='ACOMAND\n',<COLUMN>,2:0] 

до 4,6, маркеры отображались [@3,30:30='\n',<n>,1:30] и вы должны были посмотреть в файле -grammar-.tokens который лексема имеет номер n. Теперь он прекрасно переводится, и вы сразу видите, что символ новой строки был признан как токен COLUMN, а не NEWLINE, как и ожидалось. Это происходит потому, что лексические пытается сопоставить вход с каждым правилом в последовательности:

  1. делает '\n' матч [A-Z]? Нет, так что это не KEYWORD, следующее правило
  2. '\n' соответствует .+?? Да, так что это COLUMN, никаких шансов достичь правила NEWLINE.

Таким образом, вы должны поместить COLUMN правило послеNEWLINE правил.

Вы также видите, что вторая строка ввода была лексемы, как [@4,31:38='ACOMAND\n',<COLUMN>,2:0], потому что он не может соответствовать

KEYWORD: [A-Z] ... WS*? 

, поскольку правило требует белого пространства и есть только NL.Таким образом, замените WS*? на (WS* | NEWLINE).

Наконец упростить избыточные правила:

grammar Question; 

question 
    : KEYWORD COLUMN* NEWLINE 
    ; 

KEYWORD : [A-Z] {getCharPositionInLine() == 1}? ([A-Z]|'-')* (WS* | NEWLINE) {getCharPositionInLine() <= 10}? ; 
NEWLINE : '\r'? '\n' ; 
WS : [ \t] ; 
COLUMN: .+? {(getCharPositionInLine() % 10) == 0}? ; 

Теперь лексер обеспечивает:

[@0,0:9='ACOMAND ',<KEYWORD>,1:0] 
[@1,10:19='  1.0',<COLUMN>,1:10] 
[@2,20:29='  1.0',<COLUMN>,1:20] 
[@3,30:30='\n',<NEWLINE>,1:30] 
[@4,31:38='ACOMAND\n',<KEYWORD>,2:0] 

.

.

2) Но все это действительно полезно? Является ли генератор парсера правильным инструментом? Удалить один пробел и посмотреть, что происходит:

line 2:0 extraneous input 'ACOMAND\n' expecting {NEWLINE, COLUMN} 

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

Почему не скрипт Ruby? :-)

# Split 80 columns lines into 10 columns wide tokens, associate each token 
# with its stop position in line (counting from 1) and an OK/WRONG flag 
# if it is not aligned correctly. 

tokens = Array.new 

IO.readlines("data.txt").each_with_index do | line, i | 
    if i == 0 
    then 
     puts "   #{line}" 
     next 
    end 

    line_tokens = Array.new 
    line = line.chomp # remove NL 
    print "line #{i + 1} : " 
    8.times.each do | n | # n = 0 to 7 
     a = n * 10  # begin of split range counting from 0 
     b = n * 10 + 9 # end of range 
     token = line.slice(a..b) 
     next if token.nil? || token.length == 0 # nil if edge case 
     print token 
     good_position = 'OK' 
     position  = b + 1 

     case n 
     when 0 # first token must be at column 1 
      good_position = 'WRONG' if token[0] == ' ' 
     else # other tokens must be right aligned in their 10 columns width field 
      if token[-1] == ' ' && token != '   ' # not followed by NL 
      then 
       good_position = 'WRONG' 
       unless (pos = token.rindex(' ')).nil? 
        position = position - 10 + pos - 1 
       end 
      end 
      if token.length != 10 # last in line 
      then 
       good_position = 'WRONG' 
       position = position - 10 + token.length 
      end 
     end 

     line_tokens << [token.strip, position, good_position] 
     break if b > line.length 
    end 
    puts # print a NL because print doesn't do it 
    tokens << line_tokens 
end 

puts 
puts "Lists of tokens : " 
p tokens 

Входной data.txt:

....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8 
ACOMAND   1.0  1.0 
ACOMAND 
ACOMAND   1.0 
ACOMAND   1.0  1.0 1300.2    .9  1.0 
ACOMAND   1.0  1.0 1300.2     .9 
ACOMAND   OKK  1.0 1300.2     .9  1.0  WOW 
ACOMAND   1.0  1.0 1300.2 

Выход:

$ ruby -w split.rb 
     ....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8 
line 2 : ACOMAND   1.0  1.0 
line 3 : ACOMAND 
line 4 : ACOMAND   1.0 
line 5 : ACOMAND   1.0  1.0 1300.2    .9  1.0 
line 6 : ACOMAND   1.0  1.0 1300.2     .9 
line 7 : ACOMAND   OKK  1.0 1300.2     .9  1.0  WOW 
line 8 : ACOMAND   1.0  1.0 1300.2 

Lists of tokens : 
[[["ACOMAND", 10, "OK"], ["1.0", 20, "OK"], ["1.0", 29, "WRONG"]], 
[["ACOMAND", 10, "OK"]], [["ACOMAND", 10, "OK"], ["1.0", 20, "OK"]], 
[["ACOMAND", 10, "OK"], ["1.0", 20, "OK"], ["1.0", 30, "OK"], ["1300.2", 
40, "OK"], ["", 50, "OK"], [".9", 58, "WRONG"], ["1.0", 68, "WRONG"]], 
[["ACOMAND", 10, "OK"], ["1.0", 20, "OK"], ["1.0", 30, "OK"], ["1300.2", 
40, "OK"], ["", 50, "OK"], [".9", 60, "OK"]], [["ACOMAND", 10, "OK"], 
["OKK", 20, "OK"], ["1.0", 30, "OK"], ["1300.2", 40, "OK"], ["", 50, 
"OK"], [".9", 60, "OK"], ["1.0", 70, "OK"], ["WOW", 80, "OK"]], 
[["ACOMAND", 10, "OK"], ["1.0", 20, "OK"], ["1.0", 30, "OK"], ["1300.2", 
40, "OK"]]] 

 Смежные вопросы

  • Нет связанных вопросов^_^