Share
Embed
The Implementations of Advanced LR Parser Algorithm
Junichi Kobayashi
April 17, 2025
Transcript
121 slides
-
RubyKaigi 2025 Ehime Prefectural Convention Hall 2025/04/17(Thu.) The Implementations of
Advanced LR Parser Algorithm Junichi Kobayashi (@junk0612) ESM, Inc. -
自己紹介 Junichi Kobayashi • X / GitHub: @junk0612 • Working
at ESM, Inc. ◦ Work as a Rails engineer ◦ A Member of Parser Club • Committer of Lrama • Hobbies ◦ Parsers ◦ Rhythm games ◦ Board games ◦ Haiku -
Attendee @nsgc Attendee @colorbox Attendees from ESM Attendee @S.H. Karaoke
@fugakkbn Speaker @koic Attendee @wai-doi Speaker @junk0612 Attendee @kasumi8pon Attendee @mhirata Attendee @haruguchi Attendee @maimux2x -
Usage $ gem install lrama # from CLI $ lrama
-D lr.type=ielr grammar.y # in grammar file %define lr.type ielr -
✦ Theoretical side ✦ Utilize the tokens read so far
to narrow down the set of possible next tokens more accurately than LALR ✦ Practical side ✦ Propagating conflict info backward and lookahead forward ✦ Identify the conflict's cause and split states if necessary "After All, What is IELR?" -
✦ Shift-Reduce Parsing ✦ One of Bottom-up Parsing ✦ Use
2 types of actions ✦ Shift: Get a next symbol from Lexer ✦ Reduce: Collapse recognized symbols into a parent node ✦ Use one of the actions depending on the situation ✦ Match one of grammar rules: Reduce ✦ Don't match any grammar rules: Shift Basis of LR Parser -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp num -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp num exp -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp num exp -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp num exp -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp num exp num -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp num exp num exp -
Behavior Example of LR Parser exp: exp + num |
exp * num | num num: 0 | 1 1 + 0 * 1 num exp num exp num exp -
✦ Modifier conditional statements Motivating Examples statement: method_call 'if' exp
| method_call 'unless' exp method_call: method_name | method_name '(' args ')' -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
statement: method_call 'if' exp | method_call 'unless' exp method_call: method_name
| method_name '(' args ')' save!(name: 'Junichi') save! if valid? save! end valid? -
✦ The next token is... ✦ included in same (or
child) rule → Shift ✦ included in parent (or ancestors / sibling) rule → Reduce ✦ not included in both of the above → Error Select Actions -
✦ A set of tokens used to determine whether to
Reduce ✦ LALR and IELR differ in how they compute their lookahead sets ✦ LALR: The set of all tokens that can appear immediately after a given nonterminal, based on the entire language ✦ IELR: The set of all tokens that can appear immediately after a given nonterminal, but only within the subset of language elements matching the tokens read so far Lookahead Set -
✦ conditional statements Motivating Examples cond_stmt: 'if' method_call 'then' body
'end' | 'while' method_call 'do' body 'end' method_call: method_name | method_name '(' args ')' -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
cond_stmt: 'if' method_call 'then' body 'end' | 'while' method_call 'do'
body 'end' method_call: method_name | method_name '(' args ')' if valid? then save! end while valid? do save! end if valid? do save! end -
✦ original example in the paper Motivating Examples S: 'a'
A 'a' | 'b' A 'b' A: 'a' | 'a' 'a' aaa / aaaa / bab / baab -
✦ original example Motivating Examples S: 'a' A 'a' |
'b' A 'b' A: 'a' | 'a' 'a' baab -
✦ original example Motivating Examples S: 'a' A 'a' |
'b' A 'b' A: 'a' | 'a' 'a' baab -
✦ A Conflict occurs when the parser can't uniquely decide
the next action ✦ There are 2 types of Conflict ✦ Shift/Reduce Conflict ✦ Reduce/Reduce Conflict Conflicts -
LR Parser Model Parser State 0 State 1 State 2
State 3 NUM + exp State 4 State 5 State 6 * ( exp NUM - … Token Stream Source Code Lexer Grammar File Parser Generator 8 4 1 0 -
✦ Can be parsed IELR but not LALR ✦ Caused
by the difference of lookahead set ✦ May resolve by splitting states ✦ There are 3 types of Mysterious Conflicts ✦ Mysterious New Conflicts ✦ Mysterious Invasive Conflicts ✦ Mysterious Mutated Conflicts Mysterious Conflicts -
Lrama's Implementation States State @states Transition Shift Goto Reduce @transitions
@reduces @items @conflicts @id @next_sym @from_state @to_state @next_sym @from_state @to_state @look_ahead @item -
✦ Annotate conflicting states and their predecessors ✦ Recompute lookahead
set from scratch ✦ Propagate it to next state ✦ Split the next state if the propagating lookahead set has no "compatibilities" with already propagated ones IELR Basic Idea -
✦ Indicates potential contribution to conflicts ✦ Has the following
conflict information inside ✦ State ✦ Token ✦ Actions ✦ Items Inadequacy Annotations -
Inadequacy Annotation State: 22 Token: 'end' Actions: [S, R1, R2]
Items: {R1 => [...], R2 => [...]} State: 27 Token: 'do' Actions: [R5, R6] Items: {R5 => [...], R6 => [...]} -
Inadequacy Annotation State: 22 Token: 'end' Actions: [S, R1, R2]
Items: {R1 => [...], R2 => [...]} State: 27 Token: 'do' Actions: [R5, R6] Items: {R5 => [...], R6 => [...]} -
Inadequacy Annotation State: 22 Token: 'end' Actions: [S, R1, R2]
Items: {R1 => [...], R2 => [...]} State: 27 Token: 'do' Actions: [R5, R6] Items: {R5 => [...], R6 => [...]} -
Inadequacy Annotation State: 22 Token: 'end' Actions: [S, R1, R2]
Items: {R1 => [...], R2 => [...]} State: 27 Token: 'do' Actions: [R5, R6] Items: {R5 => [...], R6 => [...]} -
Inadequacy Annotation State: 22 Token: 'end' Actions: [S, R1, R2]
Items: {R1 => [...], R2 => [...]} State: 27 Token: 'do' Actions: [R5, R6] Items: {R5 => [...], R6 => [...]} -
Inadequacy Annotation State: 22 Token: 'end' Actions: [S, R1, R2]
Items: {R1 => [...], R2 => [...]} State: 27 Token: 'do' Actions: [R5, R6] Items: {R5 => [...], R6 => [...]} -
Inadequacy Annotation State: 22 Token: 'end' Actions: [S, R1, R2]
Items: {R1 => [...], R2 => [...]} State: 27 Token: 'do' Actions: [R5, R6] Items: {R5 => [...], R6 => [...]} -
1. Lookahead sets: from its associated item's lookahead 2. Kernel
lookahead: from the same item's lookahead in predecessor states (dot is one symbol to the left) 3. Non-kernel lookahead: from the follow set of the state's goto on the LHS (i.e., tokens that can appear next) 4. Goto follow set: a. the remainder of the RHS after the goto’s nonterminal b. The lookahead sets of items where the remainder is nullable Recompute Lookahead Set original: https://www.sciencedirect.com/science/article/pii/S0167642309001191 summarized by: @junk0612 -
Check Compatibilities S: 'a' A 'a' | 'b' A 'b'
A: 'a' | 'a' 'a' -
Check Compatibilities • A: 'a'・, ['a', 'b'] • A: 'a'・'a',
['a', 'b'] • S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a -
Check Compatibilities • A: 'a'・, ['a', 'b'] • A: 'a'・'a',
['a', 'b'] • S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a -
Check Compatibilities • A: 'a'・, ['a', 'b'] • A: 'a'・'a',
['a', 'b'] • S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a Token: 'a' Actions: [S, R] Items: {R => [A: 'a'・]} -
Check Compatibilities • A: 'a'・, ['a'] • A: 'a'・'a', ['a']
• S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a Token: 'a' Actions: [S, R] Items: {R => [A: 'a'・]} -
Check Compatibilities • A: 'a'・, ['a'] • A: 'a'・'a', ['a']
• S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a Token: 'a' Actions: [S, R] Items: {R => [A: 'a'・]} -
Check Compatibilities • A: 'a'・, ['b'] • A: 'a'・'a', ['b']
• S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a Token: 'a' Actions: [S, R] Items: {R => [A: 'a'・]} -
Check Compatibilities • A: 'a'・, ['b'] • A: 'a'・'a', ['b']
• S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a Token: 'a' Actions: [S, R] Items: {R => [A: 'a'・]} -
Check Compatibilities • A: 'a'・, ['a'] • A: 'a'・'a', ['a']
• S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a • A: 'a'・, ['b'] • A: 'a'・'a', ['b'] -
Check Compatibilities • A: 'a'・, ['a'] • A: 'a'・'a', ['a']
• S: 'b'・A 'b', ['#'] • A:・'a', ['b'] • A:・'a' 'a', ['b'] • S: 'a'・A 'a', ['#'] • A:・'a', ['a'] • A:・'a' 'a', ['a'] a a • A: 'a'・, ['b'] • A: 'a'・'a', ['b'] No Conflicts! -
$ bundle exec lrama -D lr.type=ielr parse.y def compute_ielr (...snip...)
rescue Interrupt end -
✦ Automaton has loops ✦ Compute lookahead sets slowly ✦
Create many "useless" annotations Why got stack -
✦ Introduce some caches ✦ Use strongly connected components algorithm
✦ Following the LALR Lookahead set computation Fasten Computation -
Useless Annotations Actions: [R1, R2] Items: {R1 => [...], R2
=> [...]} R1 R2 -
Useless Annotations Actions: [R1, R2] Items: {R1 => [...], R2
=> [...]} R1 R2 -
$ time bundle exec lrama -D lr.type=ielr parse.y 69.02s user
0.29s system 99% cpu 1:09.38 total -
Generate IELR parser simply $ lrama --report=states parse.y $ mv
parse.output lalr.output $ lrama --report=states -D lr.type=ielr parse.y $ mv parse.output ielr.output $ diff ielr.output lalr.output #=> no diffs! -
✦ There are 4 'do's in Ruby ✦ keyword_do ✦
keyword_do_cond ✦ keyword_do_block ✦ keyword_do_LAMBDA ✦ The tokens read so far provide sufficient information to decide Identify 4 'do's Smart obj.m(arg) do ... end while(true) do ... end obj.m arg do ... end -> (arg) do ... end -
while obj.m do #=> keyword_do_cond while(obj.m do #=> keyword_do while(obj.m
arg do #=> keyword_do_block while -> do #=> keyword_do_LAMBDA while obj.m -> do #=> keyword_do_LAMBDA while(obj.m -> do end do #=> keyword_do_block -
✦ Optimize IELR calculation ✦ Introduce IELR to CRuby and
make parse.y better Future Work -
✦ @yui-knk, @ydah ✦ #LR_parser_gangs ✦ @koic, @S.H. ✦ #esm_parser_club
✦ My Wife ✦ #a_beautiful_life Acknowledgements -
✦ Theoretical side ✦ Utilize the tokens read so far
to narrow down the set of possible next tokens more accurately than LALR ✦ Practical side ✦ Propagating conflict info backward and lookahead forward ✦ Identify the conflict's cause and split states if necessary "After All, What is IELR?"