class Macro
require “macro”
rubymacros - a macro preprocessor for ruby Copyright © 2008, 2016 Caleb Clausen
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <www.gnu.org/licenses/>.
Constants
- FormParameterNode
The syntax node for a form escape
- GLOBALS
- ListInNode
- Macro_ParserMixin
- PostponedMethods
all 3 of these are giant memory leaks
- QuotedStore
- RedParseWithMacros
- UNCOPYABLE
- VERSION
Public Class Methods
TODO: dead code (only used by the dead else block above).
# File lib/macro.rb, line 191 def Macro.copy obj,seen={} result=seen[obj.__id__] return result if result result= case obj when Symbol,Numeric,true,false,nil; return obj when String; seen[obj.__id__]=obj.dup when Array seen[obj.__id__]=dup=obj.dup dup.map!{|x| copy x,seen} when Hash result={} seen[obj.__id__]=result obj.each_pair{|k,v| result[copy( k )]=copy v,seen } result when Module,Proc,IO,Method, UnboundMethod,Thread,Continuation return obj else obj.dup end obj.instance_variables.each{|iv| result.instance_variable_set iv, copy(obj.instance_variable_get(iv),seen) } return result end
# File lib/macro.rb, line 85 def Macro.delete(name,context=::Object) Thread.current[:Macro_being_undefined]="macro_"+name class<<context; remove_method(Thread.current[:Macro_being_undefined]); end if context==::Object Macro::GLOBALS.delete name.to_sym end Thread.current[:Macro_being_undefined]=nil end
# File lib/macro.rb, line 105 def Macro.delete_all!(*contexts) contexts=[::Object] if contexts.empty? contexts.each{|ctx| Macro.list(ctx).each{|mac| Macro.delete mac,ctx } } end
like Kernel#eval, but allows macros (and forms) as well. beware: default for second argument is currently broken. best practice is to pass an explicit binding (see Kernel#binding) for now.
+code+:: a string of code to evaluate +binding+:: the binding in which to evaluate the code +file+:: the name of the file this code came from +line+:: the line number this code came from
# File lib/macro.rb, line 122 def Macro.eval(code,binding=nil,file="(eval)",line=1) #binding should default to Binding.of_caller, but byellgch lvars=binding ? ::Kernel.eval("local_variables()",binding) : [] code=Macro.parse(code,file,line,lvars) unless Node===code tree=Macro.expand(code,file) tree.eval binding,file,line end
remember macro definitions and expand macros within a parsetree. the first argument must be a parse tree in RedParse
format. the optional second argument is a hash of macros to be pre-loaded. (the keys of the hash are symbols and the values are Methods for the corresponding macro method. typically, callers won’t need to use any but the first argument; just define macros in the source text.) on returning, the second arg is updated with the macro definitions seen during expansion.
# File lib/macro.rb, line 382 def Macro.expand tree,macros=Macro::GLOBALS,session={},filename=nil if String===macros filename=macros macros=Macro::GLOBALS end if String===session filename=session session={} end session[:@modpath]||=[] session[:filename]||=filename filename||="(eval)" case tree when String,IO; tree=parse(tree,filename) end fail unless macros.__id__==Macro::GLOBALS.__id__ #for now tree.walk{|parent,i,subi,node| is_node=Node===node if is_node and node.respond_to? :macro_expand newnode,recurse=node.macro_expand(macros,session) #implementations of macro_expand follow, but to summarize: #look for macro definitions, save them and remove them from the tree (MacroNode) #look for macro invocations, and expand them (CallSiteNode) #disable macro definitions within classes and modules(for now) (ClassNode and ModuleNode #postpone macro expansion (and definition) in forms until they are evaled (Form) #(or returned from a macro) #but not in form parameters #postpone macro expansion in method defs til method def'n is executed #otherwise, disable other macro expansion for now. #postpone macro definitions until the definition is executed. if newnode return newnode unless parent #replacement at top level if subi target,index=parent[i],subi else target,index=parent,i end if JustNilNode===newnode and target.class==::Array || case target when UndefNode,AssigneeList,SequenceNode; true end target.delete_at index else target[index]=newnode end fail if recurse end else recurse=is_node end recurse } return tree end
# File lib/macro.rb, line 94 def Macro.list(*contexts) contexts=[::Object] if contexts.empty? contexts.map{|ctx| ctx.singleton_methods.grep(/\Amacro_/) }.flatten.map{|ctx| ctx.to_s.gsub!(/\Amacro_/,'') } end
like Kernel#load, but allows macros (and forms) as well.
+filename+:: the name of the file to load +wrap+:: whether to wrap the loaded file in an anonymous module
# File lib/macro.rb, line 69 def Macro.load(filename,wrap=false) [''].concat($:).each{|pre| pre+="/" unless %r{(\A|/)\Z}===pre if File.exist? finally=pre+filename tree=File.open(finally){|code| #code="::Module.new do\n#{code}\nend\n" if wrap Macro.expand(parse(code,finally),filename) } tree.load filename,wrap return true end } raise LoadError, "no such file to load -- "+filename end
A helper for Macro.eval
which returns a RedParse
tree for the given code string.
code
-
a string of code to evaluate
binding
-
the binding in which to evaluate the code
file
-
the name of the file this code came from
line
-
the line number this code came from
lvars
-
a list of local variables (empty unless called
recursively)
# File lib/macro.rb, line 141 def Macro.parse(code,file="(eval)",line=1,lvars=[]) if Binding===file or Array===file lvars=file file="(eval)" end if Binding===lvars lvars=eval "local_variables", lvars end ::RedParse::WithMacros.new(code,file,line,lvars).parse end
Create a node to postpone the macro (or method) definition until it is actually executed. For example, in the following code:
if foo macro bar ... end else macro bar ... end end
without postponing macro definition, the latter macro would always override the former.
node
-
the
RedParse
node for the entire method or macro defintion that is being postponed session
-
the context in which this macro is being processed
# File lib/macro.rb, line 240 def Macro.postpone node,session return node #disable postponement =begin was filename=session[:filename] unless session[:@modpath_unsure] modpath=ConstantNode[nil,*session[:@modpath]] modpath.push "Object" unless modpath.size>1 if session[:@namespace_type]==ModuleNode node=ModuleNode[modpath,node,[],nil,nil] #:(module ^modpath; ^node; end) else node=ClassNode[modpath,nil,node,[],nil,nil] #:(class ^modpath; ^node; end) end end evalname=modpath ? "load" : "eval" PostponedMethods << node #unexpanded=:(::Macro::PostponedMethods[^(PostponedMethods.size-1)].deep_copy) #expanded=:(::Macro.expand(^unexpanded,Macro::GLOBALS,{:@expand_in_defs=>true},^filename)) #return :( ^expanded.^evalname(^filename) ) unexpanded=CallNode[CallNode[ConstantNode[nil,"Macro", "PostponedMethods"], "[]",[LiteralNode[PostponedMethods.size-1]],nil,nil],"deep_copy",nil,nil,nil] expanded= CallNode[ConstantNode[nil,"Macro"],"expand", [unexpanded, ConstantNode[nil,"Macro","GLOBALS"], HashLiteralNode[LiteralNode[:@expand_in_defs], VarLikeNode["true"]], Macro.quote(filename)], nil,nil] return CallNode[expanded,evalname,[Macro.quote( filename )],nil,nil] =end end
Return a quoted node for the given scalar
obj
-
any object or node
# File lib/macro.rb, line 159 def Macro.quote obj #result= case obj when Symbol,Numeric; LiteralNode[obj] when true,false,nil; VarLikeNode[obj.inspect] when String obj=obj.gsub(/['\\]/){|ch| '\\'+ch } StringNode[obj,{:@open=>"'", :@close=>"'"}] when Reg::Formula Reg::Deferred.defang! obj when Reg::Reg obj # TODO: The following is dead code and should be removed else #result=:(::Macro::QuotedStore[^QuotedStore.size]) result=CallNode[ConstantNode[nil,"Macro","QuotedStore"],"[]", [LiteralNode[QuotedStore.size]], nil,nil] QuotedStore << obj #register obj in quoted store UNCOPYABLE===result or #result=:(::Macro.copy ^result) result= CallNode[ConstantNode[nil,"Macro"],"copy", [result], nil,nil] result end end
like Kernel#require, but allows macros (and forms) as well. c extensions (.dll,.so,etc) cannot be loaded via this method.
+filename+:: the name of the feature to require
# File lib/macro.rb, line 57 def Macro.require(filename) filename+='.rb' unless filename[/\.rb\Z/] return if $".include? filename $" << filename load filename end