rubymacros 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING.LGPL +165 -0
- data/Manifest.txt +21 -0
- data/README.txt +119 -0
- data/Rakefile +26 -0
- data/example/__dir__.rb +7 -0
- data/example/__dir___wrap.rb +4 -0
- data/example/andand.rb +3 -0
- data/example/assert.rb +42 -0
- data/example/assert_wrap.rb +4 -0
- data/example/loop.rb +23 -0
- data/example/loop_wrap.rb +4 -0
- data/example/simple.rb +4 -0
- data/example/simple_wrap.rb +4 -0
- data/example/with.rb +56 -0
- data/example/with_wrap.rb +3 -0
- data/lib/macro/form.rb +146 -0
- data/lib/macro/version.rb +22 -0
- data/lib/macro.rb +616 -0
- data/rubymacros.vpj +87 -0
- data/test/test_all.rb +3 -0
- data/test/test_expand.rb +35 -0
- data/test/test_form.rb +110 -0
- metadata +94 -0
data/lib/macro.rb
ADDED
@@ -0,0 +1,616 @@
|
|
1
|
+
=begin
|
2
|
+
rubymacros - a macro preprocessor for ruby
|
3
|
+
Copyright (C) 2008 Caleb Clausen
|
4
|
+
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
6
|
+
it under the terms of the GNU Lesser General Public License as published by
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
8
|
+
(at your option) any later version.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU Lesser General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU Lesser General Public License
|
16
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
=end
|
18
|
+
|
19
|
+
#$:.unshift "../redparse/lib"
|
20
|
+
#warn "$: hacked up to find latest redparse"
|
21
|
+
|
22
|
+
warn "rubygems require disabled"
|
23
|
+
require 'rubygems'
|
24
|
+
require 'redparse'
|
25
|
+
require "macro/form"
|
26
|
+
require "macro/version"
|
27
|
+
|
28
|
+
warn "need to insert extra parens around form params and macro texts"
|
29
|
+
|
30
|
+
class Macro
|
31
|
+
#import Node classes from RedParse
|
32
|
+
RedParse::constants.each{|k|
|
33
|
+
n=RedParse::const_get(k)
|
34
|
+
self::const_set k,n if Module===n and RedParse::Node>=n
|
35
|
+
}
|
36
|
+
#...and token classes from RubyLexer
|
37
|
+
RubyLexer::constants.each{|k|
|
38
|
+
t=RubyLexer::const_get(k)
|
39
|
+
self::const_set k,t if Module===t and RubyLexer::Token>=t
|
40
|
+
}
|
41
|
+
|
42
|
+
#all 3 of these are giant memory leaks
|
43
|
+
PostponedMethods=[]
|
44
|
+
GLOBALS={}
|
45
|
+
QuotedStore=[]
|
46
|
+
|
47
|
+
#like Kernel#require, but allows macros (and forms) as well.
|
48
|
+
#c extensions (.dll,.so,etc) cannot be loaded via this method.
|
49
|
+
def Macro.require(filename)
|
50
|
+
filename+='.rb' unless filename[/\.rb\Z/]
|
51
|
+
return if $".include? filename
|
52
|
+
$" << filename
|
53
|
+
load filename
|
54
|
+
end
|
55
|
+
|
56
|
+
#as close as I can get to an empty binding
|
57
|
+
def ::Object.new_binding() # :nodoc:
|
58
|
+
binding
|
59
|
+
end
|
60
|
+
|
61
|
+
#like Kernel#load, but allows macros (and forms) as well.
|
62
|
+
def Macro.load(filename,wrap=false)
|
63
|
+
[''].concat($:).each{|pre|
|
64
|
+
pre+="/" unless %r{(\A|/)\Z}===pre
|
65
|
+
if File.exist? finally=pre+filename
|
66
|
+
code=File.open(finally){|fd| fd.read }
|
67
|
+
#code="::Module.new do\n#{code}\nend\n" if wrap
|
68
|
+
tree=Macro.expand(parse(code,filename),filename)
|
69
|
+
|
70
|
+
tree.load filename,wrap
|
71
|
+
return true
|
72
|
+
end
|
73
|
+
}
|
74
|
+
raise LoadError, "no such file to load -- "+filename
|
75
|
+
end
|
76
|
+
|
77
|
+
#like Kernel#eval, but allows macros (and forms) as well.
|
78
|
+
#beware: default for second argument is currently broken.
|
79
|
+
#best practice is to pass an explicit binding (see
|
80
|
+
#Kernel#binding) for now.
|
81
|
+
def Macro.eval(code,binding=nil,file="(eval)",line=1)
|
82
|
+
#binding should default to Binding.of_caller, but byellgch
|
83
|
+
|
84
|
+
lvars=binding ? ::Kernel.eval("local_variables()",binding) : []
|
85
|
+
tree=Macro.expand(parse(code,file,line,lvars),file)
|
86
|
+
tree.eval binding||::Object.new_binding,file,line
|
87
|
+
end
|
88
|
+
|
89
|
+
def Macro.parse(code,file="(eval)",line=1,lvars=[])
|
90
|
+
if Binding===file or Array===file
|
91
|
+
lvars=file
|
92
|
+
file="(eval)"
|
93
|
+
end
|
94
|
+
if Binding===lvars
|
95
|
+
lvars=eval "local_variables", lvars
|
96
|
+
end
|
97
|
+
RedParseWithMacros.new(code,file,line,lvars).parse
|
98
|
+
end
|
99
|
+
|
100
|
+
UNCOPYABLE= #Symbol|Numeric|true|false|nil|
|
101
|
+
Module|Proc|IO|Method|UnboundMethod|Thread|Continuation
|
102
|
+
|
103
|
+
def Macro.quote obj
|
104
|
+
#result=
|
105
|
+
case obj
|
106
|
+
when Symbol,Numeric: LiteralNode[obj]
|
107
|
+
when true,false,nil: VarLikeNode[obj.inspect]
|
108
|
+
when String:
|
109
|
+
obj=obj.gsub(/['\\]/){|ch| '\\'+ch }
|
110
|
+
StringNode[obj,{:@open=>"'", :@close=>"'"}]
|
111
|
+
else
|
112
|
+
#result=:(::Macro::QuotedStore[^QuotedStore.size])
|
113
|
+
result=CallNode[ConstantNode[nil,"Macro","QuotedStore"],"[]",
|
114
|
+
[LiteralNode[QuotedStore.size]],
|
115
|
+
nil,nil]
|
116
|
+
QuotedStore << obj #register obj in quoted store
|
117
|
+
UNCOPYABLE===result or
|
118
|
+
#result=:(::Macro.copy ^result)
|
119
|
+
result= CallNode[ConstantNode[nil,"Macro"],"copy",
|
120
|
+
[result], nil,nil]
|
121
|
+
|
122
|
+
result
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def Macro.copy obj,seen={}
|
127
|
+
result=seen[obj.__id__]
|
128
|
+
return result if result
|
129
|
+
result=
|
130
|
+
case obj
|
131
|
+
when Symbol,Numeric,true,false,nil: return obj
|
132
|
+
when String: seen[obj.__id__]=obj.dup
|
133
|
+
when Array:
|
134
|
+
seen[obj.__id__]=dup=obj.dup
|
135
|
+
dup.map!{|x| copy x,seen}
|
136
|
+
when Hash:
|
137
|
+
result={}
|
138
|
+
seen[obj.__id__]=result
|
139
|
+
obj.each_pair{|k,v|
|
140
|
+
result[copy k]=copy v,seen
|
141
|
+
}
|
142
|
+
result
|
143
|
+
when Module,Proc,IO,Method,
|
144
|
+
UnboundMethod,Thread,Continuation:
|
145
|
+
return obj
|
146
|
+
else
|
147
|
+
obj.dup
|
148
|
+
end
|
149
|
+
obj.instance_variables.each{|iv|
|
150
|
+
result.instance_variable_set iv, copy(obj.instance_variable_get(iv),seen)
|
151
|
+
}
|
152
|
+
return result
|
153
|
+
end
|
154
|
+
|
155
|
+
def Macro.postpone node,session
|
156
|
+
filename=session[:filename]
|
157
|
+
unless session[:@modpath_unsure]
|
158
|
+
modpath=ConstantNode[nil,*session[:@modpath]]
|
159
|
+
modpath.push "Object" unless modpath.size>1
|
160
|
+
if session[:@namespace_type]==ModuleNode
|
161
|
+
node=ModuleNode[modpath,node] #:(module ^modpath; ^node; end)
|
162
|
+
else
|
163
|
+
node=ClassNode[modpath,nil,node] #:(class ^modpath; ^node; end)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
evalname=modpath ? "load" : "eval"
|
168
|
+
PostponedMethods << node
|
169
|
+
|
170
|
+
#unexpanded=:(::Macro::PostponedMethods[^(PostponedMethods.size-1)].deep_copy)
|
171
|
+
#expanded=:(::Macro.expand(^unexpanded,Macro::GLOBALS,{:@expand_in_defs=>true},^filename))
|
172
|
+
#return :( ^expanded.^evalname(^filename) )
|
173
|
+
unexpanded=CallNode[CallNode[ConstantNode[nil,"Macro", "PostponedMethods"],
|
174
|
+
"[]",[LiteralNode[PostponedMethods.size-1]],nil,nil],"deep_copy",nil,nil,nil]
|
175
|
+
expanded=
|
176
|
+
CallNode[ConstantNode[nil,"Macro"],"expand",
|
177
|
+
[unexpanded, ConstantNode[nil,"Macro","GLOBALS"],
|
178
|
+
HashLiteralNode[LiteralNode[:@expand_in_defs], VarLikeNode["true"]], Macro.quote(filename)],
|
179
|
+
nil,nil]
|
180
|
+
return CallNode[expanded,evalname,[Macro.quote filename],nil,nil]
|
181
|
+
end
|
182
|
+
|
183
|
+
class Node
|
184
|
+
#just like Kernel#eval, but allows macros (and forms) and
|
185
|
+
#input is a RedParse parse tree (as receiver).
|
186
|
+
#beware: default for binding is currently broken.
|
187
|
+
#best practice is to pass an explicit binding (see
|
188
|
+
#Kernel#binding) for now.
|
189
|
+
def eval(binding=nil,file=nil,line=nil)
|
190
|
+
#binding should default to Binding.of_caller, but.... that's expensive
|
191
|
+
|
192
|
+
if String===binding
|
193
|
+
file=binding
|
194
|
+
binding=nil
|
195
|
+
end
|
196
|
+
|
197
|
+
#references to 'self' (implicit and explicit) within this parse tree
|
198
|
+
#should be forced to refer to the self from binding, (if any)...
|
199
|
+
|
200
|
+
expanded_tree=self #Macro.expand(deep_copy,::Macro::GLOBALS)
|
201
|
+
|
202
|
+
unparsed=expanded_tree.unparse({})
|
203
|
+
#puts unparsed
|
204
|
+
::Kernel.eval unparsed, binding||::Object.new_binding,file||'(eval)',line||1
|
205
|
+
end
|
206
|
+
|
207
|
+
def load(name='',wrap=false)
|
208
|
+
expanded_tree=self #Macro.expand(deep_copy,::Macro::GLOBALS)
|
209
|
+
|
210
|
+
#replace __FILE__ in tree with the correct file name
|
211
|
+
#(otherwise, it will point to some tmp file)
|
212
|
+
filenamenode=nil
|
213
|
+
expanded_tree.walk{|parent,i,subi,node|
|
214
|
+
if VarLikeNode===node and node.ident=="__FILE__"
|
215
|
+
filenamenode||=Macro.quote name #StringNode[name.gsub(/['\\]/){|ch| '\\'+ch},{:@open=>"'", :@close=>"'"}]
|
216
|
+
if parent
|
217
|
+
subi ? parent[i][subi]=filenamenode : parent[i]=filenamenode
|
218
|
+
else
|
219
|
+
expanded_tree=filenamenode
|
220
|
+
end
|
221
|
+
end
|
222
|
+
true
|
223
|
+
}
|
224
|
+
|
225
|
+
unparsed=expanded_tree.unparse({})
|
226
|
+
#p expanded_tree
|
227
|
+
#puts unparsed
|
228
|
+
Tempfile.open("macroexpanded_"+name.gsub("/","__")){|tf|
|
229
|
+
tf.write unparsed
|
230
|
+
tf.flush
|
231
|
+
::Kernel::load tf.path, wrap
|
232
|
+
}
|
233
|
+
return true
|
234
|
+
end
|
235
|
+
|
236
|
+
def to_sexp session
|
237
|
+
self.class.name+"["+
|
238
|
+
map{|param| call_to_sexp param,session }.join(", ")+
|
239
|
+
", {"+instance_variables.map{|iv|
|
240
|
+
iv=="@data" and next
|
241
|
+
val=instance_variable_get(iv)
|
242
|
+
val=call_to_sexp(val,session)
|
243
|
+
":"+iv+"=>"+val
|
244
|
+
}.join(", ")+"}"+
|
245
|
+
"]"
|
246
|
+
end
|
247
|
+
|
248
|
+
private
|
249
|
+
def call_to_sexp param,session
|
250
|
+
if param.instance_of? ::Array
|
251
|
+
"["+param.map{|subparam| call_to_sexp(subparam,session)}.join(", ")+"]"
|
252
|
+
elsif param.respond_to?(:to_sexp)
|
253
|
+
param.to_sexp(session)
|
254
|
+
else
|
255
|
+
param.inspect
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
class ::RubyLexer::VarNameToken
|
261
|
+
def to_sexp session
|
262
|
+
ident
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
|
267
|
+
#remember macro definitions and expand macros within a parsetree.
|
268
|
+
#the first argument must be a parse tree in RedParse format. the
|
269
|
+
#optional second argument is a hash of macros to be pre-loaded.
|
270
|
+
#(the keys of the hash are symbols and the values are Methods for
|
271
|
+
#the corresponding macro method. typically, callers won't need to
|
272
|
+
#use any but the first argument; just define macros in the source text.)
|
273
|
+
#on returning, the second arg is updated with the macro definitions
|
274
|
+
#seen during expansion.
|
275
|
+
def Macro.expand tree,macros=Macro::GLOBALS,session={},filename=nil
|
276
|
+
if String===macros
|
277
|
+
filename=macros
|
278
|
+
macros=Macro::GLOBALS
|
279
|
+
end
|
280
|
+
if String===session
|
281
|
+
filename=session
|
282
|
+
session={}
|
283
|
+
end
|
284
|
+
session[:@modpath]||=[]
|
285
|
+
session[:filename]||=filename
|
286
|
+
String===tree and tree=parse(tree)
|
287
|
+
fail unless macros.__id__==Macro::GLOBALS.__id__ #for now
|
288
|
+
tree.walk{|parent,i,subi,node|
|
289
|
+
is_node=Node===node
|
290
|
+
if is_node and node.respond_to? :macro_expand
|
291
|
+
newnode,recurse=node.macro_expand(macros,session)
|
292
|
+
#implementations of macro_expand follow, but to summarize:
|
293
|
+
#look for macro definitions, save them and remove them from the tree (MacroNode)
|
294
|
+
#look for macro invocations, and expand them (CallSiteNode)
|
295
|
+
#disable macro definitions within classes and modules(for now) (ClassNode and ModuleNode
|
296
|
+
#postpone macro expansion (and definition) in forms until they are evaled (Form)
|
297
|
+
#(or returned from a macro)
|
298
|
+
#but not in form parameters
|
299
|
+
#postpone macro expansion in method defs til method def'n is executed
|
300
|
+
#otherwise, disable other macro expansion for now.
|
301
|
+
#postpone macro definitions until the definition is executed.
|
302
|
+
|
303
|
+
if newnode
|
304
|
+
return newnode unless parent #replacement at top level
|
305
|
+
subi ? parent[i][subi]=newnode : parent[i]=newnode
|
306
|
+
fail if recurse
|
307
|
+
end
|
308
|
+
else
|
309
|
+
recurse=is_node
|
310
|
+
end
|
311
|
+
recurse
|
312
|
+
}
|
313
|
+
return tree
|
314
|
+
end
|
315
|
+
|
316
|
+
#look for macro definitions, save them and convert them to method definitions
|
317
|
+
class MacroNode < ValueNode
|
318
|
+
def macro_expand(macros,session)
|
319
|
+
fail "scoped macros are not allowed (yet)" unless session[:@modpath].empty?
|
320
|
+
|
321
|
+
#varargs, &args and receivers are not allowed in macro definitions (yet)
|
322
|
+
fail "macro receivers not supported yet" if receiver
|
323
|
+
if Node===args
|
324
|
+
last=args
|
325
|
+
# else
|
326
|
+
# last=args.last
|
327
|
+
end
|
328
|
+
fail "macro varargs and block not supported yet" if UnOpNode===last and /\A[*&]@\Z/===last.op.ident
|
329
|
+
|
330
|
+
name=self.name
|
331
|
+
#macros can't be settors
|
332
|
+
fail "macro settors are not allowed" if /=$/===name
|
333
|
+
|
334
|
+
#macro definitions need to be dealt with in 2 steps: registration and activation
|
335
|
+
name=self.name
|
336
|
+
self[1]="macro_"+name unless /^macro_/===name
|
337
|
+
node=MethodNode[*self] #convert macro node to a method def node
|
338
|
+
huh(node.receiver) if node.receiver
|
339
|
+
node[0]=ParenedNode[ConstantNode[nil,"Object"]] #all macros are global for now... til we get scoped macros
|
340
|
+
#sets receiver
|
341
|
+
|
342
|
+
#node.eval #no, not here....
|
343
|
+
# macros[name.to_sym]=self.name
|
344
|
+
|
345
|
+
newnode=Macro.postpone node, session
|
346
|
+
|
347
|
+
#newnode=:((
|
348
|
+
# ^newnode
|
349
|
+
# Macro::GLOBALS[^name.to_sym]=Object.method ^node.name.to_sym
|
350
|
+
# nil
|
351
|
+
#))
|
352
|
+
newnode=ParenedNode[SequenceNode[
|
353
|
+
newnode,
|
354
|
+
CallNode[ConstantNode[nil,"Macro", "GLOBALS"],"[]=",[
|
355
|
+
LiteralNode[name.to_sym],
|
356
|
+
CallNode[ConstantNode[nil,"Object"], "method",
|
357
|
+
[LiteralNode[node.name.to_sym]],nil,nil]],
|
358
|
+
nil,nil],
|
359
|
+
VarLikeNode['nil']
|
360
|
+
]]
|
361
|
+
|
362
|
+
|
363
|
+
#newnode=RedParse::VarLikeNode["nil", {:@value=>false,}]
|
364
|
+
#subi ? parent[i][subi]=newnode : parent[i]=newnode
|
365
|
+
return newnode,false #dont keep recursing
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
#look for macro invocations, and expand them
|
370
|
+
class CallSiteNode
|
371
|
+
def macro_expand(macros,session)
|
372
|
+
|
373
|
+
name=self.name
|
374
|
+
#pp macros
|
375
|
+
#pp Macro::GLOBALS
|
376
|
+
macro=macros[name.to_sym]
|
377
|
+
unless macro
|
378
|
+
#turn off modpath surity in blocks.
|
379
|
+
#this disables modpath surity in callsite receivers and parameters as well;
|
380
|
+
#unnecessary, but no great loss.
|
381
|
+
old_unsure=session[:@modpath_unsure]
|
382
|
+
session[:@modpath_unsure]=true
|
383
|
+
map!{|node|
|
384
|
+
case node
|
385
|
+
when Node; Macro.expand node,macros,session
|
386
|
+
when Array; node.map{|item| Macro.expand item,macros,session}
|
387
|
+
else node
|
388
|
+
end
|
389
|
+
}
|
390
|
+
session[:@modpath_unsure]=old_unsure
|
391
|
+
|
392
|
+
return nil,false #change nothing, recursion done already
|
393
|
+
end
|
394
|
+
return nil,true unless macro #change nothing but keep recursing if not a macro
|
395
|
+
|
396
|
+
Method===macro or fail
|
397
|
+
|
398
|
+
args = args()||[]
|
399
|
+
|
400
|
+
#if this callsite names a macro, then it is a macro
|
401
|
+
#macro=macros[name.to_sym]=::Object.method(macro) if String===macro
|
402
|
+
#refuse macro calls with receivers, blocks, varargs, or &args: not supported yet
|
403
|
+
fail "macro receivers not supported yet" if receiver
|
404
|
+
fail "macro blocks not supported yet" if block or blockparams
|
405
|
+
fail "macro block args not supported yet" if UnOpNode===args.last and args.last.ident=="&@"
|
406
|
+
fail "macro varargs calls not supported yet" if UnaryStarNode===args.last
|
407
|
+
fail if args.class!=Array
|
408
|
+
newnode=macro.call *args
|
409
|
+
#subi ? parent[i][subi]=newnode : parent[i]=newnode
|
410
|
+
|
411
|
+
# and keep recursing, no matter what, by all means!!
|
412
|
+
newnode=Macro.expand newnode,macros,session #just do it here
|
413
|
+
return newnode,false #and not in caller
|
414
|
+
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
#postpone macro expansion in methods til method defn is executed
|
419
|
+
class MethodNode
|
420
|
+
def macro_expand(macros,session)
|
421
|
+
if session[:@expand_in_defs]
|
422
|
+
session[:@expand_in_defs]=false
|
423
|
+
expand=proc{|x| Node===x ? Macro.expand(x,macros,session) : x}
|
424
|
+
self.receiver= expand[receiver]
|
425
|
+
args.map! &expand if args
|
426
|
+
self.body= expand[body]
|
427
|
+
rescues.map! &expand if rescues
|
428
|
+
self.ensure_= expand[ensure_]
|
429
|
+
self.else_= expand[else_]
|
430
|
+
session[:@expand_in_defs]=true
|
431
|
+
return self,false
|
432
|
+
else
|
433
|
+
return Macro.postpone(self,session),false
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
#disable macro definitions within classes and modules
|
439
|
+
module DisableMacros
|
440
|
+
def macro_expand(macros,session)
|
441
|
+
old_unsure=session[:@modpath_unsure]
|
442
|
+
old_namespace_type=session[:@namespace_type]
|
443
|
+
session[:@namespace_type]=self.class
|
444
|
+
name=self.name.dup
|
445
|
+
if Node===name
|
446
|
+
case name.first
|
447
|
+
when String; #name contains only constant(s), do nothing
|
448
|
+
when nil #name in an absolute namespace
|
449
|
+
name.shift
|
450
|
+
old_modpath=session[:@modpath]
|
451
|
+
session[:@modpath]=[]
|
452
|
+
else #name in a dynamic namespace
|
453
|
+
name.shift
|
454
|
+
session[:@modpath_unsure]=true
|
455
|
+
end
|
456
|
+
unwind=name.size
|
457
|
+
else
|
458
|
+
unwind=1
|
459
|
+
end
|
460
|
+
session[:@modpath].push *name
|
461
|
+
|
462
|
+
map!{|n| Macro.expand(n,macros,session) if n}
|
463
|
+
|
464
|
+
if old_modpath
|
465
|
+
session[:@modpath]=old_modpath
|
466
|
+
else
|
467
|
+
session[:@modpath].pop unwind
|
468
|
+
end
|
469
|
+
session[:@namespace_type]=old_namespace_type
|
470
|
+
session[:@modpath_unsure]=old_unsure
|
471
|
+
|
472
|
+
return nil,false #halt further recursion: already done
|
473
|
+
end
|
474
|
+
end
|
475
|
+
class ModuleNode; include DisableMacros; end
|
476
|
+
class ClassNode; include DisableMacros; end
|
477
|
+
|
478
|
+
class MetaClassNode
|
479
|
+
def macro_expand(macros,session)
|
480
|
+
old_unsure=session[:@modpath_unsure]
|
481
|
+
session[:@modpath_unsure]=true
|
482
|
+
map!{|n| Macro.expand(n,macros,session) if n}
|
483
|
+
session[:@modpath_unsure]=old_unsure
|
484
|
+
|
485
|
+
return nil,false #halt further recursion: already done
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
#postpone macro expansion (and definition) in forms until they are evaled
|
490
|
+
#(or returned from a macro)
|
491
|
+
#but not in form parameters
|
492
|
+
class FormNode < ValueNode
|
493
|
+
def macro_expand(macros,session)
|
494
|
+
#return text.to_sexp({})
|
495
|
+
|
496
|
+
|
497
|
+
#maybe this doesn't allow expansion of parameters themselves... only within params?
|
498
|
+
each_parameter{|param| Macro.expand(param,macros,session) }
|
499
|
+
|
500
|
+
|
501
|
+
#replace (text for) form itself with a reference which will be
|
502
|
+
#looked up at runtime (and have parameters expanded at that point too)
|
503
|
+
|
504
|
+
|
505
|
+
|
506
|
+
|
507
|
+
return parses_like,false #halt further recursion: already done where necessary
|
508
|
+
end
|
509
|
+
|
510
|
+
def to_sexp session
|
511
|
+
nest=session[:form_nest_level]
|
512
|
+
session[:form_nest_level]=nest ? nest+1 : 2
|
513
|
+
|
514
|
+
result=super
|
515
|
+
|
516
|
+
if nest
|
517
|
+
session[:form_nest_level]-=1
|
518
|
+
else
|
519
|
+
session.delete :form_nest_level
|
520
|
+
end
|
521
|
+
return result
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
class FormParameterNode<ValueNode
|
526
|
+
def to_sexp session
|
527
|
+
nest=session[:form_nest_level]||1
|
528
|
+
carets=0
|
529
|
+
node=self
|
530
|
+
while FormParameterNode===node
|
531
|
+
node=node.text
|
532
|
+
carets+=1
|
533
|
+
end
|
534
|
+
if carets==nest
|
535
|
+
return node.unparse({})
|
536
|
+
else
|
537
|
+
return super
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
class MacroNode < ValueNode
|
543
|
+
param_names(:defword_,:receiver,:name,:args,:semi_,:body,:rescues,:elses,:ensures,:endword_)
|
544
|
+
def initialize(macroword,header,semi,body,rescues,else_,ensure_,endword)
|
545
|
+
#decompose header
|
546
|
+
if CallSiteNode===header
|
547
|
+
receiver=header.receiver
|
548
|
+
args=header.args
|
549
|
+
header=header.name
|
550
|
+
end
|
551
|
+
if MethNameToken===header #not needed?
|
552
|
+
header=header.ident
|
553
|
+
end
|
554
|
+
|
555
|
+
unless String===header
|
556
|
+
fail "unrecognized method header: #{header}"
|
557
|
+
end
|
558
|
+
@data=replace [receiver,header,args,body,rescues,else_,ensure_]
|
559
|
+
end
|
560
|
+
|
561
|
+
alias else_ elses
|
562
|
+
alias else elses
|
563
|
+
alias ensure_ ensures
|
564
|
+
alias ensure ensures
|
565
|
+
|
566
|
+
def unparse o
|
567
|
+
result="macro "
|
568
|
+
result+=receiver.unparse(o)+'.' if receiver
|
569
|
+
result+=name
|
570
|
+
if args and !args.empty?
|
571
|
+
result+="("
|
572
|
+
result+=args.map{|arg| arg.unparse o}.join','
|
573
|
+
result+=")"
|
574
|
+
end
|
575
|
+
result+="\n"
|
576
|
+
result+=body.unparse(o) if body
|
577
|
+
result+=rescues.map{|resc| resc.unparse o}.to_s
|
578
|
+
result+="else "+else_.unparse( o )+"\n" if else_
|
579
|
+
result+="ensure "+ensure_.unparse( o )+"\n" if ensure_
|
580
|
+
result+="\nend"
|
581
|
+
return result
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
class RedParseWithMacros < RedParse
|
586
|
+
def PRECEDENCE
|
587
|
+
result=super
|
588
|
+
return result.merge({"^@"=>result["+@"]})
|
589
|
+
end
|
590
|
+
def RULES
|
591
|
+
[
|
592
|
+
-[KW('macro'), KW(beginsendsmatcher).~.*, KW('end'), KW(/^(do|\{)$/).~.la]>>MisparsedNode
|
593
|
+
]+super+[
|
594
|
+
-[Op('^@'), Value, LowerOp]>>FormParameterNode,
|
595
|
+
-[Op(':@'), (ParenedNode&-{:size=>1})|(VarLikeNode&-{:ident=>"nil"})]>>FormNode,
|
596
|
+
-['macro', CallSiteNode, KW(';'),
|
597
|
+
Value.-, RescueNode.*, ElseNode.-, EnsureNode.-,
|
598
|
+
'end'
|
599
|
+
]>>MacroNode,
|
600
|
+
]
|
601
|
+
end
|
602
|
+
def wants_semi_context
|
603
|
+
super|KW('macro')
|
604
|
+
end
|
605
|
+
def beginsendsmatcher
|
606
|
+
return @bem||=/#{super}|^macro$/
|
607
|
+
end
|
608
|
+
|
609
|
+
def initialize(*args)
|
610
|
+
super
|
611
|
+
@lexer.enable_macros! if @lexer.respond_to? :enable_macros!
|
612
|
+
@unary_or_binary_op=/^([\^:]|#@unary_or_binary_op)$/o
|
613
|
+
end
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
data/rubymacros.vpj
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
<!DOCTYPE Project SYSTEM "http://www.slickedit.com/dtd/vse/10.0/vpj.dtd">
|
2
|
+
<Project
|
3
|
+
Version="10.0"
|
4
|
+
VendorName="SlickEdit"
|
5
|
+
WorkingDir=".">
|
6
|
+
<Config
|
7
|
+
Name="Release"
|
8
|
+
OutputFile=""
|
9
|
+
CompilerConfigName="Latest Version">
|
10
|
+
<Menu>
|
11
|
+
<Target
|
12
|
+
Name="Compile"
|
13
|
+
MenuCaption="&Compile"
|
14
|
+
CaptureOutputWith="ProcessBuffer"
|
15
|
+
SaveOption="SaveCurrent"
|
16
|
+
RunFromDir="%rw">
|
17
|
+
<Exec/>
|
18
|
+
</Target>
|
19
|
+
<Target
|
20
|
+
Name="Build"
|
21
|
+
MenuCaption="&Build"
|
22
|
+
CaptureOutputWith="ProcessBuffer"
|
23
|
+
SaveOption="SaveWorkspaceFiles"
|
24
|
+
RunFromDir="%rw">
|
25
|
+
<Exec/>
|
26
|
+
</Target>
|
27
|
+
<Target
|
28
|
+
Name="Rebuild"
|
29
|
+
MenuCaption="&Rebuild"
|
30
|
+
CaptureOutputWith="ProcessBuffer"
|
31
|
+
SaveOption="SaveWorkspaceFiles"
|
32
|
+
RunFromDir="%rw">
|
33
|
+
<Exec/>
|
34
|
+
</Target>
|
35
|
+
<Target
|
36
|
+
Name="Debug"
|
37
|
+
MenuCaption="&Debug"
|
38
|
+
SaveOption="SaveNone"
|
39
|
+
RunFromDir="%rw">
|
40
|
+
<Exec/>
|
41
|
+
</Target>
|
42
|
+
<Target
|
43
|
+
Name="Execute"
|
44
|
+
MenuCaption="E&xecute"
|
45
|
+
SaveOption="SaveNone"
|
46
|
+
RunFromDir="%rw">
|
47
|
+
<Exec CmdLine='".exe"'/>
|
48
|
+
</Target>
|
49
|
+
</Menu>
|
50
|
+
</Config>
|
51
|
+
<CustomFolders>
|
52
|
+
<Folder
|
53
|
+
Name="Source Files"
|
54
|
+
Filters="*.c;*.C;*.cc;*.cpp;*.cp;*.cxx;*.prg;*.pas;*.dpr;*.asm;*.s;*.bas;*.java;*.cs;*.sc;*.e;*.cob;*.html;*.rc;*.tcl;*.py;*.pl"/>
|
55
|
+
<Folder
|
56
|
+
Name="Header Files"
|
57
|
+
Filters="*.h;*.H;*.hh;*.hpp;*.hxx;*.inc;*.sh;*.cpy;*.if"/>
|
58
|
+
<Folder
|
59
|
+
Name="Resource Files"
|
60
|
+
Filters="*.ico;*.cur;*.dlg"/>
|
61
|
+
<Folder
|
62
|
+
Name="Bitmaps"
|
63
|
+
Filters="*.bmp"/>
|
64
|
+
<Folder
|
65
|
+
Name="Other Files"
|
66
|
+
Filters="">
|
67
|
+
</Folder>
|
68
|
+
</CustomFolders>
|
69
|
+
<Files AutoFolders="DirectoryView">
|
70
|
+
<Folder Name="example">
|
71
|
+
<F N="example/assert.rb"/>
|
72
|
+
<F N="example/assert_wrap.rb"/>
|
73
|
+
<F N="example/simple.rb"/>
|
74
|
+
<F N="example/simple_wrap.rb"/>
|
75
|
+
</Folder>
|
76
|
+
<Folder Name="lib">
|
77
|
+
<Folder Name="macro">
|
78
|
+
<F N="lib/macro/form.rb"/>
|
79
|
+
</Folder>
|
80
|
+
<F N="lib/macro.rb"/>
|
81
|
+
<F N="lib/weakkeyhash.rb"/>
|
82
|
+
</Folder>
|
83
|
+
<Folder Name="test">
|
84
|
+
<F N="test/test_form.rb"/>
|
85
|
+
</Folder>
|
86
|
+
</Files>
|
87
|
+
</Project>
|
data/test/test_all.rb
ADDED