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/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
+