(Video) How to create a Domain Specific Language ?
Posted by Stijn Van Vreckem on Oct 07, 2007
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.
<code class="ruby">
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
