rubymacros 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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="&amp;Compile"
14
+ CaptureOutputWith="ProcessBuffer"
15
+ SaveOption="SaveCurrent"
16
+ RunFromDir="%rw">
17
+ <Exec/>
18
+ </Target>
19
+ <Target
20
+ Name="Build"
21
+ MenuCaption="&amp;Build"
22
+ CaptureOutputWith="ProcessBuffer"
23
+ SaveOption="SaveWorkspaceFiles"
24
+ RunFromDir="%rw">
25
+ <Exec/>
26
+ </Target>
27
+ <Target
28
+ Name="Rebuild"
29
+ MenuCaption="&amp;Rebuild"
30
+ CaptureOutputWith="ProcessBuffer"
31
+ SaveOption="SaveWorkspaceFiles"
32
+ RunFromDir="%rw">
33
+ <Exec/>
34
+ </Target>
35
+ <Target
36
+ Name="Debug"
37
+ MenuCaption="&amp;Debug"
38
+ SaveOption="SaveNone"
39
+ RunFromDir="%rw">
40
+ <Exec/>
41
+ </Target>
42
+ <Target
43
+ Name="Execute"
44
+ MenuCaption="E&amp;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
@@ -0,0 +1,3 @@
1
+ require 'test/test_form'
2
+ require 'test/test_expand'
3
+