Lrama v0.6.10
This commit is contained in:
parent
2a1962fc4a
commit
885cf350de
Notes:
git
2024-09-12 12:06:34 +00:00
@ -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
|
||||
|
@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH << File.join(__dir__, "../lib")
|
||||
require "lrama"
|
||||
|
@ -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"
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
module Bitmap
|
||||
def self.from_array(ary)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class Derivation
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class Example
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class Path
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class ProductionPath < Path
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class StartPath < Path
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class StateItem < Struct.new(:state, :item)
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class TransitionPath < Path
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
# s: state
|
||||
|
36
tool/lrama/lib/lrama/diagnostics.rb
Normal file
36
tool/lrama/lib/lrama/diagnostics.rb
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
# Grammar file information not used by States but by Output
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Counter
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'parameterizing_rule/resolver'
|
||||
require_relative 'parameterizing_rule/rhs'
|
||||
require_relative 'parameterizing_rule/rule'
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class PercentCode
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Precedence < Struct.new(:type, :precedence, keyword_init: true)
|
||||
|
@ -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)
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
# type: :dollar or :at
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -1 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "symbols/resolver"
|
||||
|
@ -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(
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Type
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Union < Struct.new(:code, :lineno, keyword_init: true)
|
||||
|
37
tool/lrama/lib/lrama/grammar_validator.rb
Normal file
37
tool/lrama/lib/lrama/grammar_validator.rb
Normal 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
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class GrammarFile
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Location
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
|
@ -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
|
||||
|
17
tool/lrama/lib/lrama/logger.rb
Normal file
17
tool/lrama/lib/lrama/logger.rb
Normal 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
|
@ -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}\"."
|
||||
|
@ -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
|
||||
|
@ -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
@ -1,2 +1,4 @@
|
||||
require 'lrama/report/duration'
|
||||
require 'lrama/report/profile'
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'report/duration'
|
||||
require_relative 'report/profile'
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Report
|
||||
module Duration
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Report
|
||||
module Profile
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
[]
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
class ReduceReduceConflict < Struct.new(:symbols, :reduce1, :reduce2, keyword_init: true)
|
||||
|
@ -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})"
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
class Shift
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
class ShiftReduceConflict < Struct.new(:symbols, :shift, :reduce, keyword_init: true)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
30
tool/lrama/lib/lrama/trace_reporter.rb
Normal file
30
tool/lrama/lib/lrama/trace_reporter.rb
Normal 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
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
VERSION = "0.6.9".freeze
|
||||
VERSION = "0.6.10".freeze
|
||||
end
|
||||
|
@ -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. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user