Clean up and comment Item and ItemSet

This commit is contained in:
Josh Holtrop 2022-06-05 18:12:14 -04:00
parent df8088c3c6
commit 9d850294a9
3 changed files with 148 additions and 40 deletions

View File

@ -21,10 +21,10 @@ class Propane
item_set.id = @item_sets.size
@item_sets << item_set
@item_sets_set[item_set] = item_set
item_set.follow_symbols.each do |follow_symbol|
unless follow_symbol == @token_eof
follow_set = item_set.build_follow_set(follow_symbol)
eval_item_sets << follow_set
item_set.following_symbols.each do |following_symbol|
unless following_symbol == @token_eof
following_set = item_set.build_following_item_set(following_symbol)
eval_item_sets << following_set
end
end
end
@ -39,8 +39,8 @@ class Propane
puts " (in from #{ids.join(", ")})"
end
puts item_set
item_set.follow_item_set.each do |follow_symbol, follow_item_set|
puts " #{follow_symbol.name} => #{follow_item_set.id}"
item_set.following_item_set.each do |following_symbol, following_item_set|
puts " #{following_symbol.name} => #{following_item_set.id}"
end
puts
end
@ -50,12 +50,12 @@ class Propane
shift_table = []
state_table = []
@item_sets.each do |item_set|
shift_entries = item_set.follow_symbols.select do |follow_symbol|
follow_symbol.is_a?(Token)
end.map do |follow_symbol|
shift_entries = item_set.following_symbols.select do |following_symbol|
following_symbol.is_a?(Token)
end.map do |following_symbol|
{
token_id: follow_symbol.id,
state_id: item_set.follow_item_set[follow_symbol].id,
token_id: following_symbol.id,
state_id: item_set.following_item_set[following_symbol].id,
}
end
state_table << {
@ -70,11 +70,11 @@ class Propane
private
def process_item_set(item_set)
item_set.follow_symbols.each do |follow_symbol|
unless follow_symbol == @token_eof
follow_set = @item_sets_set[item_set.build_follow_set(follow_symbol)]
item_set.follow_item_set[follow_symbol] = follow_set
follow_set.in_sets << item_set
item_set.following_symbols.each do |following_symbol|
unless following_symbol == @token_eof
following_set = @item_sets_set[item_set.build_following_item_set(following_symbol)]
item_set.following_item_set[following_symbol] = following_set
following_set.in_sets << item_set
end
end
end

View File

@ -1,32 +1,67 @@
class Propane
class Parser
# Represent a parser "item", which is a position in a Rule that the parser
# could potentially be at.
class Item
# @return [Rule]
# The Rule corresponding to this Item.
attr_reader :rule
# @return [Integer]
# The parse position in this item.
attr_reader :position
# Construct an Item.
#
# @param rule [Rule]
# The Rule corresponding to this Item.
# @param position [Integer]
# The parse position in this Item.
def initialize(rule, position)
@rule = rule
@position = position
end
def next_component
@rule.components[@position]
end
# Hash function.
#
# @return [Integer]
# Hash code.
def hash
[@rule, @position].hash
end
# Compare Item objects.
#
# @param other [Item]
# Item to compare to.
#
# @return [Boolean]
# Whether the Items are equal.
def ==(other)
@rule == other.rule && @position == other.position
end
# Compare Item objects.
#
# @param other [Item]
# Item to compare to.
#
# @return [Boolean]
# Whether the Items are equal.
def eql?(other)
self == other
end
# Return the set of Items obtained by "closing" the current item.
#
# If the following symbol for the current item is another Rule name, then
# this method will return all Items for that Rule with a position of 0.
# Otherwise, an empty Array is returned.
#
# @return [Array<Item>]
# Items obtained by "closing" the current item.
def closed_items
if @rule.components[@position].is_a?(RuleSet)
@rule.components[@position].rules.map do |rule|
@ -37,29 +72,46 @@ class Propane
end
end
def follow_symbol
# Get the following symbol for the Item.
#
# That is, the symbol which follows the parse position marker in the
# current Item.
#
# @return [Token, RuleSet, nil]
# Following symbol for the Item.
def following_symbol
@rule.components[@position]
end
# Get whether this Item is followed by the provided symbol.
#
# @param symbol [Token, RuleSet]
# Symbol to query.
#
# @return [Boolean]
# Whether this Item is followed by the provided symbol.
def followed_by?(symbol)
follow_symbol == symbol
following_symbol == symbol
end
def next_position
# Get the following item for this Item.
#
# That is, the Item formed by moving the parse position marker one place
# forward from its position in this Item.
#
# @return [Item]
# The following item for this Item.
def following_item
Item.new(@rule, @position + 1)
end
# Represent the Item as a String.
#
# @return [String]
# The Item represented as a String.
def to_s
parts = []
@rule.components.each_with_index do |symbol, index|
if @position == index
parts << "."
end
parts << symbol.name
end
if @position == @rule.components.size
parts << "."
end
parts = @rule.components.map(&:name)
parts[@position, 0] = "."
"#{@rule.name} -> #{parts.join(" ")}"
end

View File

@ -1,53 +1,102 @@
class Propane
class Parser
# Represent a parser "item set", which is a set of possible items that the
# parser could currently be parsing.
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 follow symbol to its item set.
attr_reader :follow_item_set
# Maps a following symbol to its ItemSet.
attr_reader :following_item_set
# @return [Set]
# Item sets leading to this item set.
attr_reader :in_sets
# Build an ItemSet.
#
# @param items [Array<Item>]
# Items in this ItemSet.
def initialize(items)
@items = Set.new(items)
@follow_item_set = {}
@following_item_set = {}
@in_sets = Set.new
close!
end
def follow_symbols
Set.new(@items.map(&:follow_symbol).compact)
# Get the set of following symbols for all Items in this ItemSet.
#
# @return [Set<Token, RuleSet>]
# Set of following symbols for all Items in this ItemSet.
def following_symbols
Set.new(@items.map(&:following_symbol).compact)
end
def build_follow_set(symbol)
ItemSet.new(items_followed_by(symbol).map(&:next_position))
# Build a following ItemSet for the given following symbol.
#
# @param symbol [Token, RuleSet]
# Following symbol to build the following ItemSet for.
#
# @return [ItemSet]
# Following ItemSet for the given following symbol.
def build_following_item_set(symbol)
ItemSet.new(items_followed_by(symbol).map(&:following_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
# 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
while eval_items.size > 0
@ -64,6 +113,13 @@ class Propane
end
end
# Get the Items followed by the given following symbol.
#
# @param symbol [Token, RuleSet]
# Following symbol.
#
# @return [Array<Item>]
# Items followed by the given following symbol.
def items_followed_by(symbol)
@items.select do |item|
item.followed_by?(symbol)