Determine the reduce actions for each parser state
This commit is contained in:
parent
5af3179ff2
commit
60e2818075
@ -34,6 +34,7 @@ class Propane
|
|||||||
raise Error.new("Rule name collides with token name #{rule.name.inspect}")
|
raise Error.new("Rule name collides with token name #{rule.name.inspect}")
|
||||||
end
|
end
|
||||||
rule_sets[rule.name] ||= RuleSet.new(rule.name)
|
rule_sets[rule.name] ||= RuleSet.new(rule.name)
|
||||||
|
rule.rule_set = rule_sets[rule.name]
|
||||||
rule_sets[rule.name] << rule
|
rule_sets[rule.name] << rule
|
||||||
end
|
end
|
||||||
unless rule_sets["Start"]
|
unless rule_sets["Start"]
|
||||||
@ -51,6 +52,11 @@ class Propane
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
determine_possibly_empty_rulesets!(rule_sets)
|
determine_possibly_empty_rulesets!(rule_sets)
|
||||||
|
puts "Start token set"
|
||||||
|
rule_sets.each do |rule_set_name, rule_set|
|
||||||
|
puts "RuleSet #{rule_set_name}:"
|
||||||
|
puts " " + rule_set.start_token_set.map(&:name).join(", ")
|
||||||
|
end
|
||||||
@lexer = Lexer.new(@grammar.tokens, @grammar.drop_tokens)
|
@lexer = Lexer.new(@grammar.tokens, @grammar.drop_tokens)
|
||||||
@parser = Parser.new(rule_sets["Start"])
|
@parser = Parser.new(rule_sets["Start"])
|
||||||
end
|
end
|
||||||
|
@ -28,15 +28,31 @@ class Propane
|
|||||||
|
|
||||||
@item_sets.each do |item_set|
|
@item_sets.each do |item_set|
|
||||||
process_item_set(item_set)
|
process_item_set(item_set)
|
||||||
|
end
|
||||||
|
|
||||||
|
build_reduce_actions!
|
||||||
|
|
||||||
|
@item_sets.each do |item_set|
|
||||||
puts "Item set #{item_set.id}:"
|
puts "Item set #{item_set.id}:"
|
||||||
ids = item_set.in_sets.map(&:id)
|
ids = item_set.in_sets.map(&:id)
|
||||||
if ids.size > 0
|
puts " In sets: #{ids.join(", ")}"
|
||||||
puts " (in from #{ids.join(", ")})"
|
puts " Out sets:"
|
||||||
|
item_set.out_sets.each do |symbol, out_set|
|
||||||
|
puts " #{symbol.name} => #{out_set.id}"
|
||||||
end
|
end
|
||||||
puts item_set
|
puts item_set
|
||||||
item_set.following_item_set.each do |following_symbol, following_item_set|
|
item_set.following_item_set.each do |following_symbol, following_item_set|
|
||||||
puts " #{following_symbol.name} => #{following_item_set.id}"
|
puts " #{following_symbol.name} => #{following_item_set.id}"
|
||||||
end
|
end
|
||||||
|
puts " Reduce actions:"
|
||||||
|
case item_set.reduce_actions
|
||||||
|
when Rule
|
||||||
|
puts " * => #{item_set.reduce_actions.id} (#{item_set.reduce_actions.name})"
|
||||||
|
when Hash
|
||||||
|
item_set.reduce_actions.each do |token, rule|
|
||||||
|
puts " #{token.name} => #{rule.id} (#{rule.name})"
|
||||||
|
end
|
||||||
|
end
|
||||||
puts
|
puts
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -70,10 +86,124 @@ class Propane
|
|||||||
following_set = @item_sets_set[item_set.build_following_item_set(following_symbol)]
|
following_set = @item_sets_set[item_set.build_following_item_set(following_symbol)]
|
||||||
item_set.following_item_set[following_symbol] = following_set
|
item_set.following_item_set[following_symbol] = following_set
|
||||||
following_set.in_sets << item_set
|
following_set.in_sets << item_set
|
||||||
|
item_set.out_sets[following_symbol] = following_set
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Build the reduce actions for each ItemSet.
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def build_reduce_actions!
|
||||||
|
@item_sets.each do |item_set|
|
||||||
|
item_set.reduce_actions = build_reduce_actions_for_item_set(item_set)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Build the reduce actions for a single item set (parser state).
|
||||||
|
#
|
||||||
|
# @param item_set [ItemSet]
|
||||||
|
# ItemSet (parser state)
|
||||||
|
#
|
||||||
|
# @return [nil, Rule, Hash]
|
||||||
|
# If no reduce actions are possible for the given item set, nil.
|
||||||
|
# If only one reduce action is possible for the given item set, the Rule
|
||||||
|
# to reduce.
|
||||||
|
# Otherwise, a mapping of lookahead Tokens to the Rules to reduce.
|
||||||
|
def build_reduce_actions_for_item_set(item_set)
|
||||||
|
# To build the reduce actions, we start by looking at any
|
||||||
|
# "complete" items, i.e., items where the parse position is at the
|
||||||
|
# end of a rule. These are the only rules that are candidates for
|
||||||
|
# reduction in the current ItemSet.
|
||||||
|
reduce_rules = Set.new(item_set.items.select(&:complete?).map(&:rule))
|
||||||
|
|
||||||
|
# If there are no rules to reduce for this ItemSet, we're done here.
|
||||||
|
return nil if reduce_rules.size == 0
|
||||||
|
|
||||||
|
# If there is exactly one rule to reduce for this ItemSet, then do not
|
||||||
|
# figure out the lookaheads; just reduce it.
|
||||||
|
return reduce_rules.first if reduce_rules.size == 1
|
||||||
|
|
||||||
|
# Otherwise, we have more than one possible rule to reduce.
|
||||||
|
|
||||||
|
# We will be looking for all possible tokens that can follow instances of
|
||||||
|
# these rules. Rather than looking through the entire grammar for the
|
||||||
|
# possible following tokens, we will only look in the item sets leading
|
||||||
|
# up to this one. This restriction gives us a more precise lookahead set,
|
||||||
|
# and allows us to parse LALR grammars.
|
||||||
|
item_sets = item_set.leading_item_sets
|
||||||
|
reduce_rules.reduce({}) do |reduce_actions, reduce_rule|
|
||||||
|
lookahead_tokens_for_rule = build_lookahead_tokens_to_reduce(reduce_rule, item_sets)
|
||||||
|
lookahead_tokens_for_rule.each do |lookahead_token|
|
||||||
|
if existing_reduce_rule = reduce_actions[lookahead_token]
|
||||||
|
raise Error.new("Error: reduce/reduce conflict between rule #{existing_reduce_rule.id} (#{existing_reduce_rule.name}) and rule #{reduce_rule.id} (#{reduce_rule.name})")
|
||||||
|
end
|
||||||
|
reduce_actions[lookahead_token] = reduce_rule
|
||||||
|
end
|
||||||
|
reduce_actions
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Build the set of lookahead Tokens that should cause the given Rule to be
|
||||||
|
# reduced in the given context of ItemSets.
|
||||||
|
#
|
||||||
|
# @param rule [Rule]
|
||||||
|
# Rule to reduce.
|
||||||
|
# @param item_sets [Set<ItemSet>]
|
||||||
|
# ItemSets to consider for the context in which to reduce this Rule.
|
||||||
|
#
|
||||||
|
# @return [Set<Token>]
|
||||||
|
# Possible lookahead Tokens for the given Rule within the context of the
|
||||||
|
# given ItemSets.
|
||||||
|
def build_lookahead_tokens_to_reduce(rule, item_sets)
|
||||||
|
# We need to look for possible following tokens for this reduce rule. We
|
||||||
|
# do this by looking for tokens that follow the reduce rule, or the
|
||||||
|
# start token set for any other rule that follows the reduce rule.
|
||||||
|
# While doing this, the following situations could arise:
|
||||||
|
# 1. We may come across a following rule that could be empty. In this
|
||||||
|
# case, in addition to the start token set for that rule, we must also
|
||||||
|
# continue to the next following symbol after the potentially empty
|
||||||
|
# rule and continue the search for potential following tokens.
|
||||||
|
# 2. We may reach the end of a rule that was not one of the original
|
||||||
|
# reduce rules. In this case, we must also search for all potential
|
||||||
|
# following tokens for this rule as well.
|
||||||
|
lookahead_tokens = Set.new
|
||||||
|
rule_sets_to_check_after = [rule.rule_set]
|
||||||
|
checked_rule_sets = Set.new
|
||||||
|
while !rule_sets_to_check_after.empty?
|
||||||
|
rule_set = rule_sets_to_check_after.slice!(0)
|
||||||
|
checked_rule_sets << rule_set
|
||||||
|
# For each RuleSet we're checking, we're going to look through all
|
||||||
|
# items in the item sets of interest and gather all possible following
|
||||||
|
# tokens to form the lookahead token set.
|
||||||
|
item_sets.each do |item_set|
|
||||||
|
item_set.items.each do |item|
|
||||||
|
if item.following_symbol == rule_set
|
||||||
|
(1..).each do |offset|
|
||||||
|
case symbol = item.following_symbol(offset)
|
||||||
|
when nil
|
||||||
|
rule_set = item.rule.rule_set
|
||||||
|
unless checked_rule_sets.include?(rule_set)
|
||||||
|
rule_sets_to_check_after << rule_set
|
||||||
|
end
|
||||||
|
break
|
||||||
|
when Token
|
||||||
|
lookahead_tokens << symbol
|
||||||
|
break
|
||||||
|
when RuleSet
|
||||||
|
lookahead_tokens += symbol.start_token_set
|
||||||
|
unless symbol.could_be_empty?
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
lookahead_tokens
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -86,10 +86,26 @@ class Propane
|
|||||||
# That is, the symbol which follows the parse position marker in the
|
# That is, the symbol which follows the parse position marker in the
|
||||||
# current Item.
|
# current Item.
|
||||||
#
|
#
|
||||||
|
# @param offset [Integer]
|
||||||
|
# Offset from current parse position to examine.
|
||||||
|
#
|
||||||
# @return [Token, RuleSet, nil]
|
# @return [Token, RuleSet, nil]
|
||||||
# Following symbol for the Item.
|
# Following symbol for the Item.
|
||||||
def following_symbol
|
def following_symbol(offset = 0)
|
||||||
@rule.components[@position]
|
@rule.components[@position + offset]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the previous symbol for the Item.
|
||||||
|
#
|
||||||
|
# That is, the symbol which precedes the parse position marker in the
|
||||||
|
# current Item.
|
||||||
|
#
|
||||||
|
# @return [Token, RuleSet, nil]
|
||||||
|
# Previous symbol for this Item.
|
||||||
|
def previous_symbol
|
||||||
|
if @position > 0
|
||||||
|
@rule.components[@position - 1]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get whether this Item is followed by the provided symbol.
|
# Get whether this Item is followed by the provided symbol.
|
||||||
|
@ -21,6 +21,14 @@ class Propane
|
|||||||
# ItemSets leading to this item set.
|
# ItemSets leading to this item set.
|
||||||
attr_reader :in_sets
|
attr_reader :in_sets
|
||||||
|
|
||||||
|
# @return [Hash]
|
||||||
|
# ItemSets reached from this item set. Key is a Token or Rule.
|
||||||
|
attr_reader :out_sets
|
||||||
|
|
||||||
|
# @return [nil, Rule, Hash]
|
||||||
|
# Reduce actions, mapping lookahead tokens to rules.
|
||||||
|
attr_accessor :reduce_actions
|
||||||
|
|
||||||
# Build an ItemSet.
|
# Build an ItemSet.
|
||||||
#
|
#
|
||||||
# @param items [Array<Item>]
|
# @param items [Array<Item>]
|
||||||
@ -29,6 +37,7 @@ class Propane
|
|||||||
@items = Set.new(items)
|
@items = Set.new(items)
|
||||||
@following_item_set = {}
|
@following_item_set = {}
|
||||||
@in_sets = Set.new
|
@in_sets = Set.new
|
||||||
|
@out_sets = {}
|
||||||
close!
|
close!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -22,6 +22,10 @@ class Propane
|
|||||||
# Rule name.
|
# Rule name.
|
||||||
attr_reader :name
|
attr_reader :name
|
||||||
|
|
||||||
|
# @return [RuleSet]
|
||||||
|
# The RuleSet that this Rule is a part of.
|
||||||
|
attr_accessor :rule_set
|
||||||
|
|
||||||
# Construct a Rule.
|
# Construct a Rule.
|
||||||
#
|
#
|
||||||
# @param name [String]
|
# @param name [String]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user