Begin main content

Ruby, HTML, s-expressions, lambdas? Oh my!

I've been noodling around with various lisps lately, and one of the sources of lisp's elegance is the lack of separation between data and code. A practical example of that is the cl-who library which creates html from an s-expression representation, ensuring all tags are correctly closed. For example:

(with-html-output (*http-stream*)
  (loop for (link . title) in '(("http://zappa.com/" . "Frank Zappa")
                                ("http://marcusmiller.com/" . "Marcus Miller")
                                ("http://www.milesdavis.com/" . "Miles Davis"))
        do (htm (:a :href link
                  (:b (str title)))
                :br)))

There are a number of ways to replicate this style in other languages. You can do it by building up a big string, but I want to print to a stream like the example above. Using lambdas we can make a pretty good approximation in Ruby. Here's some Ruby code using a simple module I whipped up (see below) to achieve the same output as the cl-who lisp code above:

engines = [["http://google.com/", "Google"],
           ["http://yahoo.com/", "Yahoo!"],
           ["http://webcrawler.com/", "Showing my age"]]

Htm.htm(engines.map { |e| Htm.htm(:a, :href, e[0], [:b, Htm.str(e[1])],
                                :br, nil)}).call

Lets say we realise that the br tags are nasty and want to put the list of links into a list:

Htm.htm(:ul, engines.map \
        { |e| Htm.htm(:li,
                      [:a, :href, e[0], [:b, Htm.str(e[1])]])}).call

It's not quite as elegant as the lisp equivalent because we are mixing lambdas and arrays - both as nesting constructs. Here is the final output (indented for clarity - the Htm module below does no indenting):

<ul>
  <li><a href="http://google.com/"><b>Google</b></a></li>
  <li><a href="http://yahoo.com/"><b>Yahoo!</b></a></li>
  <li><a href="http://webcrawler.com/"><b>Showing my age</b></a></li>
</ul>

Now even though we are controlling the order of execution such that the various parts will be printed to the stream in the right order, we still can't print until we have closed our outermost tag (usually html). To be able to stream output while also auto-closing the tags we're going to need to use continuations - I'll cook up an example of that for a later blog. There's also other neat ways we can have self-closing tags that will allow streaming - I'll post about that later also.

Here is the Htm module in full:

require 'cgi'

module Htm
  def Htm.str(content)
    lambda { print CGI.escapeHTML(content.to_s) }
  end

  def Htm._build_tag(tag, args)

    # open the tag
    print '<' + tag.to_s

    tok = args.shift

    # find any tag attribute key/value pairs
    while tok.kind_of? Symbol
      print ' ' + tok.to_s + '="' +
        CGI.escapeHTML(args.shift.to_s) + '"'
      tok = args.shift
    end

    if tok.nil?
      # self-close tag if no content
      print ' />'
    else
      # finish open tag
      print '>'

      # output tag content
      Htm._eval_content(
                        tok.kind_of?(Symbol) ? Htm.htm(tok, args) : tok )

      # close tag
      print '</' + tag.to_s + '>'
    end
  end

  def Htm._eval_content(content)
    while content.kind_of? Proc
      content = content.call
    end

    if content.kind_of? Array
      Htm._eval_content(Htm.htm( *content ))
    elsif ! content.nil?
      print content
    end
  end

  def Htm.htm( *args )
    lambda do

      tok = args.shift

      while ! tok.nil?
        if tok.kind_of? Symbol
          Htm._build_tag(tok, args)
        else
          Htm._eval_content(tok)
        end
        tok = args.shift
      end
    end
  end
end

09:13 PM, 06 May 2009 by Mark Aufflick Permalink | Short Link | Comments (0)

XML

Blog Categories

software (27)
..cocoa (13)
  ..heads up 'tunes (5)
..ruby (4)
..lisp (1)
..perl (3)
..openacs (1)
mac (18)
embedded (2)
..microprocessor (2)
  ..avr (1)
electronics (3)
design (1)
photography (24)
..black and white (6)
..A day in Sydney (18)
..The Daily Shoot (5)
food (2)

Notifications

Icon of Envelope Request notifications

Syndication Feed

XML

Recent Comments

  1. Unregistered Visitor: To replace current categories or remove them...
  2. Unregistered Visitor: Base 256
  3. Unregistered Visitor: ...
  4. Unregistered Visitor: www.pipl.com
  5. Unregistered Visitor: This reminds me of the infinite monkeys
  6. Unregistered Visitor: Feedback
  7. Unregistered Visitor: Diito... Snow Leopard no worky
  8. Unregistered Visitor: WFM
  9. Unregistered Visitor: Pie
  10. Unregistered Visitor: Helpful