views:

184

answers:

3

Ruby enthusiasts! I am trying to write a DSL in ruby and i would like to be able to create some magic methods (not sure that is the most accurate term for what i want).

I would like to be able to do things like the following: a = [1, 2, 3] b = 2

(a contains b)

And have it resolve to true or false.

Essentially, how can i define the function "contains" so that it takes an array "a" and a variable "b" and performs a.contains?(b), but without all of the associated ruby-specific syntax?

A: 

The closest thing I could of would be:

def contains var, useless_symbol, arr
  arr.include? var
end

Then you could call it like:

contains b, :in, a


I don't think there is any way to be able to use infix notation in your own functions.

bennybdbc
why are you omitting the commas? it would be `contains b, :in, a`
banister
@banister: You're right, edited now.
bennybdbc
+1  A: 

if you want a DSL that doesn't use ruby syntax, you need to write a parser at the very least to perform the transformation (raganwalds rewrite lib might be a starting point, http://github.com/raganwald/rewrite)

That said, you don't want to do this. This is more code to maintain and Ruby has already made a lot of the tough decisions that make writing a language syntax hard. Natural language programming also isn't much easier for nonprogrammers to use as the exactness of the format is the challenging aspect (see applescript for instance).

Ben Hughes
A: 

You can abuse method_missing. The tricky thing is, that you cannot access the blocks local variables directly. You'll have to capture the blocks inner binding somewhere (unfortunately block.binding returns the block's outer binding).

You can run this code:

DSL.new do
  a = [1, 2, 3]
  b = 2
  a contains b
end

With the following:

class DSL
  attr_reader :last_binding

  def initialize(&block)
    set_trace_func method(:trace).to_proc
    instance_eval(&block)
    set_trace_func nil
  end

  def trace(event, file, line, id, binding, klass)
    if event.to_s == "call" and klass == self.class and id.to_s == "method_missing"
      @last_binding ||= @current_binding
      set_trace_func nil
    else
      @current_binding = binding
    end
  end

  def lvars
    eval('local_variables', last_binding).map(&:to_s)
  end

  def method_missing(name, *args)
    name = name.to_s
    if lvars.include? name
      eval(name, last_binding).send(*args.flatten)
    else
      ["#{name}?", *args]
    end
  end
end

class Array
  alias contains? include?
end
Konstantin Haase