Правила звуко буквенный разбор: Звуко буквенный анализ слова-пять — ответ на Uchi.ru

Разбор

— конфликт правил синтаксического анализатора ANTLR4

Ниже приведен отрывок из файла, который я пытаюсь разобрать:

 Тип раздела Символы:
агент, сообщение, факт, nat, protocol_id, набор
подпись раздела:
//Объявления супертипа
сообщение > агент
сообщение > нат
сообщение > идентификатор_протокола
...
//Объявления функций
пара: сообщение * сообщение -> сообщение
крипта: сообщение * сообщение -> сообщение
inv : сообщение -> сообщение
...
// Объявления предикатов
iknows : сообщение -> факт
содержит: сообщение * набор -> факт
свидетель: агент * агент * идентификатор_протокола * сообщение -> факт
...
 

Остальные файлы разбираются нормально, проблемы только с первыми двумя разделами. Таким образом, правила, которые должны обрабатывать это:

 //Начальное правило, остальные разделы не учитываются, потому что они работают
программа: типсимволыраздел подписьраздел;
//Приведенные ниже правила предназначены для обработки раздела Type Symbols
typesymbolssection : 'раздел typeSymbols:' typelist;
список типов : тип (',' тип)*;
тип : if_type | if_operator '('список типов')' | '{' список констант '}';
//здесь должна иметь значение только первая альтернатива.
//остальные используются только в другом месте if_type : Const_ident; Const_ident : [az][A-Za-z0-9_]*; //Приведенные ниже правила предназначены для обработки раздела подписи сигнатурный раздел: 'подпись раздела:' сигнатурный раздел0; раздел подписи0 : объявление супертипа* объявление функции* предварительное объявление*; объявление супертипа: if_type '>' if_type; Объявление функции: if_operator ':' typestar '->' type; predicateddeclaration : if_operator ':' typestar '->' 'факт'; типстар : тип | введите '*' typestar;

Эта грамматика по большей части правильно анализирует файл, за исключением одной небольшой ошибки. Выдает ошибку: «Нет жизнеспособной альтернативы при вводе« факт »». Это относится к первому факту в разделе Type Symbols. Это дает следующее дерево разбора:

 типсимволыраздел
   |
   |---->
"Тип разделаСимволы:" | |----> список типов | |---> тип ---> if_type ---> агент |---> тип ---> if_type ---> сообщение |---> тип ---> факт //Выделено красным из-за ошибки синтаксического анализа |---> тип ---> if_type ---> nat |---> тип ---> if_type ---> protocol_id |---> тип ---> if_type ---> установить

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

from:

 predicateddeclaration : if_operator ':' typestar '->' 'факт';
 

to:

 predicateddeclaration : if_operator ':' typestar '->' type;
 

, затем он запускается без каких-либо ошибок, но не описывает должным образом то, что я делаю, потому что тогда все объявления предикатов читаются как объявления функций, и он ошибочно считает, что нет определений предикатов (поскольку в этот момент определения для функций и предикаты будут одинаковыми). Что делает его предикатом, а не функцией, так это то, что он соответствует факту. Но если я уточню это, указав в кавычках, что это должно перейти к «факту», то это сработает в разделе typeSymbols. Для меня это не имеет смысла, так как они даже не родственники. Он выясняет, что это в правиле типа, а затем отказывается от ввода «факта» вместо того, чтобы передавать его в Const_ident, как другие.

Мой вопрос: почему это имеет значение и как я могу это исправить, чтобы избежать обеих ошибок? Я не понимаю, почему переход от «типа» к «факту» должен иметь значение в первом разделе. Пока что я застрял там, где у меня есть та или иная ошибка, и я бьюсь об это головой в течение трех дней. Будем признательны за любую помощь…

Руководство для начинающих по написанию DSL на Ruby

В этой статье я хочу описать, как написать DSL/парсер на Ruby с помощью парсер верхушки дерева .

Преимущество написания грамматики и синтаксического анализа на Ruby заключается в интерактивности. Ruby интерпретируется и имеет очень быстрое время запуска.

Для начала я начну писать парсер JSON. Это может показаться странным, поскольку парсеры JSON уже существуют, но я не пытаюсь их заменить, а скорее показываю концепции в этой строке, а не простую грамматику.

В конце я также познакомлю вас с грамматикой более сложного DSL для создания триггеров предупреждений.

