lib/antelope/generation/constructor/follow.rb in antelope-0.0.1 vs lib/antelope/generation/constructor/follow.rb in antelope-0.1.0
- old
+ new
@@ -1,45 +1,103 @@
module Antelope
module Generation
class Constructor
+
+ # Contains the methods to find the FOLLOW sets of nonterminals.
module Follow
+ # Initialize.
def initialize
@follows = {}
super
end
+ # Returns the FOLLOW set of the given token. If the given
+ # token isn't a nonterminal, it raises an error. It then
+ # generates the FOLLOW set for the given token, and then
+ # caches it.
+ #
+ # @return [Set<Ace::Token>]
+ # @see Constructor#incorrect_argument!
+ # @see #generate_follow_set
def follow(token)
-
- if token.nonterminal?
- token = token.name
- elsif token.is_a? Symbol
- else
- incorrect_argument! token, Ace::Token::Nonterminal, Symbol
+ unless token.is_a? Ace::Token::Nonterminal
+ incorrect_argument! token, Ace::Token::Nonterminal
end
- @follows.fetch(token) do
- @follows[token] = Set.new
- set = Set.new
+ @follows.fetch(token) { generate_follow_set(token) }
+ end
- parser.productions.each do |key, value|
- value.each do |production|
- items = production[:items]
- positions = items.each_with_index.
- find_all { |t, _| t.name == token }.
- map(&:last).map(&:succ)
- positions.map { |pos| first(items[pos..-1]) }.
- inject(set, :merge)
- positions.each do |pos|
- if pos == items.size || nullable?(items[pos..-1])
- set.merge follow(Ace::Token::Nonterminal.new(key))
- end
- end
+ private
+
+ # Generates the FOLLOW set for the given token. It finds the
+ # positions at which the token appears in the grammar, and
+ # sees what could possibly follow it. For example, given the
+ # following production:
+ #
+ # A -> aBz
+ #
+ # With `a` and `z` being any combination of terminals and
+ # nonterminals, and we're trying to find the FOLLOW set of
+ # `B` we add the FIRST set of `z` to the FOLLOW set of `B`:
+ #
+ # FOLLOW(B) = FOLLOW(B) ∪ FIRST(z)
+ #
+ # In the case that `B` is at the end of a production, like so:
+ #
+ # A -> aB
+ #
+ # or
+ #
+ # A -> aBw
+ #
+ # (with `w` being nullable) We also add the FOLLOW set of `A`
+ # to `B`:
+ #
+ # FOLLOW(B) = FOLLOW(B) ∪ FOLLOW(A)
+ #
+ # In case this operation is potentially recursive, we make
+ # sure to set the FOLLOW set of `B` to an empty set (since we
+ # cache the result of a FOLLOW set, the empty set will be
+ # returned).
+ #
+ # @param token [Ace::Token::Nonterminal]
+ # @return [Set<Ace::Token>]
+ # @see First#first
+ # @see Nullable#nullable?
+ def generate_follow_set(token)
+ # Set it to the empty set so we don't end up recursing.
+ @follows[token] = Set.new
+
+ # This is going to be the output set.
+ set = Set.new
+
+ productions.each do |rule|
+ items = rule.right
+
+ # Find all of the positions within the rule that our token
+ # occurs, and then increment that position by one.
+ positions = items.each_with_index.
+ find_all { |t, _| t == token }.
+ map(&:last).map(&:succ)
+
+ # Find the FIRST set of every item after our token, and
+ # put that in our set.
+ positions.map { |pos| first(items[pos..-1]) }.
+ inject(set, :merge)
+
+ positions.each do |pos|
+ # If we're at the end of the rule...
+ if pos == items.size || nullable?(items[pos..-1])
+ # Then add the FOLLOW set of the left-hand side to our
+ # set.
+ set.merge follow(rule.left)
end
end
-
- @follows[token] = set
end
+
+ # Replace the cached empty set with our filled set.
+ @follows[token] = set
end
end
end
end
end