# encoding: utf-8 require "set" require "antelope/generation/constructor/nullable" require "antelope/generation/constructor/first" require "antelope/generation/constructor/follow" module Antelope module Generation # Constructs the lookahead sets for all of the rules in the # grammar. class Constructor include Nullable include First include Follow # The grammar. # # @return [Ace::Grammar] attr_reader :grammar # The augmented productions generated by the constructor. # # @return [Set] attr_reader :productions # Initialize. # # @param grammar [Ace::Grammar] the grammar. def initialize(grammar) @productions = Set.new @grammar = grammar super() end # Performs the construction. First, it goes through every state # and augments the state. It then goes through every rule and # augments it. # # @return [void] # @see #augment_state # @see #augment_rule def call grammar.states.each do |state| augment_state(state) end.each do |state| augment_rules(state) end end # Augments the given state. On every rule within that state # that has a position of zero, it follows the rule throughout # the DFA until the end; it marks every nonterminal it # encounters with the transitions it took on that nonterminal. # # @param state [Recognizer::State] the state to augment. # @return [void] def augment_state(state) state.rules.select { |x| x.position.zero? }.each do |rule| production = rule.production.clone production.items = [] current_state = state old_state = state production.label.from = state production.label.to = state.transitions[rule.left.name] rule.right.each_with_index do |part, pos| transition = current_state.transitions[part.name] new_item = part.dup if part.nonterminal? new_item.from = current_state new_item.to = transition end production.items << new_item old_state = current_state current_state = transition end productions << production end end # Augments every final rule. For every rule in the current # state that has a position of zero, it follows the rule through # the DFA until the ending state; it then modifies the ending # state's lookahead set to be the FOLLOW set of the nonterminal # it reduces to. # # @param state [Recognizer::State] # @return [void] # @see Follow#follow def augment_rules(state) state.rules.select { |x| x.position.zero? }.each do |rule| current_state = state label = rule.left.dup label.from = state label.to = state.transitions[label.name] rule.right.each do |part| transition = current_state.transitions[part.name] current_state = transition end final = current_state.rule_for(rule) final.lookahead = Set.new unless final.lookahead final.lookahead.merge follow(label) end end private def incorrect_argument!(arg, *types) raise ArgumentError, "Expected one of #{types.join(", ")}, got #{arg.class}" end end end end