Чтобы ознакомиться с грамматикой, посетите JSON.org.

Парсер Ruby

Для Ruby я выбрал парсер treetop , который представляет собой грамматику синтаксического анализа (PEG).

Грамматики в нем выглядят так:

Базовая настройка файла грамматики.

 грамматика Grammarname  (1) 
  правило FirstRule  (2) 
   ..проекции..
  конец
  правило NextRule
   ..проекции..
  конец
конец 

1 Имя грамматики определяет имя парсера. См. ниже
2 Первое правило является отправной точкой грамматики.

Для нашего парсера Json верхняя часть файла json-simple.treetop будет выглядеть так:

 # Грамматика верхушки дерева для JSON
грамматика JSONGrammar
  правило json  (1) 
    массив/объект 
(2)
конец

1 Верхнее правило, синтаксический анализ начинается здесь
2 Содержимое правила, используемого для сопоставления

Таким образом, объект Json будет либо массивом , либо объектом . Поскольку оба они не являются терминальными символами в простом символе или строке (например, 'это' ), для решения этой проблемы необходимы дополнительные правила. Мы рассмотрим их ниже.

Прежде чем мы продолжим изучение грамматики, давайте посмотрим, как скомпилировать грамматику и использовать ее.

Компиляция грамматики

Treetop предоставляет инструмент командной строки под названием tt , который компилирует грамматику в код Ruby.

Составление грамматики с использованием

тт.

 $ tt json-simple.treetop  (1) 
$ лс json.rb  (2) 
json.rb 

1 Входной файл грамматики
2 Результирующий синтаксический анализатор Ruby

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

Здесь вы видите, как драйвер синтаксического анализатора может загрузить определение и создать экземпляр синтаксического анализатора.

json_parser.rb

 требуется «верхушка дерева»
класс JsonParser
  Treetop.load(File.join(__dir__, 'json-simple.treetop'))
  @@parser = JSONGrammarParser.new
  def self.parse (данные, env = {})
    # Передаем данные в экземпляр парсера
    дерево = @@parser.parse(данные)
    # Если AST равен нулю, значит во время синтаксического анализа произошла ошибка
    # нам нужно сообщить простое сообщение об ошибке, чтобы помочь пользователю 

Ключевой частью являются эти две строки:

 Treetop.load(File.join(base_path, 'json-simple.treetop'))  (1) 
  @@parser = JSONGrammarParser.new  (2)  
1 Мы загружаем файл грамматики .treetop
2 Имя парсера определяется из грамматики

Имя нашего парсера определяется из грамматика инструкция путем добавления Parser к указанному там имени грамматики.

Давайте попробуем нашу грамматику.

Проверка грамматики с помощью irb

 $ irb
2.3.0: 001 > require_relative 'json_parser'  (1) 
 => правда
2.3.0:002 > JsonParser.parse '{}'  (2) 
NameError: неопределенная локальная переменная или метод `_nt_array' для #
        from (eval):21:in `_nt_json' 

1 Загрузить анализатор
2 Попытаться разобрать пустой объект

Вы видите, что синтаксический анализ завершается с ошибкой с исключением о неизвестном методе _nt_array . Если вы посмотрите на приведенную выше грамматику, вы увидите, что мы сказали, что объект json — это либо массив , либо объект , но нигде их не определяли.

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

_nt_array отсутствует в _nt_json или, другими словами, правило json использует нетерминал ( _nt ) с именем array , который не определен.

СОВЕТ Поскольку грамматика оценивается слева направо, это также означает, что сообщения об ошибках выводятся слева направо.

 

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

Продолжаем грамматику

Итак, продолжим грамматику.

Определение массива .

 массив правил
    '[' пробел a_value? (пробел ',' пробел a_value)* пробел ']'
  end 

В этом примере вы найдете две новые вещи терминалы , заключенные в одинарные кавычки, такие как открывающая и закрывающая скобки
, '[' и ']' . Парсер ожидает такой символ или ключевое слово буквально в этом месте.

Итак, для вышеизложенного массив должен начинаться с символа [ и заканчиваться ] для распознавания.

Другая новая концепция, которую вы здесь видите, — это квантификаторы, которые вы, вероятно, уже знаете из
регулярных выражений:

  • ? Элемент может отображаться ноль или один раз
  • + Элемент должен появиться хотя бы один раз (не показано выше)
  • * Предмет можно давать любое количество раз (включая ноль)

