Compare commits

..

No commits in common. "master" and "wip" have entirely different histories.
master ... wip

6 changed files with 86 additions and 73 deletions

View File

@ -1,21 +1,8 @@
## v1.5.1
### Improvements
- Improve performance (#28)
## v1.5.0 ## v1.5.0
### New Features ### New Features
- Track start and end text positions for tokens and rules in AST node structures (#27) - Track token position in AST Token node
- Add warnings for shift/reduce conflicts to log file (#25)
- Add -w command line switch to treat warnings as errors and output to stderr (#26)
- Add rule field aliases (#24)
### Improvements
- Show line numbers of rules on conflict (#23)
## v1.4.0 ## v1.4.0

View File

@ -31,14 +31,9 @@ Propane is typically invoked from the command-line as `./propane`.
Usage: ./propane [options] <input-file> <output-file> Usage: ./propane [options] <input-file> <output-file>
Options: Options:
-h, --help Show this usage and exit. --log LOG Write log file
--log LOG Write log file. This will show all parser states and their --version Show program version and exit
associated shifts and reduces. It can be helpful when -h, --help Show this usage and exit
debugging a grammar.
--version Show program version and exit.
-w Treat warnings as errors. This option will treat shift/reduce
conflicts as fatal errors and will print them to stderr in
addition to the log file.
The user must specify the path to a Propane input grammar file and a path to an The user must specify the path to a Propane input grammar file and a path to an
output file. output file.

View File

@ -17,9 +17,6 @@ syn region propaneTarget matchgroup=propaneDelimiter start="<<" end=">>$" contai
syn match propaneComment "#.*" syn match propaneComment "#.*"
syn match propaneOperator "->" syn match propaneOperator "->"
syn match propaneFieldAlias ":[a-zA-Z0-9_]\+" contains=propaneFieldOperator
syn match propaneFieldOperator ":" contained
syn match propaneOperator "?"
syn keyword propaneKeyword ast ast_prefix ast_suffix drop module prefix ptype start token tokenid syn keyword propaneKeyword ast ast_prefix ast_suffix drop module prefix ptype start token tokenid
syn region propaneRegex start="/" end="/" skip="\\/" syn region propaneRegex start="/" end="/" skip="\\/"
@ -28,6 +25,4 @@ hi def link propaneComment Comment
hi def link propaneKeyword Keyword hi def link propaneKeyword Keyword
hi def link propaneRegex String hi def link propaneRegex String
hi def link propaneOperator Operator hi def link propaneOperator Operator
hi def link propaneFieldOperator Operator
hi def link propaneDelimiter Delimiter hi def link propaneDelimiter Delimiter
hi def link propaneFieldAlias Identifier

View File

@ -39,6 +39,7 @@ class Propane
end end
build_reduce_actions! build_reduce_actions!
build_follow_sets!
build_tables! build_tables!
write_log! write_log!
if @warnings.size > 0 && @options[:warnings_as_errors] if @warnings.size > 0 && @options[:warnings_as_errors]
@ -65,10 +66,10 @@ class Propane
state_id: state_id, state_id: state_id,
} }
end end
unless item_set.reduce_rules.empty? if item_set.reduce_actions
shift_entries.each do |shift_entry| shift_entries.each do |shift_entry|
token = shift_entry[:symbol] token = shift_entry[:symbol]
if get_lookahead_reduce_actions_for_item_set(item_set).include?(token) if item_set.reduce_actions.include?(token)
rule = item_set.reduce_actions[token] rule = item_set.reduce_actions[token]
@warnings << "Shift/Reduce conflict (state #{item_set.id}) between token #{token.name} and rule #{rule.name} (defined on line #{rule.line_number})" @warnings << "Shift/Reduce conflict (state #{item_set.id}) between token #{token.name} and rule #{rule.name} (defined on line #{rule.line_number})"
end end
@ -114,7 +115,7 @@ class Propane
# @return [void] # @return [void]
def build_reduce_actions! def build_reduce_actions!
@item_sets.each do |item_set| @item_sets.each do |item_set|
build_reduce_actions_for_item_set(item_set) item_set.reduce_actions = build_reduce_actions_for_item_set(item_set)
end end
end end
@ -123,36 +124,27 @@ class Propane
# @param item_set [ItemSet] # @param item_set [ItemSet]
# ItemSet (parser state) # ItemSet (parser state)
# #
# @return [void] # @return [nil, Hash]
# If no reduce actions are possible for the given item set, nil.
# Otherwise, a mapping of lookahead Tokens to the Rules to reduce.
def build_reduce_actions_for_item_set(item_set) def build_reduce_actions_for_item_set(item_set)
# To build the reduce actions, we start by looking at any # To build the reduce actions, we start by looking at any
# "complete" items, i.e., items where the parse position is at the # "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 # end of a rule. These are the only rules that are candidates for
# reduction in the current ItemSet. # reduction in the current ItemSet.
item_set.reduce_rules = Set.new(item_set.items.select(&:complete?).map(&:rule)) reduce_rules = Set.new(item_set.items.select(&:complete?).map(&:rule))
if item_set.reduce_rules.size == 1 if reduce_rules.size == 1
item_set.reduce_rule = item_set.reduce_rules.first item_set.reduce_rule = reduce_rules.first
end end
if item_set.reduce_rules.size > 1 if reduce_rules.size == 0
# Force item_set.reduce_actions to be built to store the lookahead nil
# tokens for the possible reduce rules if there is more than one. else
get_lookahead_reduce_actions_for_item_set(item_set) build_lookahead_reduce_actions_for_item_set(item_set)
end end
end end
# Get the reduce actions for a single item set (parser state).
#
# @param item_set [ItemSet]
# ItemSet (parser state)
#
# @return [Hash]
# Mapping of lookahead Tokens to the Rules to reduce.
def get_lookahead_reduce_actions_for_item_set(item_set)
item_set.reduce_actions ||= build_lookahead_reduce_actions_for_item_set(item_set)
end
# Build the reduce actions for a single item set (parser state). # Build the reduce actions for a single item set (parser state).
# #
# @param item_set [ItemSet] # @param item_set [ItemSet]
@ -161,13 +153,15 @@ class Propane
# @return [Hash] # @return [Hash]
# Mapping of lookahead Tokens to the Rules to reduce. # Mapping of lookahead Tokens to the Rules to reduce.
def build_lookahead_reduce_actions_for_item_set(item_set) def build_lookahead_reduce_actions_for_item_set(item_set)
reduce_rules = Set.new(item_set.items.select(&:complete?).map(&:rule))
# We will be looking for all possible tokens that can follow instances of # We will be looking for all possible tokens that can follow instances of
# these rules. Rather than looking through the entire grammar for the # these rules. Rather than looking through the entire grammar for the
# possible following tokens, we will only look in the item sets leading # 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, # up to this one. This restriction gives us a more precise lookahead set,
# and allows us to parse LALR grammars. # and allows us to parse LALR grammars.
item_sets = Set[item_set] + item_set.leading_item_sets item_sets = Set[item_set] + item_set.leading_item_sets
item_set.reduce_rules.reduce({}) do |reduce_actions, reduce_rule| 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 = build_lookahead_tokens_to_reduce(reduce_rule, item_sets)
lookahead_tokens_for_rule.each do |lookahead_token| lookahead_tokens_for_rule.each do |lookahead_token|
if existing_reduce_rule = reduce_actions[lookahead_token] if existing_reduce_rule = reduce_actions[lookahead_token]
@ -239,6 +233,51 @@ class Propane
lookahead_tokens lookahead_tokens
end end
# Build the follow sets for each ItemSet.
#
# @return [void]
def build_follow_sets!
@item_sets.each do |item_set|
item_set.follow_set = build_follow_set_for_item_set(item_set)
end
end
# Build the follow set for the given ItemSet.
#
# @param item_set [ItemSet]
# The ItemSet to build the follow set for.
#
# @return [Set]
# Follow set for the given ItemSet.
def build_follow_set_for_item_set(item_set)
follow_set = Set.new
rule_sets_to_check_after = Set.new
item_set.items.each do |item|
(1..).each do |offset|
case symbol = item.next_symbol(offset)
when nil
rule_sets_to_check_after << item.rule.rule_set
break
when Token
follow_set << symbol
break
when RuleSet
follow_set += symbol.start_token_set
unless symbol.could_be_empty?
break
end
end
end
end
reduce_lookaheads = build_lookahead_reduce_actions_for_item_set(item_set)
reduce_lookaheads.each do |token, rule_set|
if rule_sets_to_check_after.include?(rule_set)
follow_set << token
end
end
follow_set
end
def write_log! def write_log!
@log.puts Util.banner("Parser Rules") @log.puts Util.banner("Parser Rules")
@grammar.rules.each do |rule| @grammar.rules.each do |rule|

View File

@ -2,7 +2,7 @@ class Propane
class Parser class Parser
# Represent a parser "item set", which is a set of possible items that the # 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. # parser could currently be parsing.
class ItemSet class ItemSet
# @return [Set<Item>] # @return [Set<Item>]
@ -25,15 +25,15 @@ class Propane
# Rule to reduce if there is only one possibility. # Rule to reduce if there is only one possibility.
attr_accessor :reduce_rule 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] # @return [nil, Hash]
# Reduce actions, mapping lookahead tokens to rules, if there is # Reduce actions, mapping lookahead tokens to rules, if there is
# more than one rule that could be reduced. # more than one rule that could be reduced.
attr_accessor :reduce_actions attr_accessor :reduce_actions
# @return [Set<Token>]
# Follow set for the ItemSet.
attr_accessor :follow_set
# Build an ItemSet. # Build an ItemSet.
# #
# @param items [Array<Item>] # @param items [Array<Item>]
@ -50,7 +50,7 @@ class Propane
# @return [Set<Token, RuleSet>] # @return [Set<Token, RuleSet>]
# Set of next symbols for all Items in this ItemSet. # Set of next symbols for all Items in this ItemSet.
def next_symbols def next_symbols
@_next_symbols ||= Set.new(@items.map(&:next_symbol).compact) Set.new(@items.map(&:next_symbol).compact)
end end
# Build a next ItemSet for the given next symbol. # Build a next ItemSet for the given next symbol.
@ -99,8 +99,6 @@ class Propane
# @return [Set<ItemSet>] # @return [Set<ItemSet>]
# Set of all ItemSets that lead up to this ItemSet. # Set of all ItemSets that lead up to this ItemSet.
def leading_item_sets def leading_item_sets
@_leading_item_sets ||=
begin
result = Set.new result = Set.new
eval_sets = Set[self] eval_sets = Set[self]
evaled = Set.new evaled = Set.new
@ -117,7 +115,6 @@ class Propane
end end
result result
end end
end
# Represent the ItemSet as a String. # Represent the ItemSet as a String.
# #

View File

@ -1,3 +1,3 @@
class Propane class Propane
VERSION = "1.5.1" VERSION = "1.4.0"
end end