lrama 0.5.4 → 0.5.6
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/workflows/test.yaml +18 -4
- data/Gemfile +3 -2
- data/exe/lrama +1 -2
- data/lib/lrama/command.rb +15 -142
- data/lib/lrama/context.rb +0 -2
- data/lib/lrama/counterexamples.rb +1 -3
- data/lib/lrama/grammar/code.rb +0 -1
- data/lib/lrama/grammar.rb +5 -2
- data/lib/lrama/lexer/token.rb +8 -1
- data/lib/lrama/lexer.rb +18 -5
- data/lib/lrama/option_parser.rb +124 -0
- data/lib/lrama/options.rb +23 -0
- data/lib/lrama/output.rb +1 -1
- data/lib/lrama/parser.rb +8 -0
- data/lib/lrama/state.rb +0 -1
- data/lib/lrama/states.rb +7 -2
- data/lib/lrama/states_reporter.rb +4 -16
- data/lib/lrama/version.rb +1 -1
- data/lib/lrama.rb +2 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e54c51af6f1d3632293cbd7f68762bf7cff63758d3ce633113805a629b92072b
|
4
|
+
data.tar.gz: 5d053543f0e00c9fb20c40f5ca8236e499292f35fa8cf6fb8e85cac33b930814
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87bafe9650720b154855e055e53d700cfba67d31489dbc855c716bc150dff35e92d0fc974c14cb81a07ae68febbd69118c1720a628e2819fec1ebfa304acad55
|
7
|
+
data.tar.gz: 19b0c51748cef053d205bbf13e8ff238bda2fdbb101d304f6bfe9633b4f88fe2c4a1f452627528fd2c9e78bc97955a2a5f7543212aa581c3a9f7ae96dd3d70f8
|
data/.github/workflows/test.yaml
CHANGED
@@ -15,7 +15,21 @@ jobs:
|
|
15
15
|
matrix:
|
16
16
|
ruby: ['head', '3.2', '3.1', '3.0', '2.7', '2.6', '2.5']
|
17
17
|
steps:
|
18
|
-
- uses: actions/checkout@
|
18
|
+
- uses: actions/checkout@v4
|
19
|
+
- uses: ruby/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: ${{ matrix.ruby }}
|
22
|
+
bundler-cache: true
|
23
|
+
- run: bundle install
|
24
|
+
- run: bundle exec rspec
|
25
|
+
test-windows:
|
26
|
+
runs-on: windows-2022
|
27
|
+
strategy:
|
28
|
+
fail-fast: false
|
29
|
+
matrix:
|
30
|
+
ruby: ['head']
|
31
|
+
steps:
|
32
|
+
- uses: actions/checkout@v4
|
19
33
|
- uses: ruby/setup-ruby@v1
|
20
34
|
with:
|
21
35
|
ruby-version: ${{ matrix.ruby }}
|
@@ -25,7 +39,7 @@ jobs:
|
|
25
39
|
check-misc:
|
26
40
|
runs-on: ubuntu-20.04
|
27
41
|
steps:
|
28
|
-
- uses: actions/checkout@
|
42
|
+
- uses: actions/checkout@v4
|
29
43
|
# Copy from https://github.com/ruby/ruby/blob/089227e94823542acfdafa68541d330eee42ffea/.github/workflows/check_misc.yml#L27
|
30
44
|
- name: Check for trailing spaces
|
31
45
|
run: |
|
@@ -38,7 +52,7 @@ jobs:
|
|
38
52
|
matrix:
|
39
53
|
ruby: ['head']
|
40
54
|
steps:
|
41
|
-
- uses: actions/checkout@
|
55
|
+
- uses: actions/checkout@v4
|
42
56
|
- uses: ruby/setup-ruby@v1
|
43
57
|
with:
|
44
58
|
ruby-version: ${{ matrix.ruby }}
|
@@ -57,7 +71,7 @@ jobs:
|
|
57
71
|
run:
|
58
72
|
working-directory: ../ruby/build
|
59
73
|
steps:
|
60
|
-
- uses: actions/checkout@
|
74
|
+
- uses: actions/checkout@v4
|
61
75
|
- uses: ruby/setup-ruby@v1
|
62
76
|
with:
|
63
77
|
ruby-version: ${{ matrix.baseruby }}
|
data/Gemfile
CHANGED
@@ -4,8 +4,9 @@ gemspec
|
|
4
4
|
|
5
5
|
gem "rspec"
|
6
6
|
gem "pry"
|
7
|
-
|
7
|
+
# stackprof doesn't support Windows
|
8
|
+
gem "stackprof", platforms: [:ruby]
|
8
9
|
gem "rake"
|
9
10
|
gem "rbs", require: false
|
10
11
|
gem "steep", require: false
|
11
|
-
gem "simplecov", require: false
|
12
|
+
gem "simplecov", require: false
|
data/exe/lrama
CHANGED
data/lib/lrama/command.rb
CHANGED
@@ -1,58 +1,34 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
|
3
1
|
module Lrama
|
4
2
|
class Command
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
@version = nil
|
9
|
-
@skeleton = "bison/yacc.c"
|
10
|
-
@header = false
|
11
|
-
@header_file = nil
|
12
|
-
@report = []
|
13
|
-
@report_file = nil
|
14
|
-
@outfile = "y.tab.c"
|
15
|
-
@trace = []
|
16
|
-
@error_recovery = false
|
17
|
-
@grammar_file = nil
|
18
|
-
@report_file = nil
|
19
|
-
@trace_opts = nil
|
20
|
-
@report_opts = nil
|
21
|
-
end
|
22
|
-
|
23
|
-
def run
|
24
|
-
parse_option
|
25
|
-
|
26
|
-
if @version
|
27
|
-
puts Lrama::VERSION
|
28
|
-
exit 0
|
29
|
-
end
|
3
|
+
def run(argv)
|
4
|
+
options = OptionParser.new.parse(argv)
|
30
5
|
|
31
|
-
Report::Duration.enable if
|
6
|
+
Report::Duration.enable if options.trace_opts[:time]
|
32
7
|
|
33
8
|
warning = Lrama::Warning.new
|
34
|
-
grammar = Lrama::Parser.new(
|
35
|
-
|
9
|
+
grammar = Lrama::Parser.new(options.y.read).parse
|
10
|
+
options.y.close if options.y != STDIN
|
11
|
+
states = Lrama::States.new(grammar, warning, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
|
36
12
|
states.compute
|
37
13
|
context = Lrama::Context.new(states)
|
38
14
|
|
39
|
-
if
|
15
|
+
if options.report_file
|
40
16
|
reporter = Lrama::StatesReporter.new(states)
|
41
|
-
File.open(
|
42
|
-
reporter.report(f,
|
17
|
+
File.open(options.report_file, "w+") do |f|
|
18
|
+
reporter.report(f, **options.report_opts)
|
43
19
|
end
|
44
20
|
end
|
45
21
|
|
46
|
-
File.open(
|
22
|
+
File.open(options.outfile, "w+") do |f|
|
47
23
|
Lrama::Output.new(
|
48
24
|
out: f,
|
49
|
-
output_file_path:
|
50
|
-
template_name:
|
51
|
-
grammar_file_path:
|
52
|
-
header_file_path:
|
25
|
+
output_file_path: options.outfile,
|
26
|
+
template_name: options.skeleton,
|
27
|
+
grammar_file_path: options.grammar_file,
|
28
|
+
header_file_path: options.header_file,
|
53
29
|
context: context,
|
54
30
|
grammar: grammar,
|
55
|
-
error_recovery:
|
31
|
+
error_recovery: options.error_recovery,
|
56
32
|
).render
|
57
33
|
end
|
58
34
|
|
@@ -60,108 +36,5 @@ module Lrama
|
|
60
36
|
exit 1
|
61
37
|
end
|
62
38
|
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def validate_report(report)
|
67
|
-
bison_list = %w[states itemsets lookaheads solved counterexamples cex all none]
|
68
|
-
others = %w[verbose]
|
69
|
-
list = bison_list + others
|
70
|
-
not_supported = %w[cex none]
|
71
|
-
h = { grammar: true }
|
72
|
-
|
73
|
-
report.each do |r|
|
74
|
-
if list.include?(r) && !not_supported.include?(r)
|
75
|
-
h[r.to_sym] = true
|
76
|
-
else
|
77
|
-
raise "Invalid report option \"#{r}\"."
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
if h[:all]
|
82
|
-
(bison_list - not_supported).each do |r|
|
83
|
-
h[r.to_sym] = true
|
84
|
-
end
|
85
|
-
|
86
|
-
h.delete(:all)
|
87
|
-
end
|
88
|
-
|
89
|
-
return h
|
90
|
-
end
|
91
|
-
|
92
|
-
def validate_trace(trace)
|
93
|
-
list = %w[
|
94
|
-
none locations scan parse automaton bitsets
|
95
|
-
closure grammar resource sets muscles tools
|
96
|
-
m4-early m4 skeleton time ielr cex all
|
97
|
-
]
|
98
|
-
h = {}
|
99
|
-
|
100
|
-
trace.each do |t|
|
101
|
-
if list.include?(t)
|
102
|
-
h[t.to_sym] = true
|
103
|
-
else
|
104
|
-
raise "Invalid trace option \"#{t}\"."
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
return h
|
109
|
-
end
|
110
|
-
|
111
|
-
def parse_option
|
112
|
-
opt = OptionParser.new
|
113
|
-
|
114
|
-
# opt.on('-h') {|v| p v }
|
115
|
-
opt.on('-V', '--version') {|v| @version = true }
|
116
|
-
|
117
|
-
# Tuning the Parser
|
118
|
-
opt.on('-S', '--skeleton=FILE') {|v| @skeleton = v }
|
119
|
-
opt.on('-t') { } # Do nothing
|
120
|
-
|
121
|
-
# Output Files:
|
122
|
-
opt.on('-h', '--header=[FILE]') {|v| @header = true; @header_file = v }
|
123
|
-
opt.on('-d') { @header = true }
|
124
|
-
opt.on('-r', '--report=THINGS', Array) {|v| @report = v }
|
125
|
-
opt.on('--report-file=FILE') {|v| @report_file = v }
|
126
|
-
opt.on('-v') { } # Do nothing
|
127
|
-
opt.on('-o', '--output=FILE') {|v| @outfile = v }
|
128
|
-
|
129
|
-
# Hidden
|
130
|
-
opt.on('--trace=THINGS', Array) {|v| @trace = v }
|
131
|
-
|
132
|
-
# Error Recovery
|
133
|
-
opt.on('-e') {|v| @error_recovery = true }
|
134
|
-
|
135
|
-
opt.parse!(@argv)
|
136
|
-
|
137
|
-
@trace_opts = validate_trace(@trace)
|
138
|
-
@report_opts = validate_report(@report)
|
139
|
-
|
140
|
-
@grammar_file = @argv.shift
|
141
|
-
|
142
|
-
if !@grammar_file
|
143
|
-
abort "File should be specified\n"
|
144
|
-
end
|
145
|
-
|
146
|
-
if @grammar_file == '-'
|
147
|
-
@grammar_file = @argv.shift or abort "File name for STDIN should be specified\n"
|
148
|
-
@y = STDIN
|
149
|
-
else
|
150
|
-
@y = File.open(@grammar_file, 'r')
|
151
|
-
end
|
152
|
-
|
153
|
-
if !@report.empty? && @report_file.nil? && @grammar_file
|
154
|
-
@report_file = File.dirname(@grammar_file) + "/" + File.basename(@grammar_file, ".*") + ".output"
|
155
|
-
end
|
156
|
-
|
157
|
-
if !@header_file && @header
|
158
|
-
case
|
159
|
-
when @outfile
|
160
|
-
@header_file = File.dirname(@outfile) + "/" + File.basename(@outfile, ".*") + ".h"
|
161
|
-
when @grammar_file
|
162
|
-
@header_file = File.dirname(@grammar_file) + "/" + File.basename(@grammar_file, ".*") + ".h"
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
39
|
end
|
167
40
|
end
|
data/lib/lrama/context.rb
CHANGED
@@ -401,7 +401,6 @@ module Lrama
|
|
401
401
|
end
|
402
402
|
print sprintf("]\n\n")
|
403
403
|
|
404
|
-
|
405
404
|
print sprintf("width [\n")
|
406
405
|
vectors_count.times do |i|
|
407
406
|
print sprintf("%d, ", ary[i] ? ary[i][3] : 0)
|
@@ -409,7 +408,6 @@ module Lrama
|
|
409
408
|
end
|
410
409
|
print sprintf("]\n\n")
|
411
410
|
|
412
|
-
|
413
411
|
print sprintf("tally [\n")
|
414
412
|
vectors_count.times do |i|
|
415
413
|
print sprintf("%d, ", ary[i] ? ary[i][2] : 0)
|
@@ -205,7 +205,7 @@ module Lrama
|
|
205
205
|
end
|
206
206
|
|
207
207
|
def build_paths_from_state_items(state_items)
|
208
|
-
|
208
|
+
state_items.zip([nil] + state_items).map do |si, prev_si|
|
209
209
|
case
|
210
210
|
when prev_si.nil?
|
211
211
|
StartPath.new(si)
|
@@ -215,8 +215,6 @@ module Lrama
|
|
215
215
|
TransitionPath.new(prev_si, si)
|
216
216
|
end
|
217
217
|
end
|
218
|
-
|
219
|
-
paths
|
220
218
|
end
|
221
219
|
|
222
220
|
def shortest_path(conflict_state, conflict_reduce_item, conflict_term)
|
data/lib/lrama/grammar/code.rb
CHANGED
data/lib/lrama/grammar.rb
CHANGED
@@ -103,6 +103,10 @@ module Lrama
|
|
103
103
|
set_precedence(sym, Precedence.new(type: :right, precedence: precedence))
|
104
104
|
end
|
105
105
|
|
106
|
+
def add_precedence(sym, precedence)
|
107
|
+
set_precedence(sym, Precedence.new(type: :precedence, precedence: precedence))
|
108
|
+
end
|
109
|
+
|
106
110
|
def set_precedence(sym, precedence)
|
107
111
|
raise "" if sym.nterm?
|
108
112
|
sym.precedence = precedence
|
@@ -310,7 +314,6 @@ module Lrama
|
|
310
314
|
end || (raise "Nterm not found: #{id}")
|
311
315
|
end
|
312
316
|
|
313
|
-
|
314
317
|
def append_special_symbols
|
315
318
|
# YYEMPTY (token_id: -2, number: -2) is added when a template is evaluated
|
316
319
|
# term = add_term(id: Token.new(Token::Ident, "YYEMPTY"), token_id: -2)
|
@@ -512,7 +515,7 @@ module Lrama
|
|
512
515
|
sym.token_id = 11
|
513
516
|
when "\""
|
514
517
|
sym.token_id = 34
|
515
|
-
when "
|
518
|
+
when "'"
|
516
519
|
sym.token_id = 39
|
517
520
|
when "\\\\"
|
518
521
|
sym.token_id = 92
|
data/lib/lrama/lexer/token.rb
CHANGED
@@ -28,7 +28,13 @@ module Lrama
|
|
28
28
|
if lhs.referred_by?(ref_name)
|
29
29
|
'$'
|
30
30
|
else
|
31
|
-
rhs.find_index {|token| token.referred_by?(ref_name) }
|
31
|
+
index = rhs.find_index {|token| token.referred_by?(ref_name) }
|
32
|
+
|
33
|
+
if index
|
34
|
+
index + 1
|
35
|
+
else
|
36
|
+
raise "'#{ref_name}' is invalid name."
|
37
|
+
end
|
32
38
|
end
|
33
39
|
[ref[0], value, ref[2], ref[3], ref[4]]
|
34
40
|
else
|
@@ -61,6 +67,7 @@ module Lrama
|
|
61
67
|
define_type(:P_nonassoc) # %nonassoc
|
62
68
|
define_type(:P_left) # %left
|
63
69
|
define_type(:P_right) # %right
|
70
|
+
define_type(:P_precedence) # %precedence
|
64
71
|
define_type(:P_prec) # %prec
|
65
72
|
define_type(:User_code) # { ... }
|
66
73
|
define_type(:Tag) # <int>
|
data/lib/lrama/lexer.rb
CHANGED
@@ -30,7 +30,6 @@ module Lrama
|
|
30
30
|
@grammar_rules = []
|
31
31
|
@epilogue = []
|
32
32
|
|
33
|
-
#
|
34
33
|
@bison_declarations_tokens = []
|
35
34
|
@grammar_rules_tokens = []
|
36
35
|
|
@@ -155,6 +154,8 @@ module Lrama
|
|
155
154
|
tokens << create_token(Token::P_left, ss[0], line, ss.pos - column)
|
156
155
|
when ss.scan(/%right/)
|
157
156
|
tokens << create_token(Token::P_right, ss[0], line, ss.pos - column)
|
157
|
+
when ss.scan(/%precedence/)
|
158
|
+
tokens << create_token(Token::P_precedence, ss[0], line, ss.pos - column)
|
158
159
|
when ss.scan(/%prec/)
|
159
160
|
tokens << create_token(Token::P_prec, ss[0], line, ss.pos - column)
|
160
161
|
when ss.scan(/{/)
|
@@ -212,19 +213,33 @@ module Lrama
|
|
212
213
|
string, line = lex_string(ss, "'", line, lines)
|
213
214
|
str << string
|
214
215
|
next
|
216
|
+
|
217
|
+
# $ references
|
218
|
+
# It need to wrap an identifier with brackets to use ".-" for identifiers
|
215
219
|
when ss.scan(/\$(<[a-zA-Z0-9_]+>)?\$/) # $$, $<long>$
|
216
220
|
tag = ss[1] ? create_token(Token::Tag, ss[1], line, str.length) : nil
|
217
221
|
references << [:dollar, "$", tag, str.length, str.length + ss[0].length - 1]
|
218
222
|
when ss.scan(/\$(<[a-zA-Z0-9_]+>)?(\d+)/) # $1, $2, $<long>1
|
219
223
|
tag = ss[1] ? create_token(Token::Tag, ss[1], line, str.length) : nil
|
220
224
|
references << [:dollar, Integer(ss[2]), tag, str.length, str.length + ss[0].length - 1]
|
221
|
-
when ss.scan(/\$(<[a-zA-Z0-9_]+>)?([a-zA-Z_
|
225
|
+
when ss.scan(/\$(<[a-zA-Z0-9_]+>)?([a-zA-Z_][a-zA-Z0-9_]*)/) # $foo, $expr, $<long>program (named reference without brackets)
|
226
|
+
tag = ss[1] ? create_token(Token::Tag, ss[1], line, str.length) : nil
|
227
|
+
references << [:dollar, ss[2], tag, str.length, str.length + ss[0].length - 1]
|
228
|
+
when ss.scan(/\$(<[a-zA-Z0-9_]+>)?\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # $expr.right, $expr-right, $<long>program (named reference with brackets)
|
222
229
|
tag = ss[1] ? create_token(Token::Tag, ss[1], line, str.length) : nil
|
223
230
|
references << [:dollar, ss[2], tag, str.length, str.length + ss[0].length - 1]
|
231
|
+
|
232
|
+
# @ references
|
233
|
+
# It need to wrap an identifier with brackets to use ".-" for identifiers
|
224
234
|
when ss.scan(/@\$/) # @$
|
225
235
|
references << [:at, "$", nil, str.length, str.length + ss[0].length - 1]
|
226
|
-
when ss.scan(/@(\d)
|
236
|
+
when ss.scan(/@(\d+)/) # @1
|
227
237
|
references << [:at, Integer(ss[1]), nil, str.length, str.length + ss[0].length - 1]
|
238
|
+
when ss.scan(/@([a-zA-Z][a-zA-Z0-9_]*)/) # @foo, @expr (named reference without brackets)
|
239
|
+
references << [:at, ss[1], nil, str.length, str.length + ss[0].length - 1]
|
240
|
+
when ss.scan(/@\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # @expr.right, @expr-right (named reference with brackets)
|
241
|
+
references << [:at, ss[1], nil, str.length, str.length + ss[0].length - 1]
|
242
|
+
|
228
243
|
when ss.scan(/{/)
|
229
244
|
brace_count += 1
|
230
245
|
when ss.scan(/}/)
|
@@ -314,8 +329,6 @@ module Lrama
|
|
314
329
|
str << ss.getch
|
315
330
|
next
|
316
331
|
end
|
317
|
-
|
318
|
-
str << ss[0]
|
319
332
|
end
|
320
333
|
|
321
334
|
line # Reach to end of input
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Lrama
|
4
|
+
# Handle option parsing for the command line interface.
|
5
|
+
class OptionParser
|
6
|
+
def initialize
|
7
|
+
@options = Options.new
|
8
|
+
@trace = []
|
9
|
+
@report = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(argv)
|
13
|
+
parse_by_option_parser(argv)
|
14
|
+
|
15
|
+
@options.trace_opts = validate_trace(@trace)
|
16
|
+
@options.report_opts = validate_report(@report)
|
17
|
+
@options.grammar_file = argv.shift
|
18
|
+
|
19
|
+
if !@options.grammar_file
|
20
|
+
abort "File should be specified\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
if @options.grammar_file == '-'
|
24
|
+
@options.grammar_file = argv.shift or abort "File name for STDIN should be specified\n"
|
25
|
+
else
|
26
|
+
@options.y = File.open(@options.grammar_file, 'r')
|
27
|
+
end
|
28
|
+
|
29
|
+
if !@report.empty? && @options.report_file.nil? && @options.grammar_file
|
30
|
+
@options.report_file = File.dirname(@options.grammar_file) + "/" + File.basename(@options.grammar_file, ".*") + ".output"
|
31
|
+
end
|
32
|
+
|
33
|
+
if !@options.header_file && @options.header
|
34
|
+
case
|
35
|
+
when @options.outfile
|
36
|
+
@options.header_file = File.dirname(@options.outfile) + "/" + File.basename(@options.outfile, ".*") + ".h"
|
37
|
+
when @options.grammar_file
|
38
|
+
@options.header_file = File.dirname(@options.grammar_file) + "/" + File.basename(@options.grammar_file, ".*") + ".h"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@options
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def parse_by_option_parser(argv)
|
48
|
+
::OptionParser.new do |o|
|
49
|
+
o.banner = <<~BANNER
|
50
|
+
Lrama is LALR (1) parser generator written by Ruby.
|
51
|
+
|
52
|
+
Usage: lrama [options] FILE
|
53
|
+
BANNER
|
54
|
+
o.separator ''
|
55
|
+
o.separator 'Tuning the Parser:'
|
56
|
+
o.on('-S', '--skeleton=FILE', 'specify the skeleton to use') {|v| @options.skeleton = v }
|
57
|
+
o.on('-t', 'reserved, do nothing') { }
|
58
|
+
o.separator ''
|
59
|
+
o.separator 'Output:'
|
60
|
+
o.on('-h', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v }
|
61
|
+
o.on('-d', 'also produce a header file') { @options.header = true }
|
62
|
+
o.on('-r', '--report=THINGS', Array, 'also produce details on the automaton') {|v| @report = v }
|
63
|
+
o.on('--report-file=FILE', 'also produce details on the automaton output to a file named FILE') {|v| @options.report_file = v }
|
64
|
+
o.on('-o', '--output=FILE', 'leave output to FILE') {|v| @options.outfile = v }
|
65
|
+
o.on('--trace=THINGS', Array, 'also output trace logs at runtime') {|v| @trace = v }
|
66
|
+
o.on('-v', 'reserved, do nothing') { }
|
67
|
+
o.separator ''
|
68
|
+
o.separator 'Error Recovery:'
|
69
|
+
o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true }
|
70
|
+
o.separator ''
|
71
|
+
o.separator 'Other options:'
|
72
|
+
o.on('-V', '--version', "output version information and exit") {|v| puts "lrama #{Lrama::VERSION}"; exit 0 }
|
73
|
+
o.on('--help', "display this help and exit") {|v| puts o; exit 0 }
|
74
|
+
o.separator ''
|
75
|
+
o.parse!(argv)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate_report(report)
|
80
|
+
bison_list = %w[states itemsets lookaheads solved counterexamples cex all none]
|
81
|
+
others = %w[verbose]
|
82
|
+
list = bison_list + others
|
83
|
+
not_supported = %w[cex none]
|
84
|
+
h = { grammar: true }
|
85
|
+
|
86
|
+
report.each do |r|
|
87
|
+
if list.include?(r) && !not_supported.include?(r)
|
88
|
+
h[r.to_sym] = true
|
89
|
+
else
|
90
|
+
raise "Invalid report option \"#{r}\"."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
if h[:all]
|
95
|
+
(bison_list - not_supported).each do |r|
|
96
|
+
h[r.to_sym] = true
|
97
|
+
end
|
98
|
+
|
99
|
+
h.delete(:all)
|
100
|
+
end
|
101
|
+
|
102
|
+
return h
|
103
|
+
end
|
104
|
+
|
105
|
+
def validate_trace(trace)
|
106
|
+
list = %w[
|
107
|
+
none locations scan parse automaton bitsets
|
108
|
+
closure grammar resource sets muscles tools
|
109
|
+
m4-early m4 skeleton time ielr cex all
|
110
|
+
]
|
111
|
+
h = {}
|
112
|
+
|
113
|
+
trace.each do |t|
|
114
|
+
if list.include?(t)
|
115
|
+
h[t.to_sym] = true
|
116
|
+
else
|
117
|
+
raise "Invalid trace option \"#{t}\"."
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
return h
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Lrama
|
2
|
+
# Command line options.
|
3
|
+
class Options
|
4
|
+
attr_accessor :skeleton, :header, :header_file,
|
5
|
+
:report_file, :outfile,
|
6
|
+
:error_recovery, :grammar_file,
|
7
|
+
:report_file, :trace_opts, :report_opts, :y
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@skeleton = "bison/yacc.c"
|
11
|
+
@header = false
|
12
|
+
@header_file = nil
|
13
|
+
@report_file = nil
|
14
|
+
@outfile = "y.tab.c"
|
15
|
+
@error_recovery = false
|
16
|
+
@grammar_file = nil
|
17
|
+
@report_file = nil
|
18
|
+
@trace_opts = nil
|
19
|
+
@report_opts = nil
|
20
|
+
@y = STDIN
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/lrama/output.rb
CHANGED
data/lib/lrama/parser.rb
CHANGED
@@ -159,6 +159,14 @@ module Lrama
|
|
159
159
|
grammar.add_right(sym, precedence_number)
|
160
160
|
end
|
161
161
|
precedence_number += 1
|
162
|
+
when T::P_precedence
|
163
|
+
# %precedence (ident|char|string)+
|
164
|
+
ts.next
|
165
|
+
while (id = ts.consume(T::Ident, T::Char, T::String)) do
|
166
|
+
sym = grammar.add_term(id: id)
|
167
|
+
grammar.add_precedence(sym, precedence_number)
|
168
|
+
end
|
169
|
+
precedence_number += 1
|
162
170
|
when nil
|
163
171
|
# end of input
|
164
172
|
raise "Reach to end of input within declarations"
|
data/lib/lrama/state.rb
CHANGED
data/lib/lrama/states.rb
CHANGED
@@ -455,6 +455,11 @@ module Lrama
|
|
455
455
|
|
456
456
|
# shift_prec == reduce_prec, then check associativity
|
457
457
|
case sym.precedence.type
|
458
|
+
when :precedence
|
459
|
+
# %precedence only specifies precedence and not specify associativity
|
460
|
+
# then a conflict is unresolved if precedence is same.
|
461
|
+
state.conflicts << State::ShiftReduceConflict.new(symbols: [sym], shift: shift, reduce: reduce)
|
462
|
+
next
|
458
463
|
when :right
|
459
464
|
# Shift is selected
|
460
465
|
state.resolved_conflicts << State::ResolvedConflict.new(symbol: sym, reduce: reduce, which: :shift, same_prec: true)
|
@@ -515,9 +520,9 @@ module Lrama
|
|
515
520
|
|
516
521
|
state.default_reduction_rule = state.reduces.map do |r|
|
517
522
|
[r.rule, r.rule.id, (r.look_ahead || []).count]
|
518
|
-
end.
|
523
|
+
end.min_by do |rule, rule_id, count|
|
519
524
|
[-count, rule_id]
|
520
|
-
end.first
|
525
|
+
end.first
|
521
526
|
end
|
522
527
|
end
|
523
528
|
|
@@ -110,7 +110,6 @@ module Lrama
|
|
110
110
|
end
|
111
111
|
io << "\n"
|
112
112
|
|
113
|
-
|
114
113
|
# Report shifts
|
115
114
|
tmp = state.term_transitions.select do |shift, _|
|
116
115
|
!shift.not_selected
|
@@ -123,7 +122,6 @@ module Lrama
|
|
123
122
|
end
|
124
123
|
io << "\n" if !tmp.empty?
|
125
124
|
|
126
|
-
|
127
125
|
# Report error caused by %nonassoc
|
128
126
|
nl = false
|
129
127
|
tmp = state.resolved_conflicts.select do |resolved|
|
@@ -138,7 +136,6 @@ module Lrama
|
|
138
136
|
end
|
139
137
|
io << "\n" if !tmp.empty?
|
140
138
|
|
141
|
-
|
142
139
|
# Report reduces
|
143
140
|
nl = false
|
144
141
|
max_len = state.non_default_reduces.flat_map(&:look_ahead).compact.map(&:display_name).map(&:length).max || 0
|
@@ -171,7 +168,6 @@ module Lrama
|
|
171
168
|
end
|
172
169
|
io << "\n" if nl
|
173
170
|
|
174
|
-
|
175
171
|
# Report nonterminal transitions
|
176
172
|
tmp = []
|
177
173
|
max_len = 0
|
@@ -189,7 +185,6 @@ module Lrama
|
|
189
185
|
end
|
190
186
|
io << "\n" if !tmp.empty?
|
191
187
|
|
192
|
-
|
193
188
|
if solved
|
194
189
|
# Report conflict resolutions
|
195
190
|
state.resolved_conflicts.each do |resolved|
|
@@ -202,13 +197,13 @@ module Lrama
|
|
202
197
|
# Report counterexamples
|
203
198
|
examples = cex.compute(state)
|
204
199
|
examples.each do |example|
|
205
|
-
label0 = example.type == :shift_reduce ? "shift/reduce"
|
200
|
+
label0 = example.type == :shift_reduce ? "shift/reduce" : "reduce/reduce"
|
206
201
|
label1 = example.type == :shift_reduce ? "Shift derivation" : "First Reduce derivation"
|
207
202
|
label2 = example.type == :shift_reduce ? "Reduce derivation" : "Second Reduce derivation"
|
208
203
|
|
209
204
|
io << " #{label0} conflict on token #{example.conflict_symbol.id.s_value}:\n"
|
210
|
-
io << " #{example.path1_item
|
211
|
-
io << " #{example.path2_item
|
205
|
+
io << " #{example.path1_item}\n"
|
206
|
+
io << " #{example.path2_item}\n"
|
212
207
|
io << " #{label1}\n"
|
213
208
|
example.derivations1.render_strings_for_report.each do |str|
|
214
209
|
io << " #{str}\n"
|
@@ -234,7 +229,6 @@ module Lrama
|
|
234
229
|
end
|
235
230
|
io << "\n"
|
236
231
|
|
237
|
-
|
238
232
|
# Report reads_relation
|
239
233
|
io << " [Reads Relation]\n"
|
240
234
|
@states.nterms.each do |nterm|
|
@@ -248,7 +242,6 @@ module Lrama
|
|
248
242
|
end
|
249
243
|
io << "\n"
|
250
244
|
|
251
|
-
|
252
245
|
# Report read_sets
|
253
246
|
io << " [Read sets]\n"
|
254
247
|
read_sets = @states.read_sets
|
@@ -263,7 +256,6 @@ module Lrama
|
|
263
256
|
end
|
264
257
|
io << "\n"
|
265
258
|
|
266
|
-
|
267
259
|
# Report includes_relation
|
268
260
|
io << " [Includes Relation]\n"
|
269
261
|
@states.nterms.each do |nterm|
|
@@ -277,7 +269,6 @@ module Lrama
|
|
277
269
|
end
|
278
270
|
io << "\n"
|
279
271
|
|
280
|
-
|
281
272
|
# Report lookback_relation
|
282
273
|
io << " [Lookback Relation]\n"
|
283
274
|
@states.rules.each do |rule|
|
@@ -286,12 +277,11 @@ module Lrama
|
|
286
277
|
|
287
278
|
a.each do |state_id2, nterm_id2|
|
288
279
|
n = @states.nterms.find {|n| n.token_id == nterm_id2 }
|
289
|
-
io << " (Rule: #{rule
|
280
|
+
io << " (Rule: #{rule}) -> (State #{state_id2}, #{n.id.s_value})\n"
|
290
281
|
end
|
291
282
|
end
|
292
283
|
io << "\n"
|
293
284
|
|
294
|
-
|
295
285
|
# Report follow_sets
|
296
286
|
io << " [Follow sets]\n"
|
297
287
|
follow_sets = @states.follow_sets
|
@@ -306,7 +296,6 @@ module Lrama
|
|
306
296
|
end
|
307
297
|
io << "\n"
|
308
298
|
|
309
|
-
|
310
299
|
# Report LA
|
311
300
|
io << " [Look-Ahead Sets]\n"
|
312
301
|
tmp = []
|
@@ -326,7 +315,6 @@ module Lrama
|
|
326
315
|
io << "\n" if !tmp.empty?
|
327
316
|
end
|
328
317
|
|
329
|
-
|
330
318
|
# End of Report State
|
331
319
|
io << "\n"
|
332
320
|
end
|
data/lib/lrama/version.rb
CHANGED
data/lib/lrama.rb
CHANGED
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.6
|
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-
|
11
|
+
date: 2023-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: LALR (1) parser generator written by Ruby
|
14
14
|
email:
|
@@ -54,6 +54,8 @@ files:
|
|
54
54
|
- lib/lrama/lexer.rb
|
55
55
|
- lib/lrama/lexer/token.rb
|
56
56
|
- lib/lrama/lexer/token/type.rb
|
57
|
+
- lib/lrama/option_parser.rb
|
58
|
+
- lib/lrama/options.rb
|
57
59
|
- lib/lrama/output.rb
|
58
60
|
- lib/lrama/parser.rb
|
59
61
|
- lib/lrama/parser/token_scanner.rb
|
@@ -103,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
105
|
- !ruby/object:Gem::Version
|
104
106
|
version: '0'
|
105
107
|
requirements: []
|
106
|
-
rubygems_version: 3.
|
108
|
+
rubygems_version: 3.5.0.dev
|
107
109
|
signing_key:
|
108
110
|
specification_version: 4
|
109
111
|
summary: LALR (1) parser generator written by Ruby
|