Just A Summary

Piers Cawley Practices Punditry

Writing parsers for fun and convenience 4

Posted by Piers Cawley Thu, 12 Mar 2009 03:29:00 GMT

One aspect of coming back to Perl for ‘recreational’ programming is that if, like me, you’ve declared war on @_ and boilerplate code, then testing can be somewhat trying. The Perl testing framework that best fits my head is Test::Class, which is an excellent Perlish implementation of xUnit style testing. If you’re unfamiliar with the, library, Ovid is writing what’s shaping up to be an excellent series of introductory articles about it at http://www.modernperlbooks.com/.

The problem I’m having with Test::Class at the moment is that I can’t write:

use MooseX::Declare
class Test::Person
  extends Test::Class
{
  use Test::Most;

  method class_under_test {'Person'}
  
  method startup : Test(startup => 1) {
    use_ok $test->class_under_test
  }

  ...
}

Test::Class is doing too much in its initialization phase, and relies too heavily on code attributes, for it to play well with MooseX::Declare. Drat.

On reflection though, this might be a good thing, because maybe MooseX::Declare isn’t really what’s needed. What I’d like to write is something like:

use ...;

testclass Test::Person
  exercises Person
{
  startup class under test should be usable (1 test) {
    use_ok $test->class_under_test
  }
}

And have the library ‘…’ expand the testclass declaration into something that looks like the first code snippet. After all, if MooseX::Declare can work without source filters, it should be possible to come up with something nicely declarative for specifying test classes.

Obviously, there’s nothing on CPAN that does this yet though. So I went fossicking through MooseX::Declare to see how it works1 and discovered thing of Lovecraftian beauty that is…

Devel::Declare

Devel::Declare is possibly the most hostilely documented library I’ve ever come across. Its documentation only begins to make sense when you already understand enough about how it works that you don’t really need the docs. What it does is to let you declare your own Perl keywords. You could, for instance use it to introduce given/when into versions of Perl that don’t have it yet. You declare your keywords and associate them with parsers. When, during its compilation phase, perl hits one of your keywords in the right context, it hands off to your parser which can then do what the hell it likes in the way of code transformation, before handing control back to Perl, which then parses the transformed code as if that was what was there all along.

So, to want to transform that testclass syntax I just pulled out of my ass into a real Test::Class package, I just need to write an appropriate parser and code generator, perform the appropriate Devel::Declare incantations, and I’m laughing.

Making progress

So far, I’ve got to the point where I have a working testclass keyword, but nothing yet for the ‘inner’ bits (setup, test, teardown, etc). I can write:

testclass Test::Person
  exercises Person
{
  ...
}

testclass Test::Person::Employee
  extends Test::Person
  exercises Person
{
  ...
}

and, as I write this, I’m realising that the syntax I’d cooked up for using extra test helper modules:

testclass AnotherTest
  helpers -More, -Exception, Carp
{
  # use Test::More;
  # use Test::Exception;
  # use Carp;

  ...
}

would probably read better as:

testclass AnotherTest
  +uses Carp
{
  # use Test::Most;
  # use Carp;

  ...
}

and also that I want this:

testclass exercises Person {
  ...
}

to build me a Test::Person class.

What’s still blowing my mind about Devel::Declare’s possibilities is that I’m no longer constrained to writing a Domain Specific Pidgin which works by building a tower of proxy objects and weird evaluation contexts to produce something that’s legal code in the host language, but which has the feel of another language. With Devel::Declare, I control the horizontal and the vertical until I choose to hand control back to Perl. Right now that means my error reporting is disgracefully bad, but it also means that I can roll a syntax that makes sense without worrying about how I’m going to get perl to parse it.

One of the things I find frustrating about writing RSpec specifications is that describe and it both want to be first class keywords – it feels like you should be able to write:

describe SomeClass, "in some context"
  before each
    # set things up
  end
 
  it "should do something or another"
    ...
  end
end

But, because RSpec works by taking advantage of Ruby’s block magic, you have to write:

