expressir 2.4.0 → 2.4.1
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/TODO.bugs/01-stale-transformer-autoload.md +39 -0
- data/TODO.bugs/02-parser-class-instance-vars.md +36 -0
- data/TODO.bugs/03-builder-mutable-state.md +43 -0
- data/TODO.bugs/04-formatter-public-send-dispatch.md +53 -0
- data/TODO.bugs/05-anonymous-formatter-subclass.md +45 -0
- data/TODO.bugs/06-collection-registry-single-source.md +53 -0
- data/TODO.bugs/07-require-relative-cleanup.md +42 -0
- data/TODO.bugs/08-require-expressir-in-commands.md +34 -0
- data/TODO.bugs/09-parser-split.md +53 -0
- data/TODO.bugs/10-to-s-override.md +42 -0
- data/TODO.bugs/11-parser-class-variables.md +39 -0
- data/TODO.bugs/12-marker-modules-vs-registry.md +64 -0
- data/TODO.bugs/13-string-literal-scanner-limitation.md +52 -0
- data/TODO.bugs/14-model-formatting-leak.md +30 -0
- data/TODO.bugs/15-expression-children-macro.md +27 -0
- data/TODO.bugs/16-pretty-formatter-duplication.md +28 -0
- data/TODO.bugs/17-snake-case-cache-mutable-constant.md +28 -0
- data/TODO.bugs/18-const-get-private-constants.md +30 -0
- data/TODO.bugs/19-format-methods-public.md +22 -0
- data/TODO.bugs/20-coverage-nested-entities-dedup.md +20 -0
- data/TODO.bugs/21-operator-tokens-secondary-dispatch.md +21 -0
- data/TODO.bugs/22-builder-fast-path-wrappers.md +32 -0
- data/TODO.bugs/23-coverage-inverse-maps.md +21 -0
- data/TODO.bugs/24-streaming-builder-complexity.md +19 -0
- data/TODO.bugs/25-debug-puts-in-production.md +21 -0
- data/TODO.bugs/26-generic-entity-children-misplaced.md +21 -0
- data/TODO.bugs/27-package-build-god-method.md +19 -0
- data/TODO.bugs/28-package-god-class.md +30 -0
- data/TODO.bugs/29-validate-ascii-god-class.md +24 -0
- data/TODO.bugs/30-unicode-map-extraction.md +19 -0
- data/TODO.bugs/README.md +43 -0
- data/docs/_tutorials/formatting-schemas.adoc +89 -0
- data/docs/_tutorials/index.adoc +10 -0
- data/docs/lychee.toml +3 -0
- data/expressir.gemspec +4 -2
- data/lib/expressir/commands/changes_import_eengine.rb +0 -6
- data/lib/expressir/commands/changes_validate.rb +0 -2
- data/lib/expressir/commands/clean.rb +1 -1
- data/lib/expressir/commands/file_violations.rb +70 -0
- data/lib/expressir/commands/format.rb +1 -1
- data/lib/expressir/commands/non_ascii_character.rb +49 -0
- data/lib/expressir/commands/non_ascii_violation_collection.rb +301 -0
- data/lib/expressir/commands/package.rb +138 -185
- data/lib/expressir/commands/validate_ascii.rb +0 -510
- data/lib/expressir/commands/version.rb +1 -1
- data/lib/expressir/commands.rb +3 -0
- data/lib/expressir/coverage.rb +49 -117
- data/lib/expressir/express/builder.rb +74 -86
- data/lib/expressir/express/builder_context.rb +22 -0
- data/lib/expressir/express/builders/expression_builder.rb +16 -16
- data/lib/expressir/express/cache.rb +35 -8
- data/lib/expressir/express/error.rb +7 -0
- data/lib/expressir/express/formatter.rb +33 -18
- data/lib/expressir/express/formatters/declarations_formatter.rb +24 -2
- data/lib/expressir/express/formatters/remark_formatter.rb +75 -2
- data/lib/expressir/express/formatters/statements_formatter.rb +20 -3
- data/lib/expressir/express/grammar/parser.rb +705 -0
- data/lib/expressir/express/grammar.rb +11 -0
- data/lib/expressir/express/node_position_index.rb +215 -0
- data/lib/expressir/express/parser.rb +114 -884
- data/lib/expressir/express/pretty_formatter.rb +23 -5
- data/lib/expressir/express/remark_attacher.rb +475 -577
- data/lib/expressir/express/remark_scanner.rb +84 -19
- data/lib/expressir/express/schema_block_scanner.rb +136 -0
- data/lib/expressir/express/schema_source_formatter.rb +15 -0
- data/lib/expressir/express/scope_resolver.rb +194 -0
- data/lib/expressir/express/source_formatter.rb +15 -0
- data/lib/expressir/express/streaming_builder.rb +147 -176
- data/lib/expressir/express.rb +8 -6
- data/lib/expressir/model/concerns.rb +3 -0
- data/lib/expressir/model/data_types/generic_entity.rb +6 -6
- data/lib/expressir/model/declarations/entity.rb +5 -0
- data/lib/expressir/model/declarations/function.rb +5 -0
- data/lib/expressir/model/declarations/procedure.rb +5 -0
- data/lib/expressir/model/declarations/rule.rb +6 -0
- data/lib/expressir/model/declarations/schema.rb +21 -8
- data/lib/expressir/model/declarations/type.rb +3 -0
- data/lib/expressir/model/exp_file.rb +2 -0
- data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -0
- data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -0
- data/lib/expressir/model/expressions/binary_expression.rb +1 -0
- data/lib/expressir/model/expressions/entity_constructor.rb +1 -0
- data/lib/expressir/model/expressions/function_call.rb +1 -0
- data/lib/expressir/model/expressions/interval.rb +1 -0
- data/lib/expressir/model/expressions/query_expression.rb +2 -0
- data/lib/expressir/model/expressions/unary_expression.rb +1 -0
- data/lib/expressir/model/model_element.rb +128 -14
- data/lib/expressir/model/remark_format.rb +17 -0
- data/lib/expressir/model/remark_info.rb +31 -3
- data/lib/expressir/model/remark_placement.rb +29 -0
- data/lib/expressir/model/statements/alias.rb +2 -0
- data/lib/expressir/model/statements/assignment.rb +4 -0
- data/lib/expressir/model/statements/case.rb +21 -0
- data/lib/expressir/model/statements/case_action.rb +1 -0
- data/lib/expressir/model/statements/compound.rb +4 -0
- data/lib/expressir/model/statements/escape.rb +3 -0
- data/lib/expressir/model/statements/if.rb +5 -0
- data/lib/expressir/model/statements/null.rb +3 -0
- data/lib/expressir/model/statements/procedure_call.rb +3 -0
- data/lib/expressir/model/statements/repeat.rb +3 -0
- data/lib/expressir/model/statements/return.rb +3 -0
- data/lib/expressir/model/statements/skip.rb +3 -0
- data/lib/expressir/model.rb +12 -3
- data/lib/expressir/package/builder.rb +2 -2
- data/lib/expressir/version.rb +6 -1
- data/lib/expressir.rb +38 -3
- metadata +63 -5
- data/lib/expressir/express/transformer/remark_handling.rb +0 -194
|
@@ -9,6 +9,15 @@ module Expressir
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def self.register_formatter(model_class, method_name)
|
|
12
|
+
# Guard: fail at registration time if the method doesn't exist on
|
|
13
|
+
# this class (or one of its included modules). Includes private
|
|
14
|
+
# methods since format handlers are private (TODO.bugs/19).
|
|
15
|
+
unless method_defined?(method_name) || private_method_defined?(method_name)
|
|
16
|
+
raise ArgumentError,
|
|
17
|
+
"Formatter.register_formatter: method :#{method_name} " \
|
|
18
|
+
"is not defined for #{model_class}"
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
@format_registry[model_class] = method_name
|
|
13
22
|
end
|
|
14
23
|
|
|
@@ -23,14 +32,6 @@ module Expressir
|
|
|
23
32
|
include Formatters::DataTypesFormatter
|
|
24
33
|
include Formatters::DeclarationsFormatter
|
|
25
34
|
|
|
26
|
-
# Handlers for types implemented directly in this class
|
|
27
|
-
register_formatter Model::Repository, :format_repository
|
|
28
|
-
register_formatter Model::ExpFile, :format_exp_file
|
|
29
|
-
register_formatter Model::Declarations::SchemaVersionItem, :format_noop
|
|
30
|
-
register_formatter Model::Declarations::InterfacedItem, :format_noop
|
|
31
|
-
register_formatter Model::Cache, :format_noop
|
|
32
|
-
register_formatter Model::ModelElement, :format_noop
|
|
33
|
-
|
|
34
35
|
INDENT_CHAR = " ".freeze
|
|
35
36
|
INDENT_WIDTH = 2
|
|
36
37
|
INDENT = INDENT_CHAR * INDENT_WIDTH
|
|
@@ -62,12 +63,6 @@ module Expressir
|
|
|
62
63
|
Model::SupertypeExpressions::BinarySupertypeExpression::ANDOR => 2,
|
|
63
64
|
}.freeze
|
|
64
65
|
|
|
65
|
-
private_constant :INDENT_CHAR
|
|
66
|
-
private_constant :INDENT_WIDTH
|
|
67
|
-
private_constant :INDENT
|
|
68
|
-
private_constant :OPERATOR_PRECEDENCE
|
|
69
|
-
private_constant :SUPERTYPE_OPERATOR_PRECEDENCE
|
|
70
|
-
|
|
71
66
|
attr_accessor :no_remarks
|
|
72
67
|
|
|
73
68
|
def initialize(no_remarks: false)
|
|
@@ -82,12 +77,22 @@ module Expressir
|
|
|
82
77
|
return "" if node.nil?
|
|
83
78
|
|
|
84
79
|
handler = self.class.format_registry[node.class]
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
unless handler
|
|
81
|
+
warn "#{node.class.name} format not implemented"
|
|
82
|
+
return ""
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
formatted = send(handler, node)
|
|
86
|
+
# Some handlers return nil; appending must not turn that into "".
|
|
87
|
+
inline = format_inline_statement_remarks(node)
|
|
88
|
+
formatted += inline if formatted.is_a?(String) && !inline.empty?
|
|
89
|
+
|
|
90
|
+
leading = format_leading_statement_remarks(node)
|
|
91
|
+
leading.empty? ? formatted : [*leading, formatted].join("\n")
|
|
89
92
|
end
|
|
90
93
|
|
|
94
|
+
private
|
|
95
|
+
|
|
91
96
|
def format_noop(_node)
|
|
92
97
|
""
|
|
93
98
|
end
|
|
@@ -107,6 +112,16 @@ module Expressir
|
|
|
107
112
|
|
|
108
113
|
str.split("\n").map { |x| "#{INDENT}#{x}" }.join("\n")
|
|
109
114
|
end
|
|
115
|
+
|
|
116
|
+
# Handlers for types implemented directly in this class.
|
|
117
|
+
# Registered at the bottom so the methods exist when the
|
|
118
|
+
# registration-time method_defined? guard runs.
|
|
119
|
+
register_formatter Model::Repository, :format_repository
|
|
120
|
+
register_formatter Model::ExpFile, :format_exp_file
|
|
121
|
+
register_formatter Model::Declarations::SchemaVersionItem, :format_noop
|
|
122
|
+
register_formatter Model::Declarations::InterfacedItem, :format_noop
|
|
123
|
+
register_formatter Model::Cache, :format_noop
|
|
124
|
+
register_formatter Model::ModelElement, :format_noop
|
|
110
125
|
end
|
|
111
126
|
end
|
|
112
127
|
end
|
|
@@ -292,6 +292,7 @@ module Expressir
|
|
|
292
292
|
*if node.statements&.length&.positive?
|
|
293
293
|
indent(node.statements.map { |x| format(x) }.join("\n"))
|
|
294
294
|
end,
|
|
295
|
+
*format_trailing_region_remarks(node, :statements).map { |x| indent(x) },
|
|
295
296
|
[
|
|
296
297
|
"END_FUNCTION",
|
|
297
298
|
";",
|
|
@@ -413,6 +414,7 @@ module Expressir
|
|
|
413
414
|
*if node.statements&.length&.positive?
|
|
414
415
|
indent(node.statements.map { |x| format(x) }.join("\n"))
|
|
415
416
|
end,
|
|
417
|
+
*format_trailing_region_remarks(node, :statements).map { |x| indent(x) },
|
|
416
418
|
[
|
|
417
419
|
"END_PROCEDURE",
|
|
418
420
|
";",
|
|
@@ -421,14 +423,29 @@ module Expressir
|
|
|
421
423
|
].join("\n")
|
|
422
424
|
end
|
|
423
425
|
|
|
426
|
+
# True when the statement holds a remark that only it can render.
|
|
427
|
+
# Under no_remarks nothing is emitted anyway, so the statement stays
|
|
428
|
+
# filtered and remark-free output is byte-identical to before.
|
|
429
|
+
def carries_placed_remark?(statement)
|
|
430
|
+
return false if @no_remarks
|
|
431
|
+
|
|
432
|
+
Array(statement.untagged_remarks).any?(&:placement)
|
|
433
|
+
end
|
|
434
|
+
|
|
424
435
|
def format_declarations_rule(node)
|
|
425
436
|
# Filter out statements that only exist to hold remarks in rules
|
|
426
437
|
# (ALIAS/REPEAT with only Null sub-statements, query assignments, or Null statements)
|
|
427
438
|
formatted_statements = []
|
|
428
439
|
if node.statements&.length&.positive?
|
|
429
440
|
formatted_statements = node.statements.reject do |stmt|
|
|
441
|
+
# A statement holding a placed remark is the only thing that can
|
|
442
|
+
# render it, so filtering it would silently drop the comment —
|
|
443
|
+
# and PrettyFormatter, which does not filter, would then produce
|
|
444
|
+
# different content from this formatter.
|
|
445
|
+
if carries_placed_remark?(stmt)
|
|
446
|
+
false
|
|
430
447
|
# Filter ALIAS/REPEAT with only Null statements
|
|
431
|
-
|
|
448
|
+
elsif stmt.is_a?(Model::Statements::Alias) || stmt.is_a?(Model::Statements::Repeat)
|
|
432
449
|
stmt.statements&.all?(Model::Statements::Null)
|
|
433
450
|
# Filter query assignments (assignments with QueryExpression that exist only for remarks)
|
|
434
451
|
elsif stmt.is_a?(Model::Statements::Assignment) &&
|
|
@@ -494,12 +511,17 @@ module Expressir
|
|
|
494
511
|
*if formatted_statements.length.positive?
|
|
495
512
|
indent(formatted_statements.map { |x| format(x) }.join("\n"))
|
|
496
513
|
end,
|
|
514
|
+
# A RULE's executable body ends at WHERE, so its trailing
|
|
515
|
+
# remarks belong above that keyword, not after the where rules.
|
|
516
|
+
*format_trailing_region_remarks(node, :statements).map { |x| indent(x) },
|
|
497
517
|
*if node.where_rules&.length&.positive?
|
|
498
518
|
[
|
|
499
519
|
"WHERE",
|
|
500
520
|
indent(node.where_rules.map { |x| format(x) }.join("\n")),
|
|
501
521
|
]
|
|
502
522
|
end,
|
|
523
|
+
# A comment after the last where rule closes the WHERE section.
|
|
524
|
+
*format_trailing_region_remarks(node, :where_rules).map { |x| indent(x) },
|
|
503
525
|
[
|
|
504
526
|
"END_RULE",
|
|
505
527
|
";",
|
|
@@ -525,7 +547,7 @@ module Expressir
|
|
|
525
547
|
*if node.interfaces&.length&.positive?
|
|
526
548
|
[
|
|
527
549
|
"",
|
|
528
|
-
node.interfaces.map { |x| format(x) }.join("\n"),
|
|
550
|
+
node.interfaces.map { |x| format(x) }.join("\n\n"),
|
|
529
551
|
]
|
|
530
552
|
end,
|
|
531
553
|
].join("\n")
|
|
@@ -68,9 +68,11 @@ module Expressir
|
|
|
68
68
|
def format_end_scope_remark(node)
|
|
69
69
|
return "" if @no_remarks
|
|
70
70
|
return "" unless node.is_a?(Model::ModelElement)
|
|
71
|
-
return "" if node.untagged_remarks.nil? || node.untagged_remarks.empty?
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
# Only legacy (unplaced) remarks belong after the END_* keyword.
|
|
73
|
+
# Remarks carrying an explicit placement are rendered by the
|
|
74
|
+
# placement-aware emitters, and must not be emitted twice.
|
|
75
|
+
remark = Array(node.untagged_remarks).reject(&:placement).last
|
|
74
76
|
return "" unless remark.is_a?(Model::RemarkInfo)
|
|
75
77
|
|
|
76
78
|
text = remark.text
|
|
@@ -136,6 +138,77 @@ module Expressir
|
|
|
136
138
|
end.join("\n")
|
|
137
139
|
end
|
|
138
140
|
|
|
141
|
+
# Remarks that close one body of `node`, emitted just before that
|
|
142
|
+
# body's closing keyword. Only explicit TRAILING remarks for the named
|
|
143
|
+
# region qualify: legacy (nil) remarks keep their historical emission
|
|
144
|
+
# path so old caches render exactly as they did before.
|
|
145
|
+
def format_trailing_region_remarks(node, region)
|
|
146
|
+
return [] if @no_remarks
|
|
147
|
+
return [] unless node.is_a?(Model::ModelElement)
|
|
148
|
+
|
|
149
|
+
Array(node.untagged_remarks).filter_map do |remark|
|
|
150
|
+
next unless remark.trailing_region?(region)
|
|
151
|
+
|
|
152
|
+
formatted = format_untagged_remark(remark)
|
|
153
|
+
formatted unless formatted.empty?
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Remarks written after their statement on the same line. Attachment
|
|
158
|
+
# only assigns these to single-line statements, so appending keeps
|
|
159
|
+
# them on that statement's line.
|
|
160
|
+
def format_inline_statement_remarks(node)
|
|
161
|
+
return "" if @no_remarks
|
|
162
|
+
return "" unless node.is_a?(Model::Statement)
|
|
163
|
+
|
|
164
|
+
Array(node.untagged_remarks).filter_map do |remark|
|
|
165
|
+
next unless remark.inline?
|
|
166
|
+
|
|
167
|
+
formatted = format_untagged_remark(remark)
|
|
168
|
+
formatted unless formatted.empty?
|
|
169
|
+
end.map { |text| " #{text}" }.join
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def format_leading_statement_remarks(node)
|
|
173
|
+
return [] if @no_remarks
|
|
174
|
+
return [] unless node.is_a?(Model::Statement)
|
|
175
|
+
|
|
176
|
+
Array(node.untagged_remarks).filter_map do |remark|
|
|
177
|
+
next unless remark.leading?
|
|
178
|
+
|
|
179
|
+
formatted = format_untagged_remark(remark)
|
|
180
|
+
formatted unless formatted.empty?
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Block-end emission for ALIAS/REPEAT bodies. Tagged remarks are
|
|
185
|
+
# stored as bare text in node.remarks alongside a mirror of every
|
|
186
|
+
# untagged text (add_remark dual-store), so "genuinely tagged" is
|
|
187
|
+
# derivable only by multiset-subtracting the untagged texts.
|
|
188
|
+
def format_block_end_remarks(node)
|
|
189
|
+
return [] if @no_remarks
|
|
190
|
+
|
|
191
|
+
untagged = Array(node.untagged_remarks)
|
|
192
|
+
remaining = untagged.map(&:text).tally
|
|
193
|
+
tagged = Array(node.remarks).compact.reject do |text|
|
|
194
|
+
next false unless remaining[text]&.positive?
|
|
195
|
+
|
|
196
|
+
remaining[text] -= 1
|
|
197
|
+
true
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
[
|
|
201
|
+
*tagged.map { |text| format_remark(node, text) },
|
|
202
|
+
# Leading and inline remarks are emitted by their own paths;
|
|
203
|
+
# including them here would render them a second time.
|
|
204
|
+
*untagged.reject { |r| r.leading? || r.inline? }
|
|
205
|
+
.filter_map do |remark|
|
|
206
|
+
formatted = format_untagged_remark(remark)
|
|
207
|
+
formatted unless formatted.empty?
|
|
208
|
+
end,
|
|
209
|
+
]
|
|
210
|
+
end
|
|
211
|
+
|
|
139
212
|
def format_remarks(node)
|
|
140
213
|
remarks = []
|
|
141
214
|
|
|
@@ -43,7 +43,7 @@ module Expressir
|
|
|
43
43
|
*if node.statements&.length&.positive?
|
|
44
44
|
indent(node.statements.map { |x| format(x) }.join("\n"))
|
|
45
45
|
end,
|
|
46
|
-
*
|
|
46
|
+
*format_block_end_remarks(node).map { |x| indent(x) },
|
|
47
47
|
[
|
|
48
48
|
"END_ALIAS",
|
|
49
49
|
";",
|
|
@@ -88,6 +88,10 @@ module Expressir
|
|
|
88
88
|
*if node.actions&.length&.positive?
|
|
89
89
|
node.actions.map { |x| format(x) }
|
|
90
90
|
end,
|
|
91
|
+
# Closes the last action: before OTHERWISE when one exists,
|
|
92
|
+
# otherwise before END_CASE.
|
|
93
|
+
*format_trailing_region_remarks(node, :action_statements)
|
|
94
|
+
.map { |x| indent(x) },
|
|
91
95
|
*if node.otherwise_statement
|
|
92
96
|
[
|
|
93
97
|
[
|
|
@@ -98,6 +102,8 @@ module Expressir
|
|
|
98
102
|
indent(format(node.otherwise_statement)),
|
|
99
103
|
]
|
|
100
104
|
end,
|
|
105
|
+
*format_trailing_region_remarks(node, :otherwise_statements)
|
|
106
|
+
.map { |x| indent(x) },
|
|
101
107
|
[
|
|
102
108
|
"END_CASE",
|
|
103
109
|
";",
|
|
@@ -123,6 +129,7 @@ module Expressir
|
|
|
123
129
|
*if statements.length.positive?
|
|
124
130
|
indent(statements.map { |x| format(x) }.join("\n"))
|
|
125
131
|
end,
|
|
132
|
+
*format_trailing_region_remarks(node, :statements).map { |x| indent(x) },
|
|
126
133
|
[
|
|
127
134
|
"END",
|
|
128
135
|
";",
|
|
@@ -138,6 +145,14 @@ module Expressir
|
|
|
138
145
|
end
|
|
139
146
|
|
|
140
147
|
def format_statements_if(node)
|
|
148
|
+
has_else = node.else_statements&.length&.positive?
|
|
149
|
+
# A comment closing the THEN body sits before ELSE when there is
|
|
150
|
+
# one, and before END_IF otherwise.
|
|
151
|
+
then_trailing = format_trailing_region_remarks(node, :statements)
|
|
152
|
+
.map { |x| indent(x) }
|
|
153
|
+
else_trailing = format_trailing_region_remarks(node, :else_statements)
|
|
154
|
+
.map { |x| indent(x) }
|
|
155
|
+
|
|
141
156
|
[
|
|
142
157
|
[
|
|
143
158
|
"IF",
|
|
@@ -149,12 +164,14 @@ module Expressir
|
|
|
149
164
|
*if node.statements&.length&.positive?
|
|
150
165
|
indent(node.statements.map { |x| format(x) }.join("\n"))
|
|
151
166
|
end,
|
|
152
|
-
*if
|
|
167
|
+
*(then_trailing if has_else),
|
|
168
|
+
*if has_else
|
|
153
169
|
[
|
|
154
170
|
"ELSE",
|
|
155
171
|
indent(node.else_statements.map { |x| format(x) }.join("\n")),
|
|
156
172
|
].join("\n")
|
|
157
173
|
end,
|
|
174
|
+
*(has_else ? else_trailing : then_trailing),
|
|
158
175
|
[
|
|
159
176
|
"END_IF",
|
|
160
177
|
";",
|
|
@@ -214,7 +231,7 @@ module Expressir
|
|
|
214
231
|
*if statements.length.positive?
|
|
215
232
|
indent(statements.map { |x| format(x) }.join("\n"))
|
|
216
233
|
end,
|
|
217
|
-
*
|
|
234
|
+
*format_block_end_remarks(node).map { |x| indent(x) },
|
|
218
235
|
[
|
|
219
236
|
"END_REPEAT",
|
|
220
237
|
";",
|