Class: Heist::Runtime::Macro::Expansion
- Inherits:
-
Object
- Object
- Heist::Runtime::Macro::Expansion
- Defined in:
- lib/heist/runtime/callable/macro/expansion.rb
Overview
Expansion
is responsible for expanding syntactic forms matched by successful Macro
calls. Any successful Macro
call returns an Expansion
object, which is used as a signal to the evaluator that the Expression
contained in the expansion should be inlined into the syntax tree.
Instance Attribute Summary collapse
-
#expression ⇒ Object
readonly
Returns the value of attribute expression.
Instance Method Summary collapse
-
#expand(template, matches, depth = 0, ignoring_ellipses = false) ⇒ Object
Accepts a template object (a
Cons
,Identifier
or similar) and aMatches
instance and returns the result of expanding thetemplate
using thematches
. -
#initialize(lexical_scope, calling_scope, template, matches) ⇒ Expansion
constructor
An
Expansion
is initialized using the lexicalScope
of theMacro
and theScope
of the macro call site (both required for hygiene purposes), plus aCons
representing the expansion template and aMatches
object containing the input expressions to be transcribed using the template. -
#rename(id) ⇒ Object
Returns a new
Identifier
that does clash with any of the names visible in theExpansion
‘s calling scope.
Constructor Details
#initialize(lexical_scope, calling_scope, template, matches) ⇒ Expansion
An Expansion
is initialized using the lexical Scope
of the Macro
and the Scope
of the macro call site (both required for hygiene purposes), plus a Cons
representing the expansion template and a Matches
object containing the input expressions to be transcribed using the template. After initialization the expanded Expression
is available via the Expansion
object’s expression
attribute.
20 21 22 23 24 25 |
# File 'lib/heist/runtime/callable/macro/expansion.rb', line 20 def initialize(lexical_scope, calling_scope, template, matches) @lexical_scope = lexical_scope @calling_scope = calling_scope @hygienic = lexical_scope.runtime.hygienic? @expression = (template, matches) end |
Instance Attribute Details
#expression ⇒ Object (readonly)
Returns the value of attribute expression.
11 12 13 |
# File 'lib/heist/runtime/callable/macro/expansion.rb', line 11 def expression @expression end |
Instance Method Details
#expand(template, matches, depth = 0, ignoring_ellipses = false) ⇒ Object
Accepts a template object (a Cons
, Identifier
or similar) and a Matches
instance and returns the result of expanding the template
using the matches
. The depth
and ignoring_ellipses
arguments are for internal state maintainance as we recursively expand the template forms. depth
indicates the current repetition depth, i.e. how many ellipses follow the current subtemplate, and ignoring_ellipses
is true
iff we’re expanding a template in which ellipses should be transcribed verbatim. This is an R6RS feature; if a template opens with an ellipsis, we transcribe the rest of the template as normal except that any ellipses in the template are inserted as ellipses into the output, without causing their preceeding forms to repeat.
From the R5RS spec www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html
When a macro use is transcribed according to the template of the matching <syntax rule>, pattern variables that occur in the template are replaced by the subforms they match in the input. Pattern variables that occur in subpatterns followed by one or more instances of the identifier ‘…’ are allowed only in subtemplates that are followed by as many instances of ‘…’. They are replaced in the output by all of the subforms they match in the input, distributed as indicated. It is an error if the output cannot be built up as specified.
Identifiers that appear in the template but are not pattern variables or the identifier ‘…’ are inserted into the output as literal identifiers. If a literal identifier is inserted as a free identifier then it refers to the binding of that identifier within whose scope the instance of ‘syntax-rules’ appears. If a literal identifier is inserted as a bound identifier then it is in effect renamed to prevent inadvertent captures of free identifiers.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/heist/runtime/callable/macro/expansion.rb', line 59 def (template, matches, depth = 0, ignoring_ellipses = false) case template when Cons then # Return NULL if the template is an empty list return Cons::NULL if template.null? # If the template is a list opening with an ellipsis, expand # the rest of the list, transcribing ellipses verbatim return (template.cdr.car, matches, depth, true) if template.car == ELLIPSIS result, last, repeater, template_pair = nil, nil, nil, template # Set up a closure to push forms onto the output. Needs to # track both the head (+result+, for returning) and the tail # (+last+, for appending new forms). Links each inserted form # to its containing +Cons+ to enable further expansions to # be inlined. push = lambda do |value| pair = Cons.new(value) pair.hosts(value) result ||= pair last.cdr = pair if last last = pair end # Iterate over the template, inserting matches as we go while Cons === template_pair and not template_pair.null? cell = template_pair.car # Increment the repetition depth if the current subtemplate # is followed by an ellipsis and we are not treating ellipses # as literals followed_by_ellipsis = ( Cons === template_pair.cdr && template_pair.cdr.car == ELLIPSIS) && !ignoring_ellipses dx = followed_by_ellipsis ? 1 : 0 repeater = cell if followed_by_ellipsis # Once we reach an ellipsis, expand the preceeding form # the correct number of times depending on the +matches+ if cell == ELLIPSIS and not ignoring_ellipses matches.(repeater, depth + 1) do push[(repeater, matches, depth + 1)] end # If the current subtemplate is not an ellipsis and is # not followed by an ellipsis, expand it and push the # result onto the output else push[(cell, matches, depth + dx, ignoring_ellipses)] unless followed_by_ellipsis end template_pair = template_pair.cdr end # Handle the tail of improper list templates last.cdr = (template_pair, matches, depth, ignoring_ellipses) unless last.nil? result # TODO this is very similar to how we handle lists -> refactor when Vector then result, repeater = Vector.new, nil push = lambda { |value| result << value } template.each_with_index do |cell, template_index| followed_by_ellipsis = (template[template_index+1] == ELLIPSIS) && !ignoring_ellipses dx = followed_by_ellipsis ? 1 : 0 repeater = cell if followed_by_ellipsis if cell == ELLIPSIS and not ignoring_ellipses matches.(repeater, depth + 1) do push[(repeater, matches, depth + 1)] end else push[(cell, matches, depth + dx, ignoring_ellipses)] unless followed_by_ellipsis end end result when Identifier then # If the template is a pattern variable, return the current # match for that variable. See +Matches+ to see how repeated # patterns are handled. return matches.get(template) if matches.has?(template) # Otherwise, if using unhygienic macros, return the template # verbatim as a new symbol. return Identifier.new(template) unless @hygienic # If using hygienic macros: bind the identifier to the macro's # lexical scope if it is defined there, otherwise rename it # as appropriate to avoid clashes with variables in the # calling scope. @lexical_scope.defined?(template) ? Binding.new(template, @lexical_scope, false) : rename(template) else template end end |
#rename(id) ⇒ Object
Returns a new Identifier
that does clash with any of the names visible in the Expansion
‘s calling scope.
171 172 173 174 175 176 |
# File 'lib/heist/runtime/callable/macro/expansion.rb', line 171 def rename(id) return id unless @calling_scope.defined?(id) i = 1 i += 1 while @calling_scope.defined?("#{id}#{i}") Identifier.new("#{id}#{i}", id) end |