heist 0.1.0 → 0.2.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/History.txt +17 -0
- data/Manifest.txt +23 -19
- data/README.txt +84 -52
- data/lib/builtin/library.scm +208 -10
- data/lib/builtin/primitives.rb +154 -92
- data/lib/builtin/syntax.scm +22 -5
- data/lib/heist.rb +49 -17
- data/lib/parser/nodes.rb +47 -24
- data/lib/parser/ruby.rb +29 -0
- data/lib/parser/scheme.rb +455 -143
- data/lib/parser/scheme.tt +23 -5
- data/lib/repl.rb +19 -16
- data/lib/runtime/binding.rb +24 -2
- data/lib/runtime/callable/continuation.rb +23 -2
- data/lib/runtime/callable/function.rb +122 -21
- data/lib/runtime/callable/macro.rb +169 -123
- data/lib/runtime/callable/macro/expansion.rb +137 -2
- data/lib/runtime/callable/macro/matches.rb +125 -41
- data/lib/runtime/callable/macro/tree.rb +141 -0
- data/lib/runtime/callable/syntax.rb +44 -0
- data/lib/runtime/data/cons.rb +234 -0
- data/lib/runtime/data/expression.rb +15 -6
- data/lib/runtime/data/identifier.rb +19 -2
- data/lib/runtime/frame.rb +102 -35
- data/lib/runtime/runtime.rb +44 -19
- data/lib/runtime/scope.rb +145 -30
- data/lib/runtime/stack.rb +103 -1
- data/lib/runtime/stackless.rb +48 -6
- data/test/arithmetic.scm +11 -2
- data/test/continuations.scm +16 -2
- data/test/equivalence.scm +34 -0
- data/test/functional.scm +4 -0
- data/test/lists.scm +78 -0
- data/test/macro-helpers.scm +1 -0
- data/test/macros.scm +111 -24
- data/test/numbers.scm +30 -8
- data/test/test_heist.rb +67 -12
- metadata +25 -21
- data/lib/builtin/syntax.rb +0 -166
- data/lib/runtime/callable/macro/splice.rb +0 -56
- data/lib/runtime/data/list.rb +0 -36
@@ -2,10 +2,145 @@ module Heist
|
|
2
2
|
class Runtime
|
3
3
|
class Macro
|
4
4
|
|
5
|
+
# +Expansion+ is responsible for expanding syntactic forms matched by
|
6
|
+
# successful +Macro+ calls. Any successful +Macro+ call returns an
|
7
|
+
# +Expansion+ object, which is used as a signal to the evaluator that
|
8
|
+
# the +Expression+ contained in the expansion should be inlined into
|
9
|
+
# the syntax tree.
|
5
10
|
class Expansion
|
6
11
|
attr_reader :expression
|
7
|
-
|
8
|
-
|
12
|
+
|
13
|
+
# An +Expansion+ is initialized using the lexical +Scope+ of the
|
14
|
+
# +Macro+ and the +Scope+ of the macro call site (both required
|
15
|
+
# for hygiene purposes), plus a +Cons+ representing the expansion
|
16
|
+
# template and a +Matches+ object containing the input expressions
|
17
|
+
# to be transcribed using the template. After initialization the
|
18
|
+
# expanded +Expression+ is available via the +Expansion+ object's
|
19
|
+
# +expression+ attribute.
|
20
|
+
def initialize(lexical_scope, calling_scope, template, matches)
|
21
|
+
@lexical_scope = lexical_scope
|
22
|
+
@calling_scope = calling_scope
|
23
|
+
@hygienic = lexical_scope.runtime.hygienic?
|
24
|
+
@expression = expand(template, matches)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Accepts a template object (a +Cons+, +Identifier+ or similar) and a
|
28
|
+
# +Matches+ instance and returns the result of expanding the +template+
|
29
|
+
# using the +matches+. The +depth+ and +ignoring_ellipses+ arguments are
|
30
|
+
# for internal state maintainance as we recursively expand the template
|
31
|
+
# forms. +depth+ indicates the current repetition depth, i.e. how
|
32
|
+
# many ellipses follow the current subtemplate, and +ignoring_ellipses+
|
33
|
+
# is +true+ iff we're expanding a template in which ellipses should be
|
34
|
+
# transcribed verbatim. This is an R6RS feature; if a template opens
|
35
|
+
# with an ellipsis, we transcribe the rest of the template as normal
|
36
|
+
# except that any ellipses in the template are inserted as ellipses
|
37
|
+
# into the output, without causing their preceeding forms to repeat.
|
38
|
+
#
|
39
|
+
# From the R5RS spec
|
40
|
+
# http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html
|
41
|
+
#
|
42
|
+
# When a macro use is transcribed according to the template of the
|
43
|
+
# matching <syntax rule>, pattern variables that occur in the template
|
44
|
+
# are replaced by the subforms they match in the input. Pattern variables
|
45
|
+
# that occur in subpatterns followed by one or more instances of the
|
46
|
+
# identifier '...' are allowed only in subtemplates that are followed
|
47
|
+
# by as many instances of '...'. They are replaced in the output by all
|
48
|
+
# of the subforms they match in the input, distributed as indicated. It
|
49
|
+
# is an error if the output cannot be built up as specified.
|
50
|
+
#
|
51
|
+
# Identifiers that appear in the template but are not pattern variables
|
52
|
+
# or the identifier '...' are inserted into the output as literal
|
53
|
+
# identifiers. If a literal identifier is inserted as a free identifier
|
54
|
+
# then it refers to the binding of that identifier within whose scope
|
55
|
+
# the instance of 'syntax-rules' appears. If a literal identifier is
|
56
|
+
# inserted as a bound identifier then it is in effect renamed to prevent
|
57
|
+
# inadvertent captures of free identifiers.
|
58
|
+
#
|
59
|
+
def expand(template, matches, depth = 0, ignoring_ellipses = false)
|
60
|
+
case template
|
61
|
+
|
62
|
+
when Cons then
|
63
|
+
# If the template is a list opening with an ellipsis, expand
|
64
|
+
# the rest of the list, transcribing ellipses verbatim
|
65
|
+
return expand(template.cdr.car,
|
66
|
+
matches, depth, true) if template.car == ELLIPSIS
|
67
|
+
|
68
|
+
result, last, repeater, template_pair = nil, nil, nil, template
|
69
|
+
|
70
|
+
# Set up a closure to push forms onto the output. Needs to
|
71
|
+
# track both the head (+result+, for returning) and the tail
|
72
|
+
# (+last+, for appending new forms). Links each inserted form
|
73
|
+
# to its containing +Cons+ to enable further expansions to
|
74
|
+
# be inlined.
|
75
|
+
push = lambda do |value|
|
76
|
+
pair = Cons.new(value)
|
77
|
+
pair.hosts(value)
|
78
|
+
result ||= pair
|
79
|
+
last.cdr = pair if last
|
80
|
+
last = pair
|
81
|
+
end
|
82
|
+
|
83
|
+
# Iterate over the template, inserting matches as we go
|
84
|
+
while not template_pair.null?
|
85
|
+
cell = template_pair.car
|
86
|
+
|
87
|
+
# Increment the repetition depth if the current subtemplate
|
88
|
+
# is followed by an ellipsis and we are not treating ellipses
|
89
|
+
# as literals
|
90
|
+
followed_by_ellipsis = (template_pair.cdr.car == ELLIPSIS) && !ignoring_ellipses
|
91
|
+
dx = followed_by_ellipsis ? 1 : 0
|
92
|
+
|
93
|
+
repeater = cell if followed_by_ellipsis
|
94
|
+
|
95
|
+
# Once we reach an ellipsis, expand the preceeding form
|
96
|
+
# the correct number of times depending on the +matches+
|
97
|
+
if cell == ELLIPSIS and not ignoring_ellipses
|
98
|
+
matches.expand!(repeater, depth + 1) do
|
99
|
+
push[expand(repeater, matches, depth + 1)]
|
100
|
+
end
|
101
|
+
|
102
|
+
# If the current subtemplate is not an ellipsis and is
|
103
|
+
# not followed by an ellipsis, expand it and push the
|
104
|
+
# result onto the output
|
105
|
+
else
|
106
|
+
push[expand(cell, matches, depth + dx,
|
107
|
+
ignoring_ellipses)] unless followed_by_ellipsis
|
108
|
+
end
|
109
|
+
|
110
|
+
template_pair = template_pair.cdr
|
111
|
+
end
|
112
|
+
result
|
113
|
+
|
114
|
+
when Identifier then
|
115
|
+
# If the template is a pattern variable, return the current
|
116
|
+
# match for that variable. See +Matches+ to see how repeated
|
117
|
+
# patterns are handled.
|
118
|
+
return matches.get(template) if matches.has?(template)
|
119
|
+
|
120
|
+
# Otherwise, if using unhygienic macros, return the template
|
121
|
+
# verbatim as a new symbol.
|
122
|
+
return Identifier.new(template) unless @hygienic
|
123
|
+
|
124
|
+
# If using hygienic macros: bind the identifier to the macro's
|
125
|
+
# lexical scope if it is defined there, otherwise rename it
|
126
|
+
# as appropriate to avoid clashes with variables in the
|
127
|
+
# calling scope.
|
128
|
+
@lexical_scope.defined?(template) ?
|
129
|
+
Binding.new(template, @lexical_scope, false) :
|
130
|
+
rename(template)
|
131
|
+
|
132
|
+
else
|
133
|
+
template
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns a new +Identifier+ that does clash with any of the names
|
138
|
+
# visible in the <tt>Expansion</tt>'s calling scope.
|
139
|
+
def rename(id)
|
140
|
+
return id unless @calling_scope.defined?(id)
|
141
|
+
i = 1
|
142
|
+
i += 1 while @calling_scope.defined?("#{id}#{i}")
|
143
|
+
Identifier.new("#{id}#{i}")
|
9
144
|
end
|
10
145
|
end
|
11
146
|
|
@@ -2,71 +2,155 @@ module Heist
|
|
2
2
|
class Runtime
|
3
3
|
class Macro
|
4
4
|
|
5
|
+
# +Matches+ instances, with help from the +Tree+ class, are data structures
|
6
|
+
# that represent the way in which syntactic expressions match patterns found
|
7
|
+
# in +Macro+ rules. They provide an API for storing and retrieving such data,
|
8
|
+
# with the aim of removing some clutter from the macro parsing and expansion
|
9
|
+
# routines.
|
10
|
+
#
|
11
|
+
# At a pure Ruby level, a +Matches+ is a wrapper around a hash that maps
|
12
|
+
# pattern variables to +Tree+ objects, which themselves are wrappers around
|
13
|
+
# nested arrays that represent how repeated pattern matches are grouped
|
14
|
+
# together. For example, given the pattern
|
15
|
+
#
|
16
|
+
# (do ([variable init step ...] ...)
|
17
|
+
# (test expression ...)
|
18
|
+
# command ...)
|
19
|
+
#
|
20
|
+
# and the expression
|
21
|
+
#
|
22
|
+
# (do ([x 6 (- x 1)]
|
23
|
+
# [acc 1])
|
24
|
+
# ((zero? x) acc)
|
25
|
+
# (display x) (newline)
|
26
|
+
# (set! acc (* acc x)))
|
27
|
+
#
|
28
|
+
# the +Matches+ object would contain the following:
|
29
|
+
#
|
30
|
+
# @data = {
|
31
|
+
# "variable" => [ x,
|
32
|
+
# acc
|
33
|
+
# ],
|
34
|
+
#
|
35
|
+
# "init" => [ 6,
|
36
|
+
# 1
|
37
|
+
# ],
|
38
|
+
#
|
39
|
+
# "step" => [ [ (- x 1)
|
40
|
+
# ],
|
41
|
+
# []
|
42
|
+
# ],
|
43
|
+
#
|
44
|
+
# "test" => (zero? x),
|
45
|
+
#
|
46
|
+
# "expression" => [ acc
|
47
|
+
# ],
|
48
|
+
#
|
49
|
+
# "command" => [ (display x),
|
50
|
+
# (newline),
|
51
|
+
# (set! acc (* acc x))
|
52
|
+
# ]
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
# Breaking this down, we see +test+ is not followed by an ellipsis in the
|
56
|
+
# pattern, and can thus only consume one item from the input expression.
|
57
|
+
# So, its match data is a single +Expression+. +variable+, +init+, +command+
|
58
|
+
# and +expression+ are all followed by a single ellipsis (+variable+ and
|
59
|
+
# +init+ appear in a list that is followed by an ellipsis), so can consume
|
60
|
+
# several values each; their match data are arrays of expressions.
|
61
|
+
#
|
62
|
+
# +step+, on the other hand, is followed by two ellipses: it itself is
|
63
|
+
# followed by an ellipsis, and <tt>step ...</tt> appears inside a list that
|
64
|
+
# is also followed by an ellipsis. If a pattern is followed by more than
|
65
|
+
# one ellipsis, the match data it generates is a tree of nested arrays
|
66
|
+
# that describe how the expressions are grouped. Here, we see that the
|
67
|
+
# expression <tt>[variable init step ...]</tt> appears twice in the input,
|
68
|
+
# so +step+'s root match element is an array of two elements. But, +step+
|
69
|
+
# does not match any data in the second appearance (<tt>[acc 1]</tt>), so
|
70
|
+
# the second element of this array is empty. The first element is an array
|
71
|
+
# containing the single match from the first appearance (<tt>(- x 1)</tt>
|
72
|
+
# in the expression <tt>[x 6 (- x 1)]</tt>).
|
73
|
+
#
|
74
|
+
# +Matches+ tries to hide many of these details so the macro routines can
|
75
|
+
# read and write to this data structure in the simplest possible terms.
|
76
|
+
#
|
5
77
|
class Matches
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
78
|
+
|
79
|
+
# A +Matches+ is initialized using a +Cons+ representing a macro pattern,
|
80
|
+
# and an array of formal keywords supplied by the +Macro+. Keywords do
|
81
|
+
# not need to store matches so they are ignored here.
|
82
|
+
def initialize(pattern, formals)
|
83
|
+
@data = {}
|
84
|
+
names = Macro.pattern_vars(pattern, formals)
|
85
|
+
names.each { |name| @data[name] = Tree.new(name) }
|
10
86
|
end
|
11
87
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
88
|
+
# Tells the +Matches+ object that the given pattern variables (the array
|
89
|
+
# +names+) have encountered a trailing ellipsis at the given repetition
|
90
|
+
# depth. This allows +Matches+ to group repeated patterns correctly.
|
91
|
+
def descend!(names, depth)
|
92
|
+
@data.each do |name, set|
|
93
|
+
set.descend!(depth) if names.include?(name)
|
94
|
+
end
|
16
95
|
end
|
17
96
|
|
18
|
-
|
97
|
+
# Writes an expression to the +Matches+ object under the variable +name+.
|
98
|
+
# The receiver deals with storing it in the correct repetition group.
|
99
|
+
def put(name, value)
|
19
100
|
name = name.to_s
|
20
|
-
@
|
21
|
-
@data[name] ||= Splice.new(name, @depth)
|
22
|
-
@data[name] << expression unless expression.nil?
|
101
|
+
@data[name] << value if has?(name)
|
23
102
|
end
|
24
103
|
|
25
|
-
|
26
|
-
|
27
|
-
|
104
|
+
# Returns +true+ iff the receiver has a pattern variable named +name+.
|
105
|
+
def has?(name)
|
106
|
+
@data.has_key?(name.to_s)
|
28
107
|
end
|
29
108
|
|
109
|
+
# Retrieves an expression from the +Matches+ under the given +name+. The
|
110
|
+
# receiver deals with pulling the expression from the right point in the
|
111
|
+
# tree; see the <tt>expand!</tt>, <tt>iterate!</tt> and <tt>Tree#read</tt>
|
112
|
+
# and <tt>Tree#shift!</tt> methods.
|
30
113
|
def get(name)
|
31
|
-
@
|
32
|
-
@data[name.to_s].read
|
33
|
-
end
|
34
|
-
|
35
|
-
def defined?(name)
|
36
|
-
@data.has_key?(name.to_s)
|
114
|
+
@data[name.to_s].read
|
37
115
|
end
|
38
116
|
|
39
|
-
|
40
|
-
|
41
|
-
|
117
|
+
# Takes a +template+ +Expression+, a repetition +depth+ and a block, and
|
118
|
+
# calls the block +n+ times, where +n+ is the number of matches for the
|
119
|
+
# pattern variables in the template at the given depth and the current
|
120
|
+
# iteration point in the tree. After each block call, the +Matches+ object
|
121
|
+
# moves the pointer for all the applicable pattern variables along one
|
122
|
+
# place at the given depth -- see <tt>iterate!</tt> and <tt>Tree#shift!</tt>.
|
123
|
+
def expand!(template, depth)
|
124
|
+
names = Macro.pattern_vars(template)
|
125
|
+
size(names, depth).times { yield() and iterate!(names, depth) }
|
42
126
|
end
|
43
127
|
|
44
128
|
private
|
45
129
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
130
|
+
# Returns the number of matched expressions available for the given set
|
131
|
+
# of pattern variables at the given depth, at the current iteration point.
|
132
|
+
# An exception is raised if the names do not all yield the same number
|
133
|
+
# of matches; this indicates a piece of mismatched syntax that cannot be
|
134
|
+
# expanded correctly.
|
135
|
+
def size(names, depth)
|
136
|
+
sizes = []
|
137
|
+
@data.each do |name, tree|
|
138
|
+
sizes << tree.size(depth) if names.include?(name)
|
51
139
|
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def size
|
55
|
-
names = @names[@depth].uniq
|
56
|
-
splices = @data.select { |k,v| names.include?(k.to_s) }
|
57
|
-
sizes = splices.map { |pair| pair.last.size(@depth) }.uniq
|
58
140
|
|
59
|
-
|
141
|
+
sizes.uniq!
|
60
142
|
return sizes.first if sizes.size == 1
|
61
143
|
|
62
|
-
expressions = splices.map { |pair| '"' + pair.last.to_s(@depth) + '"' } * ', '
|
63
144
|
raise MacroTemplateMismatch.new(
|
64
|
-
"Macro could not be expanded:
|
145
|
+
"Macro could not be expanded: mismatched repetition patterns")
|
65
146
|
end
|
66
147
|
|
67
|
-
|
68
|
-
|
69
|
-
|
148
|
+
# Shifts the tree pointer (see <tt>Tree#shift!</tt> for all the given
|
149
|
+
# +names+ along one place at the given +depth+. This is used while
|
150
|
+
# expanding repeated patterns using <tt>expand!</tt>.
|
151
|
+
def iterate!(names, depth)
|
152
|
+
@data.each do |name, tree|
|
153
|
+
tree.shift!(depth) if names.include?(name)
|
70
154
|
end
|
71
155
|
end
|
72
156
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Heist
|
2
|
+
class Runtime
|
3
|
+
class Macro
|
4
|
+
|
5
|
+
# <tt>Tree</tt>s are used by instances of +Matches+ to store expressions
|
6
|
+
# matched by macro patterns. Patterns may contain patterns that repeat
|
7
|
+
# (indicated by following the pattern with an ellipsis), and these repetitions
|
8
|
+
# may be nested. +Tree+ instances store expressions in a set of nested arrays
|
9
|
+
# that match the repetition structure of the macro pattern being matched.
|
10
|
+
# See +Matches+ for a fuller explanation.
|
11
|
+
#
|
12
|
+
# Every +Tree+ contains an array called <tt>@data</tt> that stores the
|
13
|
+
# expressions matched by a pattern variable, a variable <tt>@depth</tt> that
|
14
|
+
# stores the maximum repetition depth of the tree (a non-repeated pattern
|
15
|
+
# has depth zero), and an array <tt>@indexes</tt> which is used to maintain
|
16
|
+
# a list of array indexes that point to the current read position in the
|
17
|
+
# tree while a macro is being expanded. For example, taking the pattern from
|
18
|
+
# the +Matches+ example:
|
19
|
+
#
|
20
|
+
# (do ([variable init step ...] ...)
|
21
|
+
# (test expression ...)
|
22
|
+
# command ...)
|
23
|
+
#
|
24
|
+
# Say we had the following expression (not entirely valid (do) syntax, but
|
25
|
+
# compatible with the above pattern):
|
26
|
+
#
|
27
|
+
# (do ([x 6 (- x 1) (- acc 1)]
|
28
|
+
# [y 5]
|
29
|
+
# [acc 1 (* x acc)])
|
30
|
+
# ((zero? x) acc)
|
31
|
+
# (display x) (newline)
|
32
|
+
# (set! acc (* acc x)))
|
33
|
+
#
|
34
|
+
# The resulting +Matches+ object would contain the following data for the
|
35
|
+
# variable +step+:
|
36
|
+
#
|
37
|
+
# "step" => [ [ (- x 1),
|
38
|
+
# (- acc 1)
|
39
|
+
# ],
|
40
|
+
#
|
41
|
+
# [],
|
42
|
+
#
|
43
|
+
# [ (* x acc)
|
44
|
+
# ]
|
45
|
+
# ]
|
46
|
+
#
|
47
|
+
# That is, the outermost repetition <tt>[variable init step ...]</tt>
|
48
|
+
# occurs three times; the first appearance includes two matches for
|
49
|
+
# <tt>step ...</tt>, the second no matches and the third one match. With
|
50
|
+
# this data, an <tt>@indexes</tt> state of <tt>[0,0]</tt> would read
|
51
|
+
# <tt>(- x 1)</tt>, a state of <tt>[0,1]</tt> would read <tt>(- acc 1)</tt>,
|
52
|
+
# and <tt>[2,0]</tt> would read <tt>(* x acc)</tt>; the latter instructing
|
53
|
+
# the +Tree+ to get the third element of the root array, then the first
|
54
|
+
# element of _that_ array to find the right value.
|
55
|
+
#
|
56
|
+
# In practise, all +Tree+ objects have an extra array around the data as
|
57
|
+
# presented above, to make the no-repetition case consistent with the
|
58
|
+
# representation for arbitrarily nested repetitions. That is, the methods
|
59
|
+
# in this class expect to read from an array in general, so the representation
|
60
|
+
# of a non-repeating pattern is just a single-element array to simplify
|
61
|
+
# the implementation of these methods in the general case. The first item
|
62
|
+
# in the <tt>@indexes</tt> array is always zero. We could remove this extra
|
63
|
+
# container and add a type check on <tt>@data</tt> when reading, but the
|
64
|
+
# current implementation seems more elegant for the moment.
|
65
|
+
#
|
66
|
+
class Tree
|
67
|
+
|
68
|
+
# A +Tree+ is initialized using the name of the pattern variable it is
|
69
|
+
# associated with (for debugging purposes).
|
70
|
+
def initialize(name)
|
71
|
+
@name = name
|
72
|
+
@data = []
|
73
|
+
@depth = 0
|
74
|
+
end
|
75
|
+
|
76
|
+
# Tells the receiving +Tree+ that its pattern variable has been visited
|
77
|
+
# at a repetition depth of +depth+ during pattern matching. This allocates
|
78
|
+
# a new empty array at an appropriate place in the tree to store matches
|
79
|
+
# (or groups of matches) if any are encountered. Calls to this method are
|
80
|
+
# also used to determine the tree's maximum depth.
|
81
|
+
def descend!(depth)
|
82
|
+
tail(depth-1) << []
|
83
|
+
@depth = depth if depth > @depth
|
84
|
+
end
|
85
|
+
|
86
|
+
# Pushes an expression onto the end of the final branch of the tree. All
|
87
|
+
# expressions should exist at the same depth (the tree's maximum depth),
|
88
|
+
# seeing as the pattern should be followed by the same number of ellipses
|
89
|
+
# every time it is encountered.
|
90
|
+
def <<(value)
|
91
|
+
return if Cons::NULL == value
|
92
|
+
tail(@depth) << value
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the expression at the current read position as instructed by
|
96
|
+
# the <tt>@indexes</tt> list.
|
97
|
+
def read
|
98
|
+
current(@depth)[indexes[@depth]]
|
99
|
+
end
|
100
|
+
|
101
|
+
# Shifts the read position at the given +depth+ along by one, by adding 1
|
102
|
+
# to one of the values in <tt>@indexes</tt>. The macro expander calls this
|
103
|
+
# while walking a template to iterate over repetition branches.
|
104
|
+
def shift!(depth)
|
105
|
+
indexes[depth] += 1
|
106
|
+
indexes[depth] = 0 if indexes[depth] >= current(depth).size
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the number of matches (or groups of matches) on the current
|
110
|
+
# read branch at the given +depth+. Returns zero if no branch exists at
|
111
|
+
# the given indexes.
|
112
|
+
def size(depth)
|
113
|
+
current(depth).size rescue 0
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Returns the rightmost branch of the tree at the given +depth+. Used
|
119
|
+
# when allocating new branches as repetition blocks are entered.
|
120
|
+
def tail(depth)
|
121
|
+
(0...depth).inject(@data) { |list, d| list.last }
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns the current read branch at the given +depth+, as instructed
|
125
|
+
# by the <tt>@indexes</tt> list.
|
126
|
+
def current(depth)
|
127
|
+
indexes[0...depth].inject(@data) { |list, i| list[i] }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Initializes the <tt>@indexes</tt> list once the maximum depth is
|
131
|
+
# known, and returns the list thereafter.
|
132
|
+
def indexes
|
133
|
+
@indexes ||= (0..@depth).map { 0 }
|
134
|
+
@indexes
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|