<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
  <title>Ana Nelson</title>
  <subtitle>she who scrivens</subtitle>
  <link href="http://ananelson.com/feeds/blog.xml" rel="self" />
  <link href="http://ananelson.com/" />
  <updated>2008-08-28T22:55:21+01:00</updated>
  <author>
    <name>Ana Nelson</name>
  </author>
  <id>http://ananelson.com/</id>
  
  <entry>
    <title>A Rails Application Factory</title>
    <link href="/blog/2008/08/a-rails-application-factory" />
    <id>tag:ananelson.com,2008-08-28:1219954459</id>
    <updated>2008-08-28T21:14:19+01:00</updated>
    <content type="html">
        &lt;p&gt;Please visit the website for source code downloads and syntax highlighting.&lt;/p&gt;&lt;h1&gt;A Rails Application Factory&lt;/h1&gt;


	&lt;p&gt;This is a post about creating fresh Rails applications quickly and maintaining them easily over time. The approach I take here could easily be applied outside of Rails to any software project.&lt;/p&gt;


	&lt;h3&gt;My Philosophy of Friction&lt;/h3&gt;


	&lt;p&gt;I like to think about productivity in terms of &amp;#8220;friction&amp;#8221;. My inner economist wants me to call this &amp;#8220;marginal cost&amp;#8221; but my inner consultant thinks &amp;#8220;friction&amp;#8221; will sell better. The principle I try to organize by is that things which you want yourself to do on a regular basis: exercise, eat well, churn out a shiny prototype in next to no time; should be quick and painless. You should try to reduce the friction between you and your desired outcomes. So, make it easy to eat well by stocking the kitchen with plenty of healthy, easy-to-prepare food. If you never go to the gym because there&amp;#8217;s never any parking, or you have to make a tricky right turn to get into the car park (I live in Ireland, remember, where right turns are the tricky ones), this is friction and the more friction there is, the less likely you are to do something. Join a different gym, or make yourself consciously aware of the friction and make the decision to go to the gym anyway. Friction is nefarious since it works on our subconscious, sometimes just being aware of it can remove its influence, but staying aware of it is difficult.&lt;/p&gt;


	&lt;p&gt;Reducing friction for good things, and increasing friction for bad things (put those cookies on the TOP shelf), can be a simple and effective guideline for orienting your practices towards your goals. (But, please do remember that marginal thinking does not show you the entire picture. Somewhere you need to think about the sum of what you are doing, not just marginal increases or decreases. Friction is very useful as a short-term heuristic, though, and it&amp;#8217;s also usually the right tool for predicting behaviour in other people, especially users of software. Check out Why We Buy by Paco Underhill, one of my all-time favourite books for its content and basically all about friction.)&lt;/p&gt;


	&lt;p&gt;So, this week I decided to reduce a few forms of friction in my programming life. To begin with, I wanted to improve my strategy for creating a fresh Rails application.&lt;/p&gt;


	&lt;h3&gt;Creating A Fresh App&lt;/h3&gt;


	&lt;p&gt;Of course, actually building a generic Rails application is very straightforward, you simply type:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;rails example
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;and you will have a new directory called &lt;code&gt;example&lt;/code&gt; which contains a skeleton Rails application. Very quickly, though, a skeleton Rails application isn&amp;#8217;t going to be enough. Most developers will have a collection of plugins, rake tasks and other goodies which they want to install on every application. I head straight for &lt;code&gt;rspec&lt;/code&gt; and &lt;code&gt;active_scaffold&lt;/code&gt;. Also, I don&amp;#8217;t have any use for &lt;code&gt;public/index.html&lt;/code&gt; or the &lt;code&gt;test/&lt;/code&gt; directory so I will always delete these. The details don&amp;#8217;t really matter, the point is that there is a series of repetitive tasks which we do whenever we set out to create a new Rails application.&lt;/p&gt;


	&lt;p&gt;My original approach to automating this was to write a shell script which went through the various steps for me:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# Usage: bash setup_rails projectname&lt;/span&gt;
rails &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec
ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails
ruby script/generate rspec
ruby script/plugin install http://svn.caldersphere.net/svn/main/plugins/rspec_autotest
svn &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;http://activescaffold.googlecode.com/svn/trunk vendor/plugins/active_scaffold
svn &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;http://activescaffoldexport.googlecode.com/svn/trunk vendor/plugins/activescaffoldexport
svn &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;http://dev.agent.ie/svn/rails/generators/ajax_rspec_resource/ lib/generators/ajax_rspec_resource
ruby script/plugin install exception_notification

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;About to drop and recreate development and test databases. Next password prompt is for mysql user password.&amp;quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;drop database if exists $1_development;&amp;quot;&lt;/span&gt; | mysql -u root -p
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;drop database if exists $1_test;&amp;quot;&lt;/span&gt; | mysql -u root -p
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;create database $1_development;&amp;quot;&lt;/span&gt; | mysql -u root -p
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;create database $1_test;&amp;quot;&lt;/span&gt; | mysql -u root -p
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;Wow, mysql and svn, that takes me back. ;-)&lt;/p&gt;


	&lt;p&gt;With this script, I could create a new Rails application in a few seconds and my favourite plugins would be installed and configured, databases would be set up and a custom generator installed and ready to go.&lt;/p&gt;


	&lt;p&gt;This was definitely better than nothing, but it had some problems. I would tend to have a spurt of starting a few new projects, and then there would be a lull of a few months where I was working on existing projects. By the time I went back to use this script again, things would have changed. Plugins would have moved or I would be using different ones. Or, I would forget I had written the script and find myself halfway through setting up an application manually before I remembered that, yes, I invented an easier way to do this once upon a time. But, the crucial flaw in the &lt;code&gt;setup_rails.sh&lt;/code&gt; concept is that it does nothing to help you automate the maintenance of your Rails applications. I had automated the birth process, but not the much more onerous ongoing maintenance which is necessary for any piece of software with external dependencies.&lt;/p&gt;


	&lt;p&gt;So, finally, this morning I worked out a strategy which is going to solve all of these problems. There&amp;#8217;s no script involved, just some really easy branching and merging using, in my case, the &lt;a href=&quot;http://bazaar-vcs.org&quot;&gt;Bazaar version control system&lt;/a&gt;.&lt;/p&gt;


