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
## 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)
### Typed Midrule Actions

View File

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

View File

@ -1,17 +1,22 @@
require "lrama/bitmap"
require "lrama/command"
require "lrama/context"
require "lrama/counterexamples"
require "lrama/digraph"
require "lrama/grammar"
require "lrama/lexer"
require "lrama/option_parser"
require "lrama/options"
require "lrama/output"
require "lrama/parser"
require "lrama/report"
require "lrama/state"
require "lrama/states"
require "lrama/states_reporter"
require "lrama/version"
require "lrama/warning"
# frozen_string_literal: true
require_relative "lrama/bitmap"
require_relative "lrama/command"
require_relative "lrama/context"
require_relative "lrama/counterexamples"
require_relative "lrama/diagnostics"
require_relative "lrama/digraph"
require_relative "lrama/grammar"
require_relative "lrama/grammar_validator"
require_relative "lrama/lexer"
require_relative "lrama/logger"
require_relative "lrama/option_parser"
require_relative "lrama/options"
require_relative "lrama/output"
require_relative "lrama/parser"
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 Bitmap
def self.from_array(ary)

View File

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

View File

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

View File

@ -1,13 +1,15 @@
# frozen_string_literal: true
require "set"
require "lrama/counterexamples/derivation"
require "lrama/counterexamples/example"
require "lrama/counterexamples/path"
require "lrama/counterexamples/production_path"
require "lrama/counterexamples/start_path"
require "lrama/counterexamples/state_item"
require "lrama/counterexamples/transition_path"
require "lrama/counterexamples/triple"
require_relative "counterexamples/derivation"
require_relative "counterexamples/example"
require_relative "counterexamples/path"
require_relative "counterexamples/production_path"
require_relative "counterexamples/start_path"
require_relative "counterexamples/state_item"
require_relative "counterexamples/transition_path"
require_relative "counterexamples/triple"
module Lrama
# See: https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf
@ -171,7 +173,13 @@ module Lrama
break
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]
@reverse_transitions[key].each do |prev_target_state_item|
next if prev_target_state_item.state != prev_state_item.state
@ -183,12 +191,6 @@ module Lrama
queue.clear
break
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
else

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
# 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
# Algorithm Digraph of https://dl.acm.org/doi/pdf/10.1145/69622.357187 (P. 625)
class Digraph

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Binding
@ -16,9 +18,18 @@ module Lrama
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)
else
@parameter_to_arg[symbol.s_value] || symbol
parameter_to_arg(symbol) || symbol
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
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
class Grammar
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/rhs'
require_relative 'parameterizing_rule/rule'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class ParameterizingRule
@ -18,13 +20,17 @@ module Lrama
end
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
def created_lhs(lhs_s_value)
@created_lhs_list.reverse.find { |created_lhs| created_lhs.s_value == lhs_s_value }
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
def select_rules(rules, token)

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
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
class Grammar
# type: :dollar or :at

View File

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

View File

