The timeless repository

SexpBuilder

Written by Magnus Holm

NOTE: In this post I use the term “S-expression” and “Sexp” quite a lot. If you’re not quite sure what they mean, check out Sexp for Rubyists.

I very often use Air Castle Driven Development. So when I see something like this:

def process_call(exp) # exp[1] => reciever. # exp[2] => method # exp[3] => (args) exp[3] = process(exp[3]) if text?(exp) s(:parkaby, :text, exp[3]) elsif tag = force_tag_call?(exp) || tag_call?(exp) s(:parkaby, :tag, *tag) else exp end end

I just write down what I want it to look like:

rule :tag_call do # Forced tag call s(:call, s(:call, nil, :tag, s(:arglist)), wild % :name, args % :args) | # or regular tag call s(:call, nil, name % :name, args % :args) end rule :text do s(:call, nil, :text, s(:arglist, wild % :content)) | s(:call, s(:self), :<<, s(:arglist, wild % :content)) end

Then I know I have a useful API and I can start implement it. Okay, the examples above aren’t quite equal, so let’s have a look at what I’m really trying to solve.

Matching complex Sexp in Parkaby

Parkaby is my little experiment to create a super-duper-feaky-fast Markaby replacement by parsing the source and “compiling” it. Ultimately, a template like this:

h1 "Hello World!" p "Welcome #{@user}"

Should be compiled into:

"<h1>Hello World!</h1><p>Welcome #{@user}</p>"

In Parkaby, this is accomplished in two steps: A processor which figues out what should be considered HTML-tags and what should be considered regular method calls, and a generator which compiles it back into Ruby.

An interesting aspect of Parkaby is that the processor is actually quite complex. For instance, it has to figure out that div.post.clearfix.main!(:style => "display:none") should be compiled to <div class="post clearfix" id="main" style="display:none"></div>.

My origianl approach was to use SexpProcessor:

def process_call(exp) # exp[1] => reciever. # exp[2] => method # exp[3] => (args) exp[3] = process(exp[3]) if text?(exp) s(:parkaby, :text, exp[3]) elsif tag = force_tag_call?(exp) || tag_call?(exp) s(:parkaby, :tag, *tag) else exp end end

Every time it finds a method call, it checks if it’s a text-code or a HTML-tag (and then turns it into a parkaby-sexp), or it just leaves it alone. The code ended up quite messy, and it’s before I even tried to implement CSS-proxies (tag.klass.klass.klass.id!): Parkaby before SexpBuilder.

Adam Samderson to the rescue!

Ka-poof! Adam Sanderson writes SexpPath, a simple DSL for matching Sexp.

Let’s say we want to match text "Hello" and self << "Hello":

# text "Hello" sexp1 = s(:call, nil, :text, s(:arglist, s(:str, "Hello"))) # self << "Hello" sexp2 = s(:call, s(:self), :<<, s(:arglist, s(:str, "Hello"))) # the old approach: def like_text?(exp) rec_meth = exp[1..2].to_a rec_meth == [[:self], :<<] || rec_meth == [nil, :text] end def text?(exp) like_text?(exp) and exp[3].length == 2 end # the SexpPath approach: query = Q? do s(:call, nil, :text, s(:arglist, wild % :content)) | s(:call, s(:self), :<<, s(:arglist, wild % :content)) end

Woah, isn’t that powerful? The vertical-bar means “or”, wild matches everything, and the percent sign captures the value. Exactly what I want!

Introducin SexpBuilder

gem install sexp_builder

Well, SexpPath alone wasn’t enough to solve my problem - I had to introduce a new library. SexpBuilder is a more complete solution to easily match and replace complex Sexp. Very much like a parser, you define matchers and rules, and by combining them with a rewriter it was breeze to implement CSS-proxy.

Have a look at The Dojo for documentation and Parkaby::Processor for a full example. There’s also a Andand.rb for a more traditional example/demo.