A well-designed DSL improves programmer productivity and communication with domain experts. The Ruby community has produced a number of very popular external DSLs--Coffeescript, HAML, SASS, and Cucumber to name a few.
Parslet makes it easy to write these kinds of DSLs in pure Ruby. In this talk you’ll learn the basics, feel out the limitations of several approaches and find some common solutions. In no time, you’ll have the power to make a great new DSL, slurp in obscure file formats, modify or fork other people’s grammars (like Gherkin, TOML, or JSON), or even write your own programming language!
14. RssReader::Application.routes.draw do
devise_for :users
resources :feeds, only: [:edit, :update, :show, :index]
post '/feeds', to: 'feeds#create', as: 'create_feed'
resources :users do
get 'page/:page', action: :index, on: :collection
end
resources :posts do
member do
put :update_category
end
end
get '/my_profile', to: 'users#my_profile', as: :my_profile
root to: "home#home"
end
15. describe Stack do
context "stack with one item" do
let(:stack) { a_stack_with_one_item }
context "when popped" do
before { stack.pop }
it { should be_empty }
end
end
end
17. desc 'Generate markup and stylesheets and open browser preview'
task :preview => FileList['*.html'] + FileList['*.css'] do |t|
sh 'open -g index.html'
end
rule '.html' => '.haml' do |t|
puts "Rebuilding #{t.name}"
sh "haml #{t.source} #{t.name}"
end
rule '.css' => lambda { |cssfile| source(cssfile) } do |t|
puts "Rebuilding #{t.name}"
sh "sass #{t.source} #{t.name}"
end
18. get '/' do
@posts = Post.all(:order => [:id.desc], :limit => 20)
erb :index
end
get '/post/new' do
erb :new
end
get '/post/:id' do
@post = Post.get(params[:id])
erb :post
end
post '/post/create' do
post = Post.new(params)
status 201
redirect "/post/#{post.id}"
end
19. rule(:table) do
(str("table") >> attributes?.as(:attributes) >> str(".n")).maybe >>
table_row.repeat(1).as(:content) >> block_end
end
rule(:table_row) do
table_row_attributes >> table_row_content >> end_table_row
end
rule(:table_row_attributes) { (attributes?.as(:attributes) >> str(". ")).maybe }
rule(:table_row_content) { (table_header | table_data).repeat(1).as(:content) }
rule(:end_table_row) { str("|") >> (block_end.present? | (str("n"))) }
rule(:table_header) do
str("|_. ") >> table_content.as(:content)
end
rule(:table_data) do
str("|") >> str("n").absent? >> td_attributes? >> table_content.as(:content)
end
24. SELECT DISTINCT sc1.id
FROM (
SELECT DATE_ADD(entry_point.start_date, INTERVAL (digits_1.digit * 10 + d
(1 << (DAYOFWEEK(DATE_ADD(entry_point.start_date, INTERVAL (digits_1.digi
FROM (SELECT CAST('2012-03-01' AS date) AS start_date) AS entry_point
INNER JOIN (SELECT 0 AS digit UNION SELECT 1 UNION SELECT 2 UNION SELECT
ON (digits_1.digit * 10 + digits_2.digit) <= (DATEDIFF(DATE_ADD(entry_poi
) AS md
INNER JOIN schedules AS sc1
INNER JOIN negative_link_influences AS neg
on (sc1.call_type_id = neg.call_type_id or neg.call_type_id = -1)
WHERE sc1.schedule_on = md.date
AND neg.affected_shift = 0;
25. BEGIN TOTAL=0 }
{
{ TOTAL = TOTAL + $1 }
END print TOTAL/NR }
{
{print "<li><a href="" $1 "">"
$1 "</a></li>"}
27. Feature: Searching music
As a User
I want to be able to search music
So I can play it
Background:
Given I am logged in
Scenario: Search music
Given I am on the search screen
When I search for "mix"
Then I should see the mixtape
29. Heroku buildpack: Ruby
======================
This is a [Heroku buildpack](http://devcenter.heroku.com/article
It uses [Bundler](http://gembundler.com) for dependency manageme
Usage
----### Ruby
Example Usage:
$ heroku create --stack cedar --buildpack https://github.com
$ git push heroku master
30. title = "T??? Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEOnLikes tater tots and
beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why
not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
31. global= {
time 4/4
key c major
tempo "Allegro" 4 = 132
set Score.skipBars = ##t
}
Violinone = new Voice { relative c''{
set Staff.midiInstrument = #"violin"
set tupletSpannerDuration = #(ly:make-moment 1 4)
% Jason solo
R1 * 9
r2 r4 times 2/3 { c4( b8)
36. h1. Give Textile a try!
A *simple* paragraph with a line break,
some _emphasis_ and a "link":http://redcloth.org
* an item
* and another
# one
# two
# three
37. h1. Give Textile a try!
A *simple* paragraph with a line break,
some _emphasis_ and a "link":http://redcloth.org
* an item
* and another
# one
# two
# three
38.
39. A_HLGN = /(?:<(?!>)|<>|=|[()]+)/
A_VLGN = /[-^~]/
C_CLAS = '(?:([^)]+))'
C_LNGE = '(?:[[^]]+])'
C_STYL = '(?:{[^}]+})'
S_CSPN = '(?:d+)'
S_RSPN = '(?:/d+)'
A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?
#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
PUNCT = Regexp::quote( '!"#$%&'*+,-./:;=?@^_`|~' )
HYPERLINK = '(S+?)([^ws/;=?]*?)(s|$)'
40. def links( text )
text.gsub!( /
([s[{(]|[#{PUNCT}])?
# $pre
"
# start
(#{C})
# $atts
([^"]+?)
# $text
s?
(?:(([^)]+?))(?="))?
# $title
":
(S+?)
# $url
(/)?
# $slash
([^w/;]*?)
# $post
(?=s|.[#{PUNCT}]+|$)
/x ) do |m|
pre,atts,text,title,url,slash,post = $~[1..7]
url = check_refs( url )
atts = pba( atts )
atts << " title="#{ title }"" if title
atts = shelve( atts ) if atts
"#{ pre }<a href="#{ url }#{ slash }"#{ atts }>" +
"#{ text }</a>#{ post }"
end
end
41. “Some people, when confronted
with a problem, think ‘I know, I'll
use regular expressions.’
Now they have two problems.”
— Jamie Zawinski
53. grammar Arithmetic
rule additive
multitive ( '+' multitive )*
end
rule multitive
primary ( [*/%] primary )*
end
rule primary
'(' additive ')' / number
end
rule number
'-'? [1-9] [0-9]*
end
end
54. require 'parslet'
class MiniParser < Parslet::Parser
rule(:integer) { match('[0-9]').repeat(1) }
root(:integer)
end
MiniParser.new.parse("1324321")
# => "1324321"@0
63. 1. Create a grammar
What should be legal syntax?
2. Annotate the grammar:
What is important data?
3. Create a transformation:
How do I want to work with that data?
64. 1. Create a grammar
What should be legal syntax?
2. Annotate the grammar:
What is important data?
3. Create a transformation:
How do I want to work with that data?
71. class HtmlTag < Parslet::Parser
root(:tag)
rule(:tag) { open_tag | close_tag | self_closing_tag | comment_tag }
rule(:open_tag) { str("<") >> tag_name >> attributes? >> str(">") }
rule(:close_tag) { str("</") >> tag_name >> str(">") }
rule(:tag_name) { match("[A-Za-z_:]") >> name_char.repeat }
...
end
class BlockHtmlTag < HtmlTag
rule(:tag_name) do
inline_tag_name.absent? >> any_tag_name
end
rule(:inline_tag_name) do
INLINE_TAGS.map {|name| str(name) }.reduce(:|)
end
end
72. > HtmlTag.new.open_tag.methods
=> [:name, :block, :try, :parslet, :to_s_inner, :parse, :apply, :cached?, ...]
> HtmlTag.new.open_tag.parse("<blockquote>")
=> "<blockquote>"@0
> HtmlTag.new.open_tag.parse("</blockquote>")
Parslet::ParseFailed: Failed to match sequence ('<' TAG_NAME ATTRIBUTES? '>') at line
1 char 2.
from /Users/jasongarber/.rvm/gems/ruby-2.0.0-p247/gems/parslet-1.5.0/lib/parslet/
cause.rb:63:in `raise'
from /Users/jasongarber/.rvm/gems/ruby-2.0.0-p247/gems/parslet-1.5.0/lib/parslet/
atoms/base.rb:46:in `parse'
from (irb):8
from /Users/jasongarber/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'
73. begin
RedClothParslet::Parser::BlockHtmlTag.new.tag.parse("<img>")
rescue Parslet::ParseFailed => failure
puts failure.cause.ascii_tree
end
Expected one of [OPEN_TAG, CLOSE_TAG, SELF_CLOSING_TAG, COMMENT_TAG] at line 1 char 1.
|- Failed to match sequence ('<' TAG_NAME ATTRIBUTES? '>') at line 1 char 2.
| `- Failed to match sequence ((!INLINE_TAG_NAME / &(INLINE_TAG_NAME NAME_CHAR{1, })) ANY_TAG_NAME)
at line 1 char 2.
|
`- Expected one of [!INLINE_TAG_NAME, &(INLINE_TAG_NAME NAME_CHAR{1, })] at line 1 char 2.
|
|- Input should not start with INLINE_TAG_NAME at line 1 char 2.
|
`- Input should start with INLINE_TAG_NAME NAME_CHAR{1, } at line 1 char 2.
|- Failed to match sequence ('</' TAG_NAME '>') at line 1 char 1.
| `- Expected "</", but got "<i" at line 1 char 1.
|- Failed to match sequence ('<' TAG_NAME ATTRIBUTES? SPACES? '/' '>') at line 1 char 2.
| `- Failed to match sequence ((!INLINE_TAG_NAME / &(INLINE_TAG_NAME NAME_CHAR{1, })) ANY_TAG_NAME)
at line 1 char 2.
|
`- Expected one of [!INLINE_TAG_NAME, &(INLINE_TAG_NAME NAME_CHAR{1, })] at line 1 char 2.
|
|- Input should not start with INLINE_TAG_NAME at line 1 char 2.
|
`- Input should start with INLINE_TAG_NAME NAME_CHAR{1, } at line 1 char 2.
`- Failed to match sequence ('<!--' (!COMMENT_TAG_END .){0, } COMMENT_TAG_END) at line 1 char 1.
`- Expected "<!--", but got "<img" at line 1 char 1.
75. describe RedClothParslet::Parser::HtmlTag do
it { should parse("<div>") }
it { should parse("<hr />") }
it { should parse("</div>") }
it { should parse("<!-- an HTML comment -->") }
describe "attribute" do
subject { described_class.new.attribute }
it { should parse(" class='awesome'") }
it { should_not parse(' 9kittens="cute"') }
end
end
76. $ rspec spec/parser/html_tag_spec.rb
1) RedClothParslet::Parser::HtmlTag tag
Failure/Error: it { should parse("</div>") }
expected TAG to be able to parse "</div>"
Expected one of [OPEN_TAG, CLOSE_TAG, SELF_CLOSING_TAG, COMMENT_TAG] at line 1 char 1.
|- Failed to match sequence ('<' TAG_NAME ATTRIBUTES? '>') at line 1 char 2.
| `- Failed to match sequence ([A-Za-z_:] NAME_CHAR{0, }) at line 1 char 2.
|
`- Failed to match [A-Za-z_:] at line 1 char 2.
|- Failed to match sequence ('<' TAG_NAME '>') at line 1 char 2.
| `- Failed to match sequence ([A-Za-z_:] NAME_CHAR{0, }) at line 1 char 2.
|
`- Failed to match [A-Za-z_:] at line 1 char 2.
|- Failed to match sequence ('<' TAG_NAME ATTRIBUTES? SPACES? '/' '>') at line 1 char 2.
| `- Failed to match sequence ([A-Za-z_:] NAME_CHAR{0, }) at line 1 char 2.
|
`- Failed to match [A-Za-z_:] at line 1 char 2.
`- Failed to match sequence ('<!--' (!COMMENT_TAG_END .){0, } COMMENT_TAG_END) at line 1 char 1.
`- Expected "<!--", but got "</di" at line 1 char 1.
# ./spec/parser/html_tag_spec.rb:9:in `block (3 levels) in <top (required)>'
13/13:
100% |==========================================| Time: 00:00:00
Finished in 0.01879 seconds
13 examples, 1 failure
77. 1. Create a grammar
What should be legal syntax?
2. Annotate the grammar:
What is important data?
3. Create a transformation:
How do I want to work with that data?
78. Transformations
tree = {left: {int: '1'},
op: '+',
right: {int: '2'}}
class T < Parslet::Transform
rule(int: simple(:x)) { Integer(x) }
end
T.new.apply(tree)
# => {:left=>1, :op=>"+", :right=>2}
79. Transformations
tree = {left: {int: '1'},
op: '+',
right: {int: '2'}}
class T < Parslet::Transform
rule(int: simple(:x)) { Integer(x) }
rule(op: '+', left: simple(:l),
right: simple(:r)) { l + r }
end
T.new.apply(tree)
# => 3
80. Transformations
rule(:content => subtree(:c), :attributes => subtree(:a)) do |dict|
{:content => dict[:c], :opts => RedCloth::Ast::Attributes.new(dict[:a])}
end
rule(:table => subtree(:a)) do
RedCloth::Ast::Table.new(a[:content], a[:opts])
end
rule(:bq => subtree(:a)) do
RedCloth::Ast::Blockquote.new(a[:content], a[:opts])
end
85. Custom Atoms
•
“Who makes the best cheesesteaks in Boston?”
•
EngTagger: a corpus-trained, probabilistic tagger
•
Custom Parslet::Atom
•
Parse a limited set of natural-language queries
91. Other Crazy Uses
•
Logs
•
Streaming text
•
The Right Reverend and Right Honourable the Lord
•
User-supplied formulas / logic
Bishop of London Richard John Carew Chartres