lrama 0.6.0 → 0.6.2
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 +27 -7
- data/Gemfile +1 -1
- data/NEWS.md +55 -0
- data/Steepfile +2 -3
- data/lib/lrama/command.rb +25 -3
- data/lib/lrama/context.rb +3 -23
- data/lib/lrama/counterexamples/example.rb +2 -2
- data/lib/lrama/grammar/binding.rb +24 -0
- data/lib/lrama/grammar/code/rule_action.rb +1 -1
- data/lib/lrama/grammar/code.rb +1 -1
- data/lib/lrama/grammar/parameterizing_rule/resolver.rb +47 -0
- data/lib/lrama/grammar/parameterizing_rule/rhs.rb +15 -0
- data/lib/lrama/grammar/parameterizing_rule/rule.rb +16 -0
- data/lib/lrama/grammar/parameterizing_rule.rb +3 -6
- data/lib/lrama/grammar/percent_code.rb +3 -3
- data/lib/lrama/grammar/rule.rb +2 -2
- data/lib/lrama/grammar/rule_builder.rb +60 -31
- data/lib/lrama/grammar/stdlib.y +80 -0
- data/lib/lrama/grammar/type.rb +13 -1
- data/lib/lrama/grammar.rb +18 -11
- data/lib/lrama/lexer/grammar_file.rb +1 -1
- data/lib/lrama/lexer/token/instantiate_rule.rb +7 -2
- data/lib/lrama/lexer/token.rb +5 -0
- data/lib/lrama/lexer.rb +3 -7
- data/lib/lrama/output.rb +2 -2
- data/lib/lrama/parser.rb +508 -467
- data/lib/lrama/states/item.rb +17 -13
- data/lib/lrama/states_reporter.rb +8 -10
- data/lib/lrama/version.rb +1 -1
- data/parser.y +12 -13
- data/sig/lrama/grammar/binding.rbs +16 -0
- data/sig/lrama/grammar/parameterizing_rule/resolver.rbs +22 -0
- data/sig/lrama/grammar/parameterizing_rule/rhs.rbs +13 -0
- data/sig/lrama/grammar/parameterizing_rule/rule.rbs +14 -0
- data/sig/lrama/grammar/parameterizing_rule.rbs +0 -4
- data/sig/lrama/grammar/percent_code.rbs +3 -3
- data/sig/lrama/grammar/rule_builder.rbs +9 -6
- data/sig/lrama/lexer/token/instantiate_rule.rbs +4 -2
- data/sig/lrama/lexer/token.rbs +1 -0
- metadata +12 -23
- data/lib/lrama/grammar/parameterizing_rule_builder.rb +0 -34
- data/lib/lrama/grammar/parameterizing_rule_resolver.rb +0 -30
- data/lib/lrama/grammar/parameterizing_rule_rhs_builder.rb +0 -53
- data/lib/lrama/grammar/parameterizing_rules/builder/base.rb +0 -36
- data/lib/lrama/grammar/parameterizing_rules/builder/list.rb +0 -28
- data/lib/lrama/grammar/parameterizing_rules/builder/nonempty_list.rb +0 -28
- data/lib/lrama/grammar/parameterizing_rules/builder/option.rb +0 -28
- data/lib/lrama/grammar/parameterizing_rules/builder/separated_list.rb +0 -39
- data/lib/lrama/grammar/parameterizing_rules/builder/separated_nonempty_list.rb +0 -34
- data/lib/lrama/grammar/parameterizing_rules/builder.rb +0 -60
- data/sig/lrama/grammar/parameterizing_rule_builder.rbs +0 -19
- data/sig/lrama/grammar/parameterizing_rule_resolver.rbs +0 -16
- data/sig/lrama/grammar/parameterizing_rule_rhs_builder.rbs +0 -18
- data/sig/lrama/grammar/parameterizing_rules/builder/base.rbs +0 -28
- data/sig/lrama/grammar/parameterizing_rules/builder/list.rbs +0 -10
- data/sig/lrama/grammar/parameterizing_rules/builder/nonempty_list.rbs +0 -10
- data/sig/lrama/grammar/parameterizing_rules/builder/option.rbs +0 -10
- data/sig/lrama/grammar/parameterizing_rules/builder/separated_list.rbs +0 -13
- data/sig/lrama/grammar/parameterizing_rules/builder/separated_nonempty_list.rbs +0 -13
- data/sig/lrama/grammar/parameterizing_rules/builder.rbs +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4158de45c42ff62eacfb00737261feaa49d8f0cc646004e30da74ba4e2e69c6
|
4
|
+
data.tar.gz: 734830227f701e18df2e9e8bc3da55d15f49c890e08530e6ac55ef87ae5f952d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52ebbe4d099ae63d73aa995bddc8e966f989a4d00ad3b39634d2abe2448da404dd9bff8f15e0dedd0577716089329c804ef2c4edcadd39ca6ba47f8d293d101d
|
7
|
+
data.tar.gz: 72e91c79618071b5850c85335cfe3f1b63ff89f11cd332b0141623a4e2a7e2c2c389dd0db9afe38a5a84b7aa891ac1834fc7a2d6c6eed8f62d87734f6b99cbbf
|
data/.github/workflows/test.yaml
CHANGED
@@ -8,12 +8,19 @@ permissions:
|
|
8
8
|
contents: read
|
9
9
|
|
10
10
|
jobs:
|
11
|
+
ruby-versions:
|
12
|
+
uses: ruby/actions/.github/workflows/ruby_versions.yml@master
|
13
|
+
with:
|
14
|
+
engine: cruby
|
15
|
+
min_version: 2.5
|
16
|
+
|
11
17
|
test:
|
18
|
+
needs: ruby-versions
|
12
19
|
runs-on: ubuntu-20.04
|
13
20
|
strategy:
|
14
21
|
fail-fast: false
|
15
22
|
matrix:
|
16
|
-
ruby:
|
23
|
+
ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
|
17
24
|
steps:
|
18
25
|
- uses: actions/checkout@v4
|
19
26
|
- uses: ruby/setup-ruby@v1
|
@@ -29,14 +36,22 @@ jobs:
|
|
29
36
|
fail-fast: false
|
30
37
|
matrix:
|
31
38
|
ruby: ['head']
|
39
|
+
defaults:
|
40
|
+
run:
|
41
|
+
shell: msys2 {0}
|
32
42
|
steps:
|
33
43
|
- uses: actions/checkout@v4
|
34
44
|
- uses: ruby/setup-ruby@v1
|
35
45
|
with:
|
36
46
|
ruby-version: ${{ matrix.ruby }}
|
37
47
|
bundler-cache: true
|
38
|
-
-
|
39
|
-
|
48
|
+
- uses: msys2/setup-msys2@v2
|
49
|
+
id: setup-msys2
|
50
|
+
with:
|
51
|
+
update: true
|
52
|
+
install: >-
|
53
|
+
flex
|
54
|
+
- run: flex --help
|
40
55
|
- run: bundle install
|
41
56
|
- run: bundle exec rspec
|
42
57
|
test-memory:
|
@@ -72,6 +87,11 @@ jobs:
|
|
72
87
|
bundler-cache: true
|
73
88
|
- run: bundle install
|
74
89
|
|
90
|
+
# Copy from https://github.com/ruby/ruby/blob/cb9a47f2acd6e373ef868b890a9d07da6f565dd4/.github/workflows/check_misc.yml#L31
|
91
|
+
- name: Check if C-sources are US-ASCII
|
92
|
+
run: |
|
93
|
+
grep -r -n --include='*.[chyS]' --include='*.asm' $'[^\t-~]' -- . && exit 1 || :
|
94
|
+
|
75
95
|
# Copy from https://github.com/ruby/ruby/blob/089227e94823542acfdafa68541d330eee42ffea/.github/workflows/check_misc.yml#L27
|
76
96
|
- name: Check for trailing spaces
|
77
97
|
run: |
|
@@ -105,8 +125,8 @@ jobs:
|
|
105
125
|
fail-fast: false
|
106
126
|
matrix:
|
107
127
|
# '3.0' is the oldest living ruby version
|
108
|
-
# '2.
|
109
|
-
baseruby: ['head', '3.0', '2.
|
128
|
+
# '2.7' is for BASERUBY
|
129
|
+
baseruby: ['head', '3.0', '2.7']
|
110
130
|
ruby_branch: ['master']
|
111
131
|
defaults:
|
112
132
|
run:
|
@@ -118,12 +138,12 @@ jobs:
|
|
118
138
|
ruby-version: ${{ matrix.baseruby }}
|
119
139
|
bundler-cache: true
|
120
140
|
- run: git clone --depth=1 https://github.com/ruby/ruby.git -b ${{ matrix.ruby_branch }} ../ruby
|
121
|
-
working-directory:
|
141
|
+
working-directory: .
|
122
142
|
- run: mkdir -p tool/lrama
|
123
143
|
working-directory: ../ruby
|
124
144
|
- name: Copy Lrama to ruby/tool
|
125
145
|
run: cp -r LEGAL.md NEWS.md MIT exe lib template ../ruby/tool/lrama
|
126
|
-
working-directory:
|
146
|
+
working-directory: .
|
127
147
|
- run: tree tool/lrama
|
128
148
|
working-directory: ../ruby
|
129
149
|
# See also https://github.com/ruby/ruby/blob/master/.github/workflows/ubuntu.yml
|
data/Gemfile
CHANGED
@@ -12,6 +12,6 @@ gem "stackprof", platforms: [:ruby] # stackprof doesn't support Windows
|
|
12
12
|
# Recent steep requires Ruby >= 3.0.0.
|
13
13
|
# Then skip install on some CI jobs.
|
14
14
|
if !ENV['GITHUB_ACTION'] || ENV['INSTALL_STEEP'] == 'true'
|
15
|
-
gem "rbs", "3.4.
|
15
|
+
gem "rbs", "3.4.1", require: false
|
16
16
|
gem "steep", "1.6.0", require: false
|
17
17
|
end
|
data/NEWS.md
CHANGED
@@ -1,5 +1,58 @@
|
|
1
1
|
# NEWS for Lrama
|
2
2
|
|
3
|
+
## Lrama 0.6.2 (2024-01-27)
|
4
|
+
|
5
|
+
### %no-stdlib directive
|
6
|
+
|
7
|
+
If `%no-stdlib` directive is set, Lrama doesn't load Lrama standard library for
|
8
|
+
parameterizing rules, stdlib.y.
|
9
|
+
|
10
|
+
https://github.com/ruby/lrama/pull/344
|
11
|
+
|
12
|
+
## Lrama 0.6.1 (2024-01-13)
|
13
|
+
|
14
|
+
### Nested parameterizing rules
|
15
|
+
|
16
|
+
Allow to pass an instantiated rule to other parameterizing rules.
|
17
|
+
|
18
|
+
```
|
19
|
+
%rule constant(X) : X
|
20
|
+
;
|
21
|
+
|
22
|
+
%rule option(Y) : /* empty */
|
23
|
+
| Y
|
24
|
+
;
|
25
|
+
|
26
|
+
%%
|
27
|
+
|
28
|
+
program : option(constant(number)) // Nested rule
|
29
|
+
;
|
30
|
+
%%
|
31
|
+
```
|
32
|
+
|
33
|
+
Allow to use nested parameterizing rules when define parameterizing rules.
|
34
|
+
|
35
|
+
```
|
36
|
+
%rule option(x) : /* empty */
|
37
|
+
| X
|
38
|
+
;
|
39
|
+
|
40
|
+
%rule double(Y) : Y Y
|
41
|
+
;
|
42
|
+
|
43
|
+
%rule double_opt(A) : option(double(A)) // Nested rule
|
44
|
+
;
|
45
|
+
|
46
|
+
%%
|
47
|
+
|
48
|
+
program : double_opt(number)
|
49
|
+
;
|
50
|
+
|
51
|
+
%%
|
52
|
+
```
|
53
|
+
|
54
|
+
https://github.com/ruby/lrama/pull/337
|
55
|
+
|
3
56
|
## Lrama 0.6.0 (2023-12-25)
|
4
57
|
|
5
58
|
### User defined parameterizing rules
|
@@ -20,6 +73,8 @@ stmt: pair(ODD, EVEN) <num>
|
|
20
73
|
;
|
21
74
|
```
|
22
75
|
|
76
|
+
https://github.com/ruby/lrama/pull/285
|
77
|
+
|
23
78
|
## Lrama 0.5.11 (2023-12-02)
|
24
79
|
|
25
80
|
### Type specification of parameterizing rules
|
data/Steepfile
CHANGED
@@ -4,13 +4,12 @@ target :lib do
|
|
4
4
|
repo_path '.gem_rbs_collection/'
|
5
5
|
signature "sig"
|
6
6
|
|
7
|
+
check "lib/lrama/grammar/binding.rb"
|
7
8
|
check "lib/lrama/grammar/code/printer_code.rb"
|
8
9
|
check "lib/lrama/grammar/code.rb"
|
9
10
|
check "lib/lrama/grammar/counter.rb"
|
10
11
|
check "lib/lrama/grammar/error_token.rb"
|
11
|
-
check "lib/lrama/grammar/
|
12
|
-
check "lib/lrama/grammar/parameterizing_rule_resolver.rb"
|
13
|
-
check "lib/lrama/grammar/parameterizing_rule_rhs_builder.rb"
|
12
|
+
check "lib/lrama/grammar/parameterizing_rule"
|
14
13
|
check "lib/lrama/grammar/parameterizing_rules"
|
15
14
|
check "lib/lrama/grammar/percent_code.rb"
|
16
15
|
check "lib/lrama/grammar/precedence.rb"
|
data/lib/lrama/command.rb
CHANGED
@@ -1,14 +1,36 @@
|
|
1
1
|
module Lrama
|
2
2
|
class Command
|
3
|
+
LRAMA_LIB = File.realpath(File.join(File.dirname(__FILE__)))
|
4
|
+
STDLIB_FILE_PATH = File.join(LRAMA_LIB, 'grammar', 'stdlib.y')
|
5
|
+
|
3
6
|
def run(argv)
|
4
|
-
|
7
|
+
begin
|
8
|
+
options = OptionParser.new.parse(argv)
|
9
|
+
rescue => e
|
10
|
+
message = e.message
|
11
|
+
message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty?
|
12
|
+
abort message
|
13
|
+
end
|
5
14
|
|
6
15
|
Report::Duration.enable if options.trace_opts[:time]
|
7
16
|
|
8
17
|
warning = Lrama::Warning.new
|
9
18
|
text = options.y.read
|
10
19
|
options.y.close if options.y != STDIN
|
11
|
-
|
20
|
+
begin
|
21
|
+
grammar = Lrama::Parser.new(text, options.grammar_file, options.debug).parse
|
22
|
+
unless grammar.no_stdlib
|
23
|
+
stdlib_grammar = Lrama::Parser.new(File.read(STDLIB_FILE_PATH), STDLIB_FILE_PATH, options.debug).parse
|
24
|
+
grammar.insert_before_parameterizing_rules(stdlib_grammar.parameterizing_rules)
|
25
|
+
end
|
26
|
+
grammar.prepare
|
27
|
+
grammar.validate!
|
28
|
+
rescue => e
|
29
|
+
raise e if options.debug
|
30
|
+
message = e.message
|
31
|
+
message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty?
|
32
|
+
abort message
|
33
|
+
end
|
12
34
|
states = Lrama::States.new(grammar, warning, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
|
13
35
|
states.compute
|
14
36
|
context = Lrama::Context.new(states)
|
@@ -39,7 +61,7 @@ module Lrama
|
|
39
61
|
end
|
40
62
|
|
41
63
|
if warning.has_error?
|
42
|
-
exit
|
64
|
+
exit false
|
43
65
|
end
|
44
66
|
end
|
45
67
|
end
|
data/lib/lrama/context.rb
CHANGED
@@ -9,7 +9,7 @@ module Lrama
|
|
9
9
|
BaseMin = -Float::INFINITY
|
10
10
|
|
11
11
|
# TODO: It might be better to pass `states` to Output directly?
|
12
|
-
attr_reader :states
|
12
|
+
attr_reader :states, :yylast, :yypact_ninf, :yytable_ninf, :yydefact, :yydefgoto
|
13
13
|
|
14
14
|
def initialize(states)
|
15
15
|
@states = states
|
@@ -41,15 +41,11 @@ module Lrama
|
|
41
41
|
def yyfinal
|
42
42
|
@states.states.find do |state|
|
43
43
|
state.items.find do |item|
|
44
|
-
item.
|
44
|
+
item.lhs.accept_symbol? && item.end_of_rule?
|
45
45
|
end
|
46
46
|
end.id
|
47
47
|
end
|
48
48
|
|
49
|
-
def yylast
|
50
|
-
@yylast
|
51
|
-
end
|
52
|
-
|
53
49
|
# Number of terms
|
54
50
|
def yyntokens
|
55
51
|
@states.terms.count
|
@@ -119,30 +115,14 @@ module Lrama
|
|
119
115
|
end
|
120
116
|
end
|
121
117
|
|
122
|
-
def yypact_ninf
|
123
|
-
@yypact_ninf
|
124
|
-
end
|
125
|
-
|
126
|
-
def yytable_ninf
|
127
|
-
@yytable_ninf
|
128
|
-
end
|
129
|
-
|
130
118
|
def yypact
|
131
119
|
@base[0...yynstates]
|
132
120
|
end
|
133
121
|
|
134
|
-
def yydefact
|
135
|
-
@yydefact
|
136
|
-
end
|
137
|
-
|
138
122
|
def yypgoto
|
139
123
|
@base[yynstates..-1]
|
140
124
|
end
|
141
125
|
|
142
|
-
def yydefgoto
|
143
|
-
@yydefgoto
|
144
|
-
end
|
145
|
-
|
146
126
|
def yytable
|
147
127
|
@table
|
148
128
|
end
|
@@ -241,7 +221,7 @@ module Lrama
|
|
241
221
|
|
242
222
|
if state.reduces.map(&:selected_look_ahead).any? {|la| !la.empty? }
|
243
223
|
# Iterate reduces with reverse order so that first rule is used.
|
244
|
-
state.reduces.
|
224
|
+
state.reduces.reverse_each do |reduce|
|
245
225
|
reduce.look_ahead.each do |term|
|
246
226
|
actions[term.number] = rule_id_to_action_number(reduce.rule.id)
|
247
227
|
end
|
@@ -40,7 +40,7 @@ module Lrama
|
|
40
40
|
current = :production
|
41
41
|
lookahead_sym = paths.last.to.item.end_of_rule? ? @conflict_symbol : nil
|
42
42
|
|
43
|
-
paths.
|
43
|
+
paths.reverse_each do |path|
|
44
44
|
item = path.to.item
|
45
45
|
|
46
46
|
case current
|
@@ -97,7 +97,7 @@ module Lrama
|
|
97
97
|
if next_sym == sym
|
98
98
|
derivation = nil
|
99
99
|
|
100
|
-
sis.
|
100
|
+
sis.reverse_each do |si|
|
101
101
|
derivation = Derivation.new(si.item, derivation)
|
102
102
|
end
|
103
103
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Lrama
|
2
|
+
class Grammar
|
3
|
+
class Binding
|
4
|
+
attr_reader :actual_args, :count
|
5
|
+
|
6
|
+
def initialize(parameterizing_rule, actual_args)
|
7
|
+
@parameters = parameterizing_rule.parameters
|
8
|
+
@actual_args = actual_args
|
9
|
+
@parameter_to_arg = @parameters.zip(actual_args).map do |param, arg|
|
10
|
+
[param.s_value, arg]
|
11
|
+
end.to_h
|
12
|
+
end
|
13
|
+
|
14
|
+
def resolve_symbol(symbol)
|
15
|
+
if symbol.is_a?(Lexer::Token::InstantiateRule)
|
16
|
+
resolved_args = symbol.args.map { |arg| resolve_symbol(arg) }
|
17
|
+
Lrama::Lexer::Token::InstantiateRule.new(s_value: symbol.s_value, location: symbol.location, args: resolved_args, lhs_tag: symbol.lhs_tag)
|
18
|
+
else
|
19
|
+
@parameter_to_arg[symbol.s_value] || symbol
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -22,7 +22,7 @@ module Lrama
|
|
22
22
|
# For the semantic action of original rule:
|
23
23
|
#
|
24
24
|
# "Rule" class: keyword_class { $1 } tSTRING { $2 + $3 } keyword_end { $class = $1 + $keyword_end }
|
25
|
-
# "Position in grammar" $1 $2 $3 $4 $5
|
25
|
+
# "Position in grammar" $1 $2 $3 $4 $5
|
26
26
|
# "Index for yyvsp" -4 -3 -2 -1 0
|
27
27
|
#
|
28
28
|
#
|
data/lib/lrama/grammar/code.rb
CHANGED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Lrama
|
2
|
+
class Grammar
|
3
|
+
class ParameterizingRule
|
4
|
+
class Resolver
|
5
|
+
attr_accessor :rules, :created_lhs_list
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@rules = []
|
9
|
+
@created_lhs_list = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_parameterizing_rule(rule)
|
13
|
+
@rules << rule
|
14
|
+
end
|
15
|
+
|
16
|
+
def find(token)
|
17
|
+
select_rules(token).last
|
18
|
+
end
|
19
|
+
|
20
|
+
def created_lhs(lhs_s_value)
|
21
|
+
@created_lhs_list.reverse.find { |created_lhs| created_lhs.s_value == lhs_s_value }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def select_rules(token)
|
27
|
+
rules = select_rules_by_name(token.rule_name)
|
28
|
+
rules = rules.select { |rule| rule.required_parameters_count == token.args_count }
|
29
|
+
if rules.empty?
|
30
|
+
raise "Invalid number of arguments. `#{token.rule_name}`"
|
31
|
+
else
|
32
|
+
rules
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def select_rules_by_name(rule_name)
|
37
|
+
rules = @rules.select { |rule| rule.name == rule_name }
|
38
|
+
if rules.empty?
|
39
|
+
raise "Parameterizing rule does not exist. `#{rule_name}`"
|
40
|
+
else
|
41
|
+
rules
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Lrama
|
2
|
+
class Grammar
|
3
|
+
class ParameterizingRule
|
4
|
+
class Rule
|
5
|
+
attr_reader :name, :parameters, :rhs_list, :required_parameters_count
|
6
|
+
|
7
|
+
def initialize(name, parameters, rhs_list)
|
8
|
+
@name = name
|
9
|
+
@parameters = parameters
|
10
|
+
@rhs_list = rhs_list
|
11
|
+
@required_parameters_count = parameters.count
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/lrama/grammar/rule.rb
CHANGED
@@ -19,7 +19,7 @@ module Lrama
|
|
19
19
|
# TODO: Change this to display_name
|
20
20
|
def to_s
|
21
21
|
l = lhs.id.s_value
|
22
|
-
r =
|
22
|
+
r = empty_rule? ? "ε" : rhs.map {|r| r.id.s_value }.join(", ")
|
23
23
|
|
24
24
|
"#{l} -> #{r}"
|
25
25
|
end
|
@@ -27,7 +27,7 @@ module Lrama
|
|
27
27
|
# Used by #user_actions
|
28
28
|
def as_comment
|
29
29
|
l = lhs.id.s_value
|
30
|
-
r =
|
30
|
+
r = empty_rule? ? "%empty" : rhs.map(&:display_name).join(" ")
|
31
31
|
|
32
32
|
"#{l}: #{r}"
|
33
33
|
end
|