@ -1,12 +1,15 @@
# frozen_string_literal: true
module Lrama
class Grammar
class RuleBuilder
attr_accessor :lhs, :line
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
@midrule_action_counter = midrule_action_counter
@parameterizing_rule_resolver = parameterizing_rule_resolver
@position_in_original_rule_rhs = position_in_original_rule_rhs
@skip_preprocess_references = skip_preprocess_references
@ -19,16 +22,12 @@ module Lrama
@rules = []
@rule_builders_for_parameterizing_rules = []
@rule_builders_for_derived_rules = []
@rule_builders_for_inline_rules = []
@parameterizing_rules = []
@inline_rules = []
@midrule_action_rules = []
end
def add_rhs(rhs)
if !@line
@line = rhs.line
end
@line ||= rhs.line
flush_user_code
@ -36,9 +35,7 @@ module Lrama
end
def user_code=(user_code)
if !@line
@line = user_code&.line
end
@line ||= user_code&.line
flush_user_code
@ -55,18 +52,41 @@ module Lrama
freeze_rhs
end
def setup_rules(parameterizing_rule_resolver)
def setup_rules
preprocess_references unless @skip_preprocess_references
if rhs.any? { |token| parameterizing_rule_resolver.find_inline(token) }
resolve_inline(parameterizing_rule_resolver)
else
process_rhs(parameterizing_rule_resolver)
end
process_rhs
build_rules
end
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
private
@ -82,31 +102,25 @@ module Lrama
def build_rules
tokens = @replaced_rhs
if tokens
rule = Rule.new(
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
)
@rules = [rule]
@parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder|
rule_builder.rules
end.flatten
@midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder|
rule_builder.rules
end.flatten
@midrule_action_rules.each do |r|
r.original_rule = rule
end
else
@inline_rules = @rule_builders_for_inline_rules.map do |rule_builder|
rule_builder.rules
end.flatten
rule = Rule.new(
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
)
@rules = [rule]
@parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder|
rule_builder.rules
end.flatten
@midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder|
rule_builder.rules
end.flatten
@midrule_action_rules.each do |r|
r.original_rule = rule
end
end
# 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`.
def process_rhs(parameterizing_rule_resolver)
def process_rhs
return if @replaced_rhs
@replaced_rhs = []
@ -118,26 +132,26 @@ module Lrama
when Lrama::Lexer::Token::Ident
@replaced_rhs << token
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
bindings = Binding.new(parameterizing_rule, token.args)
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
else
lhs_token = Lrama::Lexer::Token::Ident.new(s_value: lhs_s_value, location: token.location)
@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|
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
r.symbols.each { |sym| rule_builder.add_rhs(bindings.resolve_symbol(sym)) }
rule_builder.line = line
rule_builder.precedence_sym = r.precedence_sym
rule_builder.user_code = r.resolve_user_code(bindings)
rule_builder.complete_input
rule_builder.setup_rules(parameterizing_rule_resolver)
rule_builder.setup_rules
@rule_builders_for_parameterizing_rules << rule_builder
end
end
@ -147,11 +161,11 @@ module Lrama
new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s)
@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.user_code = token
rule_builder.complete_input
rule_builder.setup_rules(parameterizing_rule_resolver)
rule_builder.setup_rules
@rule_builders_for_derived_rules << rule_builder
else
@ -172,27 +186,10 @@ module Lrama
"#{token.rule_name}_#{s_values.join('_')}"
end
def resolve_inline(parameterizing_rule_resolver)
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)
def resolve_inline_rhs(rule_builder, inline_rhs, index, bindings = nil)
rhs.each_with_index do |token, 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
rule_builder.add_rhs(token)
end
@ -204,6 +201,11 @@ module Lrama
return user_code if user_code.nil?
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)
end
@ -238,9 +240,6 @@ module Lrama
end
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
end

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Symbols
@ -42,7 +44,9 @@ module Lrama
end
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
nterm = Symbol.new(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require "strscan"
module Lrama
@ -16,7 +18,7 @@ module Lrama
scanner = StringScanner.new(s_value)
references = []
while !scanner.eos? do
until scanner.eos? do
case
when reference = scan_reference(scanner)
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'
module Lrama
@ -16,7 +18,7 @@ module Lrama
@options.report_opts = validate_report(@report)
@options.grammar_file = argv.shift
if !@options.grammar_file
unless @options.grammar_file
abort "File should be specified\n"
end
@ -63,20 +65,35 @@ module Lrama
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('-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 'Valid Reports:'
o.on_tail " #{VALID_REPORTS.join(' ')}"
o.on_tail 'REPORTS is a list of comma-separated words that can include:'
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('-o', '--output=FILE', 'leave output to FILE') {|v| @options.outfile = v }
o.on('--trace=THINGS', Array, 'also output trace logs at runtime') {|v| @trace = v }
o.on('--trace=TRACES', Array, 'also output trace logs at runtime') {|v| @trace = v }
o.on_tail ''
o.on_tail 'Valid Traces:'
o.on_tail " #{VALID_TRACES.join(' ')}"
o.on('-v', 'reserved, do nothing') { }
o.on_tail 'TRACES is a list of comma-separated words that can include:'
o.on_tail ' automaton display states'
o.on_tail ' closure display states'
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 'Error Recovery:'
o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true }
@ -89,47 +106,55 @@ module Lrama
end
end
BISON_REPORTS = %w[states itemsets lookaheads solved counterexamples cex all none]
OTHER_REPORTS = %w[verbose]
NOT_SUPPORTED_REPORTS = %w[cex none]
VALID_REPORTS = BISON_REPORTS + OTHER_REPORTS - NOT_SUPPORTED_REPORTS
ALIASED_REPORTS = { cex: :counterexamples }.freeze
VALID_REPORTS = %i[states itemsets lookaheads solved counterexamples rules terms verbose].freeze
def validate_report(report)
list = VALID_REPORTS
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|
if list.include?(r)
h[r.to_sym] = true
aliased = aliased_report_option(r)
if VALID_REPORTS.include?(aliased)
h[aliased] = true
else
raise "Invalid report option \"#{r}\"."
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
end
def aliased_report_option(opt)
(ALIASED_REPORTS[opt.to_sym] || opt).to_sym
end
VALID_TRACES = %w[
none locations scan parse automaton bitsets
closure grammar rules actions resource
sets muscles tools m4-early m4 skeleton time
ielr cex all
]
locations scan parse automaton bitsets closure
grammar rules actions resource sets muscles
tools m4-early m4 skeleton time ielr cex
].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)
list = VALID_TRACES
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|
if list.include?(t)
if supported.include?(t)
h[t.to_sym] = true
else
raise "Invalid trace option \"#{t}\"."

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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