(Video) How to create a Domain Specific Language ?

On 3rd of October 2007, XAOP organized and hosted the first Belgian Ruby User Group(BRUG) meeting.

More than 20 individuals interested in developing, promoting, fostering, strengthening, and improving the Ruby programming language and Ruby community attended this meeting.

Interested to become a member ? Sign up to the mailinglist.

If you were unable to attend :

Peter gave a presentation on metaprogramming.

You can find the slides of the presentation on the brug rubyforge site

or watch the video of the presentation here.

The source code of the DSL developed in the presentation can be found below.


class Expression

  def method_missing(m, *args)
    FieldExpression.new(self, m)
  end

  def ==(other)
    BinaryOpExpression.new(self, other, '=')
  end

  def =~(other)
    BinaryOpExpression.new(self, other, 'RLIKE')
  end

  def &(other)
    BinaryOpExpression.new(self, other, 'AND')
  end

  def |(other)
    BinaryOpExpression.new(self, other, 'AND')
  end

  undef id

  def to_sql
    raise
  end

end

class VariableExpression < Expression

  attr_reader :varname

  def initialize(varname)
    @varname = varname
  end

  def to_sql
    @varname.to_s
  end

end

class FieldExpression < Expression

  def initialize(expression, field)
    @expression, @field = expression, field
  end

  def to_sql
    "#{@expression.to_sql}.#{@field}" 
  end

end

class BinaryOpExpression < Expression

  def initialize(expr1, expr2, op)
    @expr1, @expr2, @op = expr1, expr2, op
  end

  def to_sql
    "#{@expr1.to_sql} #{@op} #{@expr2.to_sql}" 
  end

end

class String

  def to_sql
    inspect
  end

end

class Regexp

  def to_sql
    self
  end

end

class Query

  def initialize(&blk)
    @from = {}
    instance_eval(&blk)
  end

  def to_sql
    "SELECT #{return_list} "\
    "FROM #{table_list} "\
    "WHERE #{condition_expr}" 
  end

  def from(map)
    @from = map
  end

  def select(*args)
    @select = args
  end

  def where(cond)
    @where = cond
  end

  def method_missing(m, *args)
    if args.length == 0 && @from[m]
      VariableExpression.new(m)
    else
      super
    end
  end

private

  def table_list
    @from.map { |v, t| "#{t} AS #{v}" }.join(", ")
  end

  def return_list
    @select.map { |r| r.to_sql }.join(", ")
  end

  def condition_expr
    @where.to_sql
  end

end

module Kernel

  def query(&blk)
    Query.new(&blk)
  end

end

class Object

  def self.const_missing(cid)
    file = cid.to_s.downcase
    require file
    const_get(cid)
  end

end

#==============================================

first1, first2, last = 'peter', 'glenn', 'v%'
banks = query do 
  from   :c => Customer, :b => Bank, :a => Account
  select b.name
  where  ((c.firstname == first1) |
          (c.firstname == first2)) &
         (c.lastname =~ last) &
         (a.owner == c) &
         (a.bank == b)
end

puts banks.to_sql