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...)
}
И это все! Увидеть? Нет необходимости генерировать какие-либо строки кода, просто делайте то, что делает настоящий код, но делайте это динамически!