Ruby: Как генерировать строки кода внутри программы?

Я разрабатываю синтаксический анализатор в Ruby с использованием библиотеки parslet.

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

rule(:keyword) {  
    str('keyword1')     |
    str('keyword2')     | 
    str('keyword2')     ... 

}

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

Псевдокод того, что я хочу встроить в rule(:keyword) было бы что-то вроде этого:

File.read("keywords.txt").each { |k| write_line " str(\'#{k}\') "} 

До сих пор я нашел обходной путь для отдельной программы ruby, загружающей код парсера:

keywords = ["keyword1", "keyword2","keyword3"]

subs = {:keyword_list => keywords .inject("") { |a,k| a << "str('#{k}') | \n"} }

eval( File.read("parser.rb") % subs)

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

rule(:keywords){ 
   %{keyword_list} 
 }

Есть ли более элегантный способ добиться этого?

2 ответа

Решение

Вы можете попробовать что-то вроде этого:

rule(:keyword) {  
  File.readlines("keywords.txt").map { |k| str(k.chomp) }.inject(&:|)
}

В этом случае вам не нужно "генерировать строки кода". Как @Uri пытался объяснить в своем ответе, в содержании этого нет ничего особенного rule Способ; это просто код Ruby. Из-за этого все, что вы можете делать в Ruby, вы можете делать и внутри этого метода правила, включая чтение файлов, динамический вызов методов и вызов методов на объектах.

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

rule(:keyword) {
  # Stuff here
}

Этот код прямо здесь называет rule метод и передает его :keyword и блок кода. В какой-то момент parslet вызовет этот блок и проверит его возвращаемое значение. Parslet может решить вызвать блок, используя instance_exec, который может изменить контекст, в котором выполняется блок, чтобы сделать методы недоступными вне блока (например, str возможно) доступно внутри.

str('keyword1')

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

str('keyword1') | str('keyword2')

Здесь | Оператор на самом деле просто метод вызывается на что угодно str('keyword1') возвращается Этот код эквивалентен str('keyword1').send(:'|', str('keyword2')),

str('keyword1') |
str('keyword2') | 
str('keyword2')

То же, что и раньше, кроме этого времени мы звоним | на что угодно str('keyword1').send(:'|', str('keyword2')) вернулся. Результат этого вызова метода возвращается rule метод, когда он вызывает блок.

Итак, теперь, когда вы знаете, как все это работает, вы можете выполнять точно такие же операции (вызов str с каждым ключевым словом, и используя | Метод "сложения" результатов) динамически, исходя из содержимого файла возможно:

rule(:keyword) {  
  File.readlines("keywords.txt").map(&:chomp).map { |k| str(k) }.inject(:|)
}

Сломать:

rule(:keyword) { # Call the rule method with the `:keyword` argument, and pass
                 # it this block of code.

  File.readlines("keywords.txt"). # Get an array of strings containing all the
                                  # keywords

  map(&:chomp). # Remove surrounding whitespace from each keyword in the array,
                # by calling `chomp` on them. (The strings returned by
                # `File.readlines` include the newline character at the end of
                # each string.)

  map { |k| str(k) }. # Convert each keyword in the array into whatever is
                      # returned by calling `str` with that keyword.

  inject(:|) # Reduce the returned objects to a single one using the `|`
              # method on each object. (Equivalent to obj1 | obj2 | obj3...)
}

И это все! Увидеть? Нет необходимости генерировать какие-либо строки кода, просто делайте то, что делает настоящий код, но делайте это динамически!

Другие вопросы по тегам