propane/lib/propane/parser/item_set.rb

173 lines
4.3 KiB
Ruby

class Propane
class Parser
# Represent a parser "item set", which is a set of possible items that the
# parser could currently be parsing. This is equivalent to a parser state.
class ItemSet
# @return [Set<Item>]
# Items in this ItemSet.
attr_reader :items
# @return [Integer]
# ID of this ItemSet.
attr_accessor :id
# @return [Hash]
# Maps a next symbol to its ItemSet.
attr_reader :next_item_set
# @return [Set<ItemSet>]
# ItemSets leading to this item set.
attr_reader :in_sets
# @return [nil, Rule]
# Rule to reduce if there is only one possibility.
attr_accessor :reduce_rule
# @return [Set<Rule>]
# Set of rules that could be reduced in this parser state.
attr_accessor :reduce_rules
# @return [nil, Hash]
# Reduce actions, mapping lookahead tokens to rules, if there is
# more than one rule that could be reduced.
attr_accessor :reduce_actions
# @return [Array<Hash>]
# Shift table entries.
attr_accessor :shift_entries
# Build an ItemSet.
#
# @param items [Array<Item>]
# Items in this ItemSet.
def initialize(items)
@items = Set.new(items)
@next_item_set = {}
@in_sets = Set.new
close!
end
# Get the set of next symbols for all Items in this ItemSet.
#
# @return [Set<Token, RuleSet>]
# Set of next symbols for all Items in this ItemSet.
def next_symbols
@_next_symbols ||= Set.new(@items.map(&:next_symbol).compact)
end
# Build a next ItemSet for the given next symbol.
#
# @param symbol [Token, RuleSet]
# Next symbol to build the next ItemSet for.
#
# @return [ItemSet]
# Next ItemSet for the given next symbol.
def build_next_item_set(symbol)
ItemSet.new(items_with_next(symbol).map(&:next_item))
end
# Hash function.
#
# @return [Integer]
# Hash code.
def hash
@items.hash
end
# Compare ItemSet objects.
#
# @param other [ItemSet]
# ItemSet to compare to.
#
# @return [Boolean]
# Whether the ItemSets are equal.
def ==(other)
@items.eql?(other.items)
end
# Compare ItemSet objects.
#
# @param other [ItemSet]
# ItemSet to compare to.
#
# @return [Boolean]
# Whether the ItemSets are equal.
def eql?(other)
self == other
end
# Set of ItemSets that lead to this ItemSet.
#
# @return [Set<ItemSet>]
# Set of all ItemSets that lead up to this ItemSet.
def leading_item_sets
@_leading_item_sets ||=
begin
result = Set.new
eval_sets = Set[self]
evaled = Set.new
while eval_sets.size > 0
eval_set = eval_sets.first
eval_sets.delete(eval_set)
evaled << eval_set
eval_set.in_sets.each do |in_set|
result << in_set
unless evaled.include?(in_set)
eval_sets << in_set
end
end
end
result
end
end
# Represent the ItemSet as a String.
#
# @return [String]
# The ItemSet represented as a String.
def to_s
@items.map(&:to_s).join("\n")
end
private
# Close the ItemSet.
#
# This is done by recursively adding the closed Items for each Item in
# the ItemSet.
#
# @return [void]
def close!
eval_items = @items.dup
while eval_items.size > 0
item = eval_items.first
eval_items.delete(item)
item.closed_items.each do |new_item|
unless @items.include?(new_item)
eval_items << new_item
end
end
@items += eval_items
end
end
# Get the Items with the given next symbol.
#
# @param symbol [Token, RuleSet]
# Next symbol.
#
# @return [Array<Item>]
# Items with the given next symbol.
def items_with_next(symbol)
@items.select do |item|
item.next_symbol?(symbol)
end
end
end
end
end