The strategy is:
	&lt;ol&gt;
	&lt;li&gt;create a skeleton Rails application configured just the way I like it named &lt;code&gt;template-rails&lt;/code&gt;&lt;/li&gt;
		&lt;li&gt;each time I want to create a new Rails application, I &lt;code&gt;bzr branch template-rails my_new_app&lt;/code&gt;. This creates a copy of the contents of template-rails in a new directory called my_new_app, and the my_new_app branch &amp;#8220;remembers&amp;#8221; that it originated from template-rails.&lt;/li&gt;
		&lt;li&gt;each time I upgrade a plugin, tweak a rake task, write a new spec helper or anything else that I want to share amongst more than one application, I make this change to &lt;code&gt;template-rails&lt;/code&gt; and then bring the changes into each application by just typing &lt;code&gt;bzr merge&lt;/code&gt; and, later, committing the changes.&lt;/li&gt;
	&lt;/ol&gt;


	&lt;p&gt;So, this covers both creating and maintaining applications in one easy step. Because I interact with version control on a daily basis, I&amp;#8217;m not going to forget what my setup is, whereas I might easily forget about a six-month-old shell script tucked away in some bin/ directory.&lt;/p&gt;


	&lt;h3&gt;Bazaar Tricks&lt;/h3&gt;


	&lt;p&gt;I&amp;#8217;m sure you could do this with any version control system that has good (low-friction!) support for branching and merging. I happen to be partial to bazaar because, coming from subversion, it took me 5 minutes to learn the basics but I&amp;#8217;m still discovering wonderful little features that make my life so much easier. Just this morning, for example, I learned that if you &lt;code&gt;bzr rm --new --keep&lt;/code&gt;, bazaar will remove whichever files you have just accidentally added to your repository without deleting them, they just go back to being &amp;#8220;unknown&amp;#8221; files. This is GREAT when you add several directories without adding, say, &amp;#8221;.git&amp;#8221; to your .bzrignore file. You can just undo the add, tell .bzr to ignore the pattern, then safely add just the files you want. When I use a plugin from &lt;a href=&quot;http://github.com&quot;&gt;github&lt;/a&gt; I &lt;code&gt;git clone&lt;/code&gt; it and leave the git branch information intact. It doesn&amp;#8217;t conflict with bzr and I can easily pull down changes.&lt;/p&gt;


	&lt;p&gt;A trick which is particularly useful for working with a template like I have been describing is described in &lt;a href=&quot;http://doc.bazaar-vcs.org/latest/en/user-guide/index.html#pseudo-merging&quot;&gt;Section 7.2.3 of the Bazaar user guide&lt;/a&gt;. While I would probably go into template-rails/ to pull down the latest version of a plugin, if I am editing something I have written myself like a rake task or a spec helper, chances are I&amp;#8217;m going to be working on this and testing this in a live application. But, once I have upgraded or fixed the code, I probably want to bring this change back into my template so it can be shared with my other apps and any apps I create by branching this template in the future. Here is a nice little workflow assuming I start out in my example/ application directory:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;3
