lrama 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/test.yaml +15 -1
- data/Gemfile +2 -0
- data/README.md +29 -2
- data/Steepfile +7 -0
- data/lib/lrama/grammar.rb +5 -5
- data/lib/lrama/lexer.rb +23 -3
- data/lib/lrama/parser/token_scanner.rb +55 -0
- data/lib/lrama/parser.rb +1 -52
- data/lib/lrama/state/reduce.rb +35 -0
- data/lib/lrama/state/shift.rb +13 -0
- data/lib/lrama/state.rb +184 -0
- data/lib/lrama/states.rb +6 -238
- data/lib/lrama/states_reporter.rb +4 -4
- data/lib/lrama/version.rb +1 -1
- data/lib/lrama.rb +1 -0
- data/sample/calc.output +263 -0
- data/sample/calc.y +98 -0
- data/sig/lrama/bitmap.rbs +7 -0
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20a0927bedeb14281abb8a5c59daf643fdfe556b5899cbe4872878b022804da2
|
4
|
+
data.tar.gz: 731fdd0201266db9724a2ebcc1162427a2cfaa9d0d94543e1298b3f2683eb14f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a6e7c0ef4e7266dae41dba15e3a5d7609cb7171b82e0dbd5b75595e8e3cf8a4a402c5b710cc0f714c54357ef9fc20fd0935e55b8c529c99cd3c09ea173efb87
|
7
|
+
data.tar.gz: fa867c9be43384c7f282bf22ecd2b0939cd0dca263a51914871dba42f409ddc01fb00c34d8696288c3668177fdc450ff1bc9fb5e90fdf053e2d56a5bf31bbe04
|
data/.github/workflows/test.yaml
CHANGED
@@ -22,6 +22,20 @@ jobs:
|
|
22
22
|
bundler-cache: true
|
23
23
|
- run: bundle install
|
24
24
|
- run: bundle exec rspec
|
25
|
+
steep-check:
|
26
|
+
runs-on: ubuntu-20.04
|
27
|
+
strategy:
|
28
|
+
fail-fast: false
|
29
|
+
matrix:
|
30
|
+
ruby: ['head']
|
31
|
+
steps:
|
32
|
+
- uses: actions/checkout@v3
|
33
|
+
- uses: ruby/setup-ruby@v1
|
34
|
+
with:
|
35
|
+
ruby-version: ${{ matrix.ruby }}
|
36
|
+
bundler-cache: true
|
37
|
+
- run: bundle install
|
38
|
+
- run: bundle exec steep check
|
25
39
|
test-ruby:
|
26
40
|
runs-on: ubuntu-20.04
|
27
41
|
strategy:
|
@@ -70,5 +84,5 @@ jobs:
|
|
70
84
|
- run: sudo apt-get --purge remove bison
|
71
85
|
- run: ../autogen.sh
|
72
86
|
- run: ../configure -C --disable-install-doc
|
73
|
-
- run: make
|
87
|
+
- run: make
|
74
88
|
- run: make test-all
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -21,9 +21,11 @@ $ gem install lrama
|
|
21
21
|
From source codes,
|
22
22
|
|
23
23
|
```shell
|
24
|
+
$ cd "$(lrama root)"
|
25
|
+
$ bundle install
|
24
26
|
$ bundle exec rake install
|
25
|
-
$ lrama --version
|
26
|
-
0.
|
27
|
+
$ bundle exec lrama --version
|
28
|
+
0.5.0
|
27
29
|
```
|
28
30
|
|
29
31
|
## Usage
|
@@ -33,11 +35,36 @@ $ lrama --version
|
|
33
35
|
$ lrama -d sample/parse.y
|
34
36
|
```
|
35
37
|
|
38
|
+
```shell
|
39
|
+
# "calc", "calc.c", and "calc.h" are generated
|
40
|
+
$ lrama -d sample/calc.y -o calc.c && gcc -Wall calc.c -o calc && ./calc
|
41
|
+
Enter the formula:
|
42
|
+
1
|
43
|
+
=> 1
|
44
|
+
1+2*3
|
45
|
+
=> 7
|
46
|
+
(1+2)*3
|
47
|
+
=> 9
|
48
|
+
```
|
49
|
+
|
50
|
+
## Versions and Branches
|
51
|
+
|
52
|
+
### v0_4 (`lrama_0_4` branch)
|
53
|
+
|
54
|
+
This branch generates "parse.c" compatible with Bison 3.8.2 for ruby 3.0, 3.1, 3.2. The first version migrated to ruby is ["0.4.0"](https://github.com/ruby/ruby/pull/7798) therefore keep this branch for Bison compatible branch.
|
55
|
+
|
36
56
|
## Build Ruby
|
37
57
|
|
38
58
|
1. Install Lrama
|
39
59
|
2. Run `make YACC=lrama`
|
40
60
|
|
61
|
+
## Release flow
|
62
|
+
|
63
|
+
1. Update `Lrama::VERSION`
|
64
|
+
2. Release as a gem by `rake release`
|
65
|
+
3. Update Lrama in ruby/ruby by `cp -r exe lib ruby/tool/lrama`
|
66
|
+
4. Create new release on [GitHub](https://github.com/ruby/lrama/releases)
|
67
|
+
|
41
68
|
## License
|
42
69
|
|
43
70
|
See LEGAL.md file.
|
data/Steepfile
ADDED
data/lib/lrama/grammar.rb
CHANGED
@@ -166,7 +166,7 @@ module Lrama
|
|
166
166
|
when ref.type == :at # @n
|
167
167
|
raise "@#{ref.number} can not be used in %printer."
|
168
168
|
else
|
169
|
-
raise "Unexpected. #{
|
169
|
+
raise "Unexpected. #{self}, #{ref}"
|
170
170
|
end
|
171
171
|
|
172
172
|
t_code[first_column..last_column] = str
|
@@ -205,7 +205,7 @@ module Lrama
|
|
205
205
|
i = -ref.position_in_rhs + ref.number
|
206
206
|
str = "(yylsp[#{i}])"
|
207
207
|
else
|
208
|
-
raise "Unexpected. #{
|
208
|
+
raise "Unexpected. #{self}, #{ref}"
|
209
209
|
end
|
210
210
|
|
211
211
|
t_code[first_column..last_column] = str
|
@@ -235,7 +235,7 @@ module Lrama
|
|
235
235
|
when ref.type == :at # @n
|
236
236
|
raise "@#{ref.number} can not be used in initial_action."
|
237
237
|
else
|
238
|
-
raise "Unexpected. #{
|
238
|
+
raise "Unexpected. #{self}, #{ref}"
|
239
239
|
end
|
240
240
|
|
241
241
|
t_code[first_column..last_column] = str
|
@@ -716,7 +716,7 @@ module Lrama
|
|
716
716
|
# If id is Token::Char, it uses ASCII code
|
717
717
|
if sym.term? && sym.token_id.nil?
|
718
718
|
if sym.id.type == Token::Char
|
719
|
-
#
|
719
|
+
# Ignore ' on the both sides
|
720
720
|
case sym.id.s_value[1..-2]
|
721
721
|
when "\\b"
|
722
722
|
sym.token_id = 8
|
@@ -844,7 +844,7 @@ module Lrama
|
|
844
844
|
|
845
845
|
return if invalid.empty?
|
846
846
|
|
847
|
-
raise "Symbol number is
|
847
|
+
raise "Symbol number is duplicated. #{invalid}"
|
848
848
|
end
|
849
849
|
end
|
850
850
|
end
|
data/lib/lrama/lexer.rb
CHANGED
@@ -206,6 +206,8 @@ module Lrama
|
|
206
206
|
when ss.scan(/\/\*/)
|
207
207
|
# TODO: Need to keep comment?
|
208
208
|
line = lex_comment(ss, line, lines, "")
|
209
|
+
when ss.scan(/\/\//)
|
210
|
+
line = lex_line_comment(ss, line, "")
|
209
211
|
when ss.scan(/'(.)'/)
|
210
212
|
tokens << create_token(Token::Char, ss[0], line, ss.pos - column)
|
211
213
|
when ss.scan(/'\\(.)'/) # '\\', '\t'
|
@@ -218,7 +220,7 @@ module Lrama
|
|
218
220
|
l = line - lines.first[1]
|
219
221
|
split = ss.string.split("\n")
|
220
222
|
col = ss.pos - split[0...l].join("\n").length
|
221
|
-
raise "Parse error (
|
223
|
+
raise "Parse error (unknown token): #{split[l]} \"#{ss.string[ss.pos]}\" (#{line}: #{col})"
|
222
224
|
end
|
223
225
|
end
|
224
226
|
end
|
@@ -276,6 +278,9 @@ module Lrama
|
|
276
278
|
when ss.scan(/\/\*/)
|
277
279
|
str << ss[0]
|
278
280
|
line = lex_comment(ss, line, lines, str)
|
281
|
+
when ss.scan(/\/\//)
|
282
|
+
str << ss[0]
|
283
|
+
line = lex_line_comment(ss, line, str)
|
279
284
|
else
|
280
285
|
# noop, just consume char
|
281
286
|
str << ss.getch
|
@@ -314,8 +319,6 @@ module Lrama
|
|
314
319
|
raise "Parse error (quote mismatch): #{ss.string.split("\n")[l]} \"#{ss.string[ss.pos]}\" (#{line}: #{ss.pos})"
|
315
320
|
end
|
316
321
|
|
317
|
-
# TODO: Need to handle // style comment
|
318
|
-
#
|
319
322
|
# /* */ style comment
|
320
323
|
def lex_comment(ss, line, lines, str)
|
321
324
|
while !ss.eos? do
|
@@ -337,6 +340,23 @@ module Lrama
|
|
337
340
|
raise "Parse error (comment mismatch): #{ss.string.split("\n")[l]} \"#{ss.string[ss.pos]}\" (#{line}: #{ss.pos})"
|
338
341
|
end
|
339
342
|
|
343
|
+
# // style comment
|
344
|
+
def lex_line_comment(ss, line, str)
|
345
|
+
while !ss.eos? do
|
346
|
+
case
|
347
|
+
when ss.scan(/\n/)
|
348
|
+
return line + 1
|
349
|
+
else
|
350
|
+
str << ss.getch
|
351
|
+
next
|
352
|
+
end
|
353
|
+
|
354
|
+
str << ss[0]
|
355
|
+
end
|
356
|
+
|
357
|
+
line # Reach to end of input
|
358
|
+
end
|
359
|
+
|
340
360
|
def lex_grammar_rules_tokens
|
341
361
|
lex_common(@grammar_rules, @grammar_rules_tokens)
|
342
362
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Lrama
|
2
|
+
class Parser
|
3
|
+
class TokenScanner
|
4
|
+
def initialize(tokens)
|
5
|
+
@tokens = tokens
|
6
|
+
@index = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def current_token
|
10
|
+
@tokens[@index]
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_type
|
14
|
+
current_token && current_token.type
|
15
|
+
end
|
16
|
+
|
17
|
+
def next
|
18
|
+
token = current_token
|
19
|
+
@index += 1
|
20
|
+
return token
|
21
|
+
end
|
22
|
+
|
23
|
+
def consume(*token_types)
|
24
|
+
if token_types.include?(current_type)
|
25
|
+
token = current_token
|
26
|
+
self.next
|
27
|
+
return token
|
28
|
+
end
|
29
|
+
|
30
|
+
return nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def consume!(*token_types)
|
34
|
+
consume(*token_types) || (raise "#{token_types} is expected but #{current_type}. #{current_token}")
|
35
|
+
end
|
36
|
+
|
37
|
+
def consume_multi(*token_types)
|
38
|
+
a = []
|
39
|
+
|
40
|
+
while token_types.include?(current_type)
|
41
|
+
a << current_token
|
42
|
+
self.next
|
43
|
+
end
|
44
|
+
|
45
|
+
raise "No token is consumed. #{token_types}" if a.empty?
|
46
|
+
|
47
|
+
return a
|
48
|
+
end
|
49
|
+
|
50
|
+
def eots?
|
51
|
+
current_token.nil?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/lrama/parser.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "lrama/report"
|
2
|
+
require "lrama/parser/token_scanner"
|
2
3
|
|
3
4
|
module Lrama
|
4
5
|
# Parser for parse.y, generates a grammar
|
@@ -7,58 +8,6 @@ module Lrama
|
|
7
8
|
|
8
9
|
T = Lrama::Lexer::Token
|
9
10
|
|
10
|
-
class TokenScanner
|
11
|
-
def initialize(tokens)
|
12
|
-
@tokens = tokens
|
13
|
-
@index = 0
|
14
|
-
end
|
15
|
-
|
16
|
-
def current_token
|
17
|
-
@tokens[@index]
|
18
|
-
end
|
19
|
-
|
20
|
-
def current_type
|
21
|
-
current_token && current_token.type
|
22
|
-
end
|
23
|
-
|
24
|
-
def next
|
25
|
-
token = current_token
|
26
|
-
@index += 1
|
27
|
-
return token
|
28
|
-
end
|
29
|
-
|
30
|
-
def consume(*token_types)
|
31
|
-
if token_types.include?(current_type)
|
32
|
-
token = current_token
|
33
|
-
self.next
|
34
|
-
return token
|
35
|
-
end
|
36
|
-
|
37
|
-
return nil
|
38
|
-
end
|
39
|
-
|
40
|
-
def consume!(*token_types)
|
41
|
-
consume(*token_types) || (raise "#{token_types} is expected but #{current_type}. #{current_token}")
|
42
|
-
end
|
43
|
-
|
44
|
-
def consume_multi(*token_types)
|
45
|
-
a = []
|
46
|
-
|
47
|
-
while token_types.include?(current_type)
|
48
|
-
a << current_token
|
49
|
-
self.next
|
50
|
-
end
|
51
|
-
|
52
|
-
raise "No token is consumed. #{token_types}" if a.empty?
|
53
|
-
|
54
|
-
return a
|
55
|
-
end
|
56
|
-
|
57
|
-
def eots?
|
58
|
-
current_token.nil?
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
11
|
def initialize(text)
|
63
12
|
@text = text
|
64
13
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Lrama
|
2
|
+
class State
|
3
|
+
class Reduce
|
4
|
+
# https://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html
|
5
|
+
attr_reader :item, :look_ahead, :not_selected_symbols
|
6
|
+
attr_accessor :default_reduction
|
7
|
+
|
8
|
+
def initialize(item)
|
9
|
+
@item = item
|
10
|
+
@look_ahead = nil
|
11
|
+
@not_selected_symbols = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def rule
|
15
|
+
@item.rule
|
16
|
+
end
|
17
|
+
|
18
|
+
def look_ahead=(look_ahead)
|
19
|
+
@look_ahead = look_ahead.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_not_selected_symbol(sym)
|
23
|
+
@not_selected_symbols << sym
|
24
|
+
end
|
25
|
+
|
26
|
+
def selected_look_ahead
|
27
|
+
if @look_ahead
|
28
|
+
@look_ahead - @not_selected_symbols
|
29
|
+
else
|
30
|
+
[]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/lrama/state.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
require "lrama/state/reduce"
|
2
|
+
require "lrama/state/shift"
|
3
|
+
|
4
|
+
module Lrama
|
5
|
+
class State
|
6
|
+
# * symbol: A symbol under discussion
|
7
|
+
# * reduce: A reduce under discussion
|
8
|
+
# * which: For which a conflict is resolved. :shift, :reduce or :error (for nonassociative)
|
9
|
+
ResolvedConflict = Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true) do
|
10
|
+
def report_message
|
11
|
+
s = symbol.display_name
|
12
|
+
r = reduce.rule.precedence_sym.display_name
|
13
|
+
case
|
14
|
+
when which == :shift && same_prec
|
15
|
+
msg = "resolved as #{which} (%right #{s})"
|
16
|
+
when which == :shift
|
17
|
+
msg = "resolved as #{which} (#{r} < #{s})"
|
18
|
+
when which == :reduce && same_prec
|
19
|
+
msg = "resolved as #{which} (%left #{s})"
|
20
|
+
when which == :reduce
|
21
|
+
msg = "resolved as #{which} (#{s} < #{r})"
|
22
|
+
when which == :error
|
23
|
+
msg = "resolved as an #{which} (%nonassoc #{s})"
|
24
|
+
else
|
25
|
+
raise "Unknown direction. #{self}"
|
26
|
+
end
|
27
|
+
|
28
|
+
"Conflict between rule #{reduce.rule.id} and token #{s} #{msg}."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Conflict = Struct.new(:symbols, :reduce, :type, keyword_init: true)
|
33
|
+
|
34
|
+
attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts,
|
35
|
+
:default_reduction_rule, :closure, :items
|
36
|
+
attr_accessor :shifts, :reduces
|
37
|
+
|
38
|
+
def initialize(id, accessing_symbol, kernels)
|
39
|
+
@id = id
|
40
|
+
@accessing_symbol = accessing_symbol
|
41
|
+
@kernels = kernels.freeze
|
42
|
+
@items = @kernels
|
43
|
+
# Manage relationships between items to state
|
44
|
+
# to resolve next state
|
45
|
+
@items_to_state = {}
|
46
|
+
@conflicts = []
|
47
|
+
@resolved_conflicts = []
|
48
|
+
@default_reduction_rule = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def closure=(closure)
|
52
|
+
@closure = closure
|
53
|
+
@items = @kernels + @closure
|
54
|
+
end
|
55
|
+
|
56
|
+
def non_default_reduces
|
57
|
+
reduces.select do |reduce|
|
58
|
+
reduce.rule != @default_reduction_rule
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def compute_shifts_reduces
|
63
|
+
_shifts = {}
|
64
|
+
reduces = []
|
65
|
+
items.each do |item|
|
66
|
+
# TODO: Consider what should be pushed
|
67
|
+
if item.end_of_rule?
|
68
|
+
reduces << Reduce.new(item)
|
69
|
+
else
|
70
|
+
key = item.next_sym
|
71
|
+
_shifts[key] ||= []
|
72
|
+
_shifts[key] << item.new_by_next_position
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# It seems Bison 3.8.2 iterates transitions order by symbol number
|
77
|
+
shifts = _shifts.sort_by do |next_sym, new_items|
|
78
|
+
next_sym.number
|
79
|
+
end.map do |next_sym, new_items|
|
80
|
+
Shift.new(next_sym, new_items.flatten)
|
81
|
+
end
|
82
|
+
self.shifts = shifts.freeze
|
83
|
+
self.reduces = reduces.freeze
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_items_to_state(items, next_state)
|
87
|
+
@items_to_state[items] = next_state
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
def set_look_ahead(rule, look_ahead)
|
92
|
+
reduce = reduces.find do |r|
|
93
|
+
r.rule == rule
|
94
|
+
end
|
95
|
+
|
96
|
+
reduce.look_ahead = look_ahead
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns array of [nterm, next_state]
|
100
|
+
def nterm_transitions
|
101
|
+
return @nterm_transitions if @nterm_transitions
|
102
|
+
|
103
|
+
@nterm_transitions = []
|
104
|
+
|
105
|
+
shifts.each do |shift|
|
106
|
+
next if shift.next_sym.term?
|
107
|
+
|
108
|
+
@nterm_transitions << [shift, @items_to_state[shift.next_items]]
|
109
|
+
end
|
110
|
+
|
111
|
+
@nterm_transitions
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns array of [term, next_state]
|
115
|
+
def term_transitions
|
116
|
+
return @term_transitions if @term_transitions
|
117
|
+
|
118
|
+
@term_transitions = []
|
119
|
+
|
120
|
+
shifts.each do |shift|
|
121
|
+
next if shift.next_sym.nterm?
|
122
|
+
|
123
|
+
@term_transitions << [shift, @items_to_state[shift.next_items]]
|
124
|
+
end
|
125
|
+
|
126
|
+
@term_transitions
|
127
|
+
end
|
128
|
+
|
129
|
+
def selected_term_transitions
|
130
|
+
term_transitions.select do |shift, next_state|
|
131
|
+
!shift.not_selected
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Move to next state by sym
|
136
|
+
def transition(sym)
|
137
|
+
result = nil
|
138
|
+
|
139
|
+
if sym.term?
|
140
|
+
term_transitions.each do |shift, next_state|
|
141
|
+
term = shift.next_sym
|
142
|
+
result = next_state if term == sym
|
143
|
+
end
|
144
|
+
else
|
145
|
+
nterm_transitions.each do |shift, next_state|
|
146
|
+
nterm = shift.next_sym
|
147
|
+
result = next_state if nterm == sym
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
raise "Can not transit by #{sym} #{self}" if result.nil?
|
152
|
+
|
153
|
+
result
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_reduce_by_item!(item)
|
157
|
+
reduces.find do |r|
|
158
|
+
r.item == item
|
159
|
+
end || (raise "reduce is not found. #{item}")
|
160
|
+
end
|
161
|
+
|
162
|
+
def default_reduction_rule=(default_reduction_rule)
|
163
|
+
@default_reduction_rule = default_reduction_rule
|
164
|
+
|
165
|
+
reduces.each do |r|
|
166
|
+
if r.rule == default_reduction_rule
|
167
|
+
r.default_reduction = true
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def sr_conflicts
|
173
|
+
@conflicts.select do |conflict|
|
174
|
+
conflict.type == :shift_reduce
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def rr_conflicts
|
179
|
+
@conflicts.select do |conflict|
|
180
|
+
conflict.type == :reduce_reduce
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/lrama/states.rb
CHANGED
@@ -2,228 +2,6 @@ require "forwardable"
|
|
2
2
|
require "lrama/report"
|
3
3
|
|
4
4
|
module Lrama
|
5
|
-
class State
|
6
|
-
class Reduce
|
7
|
-
# https://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html
|
8
|
-
attr_reader :item, :look_ahead, :not_selected_symbols
|
9
|
-
attr_accessor :default_reduction
|
10
|
-
|
11
|
-
def initialize(item)
|
12
|
-
@item = item
|
13
|
-
@look_ahead = nil
|
14
|
-
@not_selected_symbols = []
|
15
|
-
end
|
16
|
-
|
17
|
-
def rule
|
18
|
-
@item.rule
|
19
|
-
end
|
20
|
-
|
21
|
-
def look_ahead=(look_ahead)
|
22
|
-
@look_ahead = look_ahead.freeze
|
23
|
-
end
|
24
|
-
|
25
|
-
def add_not_selected_symbol(sym)
|
26
|
-
@not_selected_symbols << sym
|
27
|
-
end
|
28
|
-
|
29
|
-
def selected_look_ahead
|
30
|
-
if @look_ahead
|
31
|
-
@look_ahead - @not_selected_symbols
|
32
|
-
else
|
33
|
-
[]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
class Shift
|
39
|
-
attr_reader :next_sym, :next_items
|
40
|
-
attr_accessor :not_selected
|
41
|
-
|
42
|
-
def initialize(next_sym, next_items)
|
43
|
-
@next_sym = next_sym
|
44
|
-
@next_items = next_items
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# * symbol: A symbol under discussion
|
49
|
-
# * reduce: A reduce under discussion
|
50
|
-
# * which: For which a conflict is resolved. :shift, :reduce or :error (for nonassociative)
|
51
|
-
ResolvedConflict = Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true) do
|
52
|
-
def report_message
|
53
|
-
s = symbol.display_name
|
54
|
-
r = reduce.rule.precedence_sym.display_name
|
55
|
-
case
|
56
|
-
when which == :shift && same_prec
|
57
|
-
msg = "resolved as #{which} (%right #{s})"
|
58
|
-
when which == :shift
|
59
|
-
msg = "resolved as #{which} (#{r} < #{s})"
|
60
|
-
when which == :reduce && same_prec
|
61
|
-
msg = "resolved as #{which} (%left #{s})"
|
62
|
-
when which == :reduce
|
63
|
-
msg = "resolved as #{which} (#{s} < #{r})"
|
64
|
-
when which == :error
|
65
|
-
msg = "resolved as an #{which} (%nonassoc #{s})"
|
66
|
-
else
|
67
|
-
raise "Unknown direction. #{self}"
|
68
|
-
end
|
69
|
-
|
70
|
-
"Conflict between rule #{reduce.rule.id} and token #{s} #{msg}."
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
Conflict = Struct.new(:symbols, :reduce, :type, keyword_init: true)
|
75
|
-
|
76
|
-
attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts,
|
77
|
-
:default_reduction_rule, :closure, :items
|
78
|
-
attr_accessor :shifts, :reduces
|
79
|
-
|
80
|
-
def initialize(id, accessing_symbol, kernels)
|
81
|
-
@id = id
|
82
|
-
@accessing_symbol = accessing_symbol
|
83
|
-
@kernels = kernels.freeze
|
84
|
-
@items = @kernels
|
85
|
-
# Manage relationships between items to state
|
86
|
-
# to resolve next state
|
87
|
-
@items_to_state = {}
|
88
|
-
@conflicts = []
|
89
|
-
@resolved_conflicts = []
|
90
|
-
@default_reduction_rule = nil
|
91
|
-
end
|
92
|
-
|
93
|
-
def closure=(closure)
|
94
|
-
@closure = closure
|
95
|
-
@items = @kernels + @closure
|
96
|
-
end
|
97
|
-
|
98
|
-
def non_default_reduces
|
99
|
-
reduces.select do |reduce|
|
100
|
-
reduce.rule != @default_reduction_rule
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def compute_shifts_reduces
|
105
|
-
_shifts = {}
|
106
|
-
reduces = []
|
107
|
-
items.each do |item|
|
108
|
-
# TODO: Consider what should be pushed
|
109
|
-
if item.end_of_rule?
|
110
|
-
reduces << Reduce.new(item)
|
111
|
-
else
|
112
|
-
key = item.next_sym
|
113
|
-
_shifts[key] ||= []
|
114
|
-
_shifts[key] << item.new_by_next_position
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# It seems Bison 3.8.2 iterates transitions order by symbol number
|
119
|
-
shifts = _shifts.sort_by do |next_sym, new_items|
|
120
|
-
next_sym.number
|
121
|
-
end.map do |next_sym, new_items|
|
122
|
-
Shift.new(next_sym, new_items.flatten)
|
123
|
-
end
|
124
|
-
self.shifts = shifts.freeze
|
125
|
-
self.reduces = reduces.freeze
|
126
|
-
end
|
127
|
-
|
128
|
-
def set_items_to_state(items, next_state)
|
129
|
-
@items_to_state[items] = next_state
|
130
|
-
end
|
131
|
-
|
132
|
-
#
|
133
|
-
def set_look_ahead(rule, look_ahead)
|
134
|
-
reduce = reduces.find do |r|
|
135
|
-
r.rule == rule
|
136
|
-
end
|
137
|
-
|
138
|
-
reduce.look_ahead = look_ahead
|
139
|
-
end
|
140
|
-
|
141
|
-
# Returns array of [nterm, next_state]
|
142
|
-
def nterm_transitions
|
143
|
-
return @nterm_transitions if @nterm_transitions
|
144
|
-
|
145
|
-
@nterm_transitions = []
|
146
|
-
|
147
|
-
shifts.each do |shift|
|
148
|
-
next if shift.next_sym.term?
|
149
|
-
|
150
|
-
@nterm_transitions << [shift, @items_to_state[shift.next_items]]
|
151
|
-
end
|
152
|
-
|
153
|
-
@nterm_transitions
|
154
|
-
end
|
155
|
-
|
156
|
-
# Returns array of [term, next_state]
|
157
|
-
def term_transitions
|
158
|
-
return @term_transitions if @term_transitions
|
159
|
-
|
160
|
-
@term_transitions = []
|
161
|
-
|
162
|
-
shifts.each do |shift|
|
163
|
-
next if shift.next_sym.nterm?
|
164
|
-
|
165
|
-
@term_transitions << [shift, @items_to_state[shift.next_items]]
|
166
|
-
end
|
167
|
-
|
168
|
-
@term_transitions
|
169
|
-
end
|
170
|
-
|
171
|
-
def selected_term_transitions
|
172
|
-
term_transitions.select do |shift, next_state|
|
173
|
-
!shift.not_selected
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# Move to next state by sym
|
178
|
-
def transition(sym)
|
179
|
-
result = nil
|
180
|
-
|
181
|
-
if sym.term?
|
182
|
-
term_transitions.each do |shift, next_state|
|
183
|
-
term = shift.next_sym
|
184
|
-
result = next_state if term == sym
|
185
|
-
end
|
186
|
-
else
|
187
|
-
nterm_transitions.each do |shift, next_state|
|
188
|
-
nterm = shift.next_sym
|
189
|
-
result = next_state if nterm == sym
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
raise "Can not transit by #{sym} #{self}" if result.nil?
|
194
|
-
|
195
|
-
result
|
196
|
-
end
|
197
|
-
|
198
|
-
def find_reduce_by_item!(item)
|
199
|
-
reduces.find do |r|
|
200
|
-
r.item == item
|
201
|
-
end || (raise "reduce is not found. #{item}, #{state}")
|
202
|
-
end
|
203
|
-
|
204
|
-
def default_reduction_rule=(default_reduction_rule)
|
205
|
-
@default_reduction_rule = default_reduction_rule
|
206
|
-
|
207
|
-
reduces.each do |r|
|
208
|
-
if r.rule == default_reduction_rule
|
209
|
-
r.default_reduction = true
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def sr_conflicts
|
215
|
-
@conflicts.select do |conflict|
|
216
|
-
conflict.type == :shift_reduce
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def rr_conflicts
|
221
|
-
@conflicts.select do |conflict|
|
222
|
-
conflict.type == :reduce_reduce
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
5
|
# States is passed to a template file
|
228
6
|
#
|
229
7
|
# "Efficient Computation of LALR(1) Look-Ahead Sets"
|
@@ -411,23 +189,13 @@ module Lrama
|
|
411
189
|
@states.flat_map(&:rr_conflicts)
|
412
190
|
end
|
413
191
|
|
414
|
-
def initial_attrs
|
415
|
-
h = {}
|
416
|
-
|
417
|
-
attrs.each do |attr|
|
418
|
-
h[attr.id] = false
|
419
|
-
end
|
420
|
-
|
421
|
-
h
|
422
|
-
end
|
423
|
-
|
424
192
|
def trace_state
|
425
193
|
if @trace_state
|
426
194
|
yield STDERR
|
427
195
|
end
|
428
196
|
end
|
429
197
|
|
430
|
-
def create_state(accessing_symbol, kernels,
|
198
|
+
def create_state(accessing_symbol, kernels, states_created)
|
431
199
|
# A item can appear in some states,
|
432
200
|
# so need to use `kernels` (not `kernels.first`) as a key.
|
433
201
|
#
|
@@ -464,11 +232,11 @@ module Lrama
|
|
464
232
|
# string_1: string •
|
465
233
|
# string_2: string • '+'
|
466
234
|
#
|
467
|
-
return [
|
235
|
+
return [states_created[kernels], false] if states_created[kernels]
|
468
236
|
|
469
237
|
state = State.new(@states.count, accessing_symbol, kernels)
|
470
238
|
@states << state
|
471
|
-
|
239
|
+
states_created[kernels] = state
|
472
240
|
|
473
241
|
return [state, true]
|
474
242
|
end
|
@@ -532,9 +300,9 @@ module Lrama
|
|
532
300
|
def compute_lr0_states
|
533
301
|
# State queue
|
534
302
|
states = []
|
535
|
-
|
303
|
+
states_created = {}
|
536
304
|
|
537
|
-
state, _ = create_state(symbols.first, [Item.new(rule: @grammar.rules.first, position: 0)],
|
305
|
+
state, _ = create_state(symbols.first, [Item.new(rule: @grammar.rules.first, position: 0)], states_created)
|
538
306
|
enqueue_state(states, state)
|
539
307
|
|
540
308
|
while (state = states.shift) do
|
@@ -550,7 +318,7 @@ module Lrama
|
|
550
318
|
setup_state(state)
|
551
319
|
|
552
320
|
state.shifts.each do |shift|
|
553
|
-
new_state, created = create_state(shift.next_sym, shift.next_items,
|
321
|
+
new_state, created = create_state(shift.next_sym, shift.next_items, states_created)
|
554
322
|
state.set_items_to_state(shift.next_items, new_state)
|
555
323
|
enqueue_state(states, new_state) if created
|
556
324
|
end
|
@@ -210,7 +210,7 @@ module Lrama
|
|
210
210
|
io << "\n"
|
211
211
|
|
212
212
|
|
213
|
-
#
|
213
|
+
# Report reads_relation
|
214
214
|
io << " [Reads Relation]\n"
|
215
215
|
@states.nterms.each do |nterm|
|
216
216
|
a = @states.reads_relation[[state.id, nterm.token_id]]
|
@@ -224,7 +224,7 @@ module Lrama
|
|
224
224
|
io << "\n"
|
225
225
|
|
226
226
|
|
227
|
-
#
|
227
|
+
# Report read_sets
|
228
228
|
io << " [Read sets]\n"
|
229
229
|
read_sets = @states.read_sets
|
230
230
|
@states.nterms.each do |nterm|
|
@@ -239,7 +239,7 @@ module Lrama
|
|
239
239
|
io << "\n"
|
240
240
|
|
241
241
|
|
242
|
-
#
|
242
|
+
# Report includes_relation
|
243
243
|
io << " [Includes Relation]\n"
|
244
244
|
@states.nterms.each do |nterm|
|
245
245
|
a = @states.includes_relation[[state.id, nterm.token_id]]
|
@@ -267,7 +267,7 @@ module Lrama
|
|
267
267
|
io << "\n"
|
268
268
|
|
269
269
|
|
270
|
-
#
|
270
|
+
# Report follow_sets
|
271
271
|
io << " [Follow sets]\n"
|
272
272
|
follow_sets = @states.follow_sets
|
273
273
|
@states.nterms.each do |nterm|
|
data/lib/lrama/version.rb
CHANGED
data/lib/lrama.rb
CHANGED
data/sample/calc.output
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
Grammar
|
2
|
+
|
3
|
+
0 $accept: list "end of file"
|
4
|
+
|
5
|
+
1 list: ε
|
6
|
+
2 | list LF
|
7
|
+
3 | list expr LF
|
8
|
+
|
9
|
+
4 expr: NUM
|
10
|
+
5 | expr '+' expr
|
11
|
+
6 | expr '-' expr
|
12
|
+
7 | expr '*' expr
|
13
|
+
8 | expr '/' expr
|
14
|
+
9 | '(' expr ')'
|
15
|
+
|
16
|
+
|
17
|
+
State 0
|
18
|
+
|
19
|
+
0 $accept: • list "end of file"
|
20
|
+
1 list: ε •
|
21
|
+
2 | • list LF
|
22
|
+
3 | • list expr LF
|
23
|
+
|
24
|
+
$default reduce using rule 1 (list)
|
25
|
+
|
26
|
+
list go to state 1
|
27
|
+
|
28
|
+
|
29
|
+
State 1
|
30
|
+
|
31
|
+
0 $accept: list • "end of file"
|
32
|
+
2 list: list • LF
|
33
|
+
3 | list • expr LF
|
34
|
+
4 expr: • NUM
|
35
|
+
5 | • expr '+' expr
|
36
|
+
6 | • expr '-' expr
|
37
|
+
7 | • expr '*' expr
|
38
|
+
8 | • expr '/' expr
|
39
|
+
9 | • '(' expr ')'
|
40
|
+
|
41
|
+
"end of file" shift, and go to state 2
|
42
|
+
LF shift, and go to state 3
|
43
|
+
NUM shift, and go to state 4
|
44
|
+
'(' shift, and go to state 5
|
45
|
+
|
46
|
+
expr go to state 6
|
47
|
+
|
48
|
+
|
49
|
+
State 2
|
50
|
+
|
51
|
+
0 $accept: list "end of file" •
|
52
|
+
|
53
|
+
$default accept
|
54
|
+
|
55
|
+
|
56
|
+
State 3
|
57
|
+
|
58
|
+
2 list: list LF •
|
59
|
+
|
60
|
+
$default reduce using rule 2 (list)
|
61
|
+
|
62
|
+
|
63
|
+
State 4
|
64
|
+
|
65
|
+
4 expr: NUM •
|
66
|
+
|
67
|
+
$default reduce using rule 4 (expr)
|
68
|
+
|
69
|
+
|
70
|
+
State 5
|
71
|
+
|
72
|
+
4 expr: • NUM
|
73
|
+
5 | • expr '+' expr
|
74
|
+
6 | • expr '-' expr
|
75
|
+
7 | • expr '*' expr
|
76
|
+
8 | • expr '/' expr
|
77
|
+
9 | • '(' expr ')'
|
78
|
+
9 | '(' • expr ')'
|
79
|
+
|
80
|
+
NUM shift, and go to state 4
|
81
|
+
'(' shift, and go to state 5
|
82
|
+
|
83
|
+
expr go to state 7
|
84
|
+
|
85
|
+
|
86
|
+
State 6
|
87
|
+
|
88
|
+
3 list: list expr • LF
|
89
|
+
5 expr: expr • '+' expr
|
90
|
+
6 | expr • '-' expr
|
91
|
+
7 | expr • '*' expr
|
92
|
+
8 | expr • '/' expr
|
93
|
+
|
94
|
+
LF shift, and go to state 8
|
95
|
+
'+' shift, and go to state 9
|
96
|
+
'-' shift, and go to state 10
|
97
|
+
'*' shift, and go to state 11
|
98
|
+
'/' shift, and go to state 12
|
99
|
+
|
100
|
+
|
101
|
+
State 7
|
102
|
+
|
103
|
+
5 expr: expr • '+' expr
|
104
|
+
6 | expr • '-' expr
|
105
|
+
7 | expr • '*' expr
|
106
|
+
8 | expr • '/' expr
|
107
|
+
9 | '(' expr • ')'
|
108
|
+
|
109
|
+
'+' shift, and go to state 9
|
110
|
+
'-' shift, and go to state 10
|
111
|
+
'*' shift, and go to state 11
|
112
|
+
'/' shift, and go to state 12
|
113
|
+
')' shift, and go to state 13
|
114
|
+
|
115
|
+
|
116
|
+
State 8
|
117
|
+
|
118
|
+
3 list: list expr LF •
|
119
|
+
|
120
|
+
$default reduce using rule 3 (list)
|
121
|
+
|
122
|
+
|
123
|
+
State 9
|
124
|
+
|
125
|
+
4 expr: • NUM
|
126
|
+
5 | • expr '+' expr
|
127
|
+
5 | expr '+' • expr
|
128
|
+
6 | • expr '-' expr
|
129
|
+
7 | • expr '*' expr
|
130
|
+
8 | • expr '/' expr
|
131
|
+
9 | • '(' expr ')'
|
132
|
+
|
133
|
+
NUM shift, and go to state 4
|
134
|
+
'(' shift, and go to state 5
|
135
|
+
|
136
|
+
expr go to state 14
|
137
|
+
|
138
|
+
|
139
|
+
State 10
|
140
|
+
|
141
|
+
4 expr: • NUM
|
142
|
+
5 | • expr '+' expr
|
143
|
+
6 | • expr '-' expr
|
144
|
+
6 | expr '-' • expr
|
145
|
+
7 | • expr '*' expr
|
146
|
+
8 | • expr '/' expr
|
147
|
+
9 | • '(' expr ')'
|
148
|
+
|
149
|
+
NUM shift, and go to state 4
|
150
|
+
'(' shift, and go to state 5
|
151
|
+
|
152
|
+
expr go to state 15
|
153
|
+
|
154
|
+
|
155
|
+
State 11
|
156
|
+
|
157
|
+
4 expr: • NUM
|
158
|
+
5 | • expr '+' expr
|
159
|
+
6 | • expr '-' expr
|
160
|
+
7 | • expr '*' expr
|
161
|
+
7 | expr '*' • expr
|
162
|
+
8 | • expr '/' expr
|
163
|
+
9 | • '(' expr ')'
|
164
|
+
|
165
|
+
NUM shift, and go to state 4
|
166
|
+
'(' shift, and go to state 5
|
167
|
+
|
168
|
+
expr go to state 16
|
169
|
+
|
170
|
+
|
171
|
+
State 12
|
172
|
+
|
173
|
+
4 expr: • NUM
|
174
|
+
5 | • expr '+' expr
|
175
|
+
6 | • expr '-' expr
|
176
|
+
7 | • expr '*' expr
|
177
|
+
8 | • expr '/' expr
|
178
|
+
8 | expr '/' • expr
|
179
|
+
9 | • '(' expr ')'
|
180
|
+
|
181
|
+
NUM shift, and go to state 4
|
182
|
+
'(' shift, and go to state 5
|
183
|
+
|
184
|
+
expr go to state 17
|
185
|
+
|
186
|
+
|
187
|
+
State 13
|
188
|
+
|
189
|
+
9 expr: '(' expr ')' •
|
190
|
+
|
191
|
+
$default reduce using rule 9 (expr)
|
192
|
+
|
193
|
+
|
194
|
+
State 14
|
195
|
+
|
196
|
+
5 expr: expr • '+' expr
|
197
|
+
5 | expr '+' expr • [LF, '+', '-', ')']
|
198
|
+
6 | expr • '-' expr
|
199
|
+
7 | expr • '*' expr
|
200
|
+
8 | expr • '/' expr
|
201
|
+
|
202
|
+
'*' shift, and go to state 11
|
203
|
+
'/' shift, and go to state 12
|
204
|
+
|
205
|
+
$default reduce using rule 5 (expr)
|
206
|
+
|
207
|
+
Conflict between rule 5 and token '+' resolved as reduce (%left '+').
|
208
|
+
Conflict between rule 5 and token '-' resolved as reduce (%left '-').
|
209
|
+
Conflict between rule 5 and token '*' resolved as shift ('+' < '*').
|
210
|
+
Conflict between rule 5 and token '/' resolved as shift ('+' < '/').
|
211
|
+
|
212
|
+
|
213
|
+
State 15
|
214
|
+
|
215
|
+
5 expr: expr • '+' expr
|
216
|
+
6 | expr • '-' expr
|
217
|
+
6 | expr '-' expr • [LF, '+', '-', ')']
|
218
|
+
7 | expr • '*' expr
|
219
|
+
8 | expr • '/' expr
|
220
|
+
|
221
|
+
'*' shift, and go to state 11
|
222
|
+
'/' shift, and go to state 12
|
223
|
+
|
224
|
+
$default reduce using rule 6 (expr)
|
225
|
+
|
226
|
+
Conflict between rule 6 and token '+' resolved as reduce (%left '+').
|
227
|
+
Conflict between rule 6 and token '-' resolved as reduce (%left '-').
|
228
|
+
Conflict between rule 6 and token '*' resolved as shift ('-' < '*').
|
229
|
+
Conflict between rule 6 and token '/' resolved as shift ('-' < '/').
|
230
|
+
|
231
|
+
|
232
|
+
State 16
|
233
|
+
|
234
|
+
5 expr: expr • '+' expr
|
235
|
+
6 | expr • '-' expr
|
236
|
+
7 | expr • '*' expr
|
237
|
+
7 | expr '*' expr • [LF, '+', '-', '*', '/', ')']
|
238
|
+
8 | expr • '/' expr
|
239
|
+
|
240
|
+
$default reduce using rule 7 (expr)
|
241
|
+
|
242
|
+
Conflict between rule 7 and token '+' resolved as reduce ('+' < '*').
|
243
|
+
Conflict between rule 7 and token '-' resolved as reduce ('-' < '*').
|
244
|
+
Conflict between rule 7 and token '*' resolved as reduce (%left '*').
|
245
|
+
Conflict between rule 7 and token '/' resolved as reduce (%left '/').
|
246
|
+
|
247
|
+
|
248
|
+
State 17
|
249
|
+
|
250
|
+
5 expr: expr • '+' expr
|
251
|
+
6 | expr • '-' expr
|
252
|
+
7 | expr • '*' expr
|
253
|
+
8 | expr • '/' expr
|
254
|
+
8 | expr '/' expr • [LF, '+', '-', '*', '/', ')']
|
255
|
+
|
256
|
+
$default reduce using rule 8 (expr)
|
257
|
+
|
258
|
+
Conflict between rule 8 and token '+' resolved as reduce ('+' < '/').
|
259
|
+
Conflict between rule 8 and token '-' resolved as reduce ('-' < '/').
|
260
|
+
Conflict between rule 8 and token '*' resolved as reduce (%left '*').
|
261
|
+
Conflict between rule 8 and token '/' resolved as reduce (%left '/').
|
262
|
+
|
263
|
+
|
data/sample/calc.y
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
/*
|
2
|
+
* How to build and run:
|
3
|
+
*
|
4
|
+
* $ lrama -d calc.y -o calc.c && gcc -Wall calc.c -o calc && ./calc
|
5
|
+
* 1
|
6
|
+
* => 1
|
7
|
+
* 1+2*3
|
8
|
+
* => 7
|
9
|
+
* (1+2)*3
|
10
|
+
* => 9
|
11
|
+
*
|
12
|
+
*/
|
13
|
+
|
14
|
+
%{
|
15
|
+
#include <stdio.h>
|
16
|
+
#include <stdlib.h>
|
17
|
+
#include <ctype.h>
|
18
|
+
|
19
|
+
#include "calc.h"
|
20
|
+
|
21
|
+
static int yylex(YYSTYPE *val, YYLTYPE *loc);
|
22
|
+
static int yyerror(YYLTYPE *loc, const char *str);
|
23
|
+
%}
|
24
|
+
|
25
|
+
%union {
|
26
|
+
int val;
|
27
|
+
}
|
28
|
+
%token LF
|
29
|
+
%token <val> NUM
|
30
|
+
%type <val> expr
|
31
|
+
%left '+' '-'
|
32
|
+
%left '*' '/'
|
33
|
+
|
34
|
+
%%
|
35
|
+
|
36
|
+
list : /* empty */
|
37
|
+
| list LF
|
38
|
+
| list expr LF { printf("=> %d\n", $2); }
|
39
|
+
;
|
40
|
+
expr : NUM
|
41
|
+
| expr '+' expr { $$ = $1 + $3; }
|
42
|
+
| expr '-' expr { $$ = $1 - $3; }
|
43
|
+
| expr '*' expr { $$ = $1 * $3; }
|
44
|
+
| expr '/' expr { $$ = $1 / $3; }
|
45
|
+
| '(' expr ')' { $$ = $2; }
|
46
|
+
;
|
47
|
+
|
48
|
+
%%
|
49
|
+
|
50
|
+
static int yylex(YYSTYPE *yylval, YYLTYPE *loc) {
|
51
|
+
int c = getchar();
|
52
|
+
int val;
|
53
|
+
|
54
|
+
switch (c) {
|
55
|
+
case ' ': case '\t':
|
56
|
+
return yylex(yylval, loc);
|
57
|
+
|
58
|
+
case '0': case '1': case '2': case '3': case '4':
|
59
|
+
case '5': case '6': case '7': case '8': case '9':
|
60
|
+
val = c - '0';
|
61
|
+
while (1) {
|
62
|
+
c = getchar();
|
63
|
+
if (isdigit(c)) {
|
64
|
+
val = val * 10 + (c - '0');
|
65
|
+
}
|
66
|
+
else {
|
67
|
+
ungetc(c, stdin);
|
68
|
+
break;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
yylval->val = val;
|
72
|
+
return NUM;
|
73
|
+
|
74
|
+
case '\n':
|
75
|
+
return LF;
|
76
|
+
|
77
|
+
case '+': case '-': case '*': case '/': case '(': case ')':
|
78
|
+
return c;
|
79
|
+
|
80
|
+
case EOF:
|
81
|
+
exit(0);
|
82
|
+
|
83
|
+
default:
|
84
|
+
fprintf(stderr, "unknown character: %c\n", c);
|
85
|
+
exit(1);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
static int yyerror(YYLTYPE *loc, const char *str) {
|
90
|
+
fprintf(stderr, "parse error: %s\n", str);
|
91
|
+
return 0;
|
92
|
+
}
|
93
|
+
|
94
|
+
int main() {
|
95
|
+
printf("Enter the formula:\n");
|
96
|
+
yyparse();
|
97
|
+
return 0;
|
98
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lrama
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yuichiro Kaneko
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: LALR (1) parser generator written by Ruby
|
14
14
|
email:
|
@@ -18,6 +18,7 @@ executables:
|
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
|
+
- ".github/dependabot.yml"
|
21
22
|
- ".github/workflows/test.yaml"
|
22
23
|
- ".gitignore"
|
23
24
|
- ".rspec"
|
@@ -26,6 +27,7 @@ files:
|
|
26
27
|
- MIT
|
27
28
|
- README.md
|
28
29
|
- Rakefile
|
30
|
+
- Steepfile
|
29
31
|
- doc/TODO.md
|
30
32
|
- exe/lrama
|
31
33
|
- lib/lrama.rb
|
@@ -37,13 +39,20 @@ files:
|
|
37
39
|
- lib/lrama/lexer.rb
|
38
40
|
- lib/lrama/output.rb
|
39
41
|
- lib/lrama/parser.rb
|
42
|
+
- lib/lrama/parser/token_scanner.rb
|
40
43
|
- lib/lrama/report.rb
|
44
|
+
- lib/lrama/state.rb
|
45
|
+
- lib/lrama/state/reduce.rb
|
46
|
+
- lib/lrama/state/shift.rb
|
41
47
|
- lib/lrama/states.rb
|
42
48
|
- lib/lrama/states_reporter.rb
|
43
49
|
- lib/lrama/version.rb
|
44
50
|
- lib/lrama/warning.rb
|
45
51
|
- lrama.gemspec
|
52
|
+
- sample/calc.output
|
53
|
+
- sample/calc.y
|
46
54
|
- sample/parse.y
|
55
|
+
- sig/lrama/bitmap.rbs
|
47
56
|
- template/bison/yacc.c
|
48
57
|
- template/bison/yacc.h
|
49
58
|
homepage: https://github.com/yui-knk/lrama
|
@@ -65,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
74
|
- !ruby/object:Gem::Version
|
66
75
|
version: '0'
|
67
76
|
requirements: []
|
68
|
-
rubygems_version: 3.
|
77
|
+
rubygems_version: 3.5.0.dev
|
69
78
|
signing_key:
|
70
79
|
specification_version: 4
|
71
80
|
summary: LALR (1) parser generator written by Ruby
|