lrama 0.5.1 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yaml +2 -3
- data/.gitignore +2 -0
- data/Gemfile +1 -0
- data/LEGAL.md +1 -16
- data/README.md +1 -1
- data/Steepfile +3 -0
- data/doc/TODO.md +4 -3
- data/exe/lrama +1 -1
- data/lib/lrama/command.rb +90 -71
- data/lib/lrama/context.rb +11 -1
- data/lib/lrama/grammar/code.rb +123 -0
- data/lib/lrama/grammar/error_token.rb +9 -0
- data/lib/lrama/grammar/precedence.rb +11 -0
- data/lib/lrama/grammar/printer.rb +9 -0
- data/lib/lrama/grammar/reference.rb +22 -0
- data/lib/lrama/grammar/rule.rb +33 -0
- data/lib/lrama/grammar/symbol.rb +94 -0
- data/lib/lrama/grammar/union.rb +10 -0
- data/lib/lrama/grammar.rb +67 -285
- data/lib/lrama/lexer/token.rb +76 -0
- data/lib/lrama/lexer.rb +12 -48
- data/lib/lrama/output.rb +31 -3
- data/lib/lrama/parser/token_scanner.rb +4 -0
- data/lib/lrama/parser.rb +19 -5
- data/lib/lrama/report/duration.rb +25 -0
- data/lib/lrama/report/profile.rb +25 -0
- data/lib/lrama/report.rb +2 -47
- data/lib/lrama/state/resolved_conflict.rb +29 -0
- data/lib/lrama/state.rb +3 -28
- data/lib/lrama/states/item.rb +43 -0
- data/lib/lrama/states.rb +3 -41
- data/lib/lrama/version.rb +1 -1
- data/lrama.gemspec +2 -2
- data/rbs_collection.lock.yaml +26 -0
- data/rbs_collection.yaml +22 -0
- data/sig/lrama/report/duration.rbs +11 -0
- data/sig/lrama/report/profile.rbs +7 -0
- data/sig/lrama/warning.rbs +16 -0
- data/template/bison/yacc.c +397 -32
- metadata +23 -5
data/lib/lrama/parser.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require "lrama/report"
|
1
|
+
require "lrama/report/duration"
|
2
2
|
require "lrama/parser/token_scanner"
|
3
3
|
|
4
4
|
module Lrama
|
@@ -59,6 +59,13 @@ module Lrama
|
|
59
59
|
code = grammar.build_code(:printer, code)
|
60
60
|
ident_or_tags = ts.consume_multi(T::Ident, T::Tag)
|
61
61
|
grammar.add_printer(ident_or_tags: ident_or_tags, code: code, lineno: lineno)
|
62
|
+
when T::P_error_token
|
63
|
+
lineno = ts.current_token.line
|
64
|
+
ts.next
|
65
|
+
code = ts.consume!(T::User_code)
|
66
|
+
code = grammar.build_code(:printer, code)
|
67
|
+
ident_or_tags = ts.consume_multi(T::Ident, T::Tag)
|
68
|
+
grammar.add_error_token(ident_or_tags: ident_or_tags, code: code, lineno: lineno)
|
62
69
|
when T::P_lex_param
|
63
70
|
ts.next
|
64
71
|
code = ts.consume!(T::User_code)
|
@@ -175,8 +182,11 @@ module Lrama
|
|
175
182
|
# LHS
|
176
183
|
lhs = ts.consume!(T::Ident_Colon) # class:
|
177
184
|
lhs.type = T::Ident
|
185
|
+
if named_ref = ts.consume(T::Named_Ref)
|
186
|
+
lhs.alias = named_ref.s_value
|
187
|
+
end
|
178
188
|
|
179
|
-
rhs = parse_grammar_rule_rhs(ts, grammar)
|
189
|
+
rhs = parse_grammar_rule_rhs(ts, grammar, lhs)
|
180
190
|
|
181
191
|
grammar.add_rule(lhs: lhs, rhs: rhs, lineno: rhs.first ? rhs.first.line : lhs.line)
|
182
192
|
|
@@ -186,7 +196,7 @@ module Lrama
|
|
186
196
|
# |
|
187
197
|
bar_lineno = ts.current_token.line
|
188
198
|
ts.next
|
189
|
-
rhs = parse_grammar_rule_rhs(ts, grammar)
|
199
|
+
rhs = parse_grammar_rule_rhs(ts, grammar, lhs)
|
190
200
|
grammar.add_rule(lhs: lhs, rhs: rhs, lineno: rhs.first ? rhs.first.line : bar_lineno)
|
191
201
|
when T::Semicolon
|
192
202
|
# ;
|
@@ -205,13 +215,13 @@ module Lrama
|
|
205
215
|
end
|
206
216
|
end
|
207
217
|
|
208
|
-
def parse_grammar_rule_rhs(ts, grammar)
|
218
|
+
def parse_grammar_rule_rhs(ts, grammar, lhs)
|
209
219
|
a = []
|
210
220
|
prec_seen = false
|
211
221
|
code_after_prec = false
|
212
222
|
|
213
223
|
while true do
|
214
|
-
# TODO:
|
224
|
+
# TODO: String can be here
|
215
225
|
case ts.current_type
|
216
226
|
when T::Ident
|
217
227
|
# keyword_class
|
@@ -244,9 +254,13 @@ module Lrama
|
|
244
254
|
end
|
245
255
|
|
246
256
|
code = ts.current_token
|
257
|
+
code.numberize_references(lhs, a)
|
247
258
|
grammar.build_references(code)
|
248
259
|
a << code
|
249
260
|
ts.next
|
261
|
+
when T::Named_Ref
|
262
|
+
ts.previous_token.alias = ts.current_token.s_value
|
263
|
+
ts.next
|
250
264
|
when T::Bar
|
251
265
|
# |
|
252
266
|
break
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Lrama
|
2
|
+
class Report
|
3
|
+
module Duration
|
4
|
+
def self.enable
|
5
|
+
@_report_duration_enabled = true
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.enabled?
|
9
|
+
!!@_report_duration_enabled
|
10
|
+
end
|
11
|
+
|
12
|
+
def report_duration(method_name)
|
13
|
+
time1 = Time.now.to_f
|
14
|
+
result = yield
|
15
|
+
time2 = Time.now.to_f
|
16
|
+
|
17
|
+
if Duration.enabled?
|
18
|
+
puts sprintf("%s %10.5f s", method_name, time2 - time1)
|
19
|
+
end
|
20
|
+
|
21
|
+
return result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Lrama
|
2
|
+
class Report
|
3
|
+
module Profile
|
4
|
+
# 1. Wrap target method with Profile.report_profile like below:
|
5
|
+
#
|
6
|
+
# Lrama::Report::Profile.report_profile { method }
|
7
|
+
#
|
8
|
+
# 2. Run lrama command, for example
|
9
|
+
#
|
10
|
+
# $ ./exe/lrama --trace=time spec/fixtures/integration/ruby_3_2_0/parse.tmp.y
|
11
|
+
#
|
12
|
+
# 3. Generate html file
|
13
|
+
#
|
14
|
+
# $ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > tmp/flamegraph.html
|
15
|
+
#
|
16
|
+
def self.report_profile
|
17
|
+
require "stackprof"
|
18
|
+
|
19
|
+
StackProf.run(mode: :cpu, raw: true, out: 'tmp/stackprof-cpu-myapp.dump') do
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/lrama/report.rb
CHANGED
@@ -1,47 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Profile
|
4
|
-
# 1. Wrap target method with Profile.report_profile like below:
|
5
|
-
#
|
6
|
-
# Lrama::Report::Profile.report_profile { method }
|
7
|
-
#
|
8
|
-
# 2. Run lrama command, for example
|
9
|
-
#
|
10
|
-
# $ ./exe/lrama --trace=time spec/fixtures/integration/ruby_3_2_0/parse.tmp.y
|
11
|
-
#
|
12
|
-
# 3. Generate html file
|
13
|
-
#
|
14
|
-
# $ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > tmp/flamegraph.html
|
15
|
-
#
|
16
|
-
def self.report_profile
|
17
|
-
require "stackprof"
|
18
|
-
|
19
|
-
StackProf.run(mode: :cpu, raw: true, out: 'tmp/stackprof-cpu-myapp.dump') do
|
20
|
-
yield
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
module Duration
|
26
|
-
def self.enable
|
27
|
-
@_report_duration_enabled = true
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.enabled?
|
31
|
-
!!@_report_duration_enabled
|
32
|
-
end
|
33
|
-
|
34
|
-
def report_duration(method_name)
|
35
|
-
time1 = Time.now.to_f
|
36
|
-
result = yield
|
37
|
-
time2 = Time.now.to_f
|
38
|
-
|
39
|
-
if Duration.enabled?
|
40
|
-
puts sprintf("%s %10.5f s", method_name, time2 - time1)
|
41
|
-
end
|
42
|
-
|
43
|
-
return result
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
1
|
+
require 'lrama/report/duration'
|
2
|
+
require 'lrama/report/profile'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Lrama
|
2
|
+
class State
|
3
|
+
# * symbol: A symbol under discussion
|
4
|
+
# * reduce: A reduce under discussion
|
5
|
+
# * which: For which a conflict is resolved. :shift, :reduce or :error (for nonassociative)
|
6
|
+
class ResolvedConflict < Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true)
|
7
|
+
def report_message
|
8
|
+
s = symbol.display_name
|
9
|
+
r = reduce.rule.precedence_sym.display_name
|
10
|
+
case
|
11
|
+
when which == :shift && same_prec
|
12
|
+
msg = "resolved as #{which} (%right #{s})"
|
13
|
+
when which == :shift
|
14
|
+
msg = "resolved as #{which} (#{r} < #{s})"
|
15
|
+
when which == :reduce && same_prec
|
16
|
+
msg = "resolved as #{which} (%left #{s})"
|
17
|
+
when which == :reduce
|
18
|
+
msg = "resolved as #{which} (#{s} < #{r})"
|
19
|
+
when which == :error
|
20
|
+
msg = "resolved as an #{which} (%nonassoc #{s})"
|
21
|
+
else
|
22
|
+
raise "Unknown direction. #{self}"
|
23
|
+
end
|
24
|
+
|
25
|
+
"Conflict between rule #{reduce.rule.id} and token #{s} #{msg}."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/lrama/state.rb
CHANGED
@@ -1,34 +1,9 @@
|
|
1
1
|
require "lrama/state/reduce"
|
2
2
|
require "lrama/state/shift"
|
3
|
+
require "lrama/state/resolved_conflict"
|
3
4
|
|
4
5
|
module Lrama
|
5
6
|
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
7
|
Conflict = Struct.new(:symbols, :reduce, :type, keyword_init: true)
|
33
8
|
|
34
9
|
attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts,
|
@@ -96,7 +71,7 @@ module Lrama
|
|
96
71
|
reduce.look_ahead = look_ahead
|
97
72
|
end
|
98
73
|
|
99
|
-
# Returns array of [
|
74
|
+
# Returns array of [Shift, next_state]
|
100
75
|
def nterm_transitions
|
101
76
|
return @nterm_transitions if @nterm_transitions
|
102
77
|
|
@@ -111,7 +86,7 @@ module Lrama
|
|
111
86
|
@nterm_transitions
|
112
87
|
end
|
113
88
|
|
114
|
-
# Returns array of [
|
89
|
+
# Returns array of [Shift, next_state]
|
115
90
|
def term_transitions
|
116
91
|
return @term_transitions if @term_transitions
|
117
92
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# TODO: Validate position is not over rule rhs
|
2
|
+
|
3
|
+
module Lrama
|
4
|
+
class States
|
5
|
+
class Item < Struct.new(:rule, :position, keyword_init: true)
|
6
|
+
# Optimization for States#setup_state
|
7
|
+
def hash
|
8
|
+
[rule.id, position].hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def rule_id
|
12
|
+
rule.id
|
13
|
+
end
|
14
|
+
|
15
|
+
def next_sym
|
16
|
+
rule.rhs[position]
|
17
|
+
end
|
18
|
+
|
19
|
+
def end_of_rule?
|
20
|
+
rule.rhs.count == position
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_by_next_position
|
24
|
+
Item.new(rule: rule, position: position + 1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def previous_sym
|
28
|
+
rule.rhs[position - 1]
|
29
|
+
end
|
30
|
+
|
31
|
+
def display_name
|
32
|
+
r = rule.rhs.map(&:display_name).insert(position, "•").join(" ")
|
33
|
+
"#{r} (rule #{rule.id})"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Right after position
|
37
|
+
def display_rest
|
38
|
+
r = rule.rhs[position..-1].map(&:display_name).join(" ")
|
39
|
+
". #{r} (rule #{rule.id})"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/lrama/states.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "forwardable"
|
2
|
-
require "lrama/report"
|
2
|
+
require "lrama/report/duration"
|
3
|
+
require "lrama/states/item"
|
3
4
|
|
4
5
|
module Lrama
|
5
6
|
# States is passed to a template file
|
@@ -11,46 +12,7 @@ module Lrama
|
|
11
12
|
include Lrama::Report::Duration
|
12
13
|
|
13
14
|
def_delegators "@grammar", :symbols, :terms, :nterms, :rules,
|
14
|
-
:accept_symbol, :eof_symbol, :find_symbol_by_s_value!
|
15
|
-
|
16
|
-
# TODO: Validate position is not over rule rhs
|
17
|
-
Item = Struct.new(:rule, :position, keyword_init: true) do
|
18
|
-
# Optimization for States#setup_state
|
19
|
-
def hash
|
20
|
-
[rule.id, position].hash
|
21
|
-
end
|
22
|
-
|
23
|
-
def rule_id
|
24
|
-
rule.id
|
25
|
-
end
|
26
|
-
|
27
|
-
def next_sym
|
28
|
-
rule.rhs[position]
|
29
|
-
end
|
30
|
-
|
31
|
-
def end_of_rule?
|
32
|
-
rule.rhs.count == position
|
33
|
-
end
|
34
|
-
|
35
|
-
def new_by_next_position
|
36
|
-
Item.new(rule: rule, position: position + 1)
|
37
|
-
end
|
38
|
-
|
39
|
-
def previous_sym
|
40
|
-
rule.rhs[position - 1]
|
41
|
-
end
|
42
|
-
|
43
|
-
def display_name
|
44
|
-
r = rule.rhs.map(&:display_name).insert(position, "•").join(" ")
|
45
|
-
"#{r} (rule #{rule.id})"
|
46
|
-
end
|
47
|
-
|
48
|
-
# Right after position
|
49
|
-
def display_rest
|
50
|
-
r = rule.rhs[position..-1].map(&:display_name).join(" ")
|
51
|
-
". #{r} (rule #{rule.id})"
|
52
|
-
end
|
53
|
-
end
|
15
|
+
:accept_symbol, :eof_symbol, :undef_symbol, :find_symbol_by_s_value!
|
54
16
|
|
55
17
|
attr_reader :states, :reads_relation, :includes_relation, :lookback_relation
|
56
18
|
|
data/lib/lrama/version.rb
CHANGED
data/lrama.gemspec
CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
|
|
8
8
|
|
9
9
|
spec.summary = "LALR (1) parser generator written by Ruby"
|
10
10
|
spec.description = "LALR (1) parser generator written by Ruby"
|
11
|
-
spec.homepage = "https://github.com/
|
11
|
+
spec.homepage = "https://github.com/ruby/lrama"
|
12
12
|
# See LEGAL.md file for detail
|
13
|
-
spec.license = "
|
13
|
+
spec.license = "GPL-3.0-or-later"
|
14
14
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
15
15
|
|
16
16
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
sources:
|
3
|
+
- type: git
|
4
|
+
name: ruby/gem_rbs_collection
|
5
|
+
revision: 28208148c7e64a25e9b86b9723b4c3a2cef14e81
|
6
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
7
|
+
repo_dir: gems
|
8
|
+
path: ".gem_rbs_collection"
|
9
|
+
gems:
|
10
|
+
- name: erb
|
11
|
+
version: '0'
|
12
|
+
source:
|
13
|
+
type: stdlib
|
14
|
+
- name: stackprof
|
15
|
+
version: '0.2'
|
16
|
+
source:
|
17
|
+
type: git
|
18
|
+
name: ruby/gem_rbs_collection
|
19
|
+
revision: 28208148c7e64a25e9b86b9723b4c3a2cef14e81
|
20
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
21
|
+
repo_dir: gems
|
22
|
+
- name: strscan
|
23
|
+
version: '0'
|
24
|
+
source:
|
25
|
+
type: stdlib
|
26
|
+
gemfile_lock_path: Gemfile.lock
|
data/rbs_collection.yaml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Download sources
|
2
|
+
sources:
|
3
|
+
- type: git
|
4
|
+
name: ruby/gem_rbs_collection
|
5
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
6
|
+
revision: main
|
7
|
+
repo_dir: gems
|
8
|
+
|
9
|
+
# You can specify local directories as sources also.
|
10
|
+
# - type: local
|
11
|
+
# path: path/to/your/local/repository
|
12
|
+
|
13
|
+
# A directory to install the downloaded RBSs
|
14
|
+
path: .gem_rbs_collection
|
15
|
+
|
16
|
+
gems:
|
17
|
+
- name: erb
|
18
|
+
- name: strscan
|
19
|
+
# Skip loading rbs gem's RBS.
|
20
|
+
# It's unnecessary if you don't use rbs as a library.
|
21
|
+
- name: rbs
|
22
|
+
ignore: true
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Lrama
|
2
|
+
class Warning
|
3
|
+
interface _Appendable
|
4
|
+
def <<: (String message) -> self
|
5
|
+
end
|
6
|
+
|
7
|
+
@out: _Appendable
|
8
|
+
|
9
|
+
attr_reader errors: Array[String]
|
10
|
+
attr_reader warns: Array[String]
|
11
|
+
def initialize: (?_Appendable out) -> void
|
12
|
+
def error: (String message) -> void
|
13
|
+
def warn: (String message) -> void
|
14
|
+
def has_error?: -> bool
|
15
|
+
end
|
16
|
+
end
|