describe SomeClass, "in some context" do
  before :each do 
    # set things up
  end

  it "should do something or another" do 
    ...
  end
end

I definitely prefer the version without the extraneous dos and the gratuitious : before each in the before declaration. Does anyone feel like writing devel/declare.rb?

Show us the code!

If you want to see the current state of my Test::Class::Sugar art, the place to look is http://www.github.com/pdcawley/test-class-sugar. At the time of writing it relies on http://www.github.com/rafl/devel-declare and doesn’t have anything so useful as documentation, a Makefile.PL or even any tests beyond the collection of code samples that is t/initial.t. Expect all those when and if I push it to CPAN.

Caveats

Yes, I know that this sort of metasyntactic abstraction is trivial in a Lisp. I just happen to like syntax, okay?

Update 20090312

use Test::Class::Sugar

testclass exercises Foo 
    +uses -Warn
{
    ...
}

Now generates

{
  package Test::Foo;
  use base qw/Test::Class/;
  use Test::Most;
  use Test::Warn

  ...
}

So that’s one hurdle jumped. And I now know how to write the various method helpers and, when I get the appropriately shaped tuits, I shall actually write the damned things.

Then all I have to do is document it.

And write up a proposal about it for YAPC::Europe.

Update 20090314

I now know what a plan looks like:

test with multiple assertions << 3 {...}

And, more importantly, I’ve implemented, and documented everything and am almost good to cut a 0.001 distribution. I need a few ducks up on CPAN, but once that’s done, we’re good and I can get on with parameterizing some of the assumptions that are hard coded at the moment.

1 Something I swore blind I wasn’t going to do in my London.pm presentation. Seems my word isn’t to be trusted…

Mmm... parsers 1

Posted by Piers Cawley Sat, 14 Apr 2007 19:13:00 GMT

So, in my quest to get Rails routes to accept routes like:

articles/:comment[article]/comments/:comment[id]

I’ve been playing with parsers for the first time in my programming career. Quite how I’ve managed to get this far without them I leave as an exercise for the interested reader.

At the moment I have a parser that will parse

articles/2007/05/12/slug

and give back an ActionSelector that yamlizes as

<pre> --- !ruby/object:ActionSelector controller: articles action: show params: :article: :year: 2007 :month: 05 :mday: 12 :slug: slug </pre>

Which can be easily turned into the kind of params hash that rails wants from its routing system in order to dispatch the request to the right place.

For my next trick, I need refactor the parser I have so that it’s generated via a set of parser builders. Then I need to write a parser for routing specifiers that can use those builders to build a URL parser.

It’s a simple matter of programming I tell you.

Updates

RouteBuilder works (2007041608:00ish)

I now have a RouteBuilder that generates routes and has the same interface as ActionController::Routing::RouteBuilder.

My Route doesn’t have the same interface as AC::Routing::Route yet, but it’s not far off. The fun part is working out which bits of the interface are essential and which can be safely ignored. I’m hoping that I won’t have to rewrite AC::Routing::RouteSet as well, but I’m not sure how complicit it is with the innards of AC::Route.

Routes can generate paths from options (2007041612:30)

context "Given a default route" do
  setup do
    @route = RouteBuilder.new.build('/:controller/:action/:id')
  end

  def gen(hash)
    # Rails interface is a bit surprising
    @route.generate(hash.dup, hash.dup, {})
  end

  specify "generation works" do
    gen(:controller => 'accounts', :action => 'show', :id => 1) \
      .should == '/accounts/show/1'

    gen(:controller => 'accounts', :id => 1) \
      .should == '/accounts/index/1'

    gen(:controller => 'accounts', :action => ':index') \
      .should == '/accounts'
  end
end

It’s nowhere near complete, but I’m being gung ho and starting to integrate my RouteBuilder with Rails. Then I can start pulling the Rails routing tests into my own test suite and get some confidence with its robustness before I start adding the ability to handle routes like /books/:tune[book][id]/tunes/:tune[id], which is why I started this in the first place.

With any luck and some good train hacking time on the way to London I should have something good to show at LRUG this evening.



Just A Summary