lrama 0.6.3 → 0.6.5

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 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