如何在Ruby中创建自定义DSL,如YAML、Cumber、Markdown等?
我目前有一个基于Ruby的DSL,用于创建使用实例评估的幻灯片:如何在Ruby中创建自定义DSL,如YAML、Cumber、Markdown等?,ruby,dsl,Ruby,Dsl,我目前有一个基于Ruby的DSL,用于创建使用实例评估的幻灯片: # slides.rb slide { title 'Ruby Programming' subtitle 'A simple introduction' bullet 'First bullet' bullet 'Second bullet' } # implementation: class DSL class Slide def title(title) @title = title
# slides.rb
slide {
title 'Ruby Programming'
subtitle 'A simple introduction'
bullet 'First bullet'
bullet 'Second bullet'
}
# implementation:
class DSL
class Slide
def title(title)
@title = title
end
# ...etc...
end
def slide(&block)
@slides << Slide.new.instance_eval(&block)
end
end
dsl = DSL.new
dsl.instance_eval(File.read('slides.rb'))
如何为这种语法创建DSL/解析器?也许值得看看当前的一些开源实现 但我不得不问——你为什么要自己做?你为什么不使用一个已有的呢?太好了 Ruby解析器实现:我认为Cucumber是其解析器,下面是一个使用Ruby的 也很常见,还有 ,Rex和Racc。。。处理外部DSL的各种方法
有一章介绍外部DSL创建,从基本字符串解析和正则表达式到使用Treetop…您可以使用regexp进行基本解析。大概是这样的:
slides = <<EOS
title: Ruby Programming
subtitle: A simple introduction
* First bullet
* Second bullet
EOS
regexp = %r{
(title:\s+)(?<title>[^\n]*)|
(subtitle:\s+)(?<subtitle>[^\n]*)|
(\*\s+)(?<bullet>[^\n]*)
}x
tags = {
'title' => 'h1',
'subtitle' => 'h2',
'bullet' => 'li'
}
fUL = false
slides.lines.each {|line|
md = line.match(regexp)
md.names.select{|k| md[k]}.each {|k|
puts '<ul>' or fUL = true if k == 'bullet' && !fUL
puts '</ul>' or fUL = false if k != 'bullet' && fUL
puts "<#{tags[k]}>#{md[k]}</#{tags[k]}>"
}
}
puts '</ul>' if fUL
有人已经提到了欧芹,但我想我会演示它是多么容易
require 'parslet'
require 'pp'
slides = <<EOS
title: Ruby Programming
subtitle: A simple introduction
* First bullet
* Second bullet
EOS
#Best to read the parser from the bottom up.
class SlidesParser < Parslet::Parser
rule(:eol) { str("\n") | any.absent? }
rule(:ws?) { match('[\s\t]').repeat(0) }
rule(:rest_of_line) { ws? >> (str("\n").absent? >> any).repeat(1).as(:text) }
rule(:title) { ws? >> str("title:")>> rest_of_line.as(:title) >> eol }
rule(:subtitle) { ws? >> str("subtitle:")>> rest_of_line.as(:subtitle) >> eol }
rule(:bullet) { ws? >> str("*") >> rest_of_line >> eol }
rule(:bullet_list) { bullet.repeat(1).as(:bullets) }
rule(:slide) { (title >> subtitle >> bullet_list).as(:slide) }
root(:slide)
end
# Note: parts can be made optional by adding a ".maybe" eg. => subtitle.maybe
result = SlidesParser.new.parse(slides)
pp result
#{:slide=>
# {:title=>{:text=>"Ruby Programming"@9},
# :subtitle=>{:text=>"A simple introduction"@38},
# :bullets=>[{:text=>"First bullet"@64}, {:text=>"Second bullet"@81}]}}
在Parslet中,解析器只是工作的一部分,因为它们只是将文本转换为ruby结构
然后使用Transforms匹配/替换树节点,以生成所需的结构
# You can do lots of things here.. I am just replacing the 'text' element with their value
# You can use transforms to build your custom AST from the raw ruby tree
class SlidesTransform < Parslet::Transform
rule(:text => simple(:str)) { str }
# rule(
# :title => simple(:title),
# :subtitle => simple(:subtitle),
# :bullets => sequence(:bullets)) { Slide.new(title, subtitle, bullets) }
end
pp SlidesTransform.new.apply(result)
#{:slide=>
# {:title=>"Ruby Programming"@9,
# :subtitle=>"A simple introduction"@38,
# :bullets=>["First bullet"@64, "Second bullet"@81]}}
在问这个问题之前,我开始研究树梢,但是有很多行话,很难理解到底发生了什么。欧芹看起来有一个更好的教程,所以我来看看。我拥有一本雄辩的Ruby书籍,但不记得它有一个树梢示例。我还得再检查一遍。哈,是的,如果这是你第一次尝试词法分析的话,请准备好应付术语过载。好的一面是,当你做一些事情的时候,它真的很酷!令人惊叹的谢谢你的例子。
# You can do lots of things here.. I am just replacing the 'text' element with their value
# You can use transforms to build your custom AST from the raw ruby tree
class SlidesTransform < Parslet::Transform
rule(:text => simple(:str)) { str }
# rule(
# :title => simple(:title),
# :subtitle => simple(:subtitle),
# :bullets => sequence(:bullets)) { Slide.new(title, subtitle, bullets) }
end
pp SlidesTransform.new.apply(result)
#{:slide=>
# {:title=>"Ruby Programming"@9,
# :subtitle=>"A simple introduction"@38,
# :bullets=>["First bullet"@64, "Second bullet"@81]}}