Simplified Maybe
[ruby-do-notation.git] / lib / monad.rb
blob44649016aed804456b13ba800a8db8eedf68cba7
1 require 'rubygems'
2 require 'parse_tree'
3 require 'sexp_processor'
4 require 'ruby2ruby'
6 module Monad
7   def run *args, &block
8     sexp = transform_sexp(block)
9     ruby = generate_ruby(sexp)
10     eval(ruby).call(*args)
11   end
12   
13   def transform_sexp block
14     DoNotation.new.process(block.to_method.to_sexp)
15   end
16   
17   # gnarly text munging copied & pasted from ruby2ruby source
18   def generate_ruby sexp
19     ruby = Ruby2Ruby.new.process(sexp)
20     ruby.sub!(/\A(def \S+)\(([^\)]*)\)/, '\1 |\2|')     # move args
21     ruby.sub!(/\Adef[^\n\|]+/, 'proc { ')               # strip def name
22     ruby.sub!(/end\Z/, '}')                             # strip end
23     ruby.gsub!(/\s+$/, '')                              # trailing WS bugs me
24     ruby
25   end
26 end
28 class DoNotation < SexpProcessor
29   def process_bmethod exp
30     type = exp.shift
31     
32     if arg_assignment = process(exp.shift)
33       if arg_assignment.first == :dasgn or arg_assignment.first == :dasgn_curr
34         args = [arg_assignment[1]]
35       elsif arg_assignment.first == :masgn
36         args = arg_assignment[1][1..-1].collect { |e| e[1] }
37       else
38         raise DoNotationError, "I can't parse this block :("
39       end
40     else
41       args = []
42     end
43     
44     block = process(exp.shift)
45     
46     assert_type block, :block
47     block.shift
48     
49     s(:scope,
50       s(:block,
51         s(:args, *args),
52         *rewrite_assignments(block)))
53   end
54   
55   def rewrite_assignments exp
56     return [] if exp.empty?
57     
58     head = exp.shift
59     
60     if head.first == :call and head[1].first == :vcall and head[2] == :< and head[3].first == :array and head[3][1].last == :-@
61       var_name = head[1][1]
62       expression = head[3][1][1]
63       
64       body = rewrite_assignments(exp)
65       
66       if body.first.is_a? Symbol
67         body = [s(*body)]
68       end
69       
70       [s(:iter,
71          s(:call, process(expression), :bind),
72          s(:dasgn_curr, var_name),
73          *body)]
74     else
75       head + rewrite_assignments(exp)
76     end
77   end
78   
79   def self.pp(obj, indent='')
80     return obj.inspect unless obj.is_a? Array
81     return '()' if obj.empty?
83     str = '(' + pp(obj.first, indent + ' ')
85     if obj.length > 1
86       str << ' '
88       next_indent = indent + (' ' * str.length)
90       str << obj[1..-1].map{ |o| pp(o, next_indent) }.join("\n#{next_indent}")
91     end
93     str << ')'
95     str
96   end
97 end
99 class DoNotationError < StandardError; end