Class: Macro

Inherits:
Object show all
Defined in:
lib/macro/form.rb,
lib/macro.rb,
lib/rubymacros/version.rb

Overview

require “macro”

Defined Under Namespace

Modules: DisableMacros, Names Classes: CallSiteNode, ClassNode, FormEscapeNode, FormNode, IncludedSourceNode, JustNilNode, MacroNode, MetaClassNode, MethodNode, ModuleNode, Node, OneLineParenedNode

Constant Summary collapse

ListInNode =

Node helper module

RedParse::ListInNode
PostponedMethods =

all 3 of these are giant memory leaks

[]
GLOBALS =
{}
QuotedStore =
[]
UNCOPYABLE =

Symbol|Numeric|true|false|nil|

Module|Proc|IO|Method|UnboundMethod|Thread|Continuation
FormParameterNode =
FormEscapeNode
VERSION =
"0.1.6"
Macro_ParserMixin =

old name

::RedParse::MacroMixin
RedParseWithMacros =

old name

::RedParse::WithMacros

Class Method Summary collapse

Class Method Details

.copy(obj, seen = {}) ⇒ Object

TODO: dead code (only used by the dead else block above).



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# 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) ⇒ Object



85
86
87
88
89
90
91
92
# 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) ⇒ Object



105
106
107
108
109
110
# 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) ⇒ Object

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



122
123
124
125
126
127
128
129
# 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 = {}, filename = nil) ⇒ Object

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.



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# 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) ⇒ Object



94
95
96
97
# 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) ⇒ Object

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

Raises:

  • (LoadError)


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 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 = []) ⇒ Object

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)



141
142
143
144
145
146
147
148
149
150
# 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) ⇒ Object

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



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# 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) ⇒ Object

Return a quoted node for the given scalar

obj

any object or node



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# 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) ⇒ Object

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



57
58
59
60
61
62
# File 'lib/macro.rb', line 57

def Macro.require(filename) 
  filename+='.rb' unless filename[/\.rb\Z/]
  return if $".include? filename 
  $" << filename
  load filename
end