On Sat, 23 Sep 2006 07:13:37 +0900, Ruby Quiz wrote:
> The three rules of Ruby Quiz:
>
> 1. Please do not post any solutions or spoiler discussion for this quiz until
> 48 hours have passed from the time on this message.
>
> 2. Support Ruby Quiz by submitting ideas as often as you can:
>
> http://www.rubyquiz.com/
>
> 3. Enjoy!
>
> Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
> on Ruby Talk follow the discussion. Please reply to the original quiz message,
> if you can.
>
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
>
> by Ken Bloom
>
> S-expressions are a useful way of representing functional expressions in many
> aspects of computing. Lisp's syntax is based heavily on s-expressions, and the
> fact that Lisp uses them to represent both code and data allows many interesting
> libraries (such as CLSQL: http://clsql.b9.com/) which do things with functions
> besides simply evaluating them. While working on building a SQL generation
> library, I found that it would be nice to be able to generate s-expressions
> programmatically with Ruby.
>
> An s-expression is a nested list structure where the first element of each list
> is the name of the function to be called, and the remaining elements of the list
> are the arguments to that function. (Binary operators are converted to prefix
> notation). For example the s-expression (in LISP syntax)
>
> (max (count field))
>
> would correspond to
>
> max(count(field))
>
> in ordinary functional notation. Likewise,
>
> (roots x (+ (+ (* x x) x) 1 ))
>
> would correspond to
>
> roots(x, ((x*x) + x) + 1)
>
> since we treat binary operators by converting them to prefix notation.
>
> Your mission: Create a function named sxp() that can take a block (not a
> string), and create an s-expression representing the code in the block.
>
> Since my goal is to post-process the s-expressions to create SQL code, there is
> some special behavior that I will allow to make this easier. If your code
> evaluates (rather than parsing) purely numerical expressions that don't contain
> functions or field names (represented by Symbols here), then this is
> satisfactory behavior since it shouldn't matter whether Ruby evaluates them or
> the SQL database evaluates them. This means, for example, that sxp{3+5} can give
> you 8 as an s-expression, but for extra credit, try to eliminate this behavior
> as well and return [:+, 3, 5].
>
> It is very important to avoid breaking the normal semantics of Ruby when used
> outside of a code block being passed to sxp.
>
> Here are some examples and their expected result:
>
> sxp{max(count(:name))} => [:max, [:count, :name]]
> sxp{count(3+7)} => [:count, 10] or [:count, [:+, 3, 7]]
> sxp{3+:symbol} => [:+, 3, :symbol]
> sxp{3+count(:field)} => [:+, 3, [:count, :field]]
> sxp{7/:field} => [:/, 7, :field]
> sxp{:field > 5} => [:>, :field, 5]
> sxp{8} => 8
> sxp{:field1 == :field2} => [:==, :field1, :field2]
> 7/:field => throws TypeError
> 7+count(:field) => throws NoMethodError
> 5+6 => 11
> :field > 5 => throws NoMethodError
>
> (In code for this concept, I returned my s-expression as an object which had
> inspect() modified to appear as an array. You may return any convenient object
> representation of an s-expression.)
Here's my pure ruby code, which I coded up before making the quiz. I
honestly didn't even know that ParseTree was out there.
Thinking about where I made it now, what I came up with probably wasn't
strictly speaking a complete s-expression generator for arbitrary ruby
code, but something of a relatively limited domain for working with SQL
functions.
require 'singleton'
#the Blank class is shamelessly stolen from the Criteria library
class Blank
mask = ["__send__", "__id__", "inspect", "class", "is_a?", "dup",
"instance_eval"];
methods = instance_methods(true)
methods = methods - mask
methods.each do
| m |
undef_method(m)
end
end
#this is a very blank class that intercepts all free
#functions called within it.
class SExprEvalBed < Blank
include Singleton
def method_missing (name, *args)
SExpr.new(name, *args)
end
end
#this is used internally to represent an s-expression.
#I extract the array out of it before returning the results
#because arrays are easier to work with. Nevertheless, since I could use
#an s-expression class as the result of certain evaluations, it didn't
#make sense to override standard array methods
#
#other built-in classes weren't so lucky
class SExpr
def initialize(*args)
@array=args
end
attr_accessor :array
def method_missing(name,*args)
SExpr.new(name,self,*args)
end
def coerce(other)
[SQLObj.new(other),self]
end
def ==(other)
SExpr.new(:==,self,other)
end
def to_a
return @array.collect do |x|
if x.is_a?(SExpr)
x.to_a
elsif x.is_a?(SQLObj)
x.contained
else
x
end
end
end
end
#this is used for wrapping objects when they get involved in
#coercions to perform binary operations with a Symbol
class SQLObj
def initialize(contained)
@contained=contained
end
attr_accessor :contained
def method_missing (name,*args)
SExpr.new(name,self,*args)
end
def ==(other)
SExpr.new(:==,self,other)
end
end
class Symbol
def coerce(other)
#this little caller trick keeps behavior normal
#when calling from outside sxp
if caller[-2]=~/in `sxp'/
[SQLObj.new(other),SQLObj.new(self)]
else
#could just return nil, but then the
#text of the error message would change
super.method_missing(:coerce,other)
end
end
def method_missing(name, *args)
if caller[-2]=~/in `sxp'/
SExpr.new(name,self,*args)
else
super
end
end
alias_method :old_equality, :==
def ==(other)
if caller[-2]=~/in `sxp'/
SExpr.new(:==,self,other)
else
old_equality(other)
end
end
end
def sxp(&block)
r=SExprEvalBed.instance.instance_eval(&block)
if r.is_a?(SExpr)
r.to_a
elsif r.is_a?(SQLObj)
r.contained
else
r
end
end
require 'irb/xmp'
xmp <<-"end;"
sxp{max(count(:name))}
sxp{count(3+7)}
sxp{3+:symbol}
sxp{3+count(:field)}
sxp{7/:field}
sxp{:field > 5}
sxp{8}
sxp{:field1 == :field2}
sxp{count(3)==count(5)}
sxp{3==count(5)}
7/:field rescue "TypeError"
7+count(:field) rescue "NoMethodError"
5+6
:field > 5 rescue "NoMethodError"
end;
--
Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu/~kbloom1/