И последнее, но не менее важное: элементы могут быть сгруппированы вместе с помощью скобок

( и ).

Итак, массив должен начинаться с [ имеет пробел правило, ноль или одно значение и затем ноль или много раз блок, который состоит из пробела, буквального двоеточия, пробела и значения.

Теперь давайте посмотрим на a_value .

Определение a_value.

 правило a_value
    объект/массив/строка/число/истина/ложь/нуль
  конец 

Здесь вы видите косую черту /, что означает или . Таким образом, значение может быть объектом, массивом, строкой, числом или одним из значений true, false и null. Поскольку treetop является генератором синтаксического анализа PEG, он будет оценивать список слева направо.

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

Группировка подвыражений.

 правило eval_boolean
    логическое ('и' / 'или') логическое значение
  end 

Последний отрывок из грамматики — пробел .

Определение пространства .

 пространство правил
    [\s\n\t\r]*
  end 

Здесь вы видите, что пробел состоит из нуля или более пробелов, символов новой строки или табуляции.

СОВЕТ Полная грамматика до этого момента доступна в файле json-simple. treetop .

Генерация значений из ввода

До этого момента у нас была грамматика, которая может анализировать ввод JSON, но для того, чтобы синтаксический анализатор был полезен, мы на самом деле хотим преобразовать ввод в объект, содержащий вложенные списки и объекты.

Возможно, вы видели выше, что синтаксический анализатор возвращает сгенерированное им абстрактное синтаксическое дерево (AST):

 tree = @@parser.parse(data) ходить. Но есть лучший способ сделать это в верхушке дерева. 

Отдельные узлы грамматики могут предоставлять методы, которые можно вызывать во время анализа. Есть несколько вариантов сделать это — я покажу два.

Встроенный в грамматику

В этом случае функция находится непосредственно внутри файла .treetop . Это хорошо для очень простой функции, но в остальном скорее запутывает, чем помогает. Кроме того, даже если это код Ruby, ваша IDE, скорее всего, вам не поможет.

 правило val_in_parens
    '(' строка ')'  (1) 
    {  (2) 
      значение по умолчанию  (3) 
        элементы[1].text_value  (4) 
      конец
    }
  конец 
1 Обычное соответствие
2 Определение встроенной функции ограничено { и }
3 Имя функции может быть любым с параметрами и без них.
4 Оценка текущего узла, см. ниже

  Даже если имя и подпись функции произвольны, они должны быть известны потенциальному вызывающему
узлу.

Через Mixin

Здесь мы выносим функции в модуль и предоставляем только указатель на него внутри грамматики
.

 правило val_in_parens
    '(' строка ')'   (1) 
  конец 

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

Затем у нас есть файл Ruby, который содержит определение:

parser_extensions.rb

 модуль ValueNode
  значение по умолчанию
    элементы[1].text_value
  конец
end 

Вы видите, что это то же самое, что и внутри грамматики, но вы получаете всю помощь от IDE. Сейчас нужно хоть до требуется этот дополнительный файл от нашего драйвера парсера. json_parser.rb

json_parser.rb

 # Наш JsonParser
require_relative 'parser_extensions'
требуется «верхушка дерева»
class JsonParser 

Оценка от драйвера синтаксического анализатора

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

json_parser. rb

 def self.parse(data, env = {})
    # Передаем данные в экземпляр парсера
    дерево = @@parser.parse(данные)
[...]
    # вычисляем хэш и возвращаем его
    дерево.значение
  конец 

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

  В нашем случае это не требуется, так как наше правило json отклоняет только массив или объект и, таким образом, не добавляет никакого значения (каламбур).
2.3.0:003 > JsonParser.parse '{}'
NoMethodError: неопределенное значение метода для # 

Теперь большой вопрос: как мы можем получить доступ к элементам в правиле, чтобы оценить их?

Внутренне проанализированный результат выводится в Ruby-массив с именем elements . Данные начинаются с индекса 0.

Иногда это немного сложно, и я добавил позиции элементов под совпадениями.

 правило kv_pair
     строковое пространство? ':' космос? ценность
# 0 1 2 3 4
  end 

Чтобы получить ключ (строка ), вы можете использовать elements[0] и elements[4] для значения. Таким образом, наша функция value для kv_pair может выглядеть следующим образом:

 значение def (родительский)
    родитель[элементы[0].value.to_sym] = элементы[4].значение
  end 

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

Сгенерированный код в анализаторе.

 модуль KvPair0
    строка определения
      элементы[0]
    конец
    защита a_value
      элементы[4]
    конец
  конец 

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

 значение по умолчанию (родительский)
    родитель[string. value.to_sym] = a_value.value
  end 

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

Теперь предположим, что вам нужна еще одна строка для ввода. Затем Treetop назвал бы их string1 и строка2 . Более элегантное решение состоит в том, чтобы «пометить» эти входные правила нужным псевдонимом, например, правило

 kv_pair.
     ключ: строка пробел ':' пробел a_value
  end 

Чтобы вы могли получить доступ к ключевой строке как key :

parser_extensions.rb

 модуль KVNode
  значение определения (родительское)
    родитель[key.value.to_sym] = a_value.value
  конец
end 

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

  Treetop v1.6.8, по-видимому, предоставляет псевдонимы только для необходимых нетерминальных символов. В случаях, когда у вас есть необязательные символы, вам все равно нужно вернуться к тегам.

Обработка неверного ввода

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

json_parser.rb

 # Если AST равен нулю, значит при синтаксическом анализе произошла ошибка
    # нам нужно сообщить простое сообщение об ошибке, чтобы помочь пользователю
    если(дерево.ноль?)
      помещает "Ввод: >>|#{данные}|<<"
      помещает @@parser.terminal_failures.join("\n")
      поднять JsonParserException, "Ошибка синтаксического анализа: #{@@parser.failure_reason}"
    конец
    # вычисляем хэш и возвращаем его
    tree. value 

Давайте посмотрим, как это выглядит в irb.

Ошибка синтаксического анализатора, как показано в irb

>> require_relative 'json_parser'
=> правда
>> JsonParser.parse '{х'
Ввод: >>|{ х|<<
Ожидается соответствие строки [\s\n\t].
Ожидается соответствие строки '"'.
JsonParserException: ошибка синтаксического анализа: ожидается одно из [\s\n\t\r], '"' в строке 1, столбце 3 (байт 3) после {
из /Users/hrupp/src/dsl_writing/ruby/json_parser.rb:21:in `parse'
from (irb):2 

Работая в собственном коде, вы можете изменить обработку ошибок по своему вкусу.

Ловушки

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

Слишком много места

Допустим, следующая грамматика

 объект правила
    '{' пробел kv_pair? космос '}'
  конец
  правило kv_pair
     пробел строка пробел ':' пробел значение пробел
  end 

Анализ или заполнение массива из элементов может привести к неожиданному сбою. Анализатор не может
определить, какому элементу должны соответствовать различные символы пробела. Это становится немного более очевидным, когда мы «вставляем» kv_pair `объявление в `объект one:

 объект правила
    '{' пробел пробел строка пробел ':' пробел значение пробел пробел '}' 
№ 0 1 2 3 4 5 6 7 8 9 10
  end 

В позициях 1,2 и 8,9 каждый раз, когда анализатору предлагается (жадно) потреблять пустое пространство. Можно было бы возразить, что это делают позиции 1 и 8, а позиции 2 и 9 могут быть нерабочими, но, по-видимому, это не так.

Решением может быть удаление окружающих пробел в kv_pair :

 правило kv_pair
     строка пробел ':' значение пробела
  end 

Смежные вещи, которые не должны быть

Возьмите этот фрагмент грамматики:

 массив правил
    '[' ценность?']'
  end 

Treetop не сможет скомпилировать грамматику, так как между a_value нет пробела? и ']' , поэтому выражение не имеет для него смысла.

Забытая группировка

В следующем фрагменте у нас есть два aBool , которые можно связать вместе через 'и' или 'или' .

 правило eval_boolean
    aBool 'и' / 'или'
  end 

В этом случае Treetop попытается сопоставить aBool 'и' или 'или' aBool , что в большинстве случаев не соответствует нашим намерениям. Чтобы исправить это, нам нужно поместить альтернативы в группу.

 правило eval_boolean
    aBool ('и' / 'или')
  end 

Именование вещей

Treetop будет внутренне предоставлять псевдонимы для совпадений, как мы видели ранее. Возьмем этот пример:

 объект правила
    '{' строка ':' значение '}' 

Будет создан псевдоним "значение". Теперь, когда вы определяете для нее функцию со значением , вы получите
тихое столкновение.

 объект правила
    '{' строковое значение '}'
    {
       def value 

Лучше всего называть свои функции так, чтобы вы никогда не называли правила или элементы грамматики.

Просмотр DSL для создания триггера оповещения

Проект Hawkular также имеет компонент оповещения, который можно использовать для информирования пользователя о выполнении условий. Это может быть, когда определенные значения превышают пороговое значение, когда строка совпадает или доступность ресурса снижается. Определение предупреждения 9Триггер 0031 с его условиями и т. д. выполняется через REST-api.

С помощью HawkFX, моего любимого проекта, я создал проводник для Hawkular. И в связи с этим я также начал добавлять DSL для определения триггера. Я также написал об этом в блоге.

Я не буду приводить здесь весь код, а приведу несколько выдержек, чтобы дать вам некоторое представление о том, как может выглядеть более сложная грамматика. Полный код доступен в репозитории HawkFX на GitHub.

Некоторые примеры

Триггер доступности с высокой серьезностью, которая изначально отключена

 определить триггер "MyTrigger"  (1) 
  инвалид  (2) 
  серьезность ВЫСОКИЙ  (3) 
  (доступность "mymetric" НЕТ)  (4)  

1 Мы определяем новый триггер с именем MyTrigger
2 Он отключен во время создания
3 При срабатывании оповещения серьезность оповещения высокая
4 Срабатывает, когда доступность mymetric недоступна.

 

Триггер, который срабатывает при выполнении двух условий

 определить триггер "MyTrigger"
  И(  (1) 
    ( пороговый счетчик "mycount" < 5 )  (2) 
    ( строка "mymetric" CO "ERROR" )  (3) 
  ) 

1 Триггер срабатывает только при соблюдении всех условий
2 Условие истинно, если метрика счетчика 'mycount' меньше 5
3 Это условие выполняется, если строковая метрика 'mymetric' содержит строку 'ERROR'

Триггер, отключающийся после срабатывания

 определить триггер "MyTrigger"
 (порог "myvalue" > 3)
 автоматическое отключение  (1)  

1 При наличии этого ключевого слова триггер автоматически отключается после срабатывания.

Грамматика

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

Запустить правило для определения триггера

 определить правило
     «определить триггер» пространство? имя: строковое пространство?  (1) 
     отключено: отключено? космос?
     сев: серьезность? космос?
     я сделал? космос? космические условия? действие: действие?  (2) 
     ае: auto_enable? космос? объявление: auto_disable? космос?
       (3) 
   конец 
1 «определить триггер» необходимо записать как есть
2 Нам нужны условия. Почти все остальное необязательно
3 Оценка происходит в модуле DefineNode

Правило для disabled довольно простое и просто проверяет наличие ключевого слова 'disabled'.

Правило отключено

 правило отключено
     'неполноценный'
   end 

Внутри кода Ruby запрашивается метод значения для модуля DefineNode :

 модуль DefineNode
  значение определения (env = {})
    t = Hawkular::Alerts::Trigger.new({})  (1) 
    t.enabled = true, если disabled.empty?  (2) 
    t.name = name.value env
[..] 
1 Объект триггера, который мы хотим подготовить
2 Проверяем, установлено ли отключено .

Помните выше, что disabled доступен в качестве переменной для использования, потому что мы установили его в правиле определить через отключено: отключено? .

Правила условий

 правила условий
    conds:(and_cond/or_cond/условие) <УзелУсловий>
  end 

Условия могут быть либо одним определением, либо условиями, объединенными операторами «И» или «ИЛИ». Далее идет правило
для случая 'И':

Правила для союзов

 правило and_cond
    'И(' пробел? первый:условие больше:( пробел условие )* пробел? ')' 
  end 

Здесь вы видите прием, который часто используется: мы разбираем первое условие в 9Сначала 0080: условие , а затем определите остальные условия в виде списка любого количества условий в части и .

В коде это выглядит следующим образом:

Оценка AndConditionNode в Ruby

 модуль AndConditionNode
  значение определения (env = {})
    первое значение env  (1) 
    больше.элементов.каждый {|элемент| элемент.состояние.значение env}  (2) 
    env[:trigger].firing_match = :ALL
  конец
конец 

1 Оценить первое условие
2 Оцените дополнительные и необязательные условия.

Я в конце примера.

admin

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *