Lrama v0.6.10

This commit is contained in:
ydah 2024-09-11 18:32:50 +09:00 committed by Yuichiro Kaneko
parent 2a1962fc4a
commit 885cf350de
Notes: git 2024-09-12 12:06:34 +00:00
74 changed files with 1602 additions and 1213 deletions

View File

@ -1,5 +1,225 @@
# NEWS for Lrama # NEWS for Lrama
## Lrama 0.6.10 (2024-09-11)
### Aliased Named References for actions of RHS in parameterizing rules
Allow to use aliased named references for actions of RHS in parameterizing rules.
```
%rule sum(X, Y): X[summand] '+' Y[addend] { $$ = $summand + $addend }
;
```
https://github.com/ruby/lrama/pull/410
### Named References for actions of RHS in parameterizing rules caller side
Allow to use named references for actions of RHS in parameterizing rules caller side.
```
opt_nl: '\n'?[nl] <str> { $$ = $nl; }
;
```
https://github.com/ruby/lrama/pull/414
### Widen the definable position of parameterizing rules
Allow to define parameterizing rules in the middle of the grammar.
```
%rule defined_option(X): /* empty */
| X
;
%%
program : defined_option(number) <i>
| defined_list(number) <i>
;
%rule defined_list(X): /* empty */ /* <--- here */
| defined_list(X) number
;
```
https://github.com/ruby/lrama/pull/420
### Report unused terminal symbols
Support to report unused terminal symbols.
Run `exe/lrama --report=terms` to show unused terminal symbols.
```
exe/lrama --report=terms sample/calc.y
11 Unused Terms
0 YYerror
1 YYUNDEF
2 '\\\\'
3 '\\13'
4 keyword_class2
5 tNUMBER
6 tPLUS
7 tMINUS
8 tEQ
9 tEQEQ
10 '>'
```
https://github.com/ruby/lrama/pull/439
### Report unused rules
Support to report unused rules.
Run `exe/lrama --report=rules` to show unused rules.
```
exe/lrama --report=rules sample/calc.y
3 Unused Rules
0 unused_option
1 unused_list
2 unused_nonempty_list
```
https://github.com/ruby/lrama/pull/441
### Ensure compatibility with Bison for `%locations` directive
Support `%locations` directive to ensure compatibility with Bison.
Change to `%locations` directive not set by default.
https://github.com/ruby/lrama/pull/446
### Diagnostics report for parameterizing rules redefine
Support to warning redefined parameterizing rules.
Run `exe/lrama -W` or `exe/lrama --warnings` to show redefined parameterizing rules.
```
exe/lrama -W sample/calc.y
parameterizing rule redefined: redefined_method(X)
parameterizing rule redefined: redefined_method(X)
```
https://github.com/ruby/lrama/pull/448
### Support `-v` and `--verbose` option
Support to `-v` and `--verbose` option.
These options align with Bison behavior. So same as '--report=state' option.
https://github.com/ruby/lrama/pull/457
## Lrama 0.6.9 (2024-05-02)
### Callee side tag specification of parameterizing rules
Allow to specify tag on callee side of parameterizing rules.
```
%union {
int i;
}
%rule with_tag(X) <i>: X { $$ = $1; }
;
```
### Named References for actions of RHS in parameterizing rules
Allow to use named references for actions of RHS in parameterizing rules.
```
%rule option(number): /* empty */
| number { $$ = $number; }
;
```
## Lrama 0.6.8 (2024-04-29)
### Nested parameterizing rules with tag
Allow to nested parameterizing rules with tag.
```
%union {
int i;
}
%rule nested_nested_option(X): /* empty */
| X
;
%rule nested_option(X): /* empty */
| nested_nested_option(X) <i>
;
%rule option(Y): /* empty */
| nested_option(Y) <i>
;
```
## Lrama 0.6.7 (2024-04-28)
### RHS of user defined parameterizing rules contains `'symbol'?`, `'symbol'+` and `'symbol'*`.
User can use `'symbol'?`, `'symbol'+` and `'symbol'*` in RHS of user defined parameterizing rules.
```
%rule with_word_seps(X): /* empty */
| X ' '+
;
```
## Lrama 0.6.6 (2024-04-27)
### Trace actions
Support trace actions for debugging.
Run `exe/lrama --trace=actions` to show grammar rules with actions.
```
exe/lrama --trace=actions sample/calc.y
Grammar rules with actions:
$accept -> list, YYEOF {}
list -> ε {}
list -> list, LF {}
list -> list, expr, LF { printf("=> %d\n", $2); }
expr -> NUM {}
expr -> expr, '+', expr { $$ = $1 + $3; }
expr -> expr, '-', expr { $$ = $1 - $3; }
expr -> expr, '*', expr { $$ = $1 * $3; }
expr -> expr, '/', expr { $$ = $1 / $3; }
expr -> '(', expr, ')' { $$ = $2; }
```
### Inlining
Support inlining for rules.
The `%inline` directive causes all references to symbols to be replaced with its definition.
```
%rule %inline op: PLUS { + }
| TIMES { * }
;
%%
expr : number { $$ = $1; }
| expr op expr { $$ = $1 $2 $3; }
;
```
as same as
```
expr : number { $$ = $1; }
| expr '+' expr { $$ = $1 + $3; }
| expr '*' expr { $$ = $1 * $3; }
;
```
## Lrama 0.6.5 (2024-03-25) ## Lrama 0.6.5 (2024-03-25)
### Typed Midrule Actions ### Typed Midrule Actions

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << File.join(__dir__, "../lib") $LOAD_PATH << File.join(__dir__, "../lib")
require "lrama" require "lrama"

View File

@ -1,17 +1,22 @@
require "lrama/bitmap" # frozen_string_literal: true
require "lrama/command"
require "lrama/context" require_relative "lrama/bitmap"
require "lrama/counterexamples" require_relative "lrama/command"
require "lrama/digraph" require_relative "lrama/context"
require "lrama/grammar" require_relative "lrama/counterexamples"
require "lrama/lexer" require_relative "lrama/diagnostics"
require "lrama/option_parser" require_relative "lrama/digraph"
require "lrama/options" require_relative "lrama/grammar"
require "lrama/output" require_relative "lrama/grammar_validator"
require "lrama/parser" require_relative "lrama/lexer"
require "lrama/report" require_relative "lrama/logger"
require "lrama/state" require_relative "lrama/option_parser"
require "lrama/states" require_relative "lrama/options"
require "lrama/states_reporter" require_relative "lrama/output"
require "lrama/version" require_relative "lrama/parser"
require "lrama/warning" require_relative "lrama/report"
require_relative "lrama/state"
require_relative "lrama/states"
require_relative "lrama/states_reporter"
require_relative "lrama/trace_reporter"
require_relative "lrama/version"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
module Bitmap module Bitmap
def self.from_array(ary) def self.from_array(ary)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Command class Command
LRAMA_LIB = File.realpath(File.join(File.dirname(__FILE__))) LRAMA_LIB = File.realpath(File.join(File.dirname(__FILE__)))
@ -14,7 +16,6 @@ module Lrama
Report::Duration.enable if options.trace_opts[:time] Report::Duration.enable if options.trace_opts[:time]
warning = Lrama::Warning.new
text = options.y.read text = options.y.read
options.y.close if options.y != STDIN options.y.close if options.y != STDIN
begin begin
@ -31,7 +32,7 @@ module Lrama
message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty? message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty?
abort message abort message
end end
states = Lrama::States.new(grammar, warning, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure])) states = Lrama::States.new(grammar, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
states.compute states.compute
context = Lrama::Context.new(states) context = Lrama::Context.new(states)
@ -42,15 +43,8 @@ module Lrama
end end
end end
if options.trace_opts && options.trace_opts[:rules] reporter = Lrama::TraceReporter.new(grammar)
puts "Grammar rules:" reporter.report(**options.trace_opts)
puts grammar.rules
end
if options.trace_opts && options.trace_opts[:actions]
puts "Grammar rules with actions:"
grammar.rules.each { |rule| puts rule.with_actions }
end
File.open(options.outfile, "w+") do |f| File.open(options.outfile, "w+") do |f|
Lrama::Output.new( Lrama::Output.new(
@ -65,9 +59,9 @@ module Lrama
).render ).render
end end
if warning.has_error? logger = Lrama::Logger.new
exit false exit false unless Lrama::GrammarValidator.new(grammar, states, logger).valid?
end Lrama::Diagnostics.new(grammar, states, logger).run(options.diagnostic)
end end
end end
end end

View File

@ -1,4 +1,6 @@
require "lrama/report/duration" # frozen_string_literal: true
require_relative "report/duration"
module Lrama module Lrama
# This is passed to a template # This is passed to a template
@ -253,7 +255,7 @@ module Lrama
# If no default_reduction_rule, default behavior is an # If no default_reduction_rule, default behavior is an
# error then replace ErrorActionNumber with zero. # error then replace ErrorActionNumber with zero.
if !state.default_reduction_rule unless state.default_reduction_rule
actions.map! do |e| actions.map! do |e|
if e == ErrorActionNumber if e == ErrorActionNumber
0 0
@ -301,10 +303,7 @@ module Lrama
end end
@states.nterms.each do |nterm| @states.nterms.each do |nterm|
if !(states = nterm_to_next_states[nterm]) if (states = nterm_to_next_states[nterm])
default_goto = 0
not_default_gotos = []
else
default_state = states.map(&:last).group_by {|s| s }.max_by {|_, v| v.count }.first default_state = states.map(&:last).group_by {|s| s }.max_by {|_, v| v.count }.first
default_goto = default_state.id default_goto = default_state.id
not_default_gotos = [] not_default_gotos = []
@ -312,6 +311,9 @@ module Lrama
next if to_state.id == default_goto next if to_state.id == default_goto
not_default_gotos << [from_state.id, to_state.id] not_default_gotos << [from_state.id, to_state.id]
end end
else
default_goto = 0
not_default_gotos = []
end end
k = nterm_number_to_sequence_number(nterm.number) k = nterm_number_to_sequence_number(nterm.number)

View File

@ -1,13 +1,15 @@
# frozen_string_literal: true
require "set" require "set"
require "lrama/counterexamples/derivation" require_relative "counterexamples/derivation"
require "lrama/counterexamples/example" require_relative "counterexamples/example"
require "lrama/counterexamples/path" require_relative "counterexamples/path"
require "lrama/counterexamples/production_path" require_relative "counterexamples/production_path"
require "lrama/counterexamples/start_path" require_relative "counterexamples/start_path"
require "lrama/counterexamples/state_item" require_relative "counterexamples/state_item"
require "lrama/counterexamples/transition_path" require_relative "counterexamples/transition_path"
require "lrama/counterexamples/triple" require_relative "counterexamples/triple"
module Lrama module Lrama
# See: https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf # See: https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf
@ -171,7 +173,13 @@ module Lrama
break break
end end
if !si.item.beginning_of_rule? if si.item.beginning_of_rule?
key = [si.state, si.item.lhs]
@reverse_productions[key].each do |item|
state_item = StateItem.new(si.state, item)
queue << (sis + [state_item])
end
else
key = [si, si.item.previous_sym] key = [si, si.item.previous_sym]
@reverse_transitions[key].each do |prev_target_state_item| @reverse_transitions[key].each do |prev_target_state_item|
next if prev_target_state_item.state != prev_state_item.state next if prev_target_state_item.state != prev_state_item.state
@ -183,12 +191,6 @@ module Lrama
queue.clear queue.clear
break break
end end
else
key = [si.state, si.item.lhs]
@reverse_productions[key].each do |item|
state_item = StateItem.new(si.state, item)
queue << (sis + [state_item])
end
end end
end end
else else

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Counterexamples class Counterexamples
class Derivation class Derivation

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Counterexamples class Counterexamples
class Example class Example

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Counterexamples class Counterexamples
class Path class Path

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Counterexamples class Counterexamples
class ProductionPath < Path class ProductionPath < Path

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Counterexamples class Counterexamples
class StartPath < Path class StartPath < Path

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Counterexamples class Counterexamples
class StateItem < Struct.new(:state, :item) class StateItem < Struct.new(:state, :item)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Counterexamples class Counterexamples
class TransitionPath < Path class TransitionPath < Path

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Counterexamples class Counterexamples
# s: state # s: state

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Lrama
class Diagnostics
def initialize(grammar, states, logger)
@grammar = grammar
@states = states
@logger = logger
end
def run(diagnostic)
if diagnostic
diagnose_conflict
diagnose_parameterizing_redefined
end
end
private
def diagnose_conflict
if @states.sr_conflicts_count != 0
@logger.warn("shift/reduce conflicts: #{@states.sr_conflicts_count} found")
end
if @states.rr_conflicts_count != 0
@logger.warn("reduce/reduce conflicts: #{@states.rr_conflicts_count} found")
end
end
def diagnose_parameterizing_redefined
@grammar.parameterizing_rule_resolver.redefined_rules.each do |rule|
@logger.warn("parameterizing rule redefined: #{rule}")
end
end
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
# Algorithm Digraph of https://dl.acm.org/doi/pdf/10.1145/69622.357187 (P. 625) # Algorithm Digraph of https://dl.acm.org/doi/pdf/10.1145/69622.357187 (P. 625)
class Digraph class Digraph

View File

@ -1,43 +1,40 @@
# frozen_string_literal: true
require "forwardable" require "forwardable"
require "lrama/grammar/auxiliary" require_relative "grammar/auxiliary"
require "lrama/grammar/binding" require_relative "grammar/binding"
require "lrama/grammar/code" require_relative "grammar/code"
require "lrama/grammar/counter" require_relative "grammar/counter"
require "lrama/grammar/destructor" require_relative "grammar/destructor"
require "lrama/grammar/error_token" require_relative "grammar/error_token"
require "lrama/grammar/parameterizing_rule" require_relative "grammar/parameterizing_rule"
require "lrama/grammar/percent_code" require_relative "grammar/percent_code"
require "lrama/grammar/precedence" require_relative "grammar/precedence"
require "lrama/grammar/printer" require_relative "grammar/printer"
require "lrama/grammar/reference" require_relative "grammar/reference"
require "lrama/grammar/rule" require_relative "grammar/rule"
require "lrama/grammar/rule_builder" require_relative "grammar/rule_builder"
require "lrama/grammar/symbol" require_relative "grammar/symbol"
require "lrama/grammar/symbols" require_relative "grammar/symbols"
require "lrama/grammar/type" require_relative "grammar/type"
require "lrama/grammar/union" require_relative "grammar/union"
require "lrama/lexer" require_relative "lexer"
module Lrama module Lrama
# Grammar is the result of parsing an input grammar file # Grammar is the result of parsing an input grammar file
class Grammar class Grammar
extend Forwardable extend Forwardable
attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux, :parameterizing_rule_resolver
attr_accessor :union, :expect, attr_accessor :union, :expect, :printers, :error_tokens, :lex_param, :parse_param, :initial_action,
:printers, :error_tokens,
:lex_param, :parse_param, :initial_action,
:after_shift, :before_reduce, :after_reduce, :after_shift_error_token, :after_pop_stack, :after_shift, :before_reduce, :after_reduce, :after_shift_error_token, :after_pop_stack,
:symbols_resolver, :types, :symbols_resolver, :types, :rules, :rule_builders, :sym_to_rules, :no_stdlib, :locations
:rules, :rule_builders,
:sym_to_rules, :no_stdlib
def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term, def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term,
:find_symbol_by_number!, :find_symbol_by_id!, :token_to_symbol, :find_symbol_by_number!, :find_symbol_by_id!, :token_to_symbol,
:find_symbol_by_s_value!, :fill_symbol_number, :fill_nterm_type, :find_symbol_by_s_value!, :fill_symbol_number, :fill_nterm_type,
:fill_printer, :fill_destructor, :fill_error_token, :sort_by_number! :fill_printer, :fill_destructor, :fill_error_token, :sort_by_number!
def initialize(rule_counter) def initialize(rule_counter)
@rule_counter = rule_counter @rule_counter = rule_counter
@ -59,10 +56,15 @@ module Lrama
@accept_symbol = nil @accept_symbol = nil
@aux = Auxiliary.new @aux = Auxiliary.new
@no_stdlib = false @no_stdlib = false
@locations = false
append_special_symbols append_special_symbols
end end
def create_rule_builder(rule_counter, midrule_action_counter)
RuleBuilder.new(rule_counter, midrule_action_counter, @parameterizing_rule_resolver)
end
def add_percent_code(id:, code:) def add_percent_code(id:, code:)
@percent_codes << PercentCode.new(id.s_value, code.s_value) @percent_codes << PercentCode.new(id.s_value, code.s_value)
end end
@ -141,6 +143,7 @@ module Lrama
end end
def prepare def prepare
resolve_inline_rules
normalize_rules normalize_rules
collect_symbols collect_symbols
set_lhs_and_rhs set_lhs_and_rhs
@ -149,6 +152,7 @@ module Lrama
fill_sym_to_rules fill_sym_to_rules
compute_nullable compute_nullable
compute_first_set compute_first_set
set_locations
end end
# TODO: More validation methods # TODO: More validation methods
@ -255,7 +259,7 @@ module Lrama
def setup_rules def setup_rules
@rule_builders.each do |builder| @rule_builders.each do |builder|
builder.setup_rules(@parameterizing_rule_resolver) builder.setup_rules
end end
end end
@ -289,10 +293,23 @@ module Lrama
@accept_symbol = term @accept_symbol = term
end end
def resolve_inline_rules
while @rule_builders.any? {|r| r.has_inline_rules? } do
@rule_builders = @rule_builders.flat_map do |builder|
if builder.has_inline_rules?
builder.resolve_inline_rules
else
builder
end
end
end
end
def normalize_rules def normalize_rules
# Add $accept rule to the top of rules # Add $accept rule to the top of rules
lineno = @rule_builders.first ? @rule_builders.first.line : 0 rule_builder = @rule_builders.first # : RuleBuilder
@rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [@rule_builders.first.lhs, @eof_symbol.id], token_code: nil, lineno: lineno) lineno = rule_builder ? rule_builder.line : 0
@rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [rule_builder.lhs, @eof_symbol.id], token_code: nil, lineno: lineno)
setup_rules setup_rules
@ -370,12 +387,16 @@ module Lrama
rules.each do |rule| rules.each do |rule|
next if rule.lhs.nterm? next if rule.lhs.nterm?
errors << "[BUG] LHS of #{rule} (line: #{rule.lineno}) is term. It should be nterm." errors << "[BUG] LHS of #{rule.display_name} (line: #{rule.lineno}) is term. It should be nterm."
end end
return if errors.empty? return if errors.empty?
raise errors.join("\n") raise errors.join("\n")
end end
def set_locations
@locations = @locations || @rules.any? {|rule| rule.contains_at_reference? }
end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
# Grammar file information not used by States but by Output # Grammar file information not used by States but by Output

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Binding class Binding
@ -16,9 +18,18 @@ module Lrama
resolved_args = symbol.args.map { |arg| resolve_symbol(arg) } resolved_args = symbol.args.map { |arg| resolve_symbol(arg) }
Lrama::Lexer::Token::InstantiateRule.new(s_value: symbol.s_value, location: symbol.location, args: resolved_args, lhs_tag: symbol.lhs_tag) Lrama::Lexer::Token::InstantiateRule.new(s_value: symbol.s_value, location: symbol.location, args: resolved_args, lhs_tag: symbol.lhs_tag)
else else
@parameter_to_arg[symbol.s_value] || symbol parameter_to_arg(symbol) || symbol
end end
end end
private
def parameter_to_arg(symbol)
if (arg = @parameter_to_arg[symbol.s_value].dup)
arg.alias_name = symbol.alias_name
end
arg
end
end end
end end
end end

View File

@ -1,9 +1,11 @@
# frozen_string_literal: true
require "forwardable" require "forwardable"
require "lrama/grammar/code/destructor_code" require_relative "code/destructor_code"
require "lrama/grammar/code/initial_action_code" require_relative "code/initial_action_code"
require "lrama/grammar/code/no_reference_code" require_relative "code/no_reference_code"
require "lrama/grammar/code/printer_code" require_relative "code/printer_code"
require "lrama/grammar/code/rule_action" require_relative "code/rule_action"
module Lrama module Lrama
class Grammar class Grammar

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Code class Code

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Code class Code

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Code class Code

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Code class Code

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Code class Code
@ -41,6 +43,7 @@ module Lrama
when ref.type == :dollar && ref.name == "$" # $$ when ref.type == :dollar && ref.name == "$" # $$
tag = ref.ex_tag || lhs.tag tag = ref.ex_tag || lhs.tag
raise_tag_not_found_error(ref) unless tag raise_tag_not_found_error(ref) unless tag
# @type var tag: Lexer::Token::Tag
"(yyval.#{tag.member})" "(yyval.#{tag.member})"
when ref.type == :at && ref.name == "$" # @$ when ref.type == :at && ref.name == "$" # @$
"(yyloc)" "(yyloc)"
@ -50,6 +53,7 @@ module Lrama
i = -position_in_rhs + ref.index i = -position_in_rhs + ref.index
tag = ref.ex_tag || rhs[ref.index - 1].tag tag = ref.ex_tag || rhs[ref.index - 1].tag
raise_tag_not_found_error(ref) unless tag raise_tag_not_found_error(ref) unless tag
# @type var tag: Lexer::Token::Tag
"(yyvsp[#{i}].#{tag.member})" "(yyvsp[#{i}].#{tag.member})"
when ref.type == :at # @n when ref.type == :at # @n
i = -position_in_rhs + ref.index i = -position_in_rhs + ref.index
@ -69,18 +73,18 @@ module Lrama
@rule.position_in_original_rule_rhs || @rule.rhs.count @rule.position_in_original_rule_rhs || @rule.rhs.count
end end
# If this is midrule action, RHS is a RHS of the original rule. # If this is midrule action, RHS is an RHS of the original rule.
def rhs def rhs
(@rule.original_rule || @rule).rhs (@rule.original_rule || @rule).rhs
end end
# Unlike `rhs`, LHS is always a LHS of the rule. # Unlike `rhs`, LHS is always an LHS of the rule.
def lhs def lhs
@rule.lhs @rule.lhs
end end
def raise_tag_not_found_error(ref) def raise_tag_not_found_error(ref)
raise "Tag is not specified for '$#{ref.value}' in '#{@rule}'" raise "Tag is not specified for '$#{ref.value}' in '#{@rule.display_name}'"
end end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Counter class Counter

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Destructor < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true) class Destructor < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class ErrorToken < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true) class ErrorToken < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require_relative 'parameterizing_rule/resolver' require_relative 'parameterizing_rule/resolver'
require_relative 'parameterizing_rule/rhs' require_relative 'parameterizing_rule/rhs'
require_relative 'parameterizing_rule/rule' require_relative 'parameterizing_rule/rule'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class ParameterizingRule class ParameterizingRule
@ -18,13 +20,17 @@ module Lrama
end end
def find_inline(token) def find_inline(token)
@rules.select { |rule| rule.name == token.s_value && rule.is_inline }.last @rules.reverse.find { |rule| rule.name == token.s_value && rule.is_inline }
end end
def created_lhs(lhs_s_value) def created_lhs(lhs_s_value)
@created_lhs_list.reverse.find { |created_lhs| created_lhs.s_value == lhs_s_value } @created_lhs_list.reverse.find { |created_lhs| created_lhs.s_value == lhs_s_value }
end end
def redefined_rules
@rules.select { |rule| @rules.count { |r| r.name == rule.name && r.required_parameters_count == rule.required_parameters_count } > 1 }
end
private private
def select_rules(rules, token) def select_rules(rules, token)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class ParameterizingRule class ParameterizingRule
@ -13,6 +15,7 @@ module Lrama
def resolve_user_code(bindings) def resolve_user_code(bindings)
return unless user_code return unless user_code
resolved = Lexer::Token::UserCode.new(s_value: user_code.s_value, location: user_code.location)
var_to_arg = {} var_to_arg = {}
symbols.each do |sym| symbols.each do |sym|
resolved_sym = bindings.resolve_symbol(sym) resolved_sym = bindings.resolve_symbol(sym)
@ -22,14 +25,14 @@ module Lrama
end end
var_to_arg.each do |var, arg| var_to_arg.each do |var, arg|
user_code.references.each do |ref| resolved.references.each do |ref|
if ref.name == var if ref.name == var
ref.name = arg ref.name = arg
end end
end end
end end
return user_code return resolved
end end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class ParameterizingRule class ParameterizingRule
@ -12,6 +14,10 @@ module Lrama
@is_inline = is_inline @is_inline = is_inline
@required_parameters_count = parameters.count @required_parameters_count = parameters.count
end end
def to_s
"#{@name}(#{@parameters.map(&:s_value).join(', ')})"
end
end end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class PercentCode class PercentCode

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Precedence < Struct.new(:type, :precedence, keyword_init: true) class Precedence < Struct.new(:type, :precedence, keyword_init: true)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Printer < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true) class Printer < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
# type: :dollar or :at # type: :dollar or :at

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
# _rhs holds original RHS element. Use rhs to refer to Symbol. # _rhs holds original RHS element. Use rhs to refer to Symbol.
@ -16,8 +18,7 @@ module Lrama
self.lineno == other.lineno self.lineno == other.lineno
end end
# TODO: Change this to display_name def display_name
def to_s
l = lhs.id.s_value l = lhs.id.s_value
r = empty_rule? ? "ε" : rhs.map {|r| r.id.s_value }.join(" ") r = empty_rule? ? "ε" : rhs.map {|r| r.id.s_value }.join(" ")
@ -33,7 +34,7 @@ module Lrama
end end
def with_actions def with_actions
"#{to_s} {#{token_code&.s_value}}" "#{display_name} {#{token_code&.s_value}}"
end end
# opt_nl: ε <-- empty_rule # opt_nl: ε <-- empty_rule
@ -55,6 +56,12 @@ module Lrama
Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self).translated_code Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self).translated_code
end end
def contains_at_reference?
return false unless token_code
token_code.references.any? {|r| r.type == :at }
end
end end
end end
end end

View File

@ -1,12 +1,15 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class RuleBuilder class RuleBuilder
attr_accessor :lhs, :line attr_accessor :lhs, :line
attr_reader :lhs_tag, :rhs, :user_code, :precedence_sym attr_reader :lhs_tag, :rhs, :user_code, :precedence_sym
def initialize(rule_counter, midrule_action_counter, position_in_original_rule_rhs = nil, lhs_tag: nil, skip_preprocess_references: false) def initialize(rule_counter, midrule_action_counter, parameterizing_rule_resolver, position_in_original_rule_rhs = nil, lhs_tag: nil, skip_preprocess_references: false)
@rule_counter = rule_counter @rule_counter = rule_counter
@midrule_action_counter = midrule_action_counter @midrule_action_counter = midrule_action_counter
@parameterizing_rule_resolver = parameterizing_rule_resolver
@position_in_original_rule_rhs = position_in_original_rule_rhs @position_in_original_rule_rhs = position_in_original_rule_rhs
@skip_preprocess_references = skip_preprocess_references @skip_preprocess_references = skip_preprocess_references
@ -19,16 +22,12 @@ module Lrama
@rules = [] @rules = []
@rule_builders_for_parameterizing_rules = [] @rule_builders_for_parameterizing_rules = []
@rule_builders_for_derived_rules = [] @rule_builders_for_derived_rules = []
@rule_builders_for_inline_rules = []
@parameterizing_rules = [] @parameterizing_rules = []
@inline_rules = []
@midrule_action_rules = [] @midrule_action_rules = []
end end
def add_rhs(rhs) def add_rhs(rhs)
if !@line @line ||= rhs.line
@line = rhs.line
end
flush_user_code flush_user_code
@ -36,9 +35,7 @@ module Lrama
end end
def user_code=(user_code) def user_code=(user_code)
if !@line @line ||= user_code&.line
@line = user_code&.line
end
flush_user_code flush_user_code
@ -55,18 +52,41 @@ module Lrama
freeze_rhs freeze_rhs
end end
def setup_rules(parameterizing_rule_resolver) def setup_rules
preprocess_references unless @skip_preprocess_references preprocess_references unless @skip_preprocess_references
if rhs.any? { |token| parameterizing_rule_resolver.find_inline(token) } process_rhs
resolve_inline(parameterizing_rule_resolver)
else
process_rhs(parameterizing_rule_resolver)
end
build_rules build_rules
end end
def rules def rules
@parameterizing_rules + @inline_rules + @midrule_action_rules + @rules @parameterizing_rules + @midrule_action_rules + @rules
end
def has_inline_rules?
rhs.any? { |token| @parameterizing_rule_resolver.find_inline(token) }
end
def resolve_inline_rules
resolved_builders = []
rhs.each_with_index do |token, i|
if (inline_rule = @parameterizing_rule_resolver.find_inline(token))
inline_rule.rhs_list.each do |inline_rhs|
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: lhs_tag)
if token.is_a?(Lexer::Token::InstantiateRule)
resolve_inline_rhs(rule_builder, inline_rhs, i, Binding.new(inline_rule, token.args))
else
resolve_inline_rhs(rule_builder, inline_rhs, i)
end
rule_builder.lhs = lhs
rule_builder.line = line
rule_builder.precedence_sym = precedence_sym
rule_builder.user_code = replace_inline_user_code(inline_rhs, i)
resolved_builders << rule_builder
end
break
end
end
resolved_builders
end end
private private
@ -82,31 +102,25 @@ module Lrama
def build_rules def build_rules
tokens = @replaced_rhs tokens = @replaced_rhs
if tokens rule = Rule.new(
rule = Rule.new( id: @rule_counter.increment, _lhs: lhs, _rhs: tokens, lhs_tag: lhs_tag, token_code: user_code,
id: @rule_counter.increment, _lhs: lhs, _rhs: tokens, lhs_tag: lhs_tag, token_code: user_code, position_in_original_rule_rhs: @position_in_original_rule_rhs, precedence_sym: precedence_sym, lineno: line
position_in_original_rule_rhs: @position_in_original_rule_rhs, precedence_sym: precedence_sym, lineno: line )
) @rules = [rule]
@rules = [rule] @parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder|
@parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder| rule_builder.rules
rule_builder.rules end.flatten
end.flatten @midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder|
@midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder| rule_builder.rules
rule_builder.rules end.flatten
end.flatten @midrule_action_rules.each do |r|
@midrule_action_rules.each do |r| r.original_rule = rule
r.original_rule = rule
end
else
@inline_rules = @rule_builders_for_inline_rules.map do |rule_builder|
rule_builder.rules
end.flatten
end end
end end
# rhs is a mixture of variety type of tokens like `Ident`, `InstantiateRule`, `UserCode` and so on. # rhs is a mixture of variety type of tokens like `Ident`, `InstantiateRule`, `UserCode` and so on.
# `#process_rhs` replaces some kind of tokens to `Ident` so that all `@replaced_rhs` are `Ident` or `Char`. # `#process_rhs` replaces some kind of tokens to `Ident` so that all `@replaced_rhs` are `Ident` or `Char`.
def process_rhs(parameterizing_rule_resolver) def process_rhs
return if @replaced_rhs return if @replaced_rhs
@replaced_rhs = [] @replaced_rhs = []
@ -118,26 +132,26 @@ module Lrama
when Lrama::Lexer::Token::Ident when Lrama::Lexer::Token::Ident
@replaced_rhs << token @replaced_rhs << token
when Lrama::Lexer::Token::InstantiateRule when Lrama::Lexer::Token::InstantiateRule
parameterizing_rule = parameterizing_rule_resolver.find_rule(token) parameterizing_rule = @parameterizing_rule_resolver.find_rule(token)
raise "Unexpected token. #{token}" unless parameterizing_rule raise "Unexpected token. #{token}" unless parameterizing_rule
bindings = Binding.new(parameterizing_rule, token.args) bindings = Binding.new(parameterizing_rule, token.args)
lhs_s_value = lhs_s_value(token, bindings) lhs_s_value = lhs_s_value(token, bindings)
if (created_lhs = parameterizing_rule_resolver.created_lhs(lhs_s_value)) if (created_lhs = @parameterizing_rule_resolver.created_lhs(lhs_s_value))
@replaced_rhs << created_lhs @replaced_rhs << created_lhs
else else
lhs_token = Lrama::Lexer::Token::Ident.new(s_value: lhs_s_value, location: token.location) lhs_token = Lrama::Lexer::Token::Ident.new(s_value: lhs_s_value, location: token.location)
@replaced_rhs << lhs_token @replaced_rhs << lhs_token
parameterizing_rule_resolver.created_lhs_list << lhs_token @parameterizing_rule_resolver.created_lhs_list << lhs_token
parameterizing_rule.rhs_list.each do |r| parameterizing_rule.rhs_list.each do |r|
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, lhs_tag: token.lhs_tag || parameterizing_rule.tag) rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: token.lhs_tag || parameterizing_rule.tag)
rule_builder.lhs = lhs_token rule_builder.lhs = lhs_token
r.symbols.each { |sym| rule_builder.add_rhs(bindings.resolve_symbol(sym)) } r.symbols.each { |sym| rule_builder.add_rhs(bindings.resolve_symbol(sym)) }
rule_builder.line = line rule_builder.line = line
rule_builder.precedence_sym = r.precedence_sym rule_builder.precedence_sym = r.precedence_sym
rule_builder.user_code = r.resolve_user_code(bindings) rule_builder.user_code = r.resolve_user_code(bindings)
rule_builder.complete_input rule_builder.complete_input
rule_builder.setup_rules(parameterizing_rule_resolver) rule_builder.setup_rules
@rule_builders_for_parameterizing_rules << rule_builder @rule_builders_for_parameterizing_rules << rule_builder
end end
end end
@ -147,11 +161,11 @@ module Lrama
new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s) new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s)
@replaced_rhs << new_token @replaced_rhs << new_token
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, i, lhs_tag: tag, skip_preprocess_references: true) rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, i, lhs_tag: tag, skip_preprocess_references: true)
rule_builder.lhs = new_token rule_builder.lhs = new_token
rule_builder.user_code = token rule_builder.user_code = token
rule_builder.complete_input rule_builder.complete_input
rule_builder.setup_rules(parameterizing_rule_resolver) rule_builder.setup_rules
@rule_builders_for_derived_rules << rule_builder @rule_builders_for_derived_rules << rule_builder
else else
@ -172,27 +186,10 @@ module Lrama
"#{token.rule_name}_#{s_values.join('_')}" "#{token.rule_name}_#{s_values.join('_')}"
end end
def resolve_inline(parameterizing_rule_resolver) def resolve_inline_rhs(rule_builder, inline_rhs, index, bindings = nil)
rhs.each_with_index do |token, i|
if inline_rule = parameterizing_rule_resolver.find_inline(token)
inline_rule.rhs_list.each_with_index do |inline_rhs|
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, lhs_tag: lhs_tag, skip_preprocess_references: true)
resolve_inline_rhs(rule_builder, inline_rhs, i)
rule_builder.lhs = lhs
rule_builder.line = line
rule_builder.user_code = replace_inline_user_code(inline_rhs, i)
rule_builder.complete_input
rule_builder.setup_rules(parameterizing_rule_resolver)
@rule_builders_for_inline_rules << rule_builder
end
end
end
end
def resolve_inline_rhs(rule_builder, inline_rhs, index)
rhs.each_with_index do |token, i| rhs.each_with_index do |token, i|
if index == i if index == i
inline_rhs.symbols.each { |sym| rule_builder.add_rhs(sym) } inline_rhs.symbols.each { |sym| rule_builder.add_rhs(bindings.nil? ? sym : bindings.resolve_symbol(sym)) }
else else
rule_builder.add_rhs(token) rule_builder.add_rhs(token)
end end
@ -204,6 +201,11 @@ module Lrama
return user_code if user_code.nil? return user_code if user_code.nil?
code = user_code.s_value.gsub(/\$#{index + 1}/, inline_rhs.user_code.s_value) code = user_code.s_value.gsub(/\$#{index + 1}/, inline_rhs.user_code.s_value)
user_code.references.each do |ref|
next if ref.index.nil? || ref.index <= index # nil is a case for `$$`
code = code.gsub(/\$#{ref.index}/, "$#{ref.index + (inline_rhs.symbols.count-1)}")
code = code.gsub(/@#{ref.index}/, "@#{ref.index + (inline_rhs.symbols.count-1)}")
end
Lrama::Lexer::Token::UserCode.new(s_value: code, location: user_code.location) Lrama::Lexer::Token::UserCode.new(s_value: code, location: user_code.location)
end end
@ -238,9 +240,6 @@ module Lrama
end end
if ref.number if ref.number
# TODO: When Inlining is implemented, for example, if `$1` is expanded to multiple RHS tokens,
# `$2` needs to access `$2 + n` to actually access it. So, after the Inlining implementation,
# it needs resolves from number to index.
ref.index = ref.number ref.index = ref.number
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# Symbol is both of nterm and term # Symbol is both of nterm and term
# `number` is both for nterm and term # `number` is both for nterm and term
# `token_id` is tokentype for term, internal sequence number for nterm # `token_id` is tokentype for term, internal sequence number for nterm

View File

@ -1 +1,3 @@
# frozen_string_literal: true
require_relative "symbols/resolver" require_relative "symbols/resolver"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Symbols class Symbols
@ -42,7 +44,9 @@ module Lrama
end end
def add_nterm(id:, alias_name: nil, tag: nil) def add_nterm(id:, alias_name: nil, tag: nil)
return if find_symbol_by_id(id) if (sym = find_symbol_by_id(id))
return sym
end
@symbols = nil @symbols = nil
nterm = Symbol.new( nterm = Symbol.new(

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Type class Type

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Grammar class Grammar
class Union < Struct.new(:code, :lineno, keyword_init: true) class Union < Struct.new(:code, :lineno, keyword_init: true)

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
module Lrama
class GrammarValidator
def initialize(grammar, states, logger)
@grammar = grammar
@states = states
@logger = logger
end
def valid?
conflicts_within_threshold?
end
private
def conflicts_within_threshold?
return true unless @grammar.expect
[sr_conflicts_within_threshold(@grammar.expect), rr_conflicts_within_threshold(0)].all?
end
def sr_conflicts_within_threshold(expected)
return true if expected == @states.sr_conflicts_count
@logger.error("shift/reduce conflicts: #{@states.sr_conflicts_count} found, #{expected} expected")
false
end
def rr_conflicts_within_threshold(expected)
return true if expected == @states.rr_conflicts_count
@logger.error("reduce/reduce conflicts: #{@states.rr_conflicts_count} found, #{expected} expected")
false
end
end
end

View File

@ -1,15 +1,17 @@
# frozen_string_literal: true
require "strscan" require "strscan"
require "lrama/lexer/grammar_file" require_relative "lexer/grammar_file"
require "lrama/lexer/location" require_relative "lexer/location"
require "lrama/lexer/token" require_relative "lexer/token"
module Lrama module Lrama
class Lexer class Lexer
attr_reader :head_line, :head_column, :line attr_reader :head_line, :head_column, :line
attr_accessor :status, :end_symbol attr_accessor :status, :end_symbol
SYMBOLS = ['%{', '%}', '%%', '{', '}', '\[', '\]', '\(', '\)', '\,', ':', '\|', ';'] SYMBOLS = ['%{', '%}', '%%', '{', '}', '\[', '\]', '\(', '\)', '\,', ':', '\|', ';'].freeze
PERCENT_TOKENS = %w( PERCENT_TOKENS = %w(
%union %union
%token %token
@ -38,7 +40,8 @@ module Lrama
%rule %rule
%no-stdlib %no-stdlib
%inline %inline
) %locations
).freeze
def initialize(grammar_file) def initialize(grammar_file)
@grammar_file = grammar_file @grammar_file = grammar_file
@ -71,7 +74,7 @@ module Lrama
end end
def lex_token def lex_token
while !@scanner.eos? do until @scanner.eos? do
case case
when @scanner.scan(/\n/) when @scanner.scan(/\n/)
newline newline
@ -126,7 +129,7 @@ module Lrama
code = '' code = ''
reset_first_position reset_first_position
while !@scanner.eos? do until @scanner.eos? do
case case
when @scanner.scan(/{/) when @scanner.scan(/{/)
code += @scanner.matched code += @scanner.matched
@ -163,7 +166,7 @@ module Lrama
private private
def lex_comment def lex_comment
while !@scanner.eos? do until @scanner.eos? do
case case
when @scanner.scan(/\n/) when @scanner.scan(/\n/)
newline newline

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Lexer class Lexer
class GrammarFile class GrammarFile

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Lexer class Lexer
class Location class Location

View File

@ -1,8 +1,10 @@
require 'lrama/lexer/token/char' # frozen_string_literal: true
require 'lrama/lexer/token/ident'
require 'lrama/lexer/token/instantiate_rule' require_relative 'token/char'
require 'lrama/lexer/token/tag' require_relative 'token/ident'
require 'lrama/lexer/token/user_code' require_relative 'token/instantiate_rule'
require_relative 'token/tag'
require_relative 'token/user_code'
module Lrama module Lrama
class Lexer class Lexer

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Lexer class Lexer
class Token class Token

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Lexer class Lexer
class Token class Token

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Lexer class Lexer
class Token class Token

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Lexer class Lexer
class Token class Token

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require "strscan" require "strscan"
module Lrama module Lrama
@ -16,7 +18,7 @@ module Lrama
scanner = StringScanner.new(s_value) scanner = StringScanner.new(s_value)
references = [] references = []
while !scanner.eos? do until scanner.eos? do
case case
when reference = scan_reference(scanner) when reference = scan_reference(scanner)
references << reference references << reference

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Lrama
class Logger
def initialize(out = STDERR)
@out = out
end
def warn(message)
@out << message << "\n"
end
def error(message)
@out << message << "\n"
end
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'optparse' require 'optparse'
module Lrama module Lrama
@ -16,7 +18,7 @@ module Lrama
@options.report_opts = validate_report(@report) @options.report_opts = validate_report(@report)
@options.grammar_file = argv.shift @options.grammar_file = argv.shift
if !@options.grammar_file unless @options.grammar_file
abort "File should be specified\n" abort "File should be specified\n"
end end
@ -63,20 +65,35 @@ module Lrama
o.separator 'Output:' o.separator 'Output:'
o.on('-H', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v } o.on('-H', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v }
o.on('-d', 'also produce a header file') { @options.header = true } o.on('-d', 'also produce a header file') { @options.header = true }
o.on('-r', '--report=THINGS', Array, 'also produce details on the automaton') {|v| @report = v } o.on('-r', '--report=REPORTS', Array, 'also produce details on the automaton') {|v| @report = v }
o.on_tail '' o.on_tail ''
o.on_tail 'Valid Reports:' o.on_tail 'REPORTS is a list of comma-separated words that can include:'
o.on_tail " #{VALID_REPORTS.join(' ')}" o.on_tail ' states describe the states'
o.on_tail ' itemsets complete the core item sets with their closure'
o.on_tail ' lookaheads explicitly associate lookahead tokens to items'
o.on_tail ' solved describe shift/reduce conflicts solving'
o.on_tail ' counterexamples, cex generate conflict counterexamples'
o.on_tail ' rules list unused rules'
o.on_tail ' terms list unused terminals'
o.on_tail ' verbose report detailed internal state and analysis results'
o.on_tail ' all include all the above reports'
o.on_tail ' none disable all reports'
o.on('--report-file=FILE', 'also produce details on the automaton output to a file named FILE') {|v| @options.report_file = v } o.on('--report-file=FILE', 'also produce details on the automaton output to a file named FILE') {|v| @options.report_file = v }
o.on('-o', '--output=FILE', 'leave output to FILE') {|v| @options.outfile = v } o.on('-o', '--output=FILE', 'leave output to FILE') {|v| @options.outfile = v }
o.on('--trace=TRACES', Array, 'also output trace logs at runtime') {|v| @trace = v }
o.on('--trace=THINGS', Array, 'also output trace logs at runtime') {|v| @trace = v }
o.on_tail '' o.on_tail ''
o.on_tail 'Valid Traces:' o.on_tail 'TRACES is a list of comma-separated words that can include:'
o.on_tail " #{VALID_TRACES.join(' ')}" o.on_tail ' automaton display states'
o.on_tail ' closure display states'
o.on('-v', 'reserved, do nothing') { } o.on_tail ' rules display grammar rules'
o.on_tail ' actions display grammar rules with actions'
o.on_tail ' time display generation time'
o.on_tail ' all include all the above traces'
o.on_tail ' none disable all traces'
o.on('-v', '--verbose', "same as '--report=state'") {|_v| @report << 'states' }
o.separator ''
o.separator 'Diagnostics:'
o.on('-W', '--warnings', 'report the warnings') {|v| @options.diagnostic = true }
o.separator '' o.separator ''
o.separator 'Error Recovery:' o.separator 'Error Recovery:'
o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true } o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true }
@ -89,47 +106,55 @@ module Lrama
end end
end end
BISON_REPORTS = %w[states itemsets lookaheads solved counterexamples cex all none] ALIASED_REPORTS = { cex: :counterexamples }.freeze
OTHER_REPORTS = %w[verbose] VALID_REPORTS = %i[states itemsets lookaheads solved counterexamples rules terms verbose].freeze
NOT_SUPPORTED_REPORTS = %w[cex none]
VALID_REPORTS = BISON_REPORTS + OTHER_REPORTS - NOT_SUPPORTED_REPORTS
def validate_report(report) def validate_report(report)
list = VALID_REPORTS
h = { grammar: true } h = { grammar: true }
return h if report.empty?
return {} if report == ['none']
if report == ['all']
VALID_REPORTS.each { |r| h[r] = true }
return h
end
report.each do |r| report.each do |r|
if list.include?(r) aliased = aliased_report_option(r)
h[r.to_sym] = true if VALID_REPORTS.include?(aliased)
h[aliased] = true
else else
raise "Invalid report option \"#{r}\"." raise "Invalid report option \"#{r}\"."
end end
end end
if h[:all]
(BISON_REPORTS - NOT_SUPPORTED_REPORTS).each do |r|
h[r.to_sym] = true
end
h.delete(:all)
end
return h return h
end end
def aliased_report_option(opt)
(ALIASED_REPORTS[opt.to_sym] || opt).to_sym
end
VALID_TRACES = %w[ VALID_TRACES = %w[
none locations scan parse automaton bitsets locations scan parse automaton bitsets closure
closure grammar rules actions resource grammar rules actions resource sets muscles
sets muscles tools m4-early m4 skeleton time tools m4-early m4 skeleton time ielr cex
ielr cex all ].freeze
] NOT_SUPPORTED_TRACES = %w[
locations scan parse bitsets grammar resource
sets muscles tools m4-early m4 skeleton ielr cex
].freeze
def validate_trace(trace) def validate_trace(trace)
list = VALID_TRACES
h = {} h = {}
return h if trace.empty? || trace == ['none']
supported = VALID_TRACES - NOT_SUPPORTED_TRACES
if trace == ['all']
supported.each { |t| h[t.to_sym] = true }
return h
end
trace.each do |t| trace.each do |t|
if list.include?(t) if supported.include?(t)
h[t.to_sym] = true h[t.to_sym] = true
else else
raise "Invalid trace option \"#{t}\"." raise "Invalid trace option \"#{t}\"."

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true
module Lrama module Lrama
# Command line options. # Command line options.
class Options class Options
attr_accessor :skeleton, :header, :header_file, attr_accessor :skeleton, :header, :header_file,
:report_file, :outfile, :report_file, :outfile,
:error_recovery, :grammar_file, :error_recovery, :grammar_file,
:trace_opts, :report_opts, :y, :trace_opts, :report_opts,
:debug :diagnostic, :y, :debug
def initialize def initialize
@skeleton = "bison/yacc.c" @skeleton = "bison/yacc.c"
@ -17,6 +19,7 @@ module Lrama
@grammar_file = nil @grammar_file = nil
@trace_opts = nil @trace_opts = nil
@report_opts = nil @report_opts = nil
@diagnostic = false
@y = STDIN @y = STDIN
@debug = false @debug = false
end end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
require "erb" require "erb"
require "forwardable" require "forwardable"
require "lrama/report/duration" require_relative "report/duration"
module Lrama module Lrama
class Output class Output
@ -63,37 +65,29 @@ module Lrama
# A part of b4_token_enums # A part of b4_token_enums
def token_enums def token_enums
str = "" @context.yytokentype.map do |s_value, token_id, display_name|
@context.yytokentype.each do |s_value, token_id, display_name|
s = sprintf("%s = %d%s", s_value, token_id, token_id == yymaxutok ? "" : ",") s = sprintf("%s = %d%s", s_value, token_id, token_id == yymaxutok ? "" : ",")
if display_name if display_name
str << sprintf(" %-30s /* %s */\n", s, display_name) sprintf(" %-30s /* %s */\n", s, display_name)
else else
str << sprintf(" %s\n", s) sprintf(" %s\n", s)
end end
end end.join
str
end end
# b4_symbol_enum # b4_symbol_enum
def symbol_enum def symbol_enum
str = ""
last_sym_number = @context.yysymbol_kind_t.last[1] last_sym_number = @context.yysymbol_kind_t.last[1]
@context.yysymbol_kind_t.each do |s_value, sym_number, display_name| @context.yysymbol_kind_t.map do |s_value, sym_number, display_name|
s = sprintf("%s = %d%s", s_value, sym_number, (sym_number == last_sym_number) ? "" : ",") s = sprintf("%s = %d%s", s_value, sym_number, (sym_number == last_sym_number) ? "" : ",")
if display_name if display_name
str << sprintf(" %-40s /* %s */\n", s, display_name) sprintf(" %-40s /* %s */\n", s, display_name)
else else
str << sprintf(" %s\n", s) sprintf(" %s\n", s)
end end
end end.join
str
end end
def yytranslate def yytranslate
@ -132,12 +126,10 @@ module Lrama
end end
def symbol_actions_for_printer def symbol_actions_for_printer
str = "" @grammar.symbols.map do |sym|
@grammar.symbols.each do |sym|
next unless sym.printer next unless sym.printer
str << <<-STR <<-STR
case #{sym.enum_name}: /* #{sym.comment} */ case #{sym.enum_name}: /* #{sym.comment} */
#line #{sym.printer.lineno} "#{@grammar_file_path}" #line #{sym.printer.lineno} "#{@grammar_file_path}"
{#{sym.printer.translated_code(sym.tag)}} {#{sym.printer.translated_code(sym.tag)}}
@ -145,18 +137,14 @@ module Lrama
break; break;
STR STR
end end.join
str
end end
def symbol_actions_for_destructor def symbol_actions_for_destructor
str = "" @grammar.symbols.map do |sym|
@grammar.symbols.each do |sym|
next unless sym.destructor next unless sym.destructor
str << <<-STR <<-STR
case #{sym.enum_name}: /* #{sym.comment} */ case #{sym.enum_name}: /* #{sym.comment} */
#line #{sym.destructor.lineno} "#{@grammar_file_path}" #line #{sym.destructor.lineno} "#{@grammar_file_path}"
{#{sym.destructor.translated_code(sym.tag)}} {#{sym.destructor.translated_code(sym.tag)}}
@ -164,9 +152,7 @@ module Lrama
break; break;
STR STR
end end.join
str
end end
# b4_user_initial_action # b4_user_initial_action
@ -236,12 +222,10 @@ module Lrama
end end
def symbol_actions_for_error_token def symbol_actions_for_error_token
str = "" @grammar.symbols.map do |sym|
@grammar.symbols.each do |sym|
next unless sym.error_token next unless sym.error_token
str << <<-STR <<-STR
case #{sym.enum_name}: /* #{sym.comment} */ case #{sym.enum_name}: /* #{sym.comment} */
#line #{sym.error_token.lineno} "#{@grammar_file_path}" #line #{sym.error_token.lineno} "#{@grammar_file_path}"
{#{sym.error_token.translated_code(sym.tag)}} {#{sym.error_token.translated_code(sym.tag)}}
@ -249,22 +233,18 @@ module Lrama
break; break;
STR STR
end end.join
str
end end
# b4_user_actions # b4_user_actions
def user_actions def user_actions
str = "" action = @context.states.rules.map do |rule|
@context.states.rules.each do |rule|
next unless rule.token_code next unless rule.token_code
code = rule.token_code code = rule.token_code
spaces = " " * (code.column - 1) spaces = " " * (code.column - 1)
str << <<-STR <<-STR
case #{rule.id + 1}: /* #{rule.as_comment} */ case #{rule.id + 1}: /* #{rule.as_comment} */
#line #{code.line} "#{@grammar_file_path}" #line #{code.line} "#{@grammar_file_path}"
#{spaces}{#{rule.translated_code}} #{spaces}{#{rule.translated_code}}
@ -272,14 +252,12 @@ module Lrama
break; break;
STR STR
end end.join
str << <<-STR action + <<-STR
#line [@oline@] [@ofile@] #line [@oline@] [@ofile@]
STR STR
str
end end
def omit_blanks(param) def omit_blanks(param)
@ -343,7 +321,7 @@ module Lrama
# b4_parse_param_use # b4_parse_param_use
def parse_param_use(val, loc) def parse_param_use(val, loc)
str = <<-STR str = <<-STR.dup
YY_USE (#{val}); YY_USE (#{val});
YY_USE (#{loc}); YY_USE (#{loc});
STR STR
@ -357,7 +335,8 @@ module Lrama
# b4_yylex_formals # b4_yylex_formals
def yylex_formals def yylex_formals
ary = ["&yylval", "&yylloc"] ary = ["&yylval"]
ary << "&yylloc" if @grammar.locations
if @grammar.lex_param if @grammar.lex_param
ary << lex_param_name ary << lex_param_name
@ -397,17 +376,9 @@ module Lrama
def int_array_to_string(ary) def int_array_to_string(ary)
last = ary.count - 1 last = ary.count - 1
s = ary.each_with_index.each_slice(10).map do |slice| ary.each_with_index.each_slice(10).map do |slice|
str = " " " " + slice.map { |e, i| sprintf("%6d%s", e, (i == last) ? "" : ",") }.join
end.join("\n")
slice.each do |e, i|
str << sprintf("%6d%s", e, (i == last) ? "" : ",")
end
str
end
s.join("\n")
end end
def spec_mapped_header_file def spec_mapped_header_file
@ -457,26 +428,24 @@ module Lrama
end end
def template_dir def template_dir
File.expand_path("../../../template", __FILE__) File.expand_path('../../template', __dir__)
end end
def string_array_to_string(ary) def string_array_to_string(ary)
str = "" result = ""
tmp = " " tmp = " "
ary.each do |s| ary.each do |s|
s = s.gsub('\\', '\\\\\\\\') replaced = s.gsub('\\', '\\\\\\\\').gsub('"', '\\"')
s = s.gsub('"', '\\"') if (tmp + replaced + " \"\",").length > 75
result = "#{result}#{tmp}\n"
if (tmp + s + " \"\",").length > 75 tmp = " \"#{replaced}\","
str << tmp << "\n"
tmp = " \"#{s}\","
else else
tmp << " \"#{s}\"," tmp = "#{tmp} \"#{replaced}\","
end end
end end
str << tmp result + tmp
end end
def replace_special_variables(str, ofile) def replace_special_variables(str, ofile)

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,4 @@
require 'lrama/report/duration' # frozen_string_literal: true
require 'lrama/report/profile'
require_relative 'report/duration'
require_relative 'report/profile'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Report class Report
module Duration module Duration

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class Report class Report
module Profile module Profile

View File

@ -1,8 +1,10 @@
require "lrama/state/reduce" # frozen_string_literal: true
require "lrama/state/reduce_reduce_conflict"
require "lrama/state/resolved_conflict" require_relative "state/reduce"
require "lrama/state/shift" require_relative "state/reduce_reduce_conflict"
require "lrama/state/shift_reduce_conflict" require_relative "state/resolved_conflict"
require_relative "state/shift"
require_relative "state/shift_reduce_conflict"
module Lrama module Lrama
class State class State

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class State class State
class Reduce class Reduce
@ -25,6 +27,7 @@ module Lrama
def selected_look_ahead def selected_look_ahead
if @look_ahead if @look_ahead
# @type ivar @look_ahead: Array<Grammar::Symbol>
@look_ahead - @not_selected_symbols @look_ahead - @not_selected_symbols
else else
[] []

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class State class State
class ReduceReduceConflict < Struct.new(:symbols, :reduce1, :reduce2, keyword_init: true) class ReduceReduceConflict < Struct.new(:symbols, :reduce1, :reduce2, keyword_init: true)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class State class State
# * symbol: A symbol under discussion # * symbol: A symbol under discussion
@ -6,7 +8,7 @@ module Lrama
class ResolvedConflict < Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true) class ResolvedConflict < Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true)
def report_message def report_message
s = symbol.display_name s = symbol.display_name
r = reduce.rule.precedence_sym.display_name r = reduce.rule.precedence_sym&.display_name
case case
when which == :shift && same_prec when which == :shift && same_prec
msg = "resolved as #{which} (%right #{s})" msg = "resolved as #{which} (%right #{s})"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class State class State
class Shift class Shift

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class State class State
class ShiftReduceConflict < Struct.new(:symbols, :shift, :reduce, keyword_init: true) class ShiftReduceConflict < Struct.new(:symbols, :shift, :reduce, keyword_init: true)

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
require "forwardable" require "forwardable"
require "lrama/report/duration" require_relative "report/duration"
require "lrama/states/item" require_relative "states/item"
module Lrama module Lrama
# States is passed to a template file # States is passed to a template file
@ -16,9 +18,8 @@ module Lrama
attr_reader :states, :reads_relation, :includes_relation, :lookback_relation attr_reader :states, :reads_relation, :includes_relation, :lookback_relation
def initialize(grammar, warning, trace_state: false) def initialize(grammar, trace_state: false)
@grammar = grammar @grammar = grammar
@warning = warning
@trace_state = trace_state @trace_state = trace_state
@states = [] @states = []
@ -89,8 +90,6 @@ module Lrama
report_duration(:compute_conflicts) { compute_conflicts } report_duration(:compute_conflicts) { compute_conflicts }
report_duration(:compute_default_reduction) { compute_default_reduction } report_duration(:compute_default_reduction) { compute_default_reduction }
check_conflicts
end end
def reporter def reporter
@ -125,16 +124,16 @@ module Lrama
end end
end end
def sr_conflicts_count
@sr_conflicts_count ||= @states.flat_map(&:sr_conflicts).count
end
def rr_conflicts_count
@rr_conflicts_count ||= @states.flat_map(&:rr_conflicts).count
end
private private
def sr_conflicts
@states.flat_map(&:sr_conflicts)
end
def rr_conflicts
@states.flat_map(&:rr_conflicts)
end
def trace_state def trace_state
if @trace_state if @trace_state
yield STDERR yield STDERR
@ -350,7 +349,7 @@ module Lrama
# TODO: need to omit if state == state2 ? # TODO: need to omit if state == state2 ?
@includes_relation[key] ||= [] @includes_relation[key] ||= []
@includes_relation[key] << [state.id, nterm.token_id] @includes_relation[key] << [state.id, nterm.token_id]
break if !sym.nullable break unless sym.nullable
i -= 1 i -= 1
end end
end end
@ -385,7 +384,7 @@ module Lrama
@states.each do |state| @states.each do |state|
rules.each do |rule| rules.each do |rule|
ary = @lookback_relation[[state.id, rule.id]] ary = @lookback_relation[[state.id, rule.id]]
next if !ary next unless ary
ary.each do |state2_id, nterm_token_id| ary.each do |state2_id, nterm_token_id|
# q = state, A -> ω = rule, p = state2, A = nterm # q = state, A -> ω = rule, p = state2, A = nterm
@ -428,7 +427,7 @@ module Lrama
sym = shift.next_sym sym = shift.next_sym
next unless reduce.look_ahead next unless reduce.look_ahead
next if !reduce.look_ahead.include?(sym) next unless reduce.look_ahead.include?(sym)
# Shift/Reduce conflict # Shift/Reduce conflict
shift_prec = sym.precedence shift_prec = sym.precedence
@ -492,17 +491,17 @@ module Lrama
states.each do |state| states.each do |state|
count = state.reduces.count count = state.reduces.count
for i in 0...count do (0...count).each do |i|
reduce1 = state.reduces[i] reduce1 = state.reduces[i]
next if reduce1.look_ahead.nil? next if reduce1.look_ahead.nil?
for j in (i+1)...count do ((i+1)...count).each do |j|
reduce2 = state.reduces[j] reduce2 = state.reduces[j]
next if reduce2.look_ahead.nil? next if reduce2.look_ahead.nil?
intersection = reduce1.look_ahead & reduce2.look_ahead intersection = reduce1.look_ahead & reduce2.look_ahead
if !intersection.empty? unless intersection.empty?
state.conflicts << State::ReduceReduceConflict.new(symbols: intersection, reduce1: reduce1, reduce2: reduce2) state.conflicts << State::ReduceReduceConflict.new(symbols: intersection, reduce1: reduce1, reduce2: reduce2)
end end
end end
@ -514,7 +513,7 @@ module Lrama
states.each do |state| states.each do |state|
next if state.reduces.empty? next if state.reduces.empty?
# Do not set, if conflict exist # Do not set, if conflict exist
next if !state.conflicts.empty? next unless state.conflicts.empty?
# Do not set, if shift with `error` exists. # Do not set, if shift with `error` exists.
next if state.shifts.map(&:next_sym).include?(@grammar.error_symbol) next if state.shifts.map(&:next_sym).include?(@grammar.error_symbol)
@ -525,32 +524,5 @@ module Lrama
end.first end.first
end end
end end
def check_conflicts
sr_count = sr_conflicts.count
rr_count = rr_conflicts.count
if @grammar.expect
expected_sr_conflicts = @grammar.expect
expected_rr_conflicts = 0
if expected_sr_conflicts != sr_count
@warning.error("shift/reduce conflicts: #{sr_count} found, #{expected_sr_conflicts} expected")
end
if expected_rr_conflicts != rr_count
@warning.error("reduce/reduce conflicts: #{rr_count} found, #{expected_rr_conflicts} expected")
end
else
if sr_count != 0
@warning.warn("shift/reduce conflicts: #{sr_count} found")
end
if rr_count != 0
@warning.warn("reduce/reduce conflicts: #{rr_count} found")
end
end
end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# TODO: Validate position is not over rule rhs # TODO: Validate position is not over rule rhs
require "forwardable" require "forwardable"
@ -54,11 +56,11 @@ module Lrama
Item.new(rule: rule, position: position + 1) Item.new(rule: rule, position: position + 1)
end end
def symbols_before_dot def symbols_before_dot # steep:ignore
rhs[0...position] rhs[0...position]
end end
def symbols_after_dot def symbols_after_dot # steep:ignore
rhs[position..-1] rhs[position..-1]
end end
@ -73,7 +75,7 @@ module Lrama
# Right after position # Right after position
def display_rest def display_rest
r = rhs[position..-1].map(&:display_name).join(" ") r = symbols_after_dot.map(&:display_name).join(" ")
". #{r} (rule #{rule_id})" ". #{r} (rule #{rule_id})"
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
class StatesReporter class StatesReporter
include Lrama::Report::Duration include Lrama::Report::Duration
@ -14,15 +16,54 @@ module Lrama
private private
def _report(io, grammar: false, states: false, itemsets: false, lookaheads: false, solved: false, counterexamples: false, verbose: false) def _report(io, grammar: false, rules: false, terms: false, states: false, itemsets: false, lookaheads: false, solved: false, counterexamples: false, verbose: false)
# TODO: Unused terms report_unused_rules(io) if rules
# TODO: Unused rules report_unused_terms(io) if terms
report_conflicts(io) report_conflicts(io)
report_grammar(io) if grammar report_grammar(io) if grammar
report_states(io, itemsets, lookaheads, solved, counterexamples, verbose) report_states(io, itemsets, lookaheads, solved, counterexamples, verbose)
end end
def report_unused_terms(io)
look_aheads = @states.states.each do |state|
state.reduces.flat_map do |reduce|
reduce.look_ahead unless reduce.look_ahead.nil?
end
end
next_terms = @states.states.flat_map do |state|
state.shifts.map(&:next_sym).select(&:term?)
end
unused_symbols = @states.terms.select do |term|
!(look_aheads + next_terms).include?(term)
end
unless unused_symbols.empty?
io << "#{unused_symbols.count} Unused Terms\n\n"
unused_symbols.each_with_index do |term, index|
io << sprintf("%5d %s\n", index, term.id.s_value)
end
io << "\n\n"
end
end
def report_unused_rules(io)
used_rules = @states.rules.flat_map(&:rhs)
unused_rules = @states.rules.map(&:lhs).select do |rule|
!used_rules.include?(rule) && rule.token_id != 0
end
unless unused_rules.empty?
io << "#{unused_rules.count} Unused Rules\n\n"
unused_rules.each_with_index do |rule, index|
io << sprintf("%5d %s\n", index, rule.display_name)
end
io << "\n\n"
end
end
def report_conflicts(io) def report_conflicts(io)
has_conflict = false has_conflict = false
@ -37,7 +78,7 @@ module Lrama
messages << "#{cs[:reduce_reduce].count} reduce/reduce" messages << "#{cs[:reduce_reduce].count} reduce/reduce"
end end
if !messages.empty? unless messages.empty?
has_conflict = true has_conflict = true
io << "State #{state.id} conflicts: #{messages.join(', ')}\n" io << "State #{state.id} conflicts: #{messages.join(', ')}\n"
end end
@ -98,7 +139,7 @@ module Lrama
if lookaheads && item.end_of_rule? if lookaheads && item.end_of_rule?
reduce = state.find_reduce_by_item!(item) reduce = state.find_reduce_by_item!(item)
look_ahead = reduce.selected_look_ahead look_ahead = reduce.selected_look_ahead
if !look_ahead.empty? unless look_ahead.empty?
la = " [#{look_ahead.map(&:display_name).join(", ")}]" la = " [#{look_ahead.map(&:display_name).join(", ")}]"
end end
end end
@ -118,7 +159,7 @@ module Lrama
tmp.each do |term, state_id| tmp.each do |term, state_id|
io << " #{term.display_name.ljust(max_len)} shift, and go to state #{state_id}\n" io << " #{term.display_name.ljust(max_len)} shift, and go to state #{state_id}\n"
end end
io << "\n" if !tmp.empty? io << "\n" unless tmp.empty?
# Report error caused by %nonassoc # Report error caused by %nonassoc
nl = false nl = false
@ -132,7 +173,7 @@ module Lrama
nl = true nl = true
io << " #{name.ljust(max_len)} error (nonassociative)\n" io << " #{name.ljust(max_len)} error (nonassociative)\n"
end end
io << "\n" if !tmp.empty? io << "\n" unless tmp.empty?
# Report reduces # Report reduces
nl = false nl = false
@ -181,14 +222,14 @@ module Lrama
tmp.each do |nterm, state_id| tmp.each do |nterm, state_id|
io << " #{nterm.id.s_value.ljust(max_len)} go to state #{state_id}\n" io << " #{nterm.id.s_value.ljust(max_len)} go to state #{state_id}\n"
end end
io << "\n" if !tmp.empty? io << "\n" unless tmp.empty?
if solved if solved
# Report conflict resolutions # Report conflict resolutions
state.resolved_conflicts.each do |resolved| state.resolved_conflicts.each do |resolved|
io << " #{resolved.report_message}\n" io << " #{resolved.report_message}\n"
end end
io << "\n" if !state.resolved_conflicts.empty? io << "\n" unless state.resolved_conflicts.empty?
end end
if counterexamples && state.has_conflicts? if counterexamples && state.has_conflicts?
@ -219,7 +260,7 @@ module Lrama
direct_read_sets = @states.direct_read_sets direct_read_sets = @states.direct_read_sets
@states.nterms.each do |nterm| @states.nterms.each do |nterm|
terms = direct_read_sets[[state.id, nterm.token_id]] terms = direct_read_sets[[state.id, nterm.token_id]]
next if !terms next unless terms
next if terms.empty? next if terms.empty?
str = terms.map {|sym| sym.id.s_value }.join(", ") str = terms.map {|sym| sym.id.s_value }.join(", ")
@ -231,7 +272,7 @@ module Lrama
io << " [Reads Relation]\n" io << " [Reads Relation]\n"
@states.nterms.each do |nterm| @states.nterms.each do |nterm|
a = @states.reads_relation[[state.id, nterm.token_id]] a = @states.reads_relation[[state.id, nterm.token_id]]
next if !a next unless a
a.each do |state_id2, nterm_id2| a.each do |state_id2, nterm_id2|
n = @states.nterms.find {|n| n.token_id == nterm_id2 } n = @states.nterms.find {|n| n.token_id == nterm_id2 }
@ -245,7 +286,7 @@ module Lrama
read_sets = @states.read_sets read_sets = @states.read_sets
@states.nterms.each do |nterm| @states.nterms.each do |nterm|
terms = read_sets[[state.id, nterm.token_id]] terms = read_sets[[state.id, nterm.token_id]]
next if !terms next unless terms
next if terms.empty? next if terms.empty?
terms.each do |sym| terms.each do |sym|
@ -258,7 +299,7 @@ module Lrama
io << " [Includes Relation]\n" io << " [Includes Relation]\n"
@states.nterms.each do |nterm| @states.nterms.each do |nterm|
a = @states.includes_relation[[state.id, nterm.token_id]] a = @states.includes_relation[[state.id, nterm.token_id]]
next if !a next unless a
a.each do |state_id2, nterm_id2| a.each do |state_id2, nterm_id2|
n = @states.nterms.find {|n| n.token_id == nterm_id2 } n = @states.nterms.find {|n| n.token_id == nterm_id2 }
@ -271,11 +312,11 @@ module Lrama
io << " [Lookback Relation]\n" io << " [Lookback Relation]\n"
@states.rules.each do |rule| @states.rules.each do |rule|
a = @states.lookback_relation[[state.id, rule.id]] a = @states.lookback_relation[[state.id, rule.id]]
next if !a next unless a
a.each do |state_id2, nterm_id2| a.each do |state_id2, nterm_id2|
n = @states.nterms.find {|n| n.token_id == nterm_id2 } n = @states.nterms.find {|n| n.token_id == nterm_id2 }
io << " (Rule: #{rule}) -> (State #{state_id2}, #{n.id.s_value})\n" io << " (Rule: #{rule.display_name}) -> (State #{state_id2}, #{n.id.s_value})\n"
end end
end end
io << "\n" io << "\n"
@ -286,7 +327,7 @@ module Lrama
@states.nterms.each do |nterm| @states.nterms.each do |nterm|
terms = follow_sets[[state.id, nterm.token_id]] terms = follow_sets[[state.id, nterm.token_id]]
next if !terms next unless terms
terms.each do |sym| terms.each do |sym|
io << " #{nterm.id.s_value} -> #{sym.id.s_value}\n" io << " #{nterm.id.s_value} -> #{sym.id.s_value}\n"
@ -300,7 +341,7 @@ module Lrama
max_len = 0 max_len = 0
@states.rules.each do |rule| @states.rules.each do |rule|
syms = @states.la[[state.id, rule.id]] syms = @states.la[[state.id, rule.id]]
next if !syms next unless syms
tmp << [rule, syms] tmp << [rule, syms]
max_len = ([max_len] + syms.map {|s| s.id.s_value.length }).max max_len = ([max_len] + syms.map {|s| s.id.s_value.length }).max
@ -310,7 +351,7 @@ module Lrama
io << " #{sym.id.s_value.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.id.s_value})\n" io << " #{sym.id.s_value.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.id.s_value})\n"
end end
end end
io << "\n" if !tmp.empty? io << "\n" unless tmp.empty?
end end
# End of Report State # End of Report State

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Lrama
class TraceReporter
def initialize(grammar)
@grammar = grammar
end
def report(**options)
_report(**options)
end
private
def _report(rules: false, actions: false, **_)
report_rules if rules
report_actions if actions
end
def report_rules
puts "Grammar rules:"
@grammar.rules.each { |rule| puts rule.display_name }
end
def report_actions
puts "Grammar rules with actions:"
@grammar.rules.each { |rule| puts rule.with_actions }
end
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama module Lrama
VERSION = "0.6.9".freeze VERSION = "0.6.10".freeze
end end

View File

@ -1166,9 +1166,9 @@ yydestruct (const char *yymsg,
#endif #endif
enum yy_repair_type { enum yy_repair_type {
insert, inserting,
delete, deleting,
shift, shifting,
}; };
struct yy_repair { struct yy_repair {
@ -1401,27 +1401,27 @@ yyrecover(yy_state_t *yyss, yy_state_t *yyssp, int yychar<%= output.user_formals
if (current->repair_length + 1 > YYMAXREPAIR(<%= output.parse_param_name %>)) if (current->repair_length + 1 > YYMAXREPAIR(<%= output.parse_param_name %>))
continue; continue;
yy_repairs *new = (yy_repairs *) YYMALLOC (sizeof (yy_repairs)); yy_repairs *reps = (yy_repairs *) YYMALLOC (sizeof (yy_repairs));
new->id = count; reps->id = count;
new->next = 0; reps->next = 0;
new->stack_length = stack_length; reps->stack_length = stack_length;
new->states = (yy_state_t *) YYMALLOC (sizeof (yy_state_t) * (stack_length)); reps->states = (yy_state_t *) YYMALLOC (sizeof (yy_state_t) * (stack_length));
new->state = new->states + (current->state - current->states); reps->state = reps->states + (current->state - current->states);
YYCOPY (new->states, current->states, current->state - current->states + 1); YYCOPY (reps->states, current->states, current->state - current->states + 1);
new->repair_length = current->repair_length + 1; reps->repair_length = current->repair_length + 1;
new->prev_repair = current; reps->prev_repair = current;
new->repair.type = insert; reps->repair.type = inserting;
new->repair.term = (yysymbol_kind_t) yyx; reps->repair.term = (yysymbol_kind_t) yyx;
/* Process PDA assuming next token is yyx */ /* Process PDA assuming next token is yyx */
if (! yy_process_repairs (new, yyx)) if (! yy_process_repairs (reps, (yysymbol_kind_t)yyx))
{ {
YYFREE (new); YYFREE (reps);
continue; continue;
} }
tail->next = new; tail->next = reps;
tail = new; tail = reps;
count++; count++;
if (yyx == yytoken) if (yyx == yytoken)
@ -1437,7 +1437,7 @@ yyrecover(yy_state_t *yyss, yy_state_t *yyssp, int yychar<%= output.user_formals
YYDPRINTF ((stderr, YYDPRINTF ((stderr,
"New repairs is enqueued. count: %d, yystate: %d, yyx: %d\n", "New repairs is enqueued. count: %d, yystate: %d, yyx: %d\n",
count, yystate, yyx)); count, yystate, yyx));
yy_print_repairs (new<%= output.user_args %>); yy_print_repairs (reps<%= output.user_args %>);
} }
} }
} }
@ -1475,7 +1475,12 @@ int yychar;
/* The semantic value of the lookahead symbol. */ /* The semantic value of the lookahead symbol. */
/* Default value used for initialization, for pacifying older GCCs /* Default value used for initialization, for pacifying older GCCs
or non-GCC compilers. */ or non-GCC compilers. */
#ifdef __cplusplus
static const YYSTYPE yyval_default = {};
(void) yyval_default;
#else
YY_INITIAL_VALUE (static const YYSTYPE yyval_default;) YY_INITIAL_VALUE (static const YYSTYPE yyval_default;)
#endif
YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default);
/* Location data for the lookahead symbol. */ /* Location data for the lookahead symbol. */