4
5
6
7
8
9&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ../template-rails
bzr merge --uncommitted example/
bzr diff
bzr commit . -m &lt;span class=&quot;s2&quot;&gt;&amp;quot;Refactor matchers to derive description automatically from class name.&amp;quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ../example
bzr merge
bzr commit . -m &lt;span class=&quot;s2&quot;&gt;&amp;quot;Merged changes from template.&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;The &lt;code&gt;merge --uncommitted example/&lt;/code&gt; is just a fantastic little command which gets whatever changes you have made in the example/ branch but not yet committed (this does not know about &amp;#8220;unknown&amp;#8221; files, so if you create a new file you have to add it to the repository for it to show up here). So, you can happily develop and test in any of your branches and pulling the changes back to the template is a couple of quick commands away.&lt;/p&gt;


	&lt;h3&gt;Sharing Code&lt;/h3&gt;


	&lt;p&gt;A side effect of adopting a system like this is, because you have reduced the pain (or friction, or marginal cost) associated with sharing code between your applications, you are more likely to share code. You are more likely to write test helpers, custom rake tasks and your own plugins. Sharing code between your applications is a great way to improve it and the lessons you learn from testing your plugin on its 2nd Rails application will improve its performance and stability in the Rails application you originally developed it for. You are more likely to keep all your Rails applications running on an up-to-date version of Rails too.&lt;/p&gt;


	&lt;p&gt;I have mentioned Rails and some Rails-specific terminology quite a lot, but of course the idea of setting out a standard structure in a template and branching from this could be applied to very many situations. In fact, because Rails has a pretty good directory structure by default, I&amp;#8217;m guessing I will feel the benefit of this approach much more in non-Rails project. For example, I am doing a lot of data analysis work using just DataMapper and a console, no web framework of any kind required, and I have started to work with a certain self-imposed project structure but now I am going to set up a template so that I don&amp;#8217;t succumb to the temptation to re-invent this structure on every project.&lt;/p&gt;


	&lt;p&gt;One of the reasons I am pretty excited about sharing code between applications easily is that I have been developing some very fun tools over the past few weeks for rapid model prototyping in Rails using &lt;code&gt;rspec&lt;/code&gt; and &lt;code&gt;railroad&lt;/code&gt;. Stay tuned.&lt;/p&gt;
    </content>
  </entry>
  
  <entry>
    <title>JRuby Wrapping For ANTLR</title>
    <link href="/blog/2008/06/jruby-wrapping-for-antlr" />
    <id>tag:ananelson.com,2008-06-27:1214588035</id>
    <updated>2008-06-27T18:33:55+01:00</updated>
    <content type="html">
        &lt;p&gt;Please visit the website for source code downloads and syntax highlighting.&lt;/p&gt;&lt;h1&gt;JRuby Wrapping For ANTLR&lt;/h1&gt;


	&lt;p&gt;I use &lt;a href=&quot;http://antlr.org/&quot;&gt;ANTLR&lt;/a&gt;, a fantastic parser generator, for various parsing tasks and experimenting with DSLs&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. ANTLR is one of the tools that makes it possible to &amp;#8220;write code that writes code&amp;#8221;. Previously I wrote about &lt;a href=&quot;http://ananelson.com/said/on/2008/02/11/redirecting-java-output-streams-in-jruby-and-antlr-unit-testing/&quot;&gt;capturing Java&amp;#8217;s output and error streams&lt;/a&gt; in JRuby, and I used this trick to write a wrapping for ANTLR which makes it very easy to unit test grammars as well as to integrate ANTLR parsers into a Ruby class. I call it AntlrVelvet.&lt;/p&gt;


	&lt;p&gt;AntlrVelvet is just a Ruby module, so you need to require the file and then include the module in a class:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;24
25
26
27
28&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;antlr_velvet&amp;#39;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Expr&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AntlrVelvet&lt;/span&gt;  
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;And&amp;#8230; that&amp;#8217;s it. We now have a parser! By default, AntlrVelvet assumes you have given your class the same name as you gave your parser, in this case &lt;code&gt;Expr&lt;/code&gt;. (You can easily override this.) &lt;code&gt;ExprLexer.class&lt;/code&gt; and &lt;code&gt;ExprParser.class&lt;/code&gt; need to be on the Java &lt;code&gt;CLASSPATH&lt;/code&gt; somewhere, but if they&amp;#8217;re in the same directory as &lt;code&gt;antlr_velvet.rb&lt;/code&gt; this will be taken care of automatically.&lt;/p&gt;


	&lt;p&gt;Let&amp;#8217;s take a quick look at &lt;code&gt;Expr.g&lt;/code&gt;, the ANTLR grammar file, and see what super-powers we have given our Ruby class with these 4 lines of boilerplate.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;// http://www.antlr.org/wiki/display/ANTLR3/Expression+evaluator&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;// [The &amp;quot;BSD licence&amp;quot;] Copyright (c) 2005-2008 Terence Parr All rights reserved.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;grammar&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;@header&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;@members&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/** Map variable name to Integer object holding value */&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;HashMap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;                &lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NEWLINE&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;=&amp;#39;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NEWLINE&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NEWLINE&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;returns&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;multExpr&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;multExpr&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;multExpr&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;multExpr&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;returns&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;*&amp;#39;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;returns&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v!&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;undefined variable &amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;(&amp;#39;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;expr&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;)&amp;#39;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;z&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;Z&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;0&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;9&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;NEWLINE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;\r&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;WS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;\t&amp;#39;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;skip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;&lt;a href=&quot;http://www.antlr.org/wiki/display/ANTLR3/Expression+evaluator&quot;&gt;&lt;code&gt;Expr.g&lt;/code&gt;&lt;/a&gt; is taken directly from the ANTLR wiki, it is an expression evaluator. We can assign integer values to variables and perform simple arithmetic operations.&lt;/p&gt;


	&lt;p&gt;Let&amp;#8217;s start feeding in some strings. If you have &lt;a href=&quot;http://jruby.codehaus.org/&quot;&gt;JRuby&lt;/a&gt; and &lt;a href=&quot;http://antlr.org/&quot;&gt;ANTLR&lt;/a&gt; installed, you can download &lt;code&gt;antlr_velvet_demo.rb&lt;/code&gt; etc. and play along.&lt;/p&gt;


	&lt;p&gt;AntlrVelvet has convenience methods for parsing files as well as strings. We&amp;#8217;ll just parse strings. To initialize a new string parser, you do:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;30&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 1&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;&lt;code&gt;parser&lt;/code&gt; is now all ready to parse, but the parsing hasn&amp;#8217;t happened yet. Because AntlrVelvet is designed for unit testing, it doesn&amp;#8217;t make any assumptions about how you want to parse. If you call &lt;code&gt;parser.prog&lt;/code&gt;, it will parse according to the &lt;code&gt;Expr&lt;/code&gt; grammar&amp;#8217;s main &lt;code&gt;prog&lt;/code&gt; rule. You might only want to test a part of your grammar, however, such as an &lt;code&gt;atom&lt;/code&gt; or an &lt;code&gt;expr&lt;/code&gt;. You can call any rule defined in your grammar, e.g. &lt;code&gt;parser.atom&lt;/code&gt;, and the string you passed at initialization will be evaluated against that rule.&lt;/p&gt;


	&lt;p&gt;So, let&amp;#8217;s try this now:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;34&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prog&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;we get:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;line 0:-1 missing NEWLINE at &amp;#39;&amp;lt;EOF&amp;gt;&amp;#39;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;Oops. Okay, we tried comparing &lt;code&gt;&quot;1 + 1&quot;&lt;/code&gt; to the &lt;code&gt;prog&lt;/code&gt; rule and we got an error, because the &lt;code&gt;prog&lt;/code&gt; rule expects to process one or more &lt;code&gt;stat&lt;/code&gt; rules, each of which are newline-terminated. If we add a newline at the end:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;41
42&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 1&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prog&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;we get:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;nil
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;&lt;code&gt;nil&lt;/code&gt;? Honestly, how hard is &lt;code&gt;1 + 1 = 2&lt;/code&gt;? Okay, looking back at &lt;code&gt;Expr.g&lt;/code&gt; I see that &lt;code&gt;prog&lt;/code&gt; doesn&amp;#8217;t actually return a value. Let&amp;#8217;s try an &lt;code&gt;expr&lt;/code&gt; since according to the grammar that should return an &lt;code&gt;int&lt;/code&gt;.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;46
47&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 1&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expr&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;2
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;2! Excellent. After all this we can add 1 and 1.&lt;/p&gt;


	&lt;p&gt;Now, let&amp;#8217;s take another look at &lt;code&gt;stat&lt;/code&gt;, since that really does seem to be the main action. We don&amp;#8217;t return anything, but we do write to &lt;code&gt;System.out.println()&lt;/code&gt;. Remember AntlrVelvet captures Java&amp;#8217;s output and error streams so it can look for parsing errors and raise an exception if it sees one. If you want to get at the contents of the output stream, you do this via the &lt;code&gt;output&lt;/code&gt; method.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;51
52
53&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 1&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stat&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inspect&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;[&amp;quot;2&amp;quot;]
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;So, we have an array with a 2 in it. This is because &lt;code&gt;stat&lt;/code&gt; prints out &lt;code&gt;expr.value&lt;/code&gt; every time it gets called. Now, let&amp;#8217;s try calling &lt;code&gt;prog&lt;/code&gt; again with some additional input.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;57
58
59
60
61
62
63
64&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;1 + 1&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;x = 1&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;y = 2&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;3*(x+y)&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prog&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inspect&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;[&amp;quot;2&amp;quot;, &amp;quot;9&amp;quot;]
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;The first &amp;#8220;2&amp;#8221; comes from 1 + 1. The lines x = 1 and y = 2 are of the form &lt;code&gt;ID = expr NEWLINE&lt;/code&gt;, they are variable assignments rather than &lt;code&gt;expr&lt;/code&gt; evaluations, so nothing is added to the output buffer, instead the parser executes the &lt;code&gt;memory.put ...&lt;/code&gt; action. The final line is an &lt;code&gt;expr&lt;/code&gt; which evaluates to 9 and so this is added to the output buffer. Note the extra newline at the end of our input, if this wasn&amp;#8217;t there we would end up with an &lt;code&gt;EOF&lt;/code&gt; error. (Of course you can easily write a grammar which doesn&amp;#8217;t need a newline at the end if you want to.)&lt;/p&gt;


	&lt;p&gt;Let&amp;#8217;s do one more example.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;68&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 2 * (3 + 4 * (5 + 6 * (7 + 8 * 9)))&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expr&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;3839
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;There&amp;#8217;s some nice mutual recursion in &lt;code&gt;Expr.g&lt;/code&gt; that lets it handle nested parentheses like this, go check it out if you haven&amp;#8217;t seen it before.&lt;/p&gt;


	&lt;p&gt;Now, &lt;code&gt;Expr.g&lt;/code&gt; doesn&amp;#8217;t do anything mathematically that we couldn&amp;#8217;t do in Ruby. What it does do is let us accept a string and only perform this calculation if the string conforms to our grammar. So, if, say, you were writing a web application and wanted to allow your users to perform arithmetic on your website, but for some reason you didn&amp;#8217;t feel like doing this via&lt;sup&gt;&lt;a href=&quot;#fn2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;code&gt;&amp;lt;%= eval(params[:arithmetic_expression]) %&amp;gt;&lt;/code&gt;, something like an ANTLR grammar might let you nicely sandbox and sanitize your user&amp;#8217;s inputs. And, &lt;code&gt;Expr.g&lt;/code&gt; might not be able to do anything all that fancy, but a more complex grammar might give you (and your untrustworthy users) some really interesting functionality.&lt;/p&gt;


	&lt;p&gt;So, to recap, &lt;code&gt;Expr.string_parser&lt;/code&gt; returns an object of class &lt;code&gt;Expr&lt;/code&gt;, a Ruby class. We can call any of the grammar&amp;#8217;s rules as a method on that object and they will parse the string according to that rule, and the method will return a value if the rule returns a value.&lt;/p&gt;


	&lt;p&gt;After we have done the parsing, we can call &lt;code&gt;parser.output&lt;/code&gt; to see what&amp;#8217;s in the output buffer. This will either be an array of strings or &lt;code&gt;nil&lt;/code&gt;. We can also call &lt;code&gt;parser.parser&lt;/code&gt; or &lt;code&gt;parser.lexer&lt;/code&gt; to return the &lt;code&gt;ExprParser&lt;/code&gt; or &lt;code&gt;ExprLexer&lt;/code&gt;, the actual ANTLR objects which do the parsing. Remember Expr is just our wrapper class. We can even call &lt;code&gt;parser.token_stream&lt;/code&gt;. And, since this is JRuby, we can have some introspection fun with these objects:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;93&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java_methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;already_parsed_rule
atom
backtracking_level
begin_resync
consume_until
display_recognition_error
emit_error_message
end_resync
equals
expr
grammar_file_name
hash_code
input
java_class
java_object
match
match_any
memoize
mismatch_is_missing_token
mismatch_is_unwanted_token
mult_expr
notify
notify_all
number_of_syntax_errors
prog
recover
recover_from_mismatched_set
report_error
reset
rule_invocation_stack
rule_memoization_cache_size
source_name
stat
synchronized
to_java_object
to_string
to_strings
token_names
token_stream
trace_in
trace_out
wait
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;97&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java_methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;consume
discard_off_channel_tokens
discard_token_type
equals
get
hash_code
index
java_class
java_object
la
lt
mark
notify
notify_all
release
reset
rewind
seek
size
source_name
synchronized
to_java_object
to_string
token_source
tokens
wait
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;101&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java_methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lexer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;already_parsed_rule
backtracking_level
begin_resync
char_error_display
char_index
char_position_in_line
char_stream
consume_until
display_recognition_error
emit
emit_error_message
end_resync
equals
grammar_file_name
hash_code
java_class
java_object
line
m_id
m_int
m_newline
m_t__10
m_t__11
m_t__12
m_t__13
m_t__8
m_t__9
m_tokens
m_ws
match
match_any
match_range
memoize
mismatch_is_missing_token
mismatch_is_unwanted_token
next_token
notify
notify_all
number_of_syntax_errors
recover
recover_from_mismatched_set
report_error
reset
rule_invocation_stack
rule_memoization_cache_size
skip
source_name
synchronized
text
to_java_object
to_string
to_strings
token_names
trace_in
trace_out
wait
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;So, here we have the internals of ANTLR laid out. You can see the values of any of these methods. Play with them. Use them for testing. Go into an interactive &lt;code&gt;jirb&lt;/code&gt; session and follow the lexing and parsing step-by-step until you know exactly how the grammar does its thing.&lt;/p&gt;


	&lt;p&gt;By the way, AntlrVelvet uses &lt;code&gt;method_missing&lt;/code&gt; to pass method calls like &lt;code&gt;prog&lt;/code&gt; and &lt;code&gt;stat&lt;/code&gt; on to the embedded ExprParser object, but you could also explicitly define any of these methods and call the ExprParser yourself. This can be useful if you want to do some Ruby post-processing or perhaps debugging. If you do this, you will need to invoke &lt;code&gt;AntlrVelvet&lt;/code&gt;&amp;#8217;s &lt;code&gt;capture_java_streams&lt;/code&gt; yourself if you don&amp;#8217;t want Java writing to your console. And remember, in JRuby Java&amp;#8217;s System.out and Ruby&amp;#8217;s $stdout are totally independent of each other. So if you have Ruby&amp;#8217;s $stdout redirected to a logfile, Java&amp;#8217;s System.out will still write to the console unless it, too, has been redirected.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;72
73
74
75
76
77
78&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Expr&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;atom&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;Your atom is &amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_s&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;99&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atom&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;





&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;Your atom is 99
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;On to testing! I&amp;#8217;m just using Ruby&amp;#8217;s Test::Unit for now. To begin with, I define some custom assertions and convenience methods:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;assert_nothing_left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;assert_something_left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;assert_valid_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_nothing_raised&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;assert_nothing_left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;assert_too_much_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_something_left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;assert_invalid_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_raise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AntlrError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;parsed_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;parser_output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string_parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;To recap the examples from earlier in test form:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_prog_without_newline&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_invalid_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 1&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_prog_with_newline&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_valid_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 1&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_expr&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parsed_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 1&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_stat_output&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser_output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 1&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_prog_output&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;     1 + 1&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;     x = 1&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;     y = 2&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;     3*(x+y)&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;     &amp;quot;&lt;/span&gt;

     &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;9&amp;quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser_output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

   &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_nasty_expr&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3839&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parsed_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;1 + 2 * (3 + 4 * (5 + 6 * (7 + 8 * 9)))&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;The &lt;code&gt;assert_valid_input&lt;/code&gt;, &lt;code&gt;assert_invalid_input&lt;/code&gt; and &lt;code&gt;assert_too_much_input&lt;/code&gt; just take a symbol and an input string for the parser. &lt;code&gt;assert_invalid_input&lt;/code&gt; expects the parsing to throw an AntlrError, meaning that ANTLR wrote something to Java&amp;#8217;s System.err. This is used when you want to make sure that the input string is illegal for the rule in question, either too short or containing illegal characters or sequences. &lt;code&gt;assert_too_much_input&lt;/code&gt; means that the input is acceptable, but after processing there&amp;#8217;s something left over. In order for &lt;code&gt;assert_valid_input&lt;/code&gt; to pass, ANTLR must not find any errors and the input string must be entirely consumed.&lt;/p&gt;


	&lt;p&gt;&lt;code&gt;parsed_value&lt;/code&gt; and &lt;code&gt;parser_output&lt;/code&gt; also take a symbol and an input string as arguments. They parse the string and return the grammar rule&amp;#8217;s return value or the text written to System.out respectively. Then you can write assertions for what the value or output should be, as shown. &lt;code&gt;assert_nothing_left&lt;/code&gt; and &lt;code&gt;assert_something_left&lt;/code&gt; are mostly for internal use, you have to initialize and run a parser yourself before passing the parser in to these functions, but of course you can call these directly if you want to.&lt;/p&gt;


	&lt;p&gt;I use ANTLR&amp;#8217;s Java target because, as of this writing, the Ruby target for ANTLRv3 isn&amp;#8217;t complete enough to be practical. Even though I&amp;#8217;m not a fan of Java, I have to say that I really don&amp;#8217;t mind the little bit of it I have to write in a ANTLR grammar. If you are a Ruby programmer, don&amp;#8217;t let a bit of Java scare you away from the possibilities that ANTLR opens up. And, if you are a Java programmer, I hope this has demonstrated that JRuby can be a useful complement to Java, giving you a fantastic scripting language and testing environment for development, even if it doesn&amp;#8217;t play any role in your final product.&lt;/p&gt;


	&lt;p&gt;As usual, all the source code is available for download from the sidebar. If you have any difficulty running any of the files, please let me know in the comments.&lt;/p&gt;


	&lt;p class=&quot;small&quot; id=&quot;fn1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt; However, if you want to write a &lt;a href=&quot;http://www.theonion.com/content/node/37408&quot;&gt;syndicated advice column protocol&lt;/a&gt; then you might want a more state-machine-oriented tool like &lt;a href=&quot;http://research.cs.queensu.ca/~thurston/ragel/&quot;&gt;Ragel&lt;/a&gt;.&lt;/p&gt;


	&lt;p class=&quot;small&quot; id=&quot;fn2&quot;&gt;&lt;sup&gt;2&lt;/sup&gt; Joke!! I&amp;#8217;m KIDDING people!! You really think I would put something so unsafe in my code? Of course I meant &amp;lt;%=&lt;span style=&quot;color:red&quot;&gt;h&lt;/span&gt; eval(params[:arithmetic_expression]) %&amp;gt;.&lt;/p&gt;
    </content>
  </entry>
  
  <entry>
    <title>In Touch With .htaccess</title>
    <link href="/blog/2008/06/in-touch-with-htaccess" />
    <id>tag:ananelson.com,2008-06-20:1213960621</id>
    <updated>2008-06-20T12:17:01+01:00</updated>
    <content type="html">
        &lt;p&gt;Please visit the website for source code downloads and syntax highlighting.&lt;/p&gt;&lt;h1&gt;In Touch With .htaccess&lt;/h1&gt;


	&lt;p&gt;(I&amp;#8217;m actually not sure why I have named this post that, but it has stuck in my brain and refuses to budge. Also I seem to have a four-word title thing going on since moving to &lt;a href=&quot;http://webby.rubyforge.org&quot;&gt;Webby&lt;/a&gt;, so I&amp;#8217;m going with it for now.)&lt;/p&gt;


	&lt;p&gt;One of the many, many things I am loving about Webby is getting to play with .htaccess files. They are such brilliant little utilities to have at your disposal. I actually still find it a novelty to upload files and directories to a server and then see those pages in my web browser, and getting to do fun tricks with .htaccess is just icing on a very yummy cake. I&amp;#8217;m not going anti-web-framework or anything, really I&amp;#8217;m not, but it sure is refreshing to get back to basics.&lt;/p&gt;


	&lt;h3&gt;.htaccess vs. Sitewide Config&lt;/h3&gt;


	&lt;p&gt;&lt;a href=&quot;http://httpd.apache.org/docs/trunk/howto/htaccess.html#when&quot;&gt;According to Apache&lt;/a&gt;, you should avoid using .htaccess files as much as possible and instead put your instructions into the main server configuration file. They have some good arguments, but I think .htaccess files make a lot more sense for the uses I&amp;#8217;m describing. (Of course, it&amp;#8217;s a moot point since I am on shared hosting and don&amp;#8217;t have access to my main server configuration file.)&lt;/p&gt;


	&lt;p&gt;I like .htaccess files since they are utterly immediate. You put them right where you need their functionality. Your 5 lines of code aren&amp;#8217;t lost in a 4,000 line conf file in some directory you can never find. You can see it, change it, version it. You won&amp;#8217;t forget that it&amp;#8217;s there. If you delete the directory, you delete the configuration along with it. It will always be deployed, automatically, at the same time as the rest of your site. It&amp;#8217;s a perfect fit with Webby.&lt;/p&gt;


	&lt;h3&gt;Redirects&lt;/h3&gt;


	&lt;p&gt;I set up my first .htaccess file to do some permanent redirects. I made a big mistake with my old blog. I displayed the full text of articles on lots of pages throughout the site, like tag pages and monthly and yearly archive pages, but I didn&amp;#8217;t put &lt;a href=&quot;http://www.robotstxt.org/meta.html&quot;&gt;NOINDEX tags&lt;/a&gt; on these pages. So, search engines had no way of knowing which was the actual permanent location of the blog post content, and which was just a /tagged/with/buzzword page. Unfortunately, in many cases the tag page ended up being much more prominent on Google than the actual blog post. I&amp;#8217;m guessing this was because I was silly enough to put tag clouds in my sidebar, so these /tagged/with pages had lots of internal links pointing to them, hence they looked more important.&lt;/p&gt;


	&lt;p&gt;I have now decided that tags are a total waste of time anyway. Nobody needs tags to find your content. That&amp;#8217;s what search is for. Google knows how to semantically parse your content much better than you do. It will find the important words. If you really want to have tags on your blog then go bookmark all your blog posts in &lt;a href=&quot;http://del.icio.us&quot;&gt;del.icio.us&lt;/a&gt;. Or, better yet, go and see what other people have tagged your posts with. Those are the tags that really matter anyway, the ones your readers assign.&lt;/p&gt;


	&lt;p&gt;Getting back to the point, if you have any of your content duplicated anywhere on your site, then NOINDEX tags are your friend. Only let the search engines see 1 copy of your content. Everyone will be much happier that way. Since I didn&amp;#8217;t do this, and I still get (relatively) plenty of inward traffic to some of these /tagged/with pages, the first .htaccess file I set up was along these lines:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1
2
3&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;Redirect&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;301&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;/tagged/with/pictobrowser&lt;/span&gt; http://ananelson.com/said/on/2007/12/17/screencasts-are-so-2005
&lt;span class=&quot;nb&quot;&gt;Redirect&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;301&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;/tagged/with/skitch&lt;/span&gt; http://ananelson.com/said/on/2007/12/17/screencasts-are-so-2005
&lt;span class=&quot;nb&quot;&gt;Redirect&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;301&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;/tagged/with/loldocs&lt;/span&gt; http://ananelson.com/said/on/2007/12/17/screencasts-are-so-2005
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;This is the sort of thing I probably would put into a sitewide conf file if I had access to it. They are redirects which can be safely forgotten about, I&amp;#8217;ll probably take them out eventually but they won&amp;#8217;t do any harm if they stay there indefinitely. I redirected the more popular tag pages directly to the blog posts they refer to, then I added a catch-all to redirect any other pages to the list of all my old blog posts:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;7&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;RedirectMatch&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;303&lt;/span&gt; ^/tagged http://ananelson.com/said/on
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;As with any regular expression based scenario, make sure catch-alls go AFTER everything else that their pattern might match.&lt;/p&gt;


	&lt;p&gt;By the way, you can increase the verbosity of &lt;a href=&quot;http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#rewriteloglevel&quot;&gt;Apache mod_rewrite logging&lt;/a&gt; temporarily to help you debug RedirectMatch statements. Comes in handy if you are trying to figure out how to do fancy regular expression redirects. (But don&amp;#8217;t leave it on a high setting unless you are &lt;a href=&quot;http://www.biostat.jhsph.edu/courses/bio623/misc/baby_got_stats.mp3&quot;&gt;impressed by the size of really large log files&lt;/a&gt;.)&lt;/p&gt;


	&lt;h3&gt;Custom Directory Listings&lt;/h3&gt;


	&lt;p&gt;I have a &lt;a href=&quot;http://ananelson.com/tmp&quot;&gt;/tmp directory&lt;/a&gt; which I am experimenting with at the moment. It&amp;#8217;s, well, a place for me to dump temporary files. I knew, of course, that Apache will simply display the contents of a directory unless you tell it not to. I didn&amp;#8217;t know until yesterday that you can &lt;a href=&quot;http://httpd.apache.org/docs/2.0/mod/mod_autoindex.html#indexoptions&quot;&gt;customize&lt;/a&gt; the way it does this. You can even add your own stylesheets so that the directory contents list looks like the rest of your site. So, of course I had to play with this! I have a .htaccess file in /tmp which says:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;10
11
12&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;IndexOptions&lt;/span&gt; +SuppressHTMLPreamble +IgnoreCase NameWidth=*
&lt;span class=&quot;nb&quot;&gt;HeaderName&lt;/span&gt; apache-index-header.html
&lt;span class=&quot;nb&quot;&gt;ReadmeName&lt;/span&gt; apache-index-footer.html
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;The &lt;code&gt;+SuppressHTMLPreamble&lt;/code&gt; lets you define our own HTML headers (and therefore stylesheets), otherwise you are stuck with HTML 3! You specify a file HeaderName which should include opening &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tags, and then a file called ReadmeName which should include the closing &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;/html&amp;gt;&lt;/code&gt; tags. Apache will stick your directory contents in the middle.&lt;/p&gt;


	&lt;h3&gt;Restricting Access&lt;/h3&gt;


	&lt;p&gt;Another obvious use for .htaccess files is to restrict access to places you don&amp;#8217;t want people to go. Sometimes, you just don&amp;#8217;t want a directory&amp;#8217;s contents to be listed but the items in that directory still need to be accessible. I have a /bin directory which contains some files, like the comment submission form, which need to be accessible, but I don&amp;#8217;t really want people snooping in my /bin directory. (Not for security, just aesthetics.) So, there&amp;#8217;s a .htaccess file in there with &lt;code&gt;Options -Indexes&lt;/code&gt;.  I have some other directories which contain files that are only going to be used via the file system on the server. These I can restrict access to completely with &lt;code&gt;deny from all&lt;/code&gt;. It&amp;#8217;s convenient for me to deploy these files using Webby, and with &lt;code&gt;deny from all&lt;/code&gt; they are neatly hidden.&lt;/p&gt;


	&lt;h3&gt;Drafts in Webby&lt;/h3&gt;


	&lt;p&gt;A nice way to work on drafts of blog posts in Webby is to add a .htaccess file to the directory containing your post with a &lt;code&gt;deny from all&lt;/code&gt; statement in it. (I put each post in its own directory with the post in an index.html file.) In this way the blog post is invisible on your website, so you can continue to deploy your site as normal, but locally Webby&amp;#8217;s built in heel server will ignore .htaccess files so you will be able to see the drafts yourself when you autobuild. You need to combine this with some way to prevent the blog post from being mentioned in your RSS feed or on your archive pages. I stick &lt;code&gt;index: false&lt;/code&gt; in the metadata until I am ready to publish (see &lt;a href=&quot;http://groups.google.com/group/webby-forum/browse_thread/thread/f10c08f7f72878fa&quot;&gt;this thread on the Webby mailing list&lt;/a&gt;) and I comment out the created at timestamp. In fact I do this in my blog template so blog posts are &amp;#8220;unpublished&amp;#8221; by default.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1
2
3
4
5
6
7
8&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;#####&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Uncomment created_at time,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# delete index: false&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# and delete the .htaccess file&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# when ready to post this article.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &amp;lt;%= Time.now.to_y %&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#####&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;Those of you who got a feedful of lorem from me last night will, no doubt, appreciate this precaution. Sorry about that, and props to &lt;a href=&quot;http://fintanp.wordpress.com/&quot;&gt;Fintan&lt;/a&gt; who took the time to reply in Latin.&lt;/p&gt;


	&lt;h3&gt;You put the feed where?&lt;/h3&gt;


	&lt;p&gt;When I was setting up my new atom feed I noticed that WordPress put the old feed at http://ananelson.com/feed/. A directory. Seriously, mod_rewrite has a lot to answer for. .htaccess to the rescue, though. I was able to solve this by creating a directory feed/ and putting&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;15&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;DirectoryIndex&lt;/span&gt; blog.xml
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;so that requests for /feed/ fetch the blog.xml file. But please, this is just wrong, update your subscriptions to &lt;a href=&quot;http://ananelson.com/feeds/blog.xml&quot;&gt;the new feed&lt;/a&gt;.&lt;/p&gt;


	&lt;h3&gt;Mime Trouble&lt;/h3&gt;


	&lt;p&gt;You may have noticed the source code files in the sidebar. They will get an explanatory post of their own soon. There is lovely &lt;a href=&quot;http://pygments.org&quot;&gt;pygments&lt;/a&gt; syntax highlighting&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; in my blog posts, but in the sidebar I want people to be able to click and just view the full plain source in their browsers. (If you would rather download that&amp;#8217;s what the &lt;code&gt;.tgz&lt;/code&gt; is for.) Unfortunately some file extensions won&amp;#8217;t display in the browser, they will download and try to open themselves with really bizarre applications. QuickTime for Ragel files?&lt;/p&gt;


	&lt;p&gt;But, as you may have guessed, this can be &lt;a href=&quot;http://httpd.apache.org/docs/2.0/mod/mod_mime.html#addtype&quot;&gt;fixed&lt;/a&gt; with a .htaccess file! All we need to do is:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;18
19
20&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;AddType&lt;/span&gt; text/plain .sh
&lt;span class=&quot;nb&quot;&gt;AddType&lt;/span&gt; text/plain .rl
&lt;span class=&quot;nb&quot;&gt;AddType&lt;/span&gt; text/plain .php
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;For whichever file extensions we want. I put this .htaccess file in my blog/ directory so it only affects subdirectories of that and I can have working files with these extensions elsewhere in my site.&lt;/p&gt;


	&lt;p&gt;Right, that&amp;#8217;s all. I&amp;#8217;m going off to play with &lt;code&gt;mod_expires&lt;/code&gt; now.&lt;/p&gt;


	&lt;p class=&quot;small&quot; id=&quot;fn1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt; As used by GitHub. Yes, that&amp;#8217;s right, they use a syntax highlighter written in Python.&lt;/p&gt;
    </content>
  </entry>
  
  <entry>
    <title>Beware POST Request Redirects</title>
    <link href="/blog/2008/06/beware-post-request-redirects" />
    <id>tag:ananelson.com,2008-06-18:1213808942</id>
    <updated>2008-06-18T18:09:02+01:00</updated>
    <content type="html">
        &lt;p&gt;Please visit the website for source code downloads and syntax highlighting.&lt;/p&gt;&lt;h1&gt;Beware POST Request Redirects&lt;/h1&gt;


	&lt;p&gt;You probably know that you should choose either &lt;code&gt;example.com&lt;/code&gt; or &lt;code&gt;www.example.com&lt;/code&gt; for your website, but allowing both of these effectively means that you are splitting your search engine ranking in two (if you care about such things). I use &lt;a href=&quot;http://www.dreamhost.com/r.cgi?109541&quot; title=&quot;referral link to Dreamhost&quot;&gt;Dreamhost&lt;/a&gt; and they make it very easy to redirect all traffic to one or the other of these. They do this using a 301 (permanent) redirect. For example, if you type &lt;code&gt;curl http://www.ananelson.com&lt;/code&gt;, you will see:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE HTML PUBLIC &quot;-//IETF//DTD HTML 2.0//EN&quot;&amp;gt;
&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;
&amp;lt;TITLE&amp;gt;301 Moved Permanently&amp;lt;/TITLE&amp;gt;
&amp;lt;/HEAD&amp;gt;&amp;lt;BODY&amp;gt;
&amp;lt;H1&amp;gt;Moved Permanently&amp;lt;/H1&amp;gt;
The document has moved &amp;lt;A HREF=&quot;http://ananelson.com/&quot;&amp;gt;here&amp;lt;/A&amp;gt;.&amp;lt;P&amp;gt;
&amp;lt;/BODY&amp;gt;&amp;lt;/HTML&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;You can easily set this up yourself in your Apache config or a .htaccess file.&lt;/p&gt;


	&lt;p&gt;Now, something I&amp;#8217;ve managed to do twice in the last week is to POST a form to the www-version of my domain, which then was redirected to the non-www version. When this happens, all the POST data is lost! (See the &lt;a href=&quot;http://httpd.apache.org/docs/1.3/misc/howto.html&quot;&gt;Apache documentation&lt;/a&gt;.) So, remember, when creating a form set its action URL to whichever is the preferred variant of your domain name so that no redirect takes place.&lt;/p&gt;
    </content>
  </entry>
  
  <entry>
    <title>Converting WordPress To Webby</title>
    <link href="/blog/2008/06/converting-wordpress-to-webby" />
    <id>tag:ananelson.com,2008-06-16:1213654340</id>
    <updated>2008-06-16T23:12:20+01:00</updated>
    <content type="html">
        &lt;p&gt;Please visit the website for source code downloads and syntax highlighting.&lt;/p&gt;&lt;h1&gt;Converting WordPress to Webby&lt;/h1&gt;


	&lt;p&gt;The process of converting my old &lt;a href=&quot;http://wordpress.org/&quot;&gt;WordPress&lt;/a&gt; posts to &lt;a href=&quot;http://webby.rubyforge.org/&quot;&gt;Webby&lt;/a&gt; was relatively painless, but there are a few things worth sharing.&lt;/p&gt;


	&lt;p&gt;The first step was to export my WordPress MySQL database and create a local copy, and then to create &lt;a href=&quot;http://datamapper.org/&quot;&gt;DataMapper&lt;/a&gt; classes corresponding to the two tables I was interested in, &lt;code&gt;wp_posts&lt;/code&gt; and &lt;code&gt;wp_comments&lt;/code&gt;.&lt;/p&gt;


&lt;pre&gt;
mysql&amp;gt; describe wp_posts;
+-----------------------+---------------------+
| Field                 | Type                |
+-----------------------+---------------------+
| ID                    | bigint(20) unsigned |
| post_author           | bigint(20)          |
| post_date             | datetime            |
| post_date_gmt         | datetime            |
| post_content          | longtext            |
| post_title            | text                |
| post_category         | int(4)              |
| post_excerpt          | text                |
| post_status           | varchar(20)         |
| comment_status        | varchar(20)         |
| ping_status           | varchar(20)         |
| post_password         | varchar(20)         |
| post_name             | varchar(200)        |
| to_ping               | text                |
| pinged                | text                |
| post_modified         | datetime            |
| post_modified_gmt     | datetime            |
| post_content_filtered | text                |
| post_parent           | bigint(20)          |
| guid                  | varchar(255)        |
| menu_order            | int(11)             |
| post_type             | varchar(20)         |
| post_mime_type        | varchar(100)        |
| comment_count         | bigint(20)          |
+-----------------------+---------------------+
24 rows in set (0.01 sec)                      

mysql&amp;gt; describe wp_comments;                   
+----------------------+---------------------+
| Field                | Type                |
+----------------------+---------------------+
| comment_ID           | bigint(20) unsigned |
| comment_post_ID      | int(11)             |
| comment_author       | tinytext            |
| comment_author_email | varchar(100)        |
| comment_author_url   | varchar(200)        |
| comment_author_IP    | varchar(100)        |
| comment_date         | datetime            |
| comment_date_gmt     | datetime            |
| comment_content      | text                |
| comment_karma        | int(11)             |
| comment_approved     | varchar(20)         |
| comment_agent        | varchar(255)        |
| comment_type         | varchar(20)         |
| comment_parent       | bigint(20)          |
| user_id              | bigint(20)          |
+----------------------+---------------------+
15 rows in set (0.00 sec)
&lt;/pre&gt;

	&lt;p&gt;And no, I don&amp;#8217;t know why they have &lt;code&gt;wp_posts.ID&lt;/code&gt; as a &lt;code&gt;bigint(20)&lt;/code&gt; and then &lt;code&gt;wp_comments.comment_post_ID&lt;/code&gt;, which should be the same size, as an &lt;code&gt;int(11)&lt;/code&gt;. This is a database that has been upgraded a few times so perhaps that&amp;#8217;s a legacy thing.&lt;/p&gt;


	&lt;p&gt;While DataMapper can easily accept a non-standard primary key in a table, it gets a little trickier when you are linking two tables together using &lt;code&gt;has n&lt;/code&gt; and &lt;code&gt;belongs_to&lt;/code&gt;. I found it simpler to just change the names of the primary keys and foreign key. So, after creating a new database and loading the mysqldump file with all my blog&amp;#8217;s data, I ran the following:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1
2
3&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wp_posts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CHANGE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ID&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unsigned&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wp_comments&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CHANGE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;comment_ID&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unsigned&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wp_comments&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CHANGE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;comment_post_ID&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



&lt;div class=&quot;box&quot;&gt;
    &lt;p&gt;Update: I think I cracked the custom &lt;code&gt;parent_key&lt;/code&gt;, &lt;code&gt;child_key&lt;/code&gt; bit in DataMapper.&lt;/p&gt;

    

&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;22
23
24
25
26
27
28
29
30
31
32&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;       &lt;span class=&quot;ss&quot;&gt;:comments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
               &lt;span class=&quot;ss&quot;&gt;:parent_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
               &lt;span class=&quot;ss&quot;&gt;:child_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:comment_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comment&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt;   &lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
               &lt;span class=&quot;ss&quot;&gt;:parent_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:comment_post_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
               &lt;span class=&quot;ss&quot;&gt;:child_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:comment_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



    See &lt;a href=&quot;http://ananelson.com/blog/2008/06/converting-wordpress-to-webby/parent_key_example.rb&quot;&gt;&lt;code&gt;parent_key_example.rb&lt;/code&gt;&lt;/a&gt; for a full working example. This should negate the need to change field names as above but I haven&amp;#8217;t fully tested it.
&lt;/div&gt;

	&lt;p&gt;One of the really nice things about DataMapper is that it will happily ignore any fields in your database which you don&amp;#8217;t mention explicitly. So, you only have to define DataMapper properties for the fields you want to be able to work with. The top of my &lt;code&gt;post.rb&lt;/code&gt; file looks like:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DataMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Resource&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;storage_names&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;wp_posts&amp;#39;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:serial&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# original field name ID&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DateTime&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Text&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;has&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comment_approved&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:comment_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;And my &lt;code&gt;comment.rb&lt;/code&gt; file starts with:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comment&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DataMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Resource&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;storage_names&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;wp_comments&amp;#39;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:serial&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# original field name comment_ID&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# original field name comment_post_ID&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comment_author&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comment_author_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comment_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DateTime&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comment_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comment_approved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Boolean&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Integer&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;So, just like that I can access all my posts and comments using DataMapper classes, and I can do things like &lt;code&gt;post.comments&lt;/code&gt;.&lt;/p&gt;


	&lt;p&gt;The initialization for DataMapper is simply:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1
2
3
4
5
6
7&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;rubygems&amp;quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;dm-core&amp;quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;DataMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;mysql://localhost/ananelson_wordpress?socket=/tmp/mysql.sock&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Local files&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;lib/comment&amp;quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;lib/post&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;Now, how do I get the content formatted nicely? Wordpress takes the data stored in the database and feeds it through a PHP function called &lt;code&gt;the_content&lt;/code&gt;.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;1
2
3
4
5
6
7&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;x&quot;&gt;// This is an excerpt from the WordPress source code. http://wordpress.org/about/gpl/&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;function the_content($more_link_text = &amp;#39;(more...)&amp;#39;, $stripteaser = 0, $more_file = &amp;#39;&amp;#39;) {&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;    $content = get_the_content($more_link_text, $stripteaser, $more_file);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;    $content = apply_filters(&amp;#39;the_content&amp;#39;, $content);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;    $content = str_replace(&amp;#39;]]&amp;gt;&amp;#39;, &amp;#39;]]&amp;amp;gt;&amp;#39;, $content);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;    echo $content;&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;The &lt;code&gt;apply_filters&lt;/code&gt; function is the thing that interests me. More digging in the WordPress source revealed:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;x&quot;&gt;// This is an excerpt from the WordPress source code. http://wordpress.org/about/gpl/&lt;/span&gt;

&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;the_content&amp;#39;, &amp;#39;wptexturize&amp;#39;);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;the_content&amp;#39;, &amp;#39;convert_smilies&amp;#39;);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;the_content&amp;#39;, &amp;#39;convert_chars&amp;#39;);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;the_content&amp;#39;, &amp;#39;wpautop&amp;#39;);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;the_content&amp;#39;, &amp;#39;prepend_attachment&amp;#39;);&lt;/span&gt;

&lt;span class=&quot;x&quot;&gt;# snip...&lt;/span&gt;

&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;comment_text&amp;#39;, &amp;#39;wptexturize&amp;#39;);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;comment_text&amp;#39;, &amp;#39;convert_chars&amp;#39;);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;comment_text&amp;#39;, &amp;#39;make_clickable&amp;#39;, 9);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;comment_text&amp;#39;, &amp;#39;force_balance_tags&amp;#39;, 25);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;comment_text&amp;#39;, &amp;#39;convert_smilies&amp;#39;, 20);&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;add_filter(&amp;#39;comment_text&amp;#39;, &amp;#39;wpautop&amp;#39;, 30);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;So, WordPress has a number of filters which are applied to the post content and the comments after the text is pulled out of the database. The simplest way I could think of to replicate this behaviour was to just use these same WordPress filters. I decided that I could live without the &lt;code&gt;convert_smilies&lt;/code&gt;, and that there was no reason not to use &lt;code&gt;make_clickable&lt;/code&gt; for my posts as well as for the comments, so that left me with a standard list of filters. I wrote a short php-based shell script:&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env php -q&lt;/span&gt;
&amp;lt;?php

include &lt;span class=&quot;s1&quot;&gt;&amp;#39;wp/plugin.php&amp;#39;&lt;/span&gt;;

include &lt;span class=&quot;s1&quot;&gt;&amp;#39;wp/kses.php&amp;#39;&lt;/span&gt;;
include &lt;span class=&quot;s1&quot;&gt;&amp;#39;wp/formatting.php&amp;#39;&lt;/span&gt;;
include &lt;span class=&quot;s1&quot;&gt;&amp;#39;wp/shortcodes.php&amp;#39;&lt;/span&gt;;

&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; file_get_contents&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;;

&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; wptexturize&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; convert_chars&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; make_clickable&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; force_balance_tags&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; wpautop&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt;;
?&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;Then I just had to wrap the shell script in Ruby.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;10
11
12
13
14
15
16
17
18
19
20&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;wp_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tmpfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;temp.txt&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmpfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`./wp_format &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmpfile&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
  &lt;span class=&quot;sb&quot;&gt;`rm &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmpfile&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;For some reason Ruby&amp;#8217;s Tempfile library gave me some strange filenames which either got garbled or weren&amp;#8217;t palatable to system(), so I just used &amp;#8220;temp.txt&amp;#8221;. You could always add a timestamp if you wanted to.&lt;/p&gt;


	&lt;p&gt;Now, I need to recreate the perma-url scheme I had set up in WordPress.&lt;/p&gt;




&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;14
15
16
17
18
19
20
21
22
23&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;filedir&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;../content/&amp;quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# relative path to webby content dir&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;said/on/&amp;quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;%Y/%m/%d/&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post_name&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;filename&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;filedir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;/index.txt&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;I used a directory &amp;#8220;said/on&amp;#8221; (yeah, sorry, I was feeling too clever that day) followed by Year/Month/Day and then the post slug. So, in my Post class I have two functions, &lt;code&gt;filedir&lt;/code&gt; which creates the directory and then &lt;code&gt;filename&lt;/code&gt; which adds the post slug and a .txt extension (.txt since this is going into Webby).&lt;/p&gt;


	&lt;p&gt;Finally, I need code which formats comments and posts, and then a method to iterate over all published posts and all approved comments to print them in that format.&lt;/p&gt;


In &lt;code&gt;post.rb&lt;/code&gt;:


&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;webby_header&lt;/span&gt;
&lt;span class=&quot;sx&quot;&gt;%{---&lt;/span&gt;
&lt;span class=&quot;sx&quot;&gt;title: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post_title&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;sx&quot;&gt;created_at: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;sx&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;sx&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;publish&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;FileUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mkdir_p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filedir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;w&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webby_header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Post no. 33 and wp_format don&amp;#39;t get along.&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wp_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comments&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty?&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;h3&amp;gt;Comments&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;comments&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish_all&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;FileUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rm_rf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;../content/said&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post_status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&amp;#39;publish&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publish&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



In &lt;code&gt;comment.rb&lt;/code&gt;:


&lt;table class=&quot;highlighttable&quot;&gt;&lt;tr&gt;&lt;td class=&quot;linenos&quot;&gt;&lt;pre&gt;16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;author_with_url&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;comment_author_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;comment_author&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;sx&quot;&gt;%{&amp;lt;a href=&amp;quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comment_author_url&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comment_author&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;&amp;lt;/a&amp;gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_html&lt;/span&gt;
    &lt;span class=&quot;sx&quot;&gt;%{&lt;/span&gt;
&lt;span class=&quot;sx&quot;&gt;&amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;author_with_url&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;&amp;lt;/b&amp;gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comment_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;%d %b %Y&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wp_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comment_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;&lt;/span&gt;

&lt;span class=&quot;sx&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;



	&lt;p&gt;Not the most beautiful of code, but I&amp;#8217;m only using it once and it works.&lt;/p&gt;


	&lt;p&gt;So, when I call &lt;code&gt;Post.publish_all&lt;/code&gt;, I get a directory structure like this in my Webby content director