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

copy(obj,seen={}) click to toggle source

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
delete(name,context=::Object) click to toggle source
# 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
delete_all!(*contexts) click to toggle source
# 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
eval(code,binding=nil,file="(eval)",line=1) click to toggle source

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
expand(tree,macros=Macro::GLOBALS,session={}) click to toggle source

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
list(*contexts) click to toggle source
# 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
load(filename,wrap=false) click to toggle source

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
parse(code,file="(eval)",line=1,lvars=[]) click to toggle source

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
postpone(node,session) click to toggle source

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
quote(obj) click to toggle source

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
require(filename) click to toggle source

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