lib/antelope/generation/constructor/follow.rb in antelope-0.2.0 vs lib/antelope/generation/constructor/follow.rb in antelope-0.2.2

- old
+ new

@@ -1,103 +1,103 @@ -# encoding: utf-8 - -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) - unless token.is_a? Ace::Token::Nonterminal - incorrect_argument! token, Ace::Token::Nonterminal - end - - @follows.fetch(token) { generate_follow_set(token) } - 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. - set = @follows[token] = Set.new - - productions.each do |rule| - items = rule.items - - # 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.label) - end - end - end - - # Replace the cached empty set with our filled set. - @follows[token] = set - end - end - end - end -end +# encoding: utf-8 + +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) + unless token.is_a? Ace::Token::Nonterminal + incorrect_argument! token, Ace::Token::Nonterminal + end + + @follows.fetch(token) { generate_follow_set(token) } + 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. + set = @follows[token] = Set.new + + productions.each do |rule| + items = rule.items + + # 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.label) + end + end + end + + # Replace the cached empty set with our filled set. + @follows[token] = set + end + end + end + end +end