繁体   English   中英

如何在Ruby中创建YAML,Cucumber,Markdown等自定义DSL?

[英]How to create a custom DSL in Ruby like YAML, Cucumber, Markdown, etc?

我目前有一个基于Ruby的DSL来创建使用实例eval的幻灯片:

# 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'))

结果是这样的:


Ruby编程

简单介绍

  • 第一颗子弹
  • 第二弹

我想通过创建不使用Ruby语法的DSL将其带入一个新的水平。 也许更像是YAML或Markdown:

title: Ruby Programming
subtitle: A simple introduction
* First bullet
* Second bullet

如何为这种语法创建DSL /解析器?

有人已经提到过Parslet,但是我想我可以演示一下它多么简单。

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中,解析器只是工作的一部分,因为它们只是将文本转换为红宝石结构。

然后,您可以使用变形来匹配/替换树节点以创建所需的结构。

# 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]}}

也许值得一看一些当前的开源实现。

但是我不得不问-你为什么要自己做? 您为什么不使用现有的呢? TOML很棒。

Ruby解析器实现: https//github.com/jm/toml

我相信Cucumber的解析器使用Ragel ,这是使用Ruby的不错的介绍 ...

树顶Parslet也很常见。

ANTLR ,Rex和Racc ...处理外部DSL的各种方式。

雄辩的Ruby撰写了有关外部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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM