lrama 0.6.3 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecd30d3fab4dd73442ed6d3b2802db5b463159cb6ddf1f1835d9b8e860d4c9dd
4
- data.tar.gz: 79b6087e68d3c2e95db81fa1d25f58280a5543e9c5fa91f5b6ecc7c40b5599d7
3
+ metadata.gz: '0278f10f4ec931a33647698e0b504d6f7051049b4f13429b804b30911019787e'
4
+ data.tar.gz: 51b687fabf61ac011848b17c34a151409e9bcd4cf14382ec5f9db365cb480d13
5
5
  SHA512:
6
- metadata.gz: f3302156423399987015deb90afbaa0d6916e5e61b14c5297ff6a0e01ab9db3bbd164b334a11e26d38cf626425b7faee404e5d1cdec236a9b08b576ced4fe201
7
- data.tar.gz: 380d8d31c93e5ae6c5a406c2b2eedad0d4b52dd311ecd742ca1412bd1acade7dae8fe12f7a6325ef8a0b4922ffd927b0dfa2caf3cd54c095669ab2b2cab85516
6
+ metadata.gz: bf16ab8c270f7b694798ffaa1ea6e30e09c0e7b5139866fee44e4b7e0ee1b54148c2c237b3155f31b5a599c7cd141505190de3089ae4f2521a6d0de2901a9a92
7
+ data.tar.gz: 84cca8a68fb47d48e11782c11675a7d43f18a4dec1d4472ccf764e6995e7e79ecd54b437428ba15e177d36fd9e9be76b71f0aa59fd63976f13594b68a69a353b
@@ -124,9 +124,8 @@ jobs:
124
124
  strategy:
125
125
  fail-fast: false
126
126
  matrix:
127
- # '3.0' is the oldest living ruby version
128
- # '2.7' is for BASERUBY
129
- baseruby: ['head', '3.0', '2.7']
127
+ # '3.0' is the oldest living ruby version and minimal BASERUBY version
128
+ baseruby: ['head', '3.0']
130
129
  ruby_branch: ['master']
131
130
  defaults:
132
131
  run:
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.1", require: false
15
+ gem "rbs", "3.4.4", require: false
16
16
  gem "steep", "1.6.0", require: false
17
17
  end
data/NEWS.md CHANGED
@@ -1,5 +1,109 @@
1
1
  # NEWS for Lrama
2
2
 
3
+ ## Lrama 0.6.5 (2024-03-25)
4
+
5
+ ### Typed Midrule Actions
6
+
7
+ User can specify the type of mid rule action by tag (`<bar>`) instead of specifying it with in an action.
8
+
9
+ ```
10
+ primary: k_case expr_value terms?
11
+ {
12
+ $<val>$ = p->case_labels;
13
+ p->case_labels = Qnil;
14
+ }
15
+ case_body
16
+ k_end
17
+ {
18
+ ...
19
+ }
20
+ ```
21
+
22
+ can be written as
23
+
24
+ ```
25
+ primary: k_case expr_value terms?
26
+ {
27
+ $$ = p->case_labels;
28
+ p->case_labels = Qnil;
29
+ }<val>
30
+ case_body
31
+ k_end
32
+ {
33
+ ...
34
+ }
35
+ ```
36
+
37
+ `%destructor` for midrule action is invoked only when tag is specified by Typed Midrule Actions.
38
+
39
+ Difference from Bison's Typed Midrule Actions is that tag is postposed in Lrama however it's preposed in Bison.
40
+
41
+ Bison supports this feature from 3.1.
42
+
43
+ ## Lrama 0.6.4 (2024-03-22)
44
+
45
+ ### Parameterizing rules (preceded, terminated, delimited)
46
+
47
+ Support `preceded`, `terminated` and `delimited` rules.
48
+
49
+ ```
50
+ program: preceded(opening, X)
51
+
52
+ // Expanded to
53
+
54
+ program: preceded_opening_X
55
+ preceded_opening_X: opening X
56
+ ```
57
+
58
+ ```
59
+ program: terminated(X, closing)
60
+
61
+ // Expanded to
62
+
63
+ program: terminated_X_closing
64
+ terminated_X_closing: X closing
65
+ ```
66
+
67
+ ```
68
+ program: delimited(opening, X, closing)
69
+
70
+ // Expanded to
71
+
72
+ program: delimited_opening_X_closing
73
+ delimited_opening_X_closing: opening X closing
74
+ ```
75
+
76
+ https://github.com/ruby/lrama/pull/382
77
+
78
+ ### Support `%destructor` declaration
79
+
80
+ User can set codes for freeing semantic value resources by using `%destructor`.
81
+ In general, these resources are freed by actions or after parsing.
82
+ However if syntax error happens in parsing, these codes may not be executed.
83
+ Codes associated to `%destructor` are executed when semantic value is popped from the stack by an error.
84
+
85
+ ```
86
+ %token <val1> NUM
87
+ %type <val2> expr2
88
+ %type <val3> expr
89
+
90
+ %destructor {
91
+ printf("destructor for val1: %d\n", $$);
92
+ } <val1> // printer for TAG
93
+
94
+ %destructor {
95
+ printf("destructor for val2: %d\n", $$);
96
+ } <val2>
97
+
98
+ %destructor {
99
+ printf("destructor for expr: %d\n", $$);
100
+ } expr // printer for symbol
101
+ ```
102
+
103
+ Bison supports this feature from 1.75b.
104
+
105
+ https://github.com/ruby/lrama/pull/385
106
+
3
107
  ## Lrama 0.6.3 (2024-02-15)
4
108
 
5
109
  ### Bring Your Own Stack
@@ -34,6 +138,8 @@ primary: k_if expr_value then compstmt if_tail k_end
34
138
  }
35
139
  ```
36
140
 
141
+ https://github.com/ruby/lrama/pull/367
142
+
37
143
  ## Lrama 0.6.2 (2024-01-27)
38
144
 
39
145
  ### %no-stdlib directive
@@ -51,7 +157,7 @@ Allow to pass an instantiated rule to other parameterizing rules.
51
157
 
52
158
  ```
53
159
  %rule constant(X) : X
54
- ;
160
+ ;
55
161
 
56
162
  %rule option(Y) : /* empty */
57
163
  | Y
data/Steepfile CHANGED
@@ -5,6 +5,7 @@ target :lib do
5
5
  signature "sig"
6
6
 
7
7
  check "lib/lrama/grammar/binding.rb"
8
+ check "lib/lrama/grammar/code/destructor_code.rb"
8
9
  check "lib/lrama/grammar/code/printer_code.rb"
9
10
  check "lib/lrama/grammar/code.rb"
10
11
  check "lib/lrama/grammar/counter.rb"
@@ -14,6 +15,7 @@ target :lib do
14
15
  check "lib/lrama/grammar/symbols"
15
16
  check "lib/lrama/grammar/percent_code.rb"
16
17
  check "lib/lrama/grammar/precedence.rb"
18
+ check "lib/lrama/grammar/destructor.rb"
17
19
  check "lib/lrama/grammar/printer.rb"
18
20
  check "lib/lrama/grammar/reference.rb"
19
21
  check "lib/lrama/grammar/rule_builder.rb"
@@ -23,5 +25,6 @@ target :lib do
23
25
  check "lib/lrama/report"
24
26
  check "lib/lrama/bitmap.rb"
25
27
  check "lib/lrama/digraph.rb"
28
+ check "lib/lrama/options.rb"
26
29
  check "lib/lrama/warning.rb"
27
30
  end
@@ -0,0 +1,40 @@
1
+ module Lrama
2
+ class Grammar
3
+ class Code
4
+ class DestructorCode < Code
5
+ def initialize(type:, token_code:, tag:)
6
+ super(type: type, token_code: token_code)
7
+ @tag = tag
8
+ end
9
+
10
+ private
11
+
12
+ # * ($$) *yyvaluep
13
+ # * (@$) *yylocationp
14
+ # * ($:$) error
15
+ # * ($1) error
16
+ # * (@1) error
17
+ # * ($:1) error
18
+ def reference_to_c(ref)
19
+ case
20
+ when ref.type == :dollar && ref.name == "$" # $$
21
+ member = @tag.member
22
+ "((*yyvaluep).#{member})"
23
+ when ref.type == :at && ref.name == "$" # @$
24
+ "(*yylocationp)"
25
+ when ref.type == :index && ref.name == "$" # $:$
26
+ raise "$:#{ref.value} can not be used in #{type}."
27
+ when ref.type == :dollar # $n
28
+ raise "$#{ref.value} can not be used in #{type}."
29
+ when ref.type == :at # @n
30
+ raise "@#{ref.value} can not be used in #{type}."
31
+ when ref.type == :index # $:n
32
+ raise "$:#{ref.value} can not be used in #{type}."
33
+ else
34
+ raise "Unexpected. #{self}, #{ref}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,4 +1,5 @@
1
1
  require "forwardable"
2
+ require "lrama/grammar/code/destructor_code"
2
3
  require "lrama/grammar/code/initial_action_code"
3
4
  require "lrama/grammar/code/no_reference_code"
4
5
  require "lrama/grammar/code/printer_code"
@@ -0,0 +1,9 @@
1
+ module Lrama
2
+ class Grammar
3
+ class Destructor < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)
4
+ def translated_code(tag)
5
+ Code::DestructorCode.new(type: :destructor, token_code: token_code, tag: tag).translated_code
6
+ end
7
+ end
8
+ end
9
+ end
@@ -115,12 +115,12 @@ module Lrama
115
115
  @replaced_rhs << lhs_token
116
116
  parameterizing_rule_resolver.created_lhs_list << lhs_token
117
117
  parameterizing_rule.rhs_list.each do |r|
118
- rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, i, lhs_tag: token.lhs_tag, skip_preprocess_references: true)
118
+ rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, lhs_tag: token.lhs_tag, skip_preprocess_references: true)
119
119
  rule_builder.lhs = lhs_token
120
120
  r.symbols.each { |sym| rule_builder.add_rhs(bindings.resolve_symbol(sym)) }
121
121
  rule_builder.line = line
122
- rule_builder.user_code = r.user_code
123
122
  rule_builder.precedence_sym = r.precedence_sym
123
+ rule_builder.user_code = r.user_code
124
124
  rule_builder.complete_input
125
125
  rule_builder.setup_rules(parameterizing_rule_resolver)
126
126
  @rule_builders_for_parameterizing_rules << rule_builder
@@ -128,10 +128,11 @@ module Lrama
128
128
  end
129
129
  when Lrama::Lexer::Token::UserCode
130
130
  prefix = token.referred ? "@" : "$@"
131
+ tag = token.tag || lhs_tag
131
132
  new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s)
132
133
  @replaced_rhs << new_token
133
134
 
134
- rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, i, lhs_tag: lhs_tag, skip_preprocess_references: true)
135
+ rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, i, lhs_tag: tag, skip_preprocess_references: true)
135
136
  rule_builder.lhs = new_token
136
137
  rule_builder.user_code = token
137
138
  rule_builder.complete_input
@@ -8,6 +8,9 @@
8
8
 
9
9
  **********************************************************************/
10
10
 
11
+ // -------------------------------------------------------------------
12
+ // Options
13
+
11
14
  /*
12
15
  * program: option(number)
13
16
  *
@@ -21,6 +24,45 @@
21
24
  | X
22
25
  ;
23
26
 
27
+ // -------------------------------------------------------------------
28
+ // Sequences
29
+
30
+ /*
31
+ * program: preceded(opening, X)
32
+ *
33
+ * =>
34
+ *
35
+ * program: preceded_opening_X
36
+ * preceded_opening_X: opening X
37
+ */
38
+ %rule preceded(opening, X): opening X { $$ = $2; }
39
+ ;
40
+
41
+ /*
42
+ * program: terminated(X, closing)
43
+ *
44
+ * =>
45
+ *
46
+ * program: terminated_X_closing
47
+ * terminated_X_closing: X closing
48
+ */
49
+ %rule terminated(X, closing): X closing { $$ = $1; }
50
+ ;
51
+
52
+ /*
53
+ * program: delimited(opening, X, closing)
54
+ *
55
+ * =>
56
+ *
57
+ * program: delimited_opening_X_closing
58
+ * delimited_opening_X_closing: opening X closing
59
+ */
60
+ %rule delimited(opening, X, closing): opening X closing { $$ = $2; }
61
+ ;
62
+
63
+ // -------------------------------------------------------------------
64
+ // Lists
65
+
24
66
  /*
25
67
  * program: list(number)
26
68
  *
@@ -7,11 +7,12 @@
7
7
  module Lrama
8
8
  class Grammar
9
9
  class Symbol
10
- attr_accessor :id, :alias_name, :tag, :number, :token_id, :nullable, :precedence, :printer, :error_token, :first_set, :first_set_bitmap
10
+ attr_accessor :id, :alias_name, :tag, :number, :token_id, :nullable, :precedence,
11
+ :printer, :destructor, :error_token, :first_set, :first_set_bitmap
11
12
  attr_reader :term
12
13
  attr_writer :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol
13
14
 
14
- def initialize(id:, term:, alias_name: nil, number: nil, tag: nil, token_id: nil, nullable: nil, precedence: nil, printer: nil)
15
+ def initialize(id:, term:, alias_name: nil, number: nil, tag: nil, token_id: nil, nullable: nil, precedence: nil, printer: nil, destructor: nil)
15
16
  @id = id
16
17
  @alias_name = alias_name
17
18
  @number = number
@@ -21,6 +22,7 @@ module Lrama
21
22
  @nullable = nullable
22
23
  @precedence = precedence
23
24
  @printer = printer
25
+ @destructor = destructor
24
26
  end
25
27
 
26
28
  def term?
@@ -58,7 +58,7 @@ module Lrama
58
58
  end
59
59
 
60
60
  def find_symbol_by_s_value!(s_value)
61
- find_symbol_by_s_value(s_value) || (raise "Symbol not found: #{s_value}")
61
+ find_symbol_by_s_value(s_value) || (raise "Symbol not found. value: `#{s_value}`")
62
62
  end
63
63
 
64
64
  def find_symbol_by_id(id)
@@ -68,7 +68,7 @@ module Lrama
68
68
  end
69
69
 
70
70
  def find_symbol_by_id!(id)
71
- find_symbol_by_id(id) || (raise "Symbol not found: #{id}")
71
+ find_symbol_by_id(id) || (raise "Symbol not found. #{id}")
72
72
  end
73
73
 
74
74
  def find_symbol_by_token_id(token_id)
@@ -78,7 +78,7 @@ module Lrama
78
78
  def find_symbol_by_number!(number)
79
79
  sym = symbols[number]
80
80
 
81
- raise "Symbol not found: #{number}" unless sym
81
+ raise "Symbol not found. number: `#{number}`" unless sym
82
82
  raise "[BUG] Symbol number mismatch. #{number}, #{sym}" if sym.number != number
83
83
 
84
84
  sym
@@ -118,6 +118,23 @@ module Lrama
118
118
  end
119
119
  end
120
120
 
121
+ def fill_destructor(destructors)
122
+ symbols.each do |sym|
123
+ destructors.each do |destructor|
124
+ destructor.ident_or_tags.each do |ident_or_tag|
125
+ case ident_or_tag
126
+ when Lrama::Lexer::Token::Ident
127
+ sym.destructor = destructor if sym.id == ident_or_tag
128
+ when Lrama::Lexer::Token::Tag
129
+ sym.destructor = destructor if sym.tag == ident_or_tag
130
+ else
131
+ raise "Unknown token type. #{destructor}"
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
121
138
  def fill_error_token(error_tokens)
122
139
  symbols.each do |sym|
123
140
  error_tokens.each do |token|
@@ -154,7 +171,7 @@ module Lrama
154
171
  def find_nterm_by_id!(id)
155
172
  @nterms.find do |s|
156
173
  s.id == id
157
- end || (raise "Symbol not found: #{id}")
174
+ end || (raise "Symbol not found. #{id}")
158
175
  end
159
176
 
160
177
  def fill_terms_number
data/lib/lrama/grammar.rb CHANGED
@@ -3,6 +3,7 @@ require "lrama/grammar/auxiliary"
3
3
  require "lrama/grammar/binding"
4
4
  require "lrama/grammar/code"
5
5
  require "lrama/grammar/counter"
6
+ require "lrama/grammar/destructor"
6
7
  require "lrama/grammar/error_token"
7
8
  require "lrama/grammar/parameterizing_rule"
8
9
  require "lrama/grammar/percent_code"
@@ -34,7 +35,7 @@ module Lrama
34
35
  def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term,
35
36
  :find_symbol_by_number!, :find_symbol_by_id!, :token_to_symbol,
36
37
  :find_symbol_by_s_value!, :fill_symbol_number, :fill_nterm_type,
37
- :fill_printer, :fill_error_token, :sort_by_number!
38
+ :fill_printer, :fill_destructor, :fill_error_token, :sort_by_number!
38
39
 
39
40
 
40
41
  def initialize(rule_counter)
@@ -43,6 +44,7 @@ module Lrama
43
44
  # Code defined by "%code"
44
45
  @percent_codes = []
45
46
  @printers = []
47
+ @destructors = []
46
48
  @error_tokens = []
47
49
  @symbols_resolver = Grammar::Symbols::Resolver.new
48
50
  @types = []
@@ -65,6 +67,10 @@ module Lrama
65
67
  @percent_codes << PercentCode.new(id.s_value, code.s_value)
66
68
  end
67
69
 
70
+ def add_destructor(ident_or_tags:, token_code:, lineno:)
71
+ @destructors << Destructor.new(ident_or_tags: ident_or_tags, token_code: token_code, lineno: lineno)
72
+ end
73
+
68
74
  def add_printer(ident_or_tags:, token_code:, lineno:)
69
75
  @printers << Printer.new(ident_or_tags: ident_or_tags, token_code: token_code, lineno: lineno)
70
76
  end
@@ -345,6 +351,7 @@ module Lrama
345
351
  fill_symbol_number
346
352
  fill_nterm_type(@types)
347
353
  fill_printer(@printers)
354
+ fill_destructor(@destructors)
348
355
  fill_error_token(@error_tokens)
349
356
  sort_by_number!
350
357
  end
@@ -4,6 +4,8 @@ module Lrama
4
4
  class Lexer
5
5
  class Token
6
6
  class UserCode < Token
7
+ attr_accessor :tag
8
+
7
9
  def references
8
10
  @references ||= _references
9
11
  end
@@ -18,7 +18,7 @@ module Lrama
18
18
  end
19
19
 
20
20
  def to_s
21
- "#{super} location: #{location}"
21
+ "value: `#{s_value}`, location: #{location}"
22
22
  end
23
23
 
24
24
  def referred_by?(string)
data/lib/lrama/lexer.rb CHANGED
@@ -21,6 +21,7 @@ module Lrama
21
21
  %define
22
22
  %require
23
23
  %printer
24
+ %destructor
24
25
  %lex-param
25
26
  %parse-param
26
27
  %initial-action
@@ -64,9 +64,18 @@ module Lrama
64
64
  o.on('-H', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v }
65
65
  o.on('-d', 'also produce a header file') { @options.header = true }
66
66
  o.on('-r', '--report=THINGS', Array, 'also produce details on the automaton') {|v| @report = v }
67
+ o.on_tail ''
68
+ o.on_tail 'Valid Reports:'
69
+ o.on_tail " #{VALID_REPORTS.join(' ')}"
70
+
67
71
  o.on('--report-file=FILE', 'also produce details on the automaton output to a file named FILE') {|v| @options.report_file = v }
68
72
  o.on('-o', '--output=FILE', 'leave output to FILE') {|v| @options.outfile = v }
73
+
69
74
  o.on('--trace=THINGS', Array, 'also output trace logs at runtime') {|v| @trace = v }
75
+ o.on_tail ''
76
+ o.on_tail 'Valid Traces:'
77
+ o.on_tail " #{VALID_TRACES.join(' ')}"
78
+
70
79
  o.on('-v', 'reserved, do nothing') { }
71
80
  o.separator ''
72
81
  o.separator 'Error Recovery:'
@@ -75,20 +84,22 @@ module Lrama
75
84
  o.separator 'Other options:'
76
85
  o.on('-V', '--version', "output version information and exit") {|v| puts "lrama #{Lrama::VERSION}"; exit 0 }
77
86
  o.on('-h', '--help', "display this help and exit") {|v| puts o; exit 0 }
78
- o.separator ''
87
+ o.on_tail
79
88
  o.parse!(argv)
80
89
  end
81
90
  end
82
91
 
92
+ BISON_REPORTS = %w[states itemsets lookaheads solved counterexamples cex all none]
93
+ OTHER_REPORTS = %w[verbose]
94
+ NOT_SUPPORTED_REPORTS = %w[cex none]
95
+ VALID_REPORTS = BISON_REPORTS + OTHER_REPORTS - NOT_SUPPORTED_REPORTS
96
+
83
97
  def validate_report(report)
84
- bison_list = %w[states itemsets lookaheads solved counterexamples cex all none]
85
- others = %w[verbose]
86
- list = bison_list + others
87
- not_supported = %w[cex none]
98
+ list = VALID_REPORTS
88
99
  h = { grammar: true }
89
100
 
90
101
  report.each do |r|
91
- if list.include?(r) && !not_supported.include?(r)
102
+ if list.include?(r)
92
103
  h[r.to_sym] = true
93
104
  else
94
105
  raise "Invalid report option \"#{r}\"."
@@ -96,7 +107,7 @@ module Lrama
96
107
  end
97
108
 
98
109
  if h[:all]
99
- (bison_list - not_supported).each do |r|
110
+ (BISON_REPORTS - NOT_SUPPORTED_REPORTS).each do |r|
100
111
  h[r.to_sym] = true
101
112
  end
102
113
 
@@ -106,12 +117,14 @@ module Lrama
106
117
  return h
107
118
  end
108
119
 
120
+ VALID_TRACES = %w[
121
+ none locations scan parse automaton bitsets
122
+ closure grammar rules resource sets muscles tools
123
+ m4-early m4 skeleton time ielr cex all
124
+ ]
125
+
109
126
  def validate_trace(trace)
110
- list = %w[
111
- none locations scan parse automaton bitsets
112
- closure grammar rules resource sets muscles tools
113
- m4-early m4 skeleton time ielr cex all
114
- ]
127
+ list = VALID_TRACES
115
128
  h = {}
116
129
 
117
130
  trace.each do |t|
data/lib/lrama/options.rb CHANGED
@@ -18,6 +18,7 @@ module Lrama
18
18
  @trace_opts = nil
19
19
  @report_opts = nil
20
20
  @y = STDIN
21
+ @debug = false
21
22
  end
22
23
  end
23
24
  end
data/lib/lrama/output.rb CHANGED
@@ -150,6 +150,25 @@ module Lrama
150
150
  str
151
151
  end
152
152
 
153
+ def symbol_actions_for_destructor
154
+ str = ""
155
+
156
+ @grammar.symbols.each do |sym|
157
+ next unless sym.destructor
158
+
159
+ str << <<-STR
160
+ case #{sym.enum_name}: /* #{sym.comment} */
161
+ #line #{sym.destructor.lineno} "#{@grammar_file_path}"
162
+ {#{sym.destructor.translated_code(sym.tag)}}
163
+ #line [@oline@] [@ofile@]
164
+ break;
165
+
166
+ STR
167
+ end
168
+
169
+ str
170
+ end
171
+
153
172
  # b4_user_initial_action
154
173
  def user_initial_action(comment = "")
155
174
  return "" unless @grammar.initial_action