rubymacros 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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