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.