Old thread, but here’s my two cents. Can something like this make sense?
(alert: quick and dirty, proof of concept, incomplete and probably buggy but somewhat functioning code):
private macro _fix_rcvrless(rcvr,type,node) {% puts node %} {%
if type.is_a?(Call) %}{% type = type.receiver.resolve %}{%
elsif type.is_a?(Path) %}{% type = type.resolve %}{%
end %}{%
if node.is_a? Call %} {% if !node.receiver && type.methods.find{|m|m.name==node.name} %} {{rcvr}}.{{node}} {% # the actual thing -- all the rest is just to get here
elsif node.block %}{% begin %}{%if node.receiver %}{{node.receiver}}.{%end%}{{ node.name }} do |{{*node.block.args}}| _fix_rcvrless({{rcvr}},{{type}},{{node.block.body}}) end {%end%}{% # FIXME: block.args
else %} {{node}} {%
end %} {%
elsif node.is_a? Assign %} {{node.target}} = _fix_rcvrless({{rcvr}},{{type}},{{node.value}}) {%
elsif node.is_a? And %} _fix_rcvrless({{rcvr}},{{type}},{{node.left}}) && _fix_rcvrless({{rcvr}},{{type}},{{node.right}}) {%
elsif node.is_a? Or %} _fix_rcvrless({{rcvr}},{{type}},{{node.left}}) || _fix_rcvrless({{rcvr}},{{type}},{{node.right}}) {%
elsif node.is_a? BinaryOp %} {{ node }} {% #TODO?
elsif node.is_a? Block %} do |{{ *node.args }}| _fix_rcvrless({{rcvr}},{{type}},{{node.body}},true) end {% #FIXME -- args could be empty, splat_index, how to know if it's do/end or {} and does it matter?
elsif node.is_a? Case %} {{ node }} {% #TODO
elsif node.is_a? Expressions %} {% for e in node.expressions %}_fix_rcvrless(rcvr, e); {% end %} {% # newlines?
elsif node.is_a? If %} if _fix_rcvrless({{rcvr}},{{type}},{{node.cond}}) then _fix_rcvrless({{rcvr}},{{type}},{{node.then}}) else _fix_rcvrless({{rcvr}},{{type}},{{node.else}}) end {% # newlines?
elsif node.is_a? MultiAssign %} {{ node }} {% #TODO
elsif node.is_a? Path %} {{ node }} {% #TODO
elsif node.is_a? ProcLiteral %} {{ node }} {% #TODO
elsif node.is_a? ProcNotation %} {{ node }} {% #TODO
elsif node.is_a? RangeLiteral %} {{ node }} {% #TODO
elsif node.is_a? StringInterpolation %} {{ node }} {% #TODO
elsif node.is_a? UnaryExpression %} {{ node }} {% #TODO
elsif node.is_a? When %} {{ node }} {% #TODO
elsif node.is_a? While %} {{ node }} {% #TODO
else %}{{ node }}{%
end %}
end
macro _in(context,&block)
{{ context }} do |ctx|
_fix_rcvrless(ctx,{{context}},{{block.body}})
end
end
begin # example
module MyDslContext
extend self # because no `TypeNode#class_methods`, I can't `def self.xyz`
def something
yield self
end
def dsl_method1(arg)
puts {{@def.name.stringify}} + " " + arg.to_s
end
end
def a_method
_in MyDslContext.something do
dsl_method1 :blah
end
end
a_method
class SomeClass
def yielder; yield 123 end
def another_method
_in MyDslContext.something do
yielder do |num|
dsl_method1 num
end
end
end
end
SomeClass.new.another_method
end # example
The funky placement of macro delimiters is to suppress extra newlines in the output (I didn’t yet notice there’s \ line continuation when I wrote this).
On the good side, it apparently allows to comment out a faulty macro line, at least in some cases.
Again, note that this is incomplete and there may be issues with this approach down the road.
As far as I can tell, I have to assume the implicit receiver will come from a yield self because that allows to use the context argument as the type of the receiver. I haven’t yet found a way to know in macro code what the actual yielded type is. And it could be useful to yield an instance of some other type instead of self.
I’d like to hear opinions on this approach to “code rewriting” and traversing the parse tree in general.
Don’t worry, I myself question its sanity.
I particularly hate “reconstructing” the parts I don’t want to modify, ideally I’d like to do {% node.something = something_else %} and then {{ node }}. And be able to define and call a possibly recursive “macro subroutine”. BTW I noticed I can def inside {% %} but then cannot call it, and the same goes for a proc.