Skip to content

Commit

Permalink
Implement %precedence Operator Precedence
Browse files Browse the repository at this point in the history
This only specifies precedence and not specify associativity
then a conflict is unresolved if precedence is same.
  • Loading branch information
yui-knk committed Aug 28, 2023
1 parent e82f4da commit 59b0c10
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 2 deletions.
4 changes: 4 additions & 0 deletions lib/lrama/grammar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ def add_right(sym, precedence)
set_precedence(sym, Precedence.new(type: :right, precedence: precedence))
end

def add_precedence(sym, precedence)
set_precedence(sym, Precedence.new(type: :precedence, precedence: precedence))
end

def set_precedence(sym, precedence)
raise "" if sym.nterm?
sym.precedence = precedence
Expand Down
2 changes: 2 additions & 0 deletions lib/lrama/lexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ def lex_common(lines, tokens)
tokens << create_token(Token::P_left, ss[0], line, ss.pos - column)
when ss.scan(/%right/)
tokens << create_token(Token::P_right, ss[0], line, ss.pos - column)
when ss.scan(/%precedence/)
tokens << create_token(Token::P_precedence, ss[0], line, ss.pos - column)
when ss.scan(/%prec/)
tokens << create_token(Token::P_prec, ss[0], line, ss.pos - column)
when ss.scan(/{/)
Expand Down
1 change: 1 addition & 0 deletions lib/lrama/lexer/token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def self.define_type(name)
define_type(:P_nonassoc) # %nonassoc
define_type(:P_left) # %left
define_type(:P_right) # %right
define_type(:P_precedence) # %precedence
define_type(:P_prec) # %prec
define_type(:User_code) # { ... }
define_type(:Tag) # <int>
Expand Down
8 changes: 8 additions & 0 deletions lib/lrama/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ def parse_bison_declarations(ts, grammar)
grammar.add_right(sym, precedence_number)
end
precedence_number += 1
when T::P_precedence
# %precedence (ident|char|string)+
ts.next
while (id = ts.consume(T::Ident, T::Char, T::String)) do
sym = grammar.add_term(id: id)
grammar.add_precedence(sym, precedence_number)
end
precedence_number += 1
when nil
# end of input
raise "Reach to end of input within declarations"
Expand Down
5 changes: 5 additions & 0 deletions lib/lrama/states.rb
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@ def compute_shift_reduce_conflicts

# shift_prec == reduce_prec, then check associativity
case sym.precedence.type
when :precedence
# %precedence only specifies precedence and not specify associativity
# then a conflict is unresolved if precedence is same.
state.conflicts << State::ShiftReduceConflict.new(symbols: [sym], shift: shift, reduce: reduce)
next
when :right
# Shift is selected
state.resolved_conflicts << State::ResolvedConflict.new(symbol: sym, reduce: reduce, which: :shift, same_prec: true)
Expand Down
135 changes: 133 additions & 2 deletions spec/lrama/states_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1157,8 +1157,8 @@ class go to state 5
end
end

describe "conflict happens on nonassoc operator" do
it "resolved as error" do
describe "conflict happens on %nonassoc operator" do
it "resolved as 'run-time' error" do
y = <<~INPUT
%{
// Prologue
Expand Down Expand Up @@ -1250,6 +1250,137 @@ class go to state 5
Conflict between rule 2 and token "=" resolved as an error (%nonassoc "=").
STR
end
end

describe "conflict happens on %precedence operator" do
it "resolved as 'run-time' error so that conflict is kept" do
y = <<~INPUT
%{
// Prologue
%}
%token NUM
%precedence '+'
%precedence '*'
%%
program: expr ;
expr: expr '+' expr
| expr '*' expr
| NUM
;
%%
INPUT
grammar = Lrama::Parser.new(y).parse
states = Lrama::States.new(grammar, warning)
states.compute

str = ""
states.reporter.report(str, states: true, solved: true)

expect(str).to eq(<<~STR)
State 7 conflicts: 1 shift/reduce
State 8 conflicts: 1 shift/reduce
State 0
0 $accept: • program "end of file"
NUM shift, and go to state 1
program go to state 2
expr go to state 3
State 1
4 expr: NUM •
$default reduce using rule 4 (expr)
State 2
0 $accept: program • "end of file"
"end of file" shift, and go to state 4
State 3
1 program: expr •
2 expr: expr • '+' expr
3 | expr • '*' expr
'+' shift, and go to state 5
'*' shift, and go to state 6
$default reduce using rule 1 (program)
State 4
0 $accept: program "end of file" •
$default accept
State 5
2 expr: expr '+' • expr
NUM shift, and go to state 1
expr go to state 7
State 6
3 expr: expr '*' • expr
NUM shift, and go to state 1
expr go to state 8
State 7
2 expr: expr • '+' expr
2 | expr '+' expr •
3 | expr • '*' expr
'+' shift, and go to state 5
'*' shift, and go to state 6
"end of file" reduce using rule 2 (expr)
'+' reduce using rule 2 (expr)
'*' reduce using rule 2 (expr)
Conflict between rule 2 and token '*' resolved as shift ('+' < '*').
State 8
2 expr: expr • '+' expr
3 | expr • '*' expr
3 | expr '*' expr •
'*' shift, and go to state 6
"end of file" reduce using rule 3 (expr)
'+' reduce using rule 3 (expr)
'*' reduce using rule 3 (expr)
Conflict between rule 3 and token '+' resolved as reduce ('+' < '*').
STR
end
end
Expand Down

0 comments on commit 59b0c10

Please sign in to comment.