expressir 2.4.0 → 2.4.2
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/TODO.max-perf/01-restore-ci-green.md +29 -0
- data/TODO.max-perf/02-streaming-parse-path.md +31 -0
- data/TODO.max-perf/03-cli-parallel-opt-in.md +27 -0
- data/TODO.max-perf/04-benchmark-harness.md +28 -0
- data/TODO.max-perf/05-parallel-fidelity-specs.md +22 -0
- data/TODO.max-perf/06-builder-cpu-audit.md +41 -0
- data/TODO.max-perf/07-upstream-parsanol-roadmap.md +27 -0
- data/TODO.max-perf/08-builder-build-perf.md +45 -0
- data/TODO.max-perf/09-grammar-cold-start.md +25 -0
- data/TODO.max-perf/10-parser-facade-hygiene.md +23 -0
- data/TODO.max-perf/11-ci-green-closeout.md +25 -0
- data/TODO.max-perf/12-require-boot-profile.md +25 -0
- data/TODO.max-perf/13-key-conversion-specs.md +26 -0
- data/TODO.max-perf/14-builder-call-handler-audit.md +28 -0
- data/benchmark/srl_benchmark.rb +76 -17
- 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/cli.rb +3 -0
- 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/coverage.rb +6 -2
- 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 +143 -187
- 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/ast_key_converter.rb +114 -0
- data/lib/expressir/express/builder.rb +71 -194
- 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 +24 -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/parallel_files.rb +229 -0
- data/lib/expressir/express/parser.rb +138 -950
- data/lib/expressir/express/pretty_formatter.rb +23 -5
- data/lib/expressir/express/remark_attacher.rb +497 -581
- data/lib/expressir/express/remark_scanner.rb +84 -19
- data/lib/expressir/express/schema_block_scanner.rb +137 -0
- data/lib/expressir/express/schema_source_formatter.rb +15 -0
- data/lib/expressir/express/scope_resolver.rb +223 -0
- data/lib/expressir/express/source_formatter.rb +15 -0
- data/lib/expressir/express/streaming_builder.rb +147 -176
- data/lib/expressir/express.rb +10 -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 +133 -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/repository.rb +18 -5
- 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 +56 -3
- metadata +81 -7
- data/lib/expressir/express/transformer/remark_handling.rb +0 -194
|
@@ -10,9 +10,9 @@ module Expressir
|
|
|
10
10
|
# Expression
|
|
11
11
|
def build_expression(ast_data)
|
|
12
12
|
left = if ast_data[:simple_expression]
|
|
13
|
-
Builder.
|
|
13
|
+
Builder.build_node(:simple_expression, ast_data[:simple_expression])
|
|
14
14
|
elsif ast_data[:logical_expression]
|
|
15
|
-
Builder.
|
|
15
|
+
Builder.build_node(:simple_expression, ast_data[:logical_expression][:simple_expression])
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
if ast_data[:rel_op_extended] && ast_data[:rhs]
|
|
@@ -29,18 +29,18 @@ module Expressir
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def build_logical_expression(ast_data)
|
|
32
|
-
Builder.
|
|
32
|
+
Builder.build_node(:simple_expression, ast_data[:simple_expression])
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def build_numeric_expression(ast_data)
|
|
36
|
-
Builder.
|
|
36
|
+
Builder.build_node(:simple_expression, ast_data[:simple_expression])
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# Simple expression (addition/subtraction chain)
|
|
40
40
|
def build_simple_expression(ast_data)
|
|
41
41
|
return nil unless ast_data[:term]
|
|
42
42
|
|
|
43
|
-
term = Builder.
|
|
43
|
+
term = Builder.build_node(:term, ast_data[:term])
|
|
44
44
|
rhs = ast_data[:rhs]
|
|
45
45
|
|
|
46
46
|
return term if rhs.nil? || (rhs.is_a?(Array) && rhs.empty?)
|
|
@@ -60,7 +60,7 @@ module Expressir
|
|
|
60
60
|
op_data = item[:operator]
|
|
61
61
|
operators << extract_operator(op_data[:add_like_op]) if op_data
|
|
62
62
|
if item[:term]
|
|
63
|
-
operands << Builder.
|
|
63
|
+
operands << Builder.build_node(:term, item[:term])
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -71,7 +71,7 @@ module Expressir
|
|
|
71
71
|
def build_term(ast_data)
|
|
72
72
|
return nil unless ast_data[:factor]
|
|
73
73
|
|
|
74
|
-
factor = Builder.
|
|
74
|
+
factor = Builder.build_node(:factor, ast_data[:factor])
|
|
75
75
|
rhs = ast_data[:rhs]
|
|
76
76
|
|
|
77
77
|
return factor if rhs.nil? || (rhs.is_a?(Array) && rhs.empty?)
|
|
@@ -90,7 +90,7 @@ module Expressir
|
|
|
90
90
|
item = r[:item] || r
|
|
91
91
|
op_data = item[:multiplication_like_op] || item[:mul_like_op]
|
|
92
92
|
operators << extract_operator(op_data) if op_data
|
|
93
|
-
operands << Builder.
|
|
93
|
+
operands << Builder.build_node(:factor, item[:factor]) if item[:factor]
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
build_binary_expression(operands, operators)
|
|
@@ -99,14 +99,14 @@ module Expressir
|
|
|
99
99
|
def build_factor(ast_data)
|
|
100
100
|
return nil unless ast_data[:simple_factor]
|
|
101
101
|
|
|
102
|
-
Builder.
|
|
102
|
+
Builder.build_node(:simple_factor, ast_data[:simple_factor])
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def build_simple_factor(ast_data)
|
|
106
106
|
return nil unless ast_data
|
|
107
107
|
|
|
108
108
|
if ast_data[:primary]
|
|
109
|
-
Builder.
|
|
109
|
+
Builder.build_node(:primary, ast_data[:primary])
|
|
110
110
|
elsif ast_data[:simple_factor_expression]
|
|
111
111
|
Builder.build_node(:simple_factor_expression,
|
|
112
112
|
ast_data[:simple_factor_expression])
|
|
@@ -116,7 +116,7 @@ module Expressir
|
|
|
116
116
|
elsif ast_data[:constant_factor]
|
|
117
117
|
Builder.build_node(:constant_factor, ast_data[:constant_factor])
|
|
118
118
|
elsif ast_data[:expression]
|
|
119
|
-
Builder.
|
|
119
|
+
Builder.build_node(:expression, ast_data[:expression])
|
|
120
120
|
elsif ast_data[:aggregate_initializer]
|
|
121
121
|
Builder.build_node(:aggregate_initializer,
|
|
122
122
|
ast_data[:aggregate_initializer])
|
|
@@ -137,21 +137,21 @@ module Expressir
|
|
|
137
137
|
|
|
138
138
|
def build_simple_factor_expression(ast_data)
|
|
139
139
|
if ast_data[:primary]
|
|
140
|
-
Builder.
|
|
140
|
+
Builder.build_node(:primary, ast_data[:primary])
|
|
141
141
|
elsif ast_data[:expression]
|
|
142
|
-
Builder.
|
|
142
|
+
Builder.build_node(:expression, ast_data[:expression])
|
|
143
143
|
end
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def build_simple_factor_unary_expression(ast_data)
|
|
147
147
|
op = extract_unary_op(ast_data[:unary_op])
|
|
148
148
|
operand = if ast_data[:simple_factor]
|
|
149
|
-
Builder.
|
|
149
|
+
Builder.build_node(:simple_factor, ast_data[:simple_factor])
|
|
150
150
|
elsif ast_data[:simple_factor_expression]
|
|
151
151
|
Builder.build_node(:simple_factor_expression,
|
|
152
152
|
ast_data[:simple_factor_expression])
|
|
153
153
|
elsif ast_data[:primary]
|
|
154
|
-
Builder.
|
|
154
|
+
Builder.build_node(:primary, ast_data[:primary])
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
if op
|
|
@@ -181,7 +181,7 @@ module Expressir
|
|
|
181
181
|
elsif ast_data[:population]
|
|
182
182
|
build_population(ast_data[:population], ast_data[:qualifier])
|
|
183
183
|
elsif ast_data[:expression]
|
|
184
|
-
Builder.
|
|
184
|
+
Builder.build_node(:expression, ast_data[:expression])
|
|
185
185
|
end
|
|
186
186
|
end
|
|
187
187
|
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
require "digest"
|
|
1
2
|
require "zlib"
|
|
2
3
|
|
|
3
4
|
module Expressir
|
|
4
5
|
module Express
|
|
5
6
|
class Cache
|
|
7
|
+
# Format: magic + SHA-256 (raw, 32 bytes) + Zlib-deflated Marshal.dump
|
|
8
|
+
# of Model::Cache. The digest detects corruption; a corrupted cache is
|
|
9
|
+
# disposable — callers drop the file and regenerate. Marshal is only
|
|
10
|
+
# safe for trusted, locally generated cache files.
|
|
11
|
+
MAGIC = "EXPRC1".freeze
|
|
12
|
+
|
|
13
|
+
DIGEST_BYTES = Digest::SHA256.digest("").bytesize
|
|
14
|
+
|
|
6
15
|
# Save Express model into a cache file
|
|
7
16
|
# @param file [String] cache file path
|
|
8
17
|
# @param content [Model::ModelElement] Express model
|
|
@@ -11,7 +20,7 @@ module Expressir
|
|
|
11
20
|
# @return [nil]
|
|
12
21
|
def self.to_file(file, content, root_path: nil,
|
|
13
22
|
test_overwrite_version: nil)
|
|
14
|
-
version = test_overwrite_version || VERSION
|
|
23
|
+
version = test_overwrite_version || Expressir::Version::VERSION
|
|
15
24
|
|
|
16
25
|
cache = Model::Cache.new(
|
|
17
26
|
version: version,
|
|
@@ -19,10 +28,9 @@ test_overwrite_version: nil)
|
|
|
19
28
|
root_path: root_path,
|
|
20
29
|
)
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
data = Zlib::Deflate.deflate(Marshal.dump(cache), 1)
|
|
32
|
+
File.binwrite(file, "#{MAGIC}#{Digest::SHA256.digest(data)}#{data}")
|
|
24
33
|
|
|
25
|
-
File.binwrite(file, yaml_compressed)
|
|
26
34
|
nil
|
|
27
35
|
end
|
|
28
36
|
|
|
@@ -31,18 +39,37 @@ test_overwrite_version: nil)
|
|
|
31
39
|
# @param root_path [String] Express repository root path, to be prepended to Express file paths if loading a portable cache file
|
|
32
40
|
# @param test_overwrite_version [String] don't use, only for tests
|
|
33
41
|
# @return [Model::ModelElement] Express model
|
|
42
|
+
# @raise [Error::CacheCorruptedError] if the file is not a valid cache file or its digest does not match
|
|
43
|
+
# @raise [Error::CacheVersionMismatchError] if the cache was written by another Expressir version
|
|
34
44
|
def self.from_file(file, root_path: nil, test_overwrite_version: nil)
|
|
35
|
-
version = test_overwrite_version || VERSION
|
|
45
|
+
version = test_overwrite_version || Expressir::Version::VERSION
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
cache =
|
|
47
|
+
raw = File.binread(file)
|
|
48
|
+
data = Zlib::Inflate.inflate(validated_data(raw))
|
|
49
|
+
cache = Marshal.load(data) # rubocop:disable Security/MarshalLoad
|
|
40
50
|
|
|
41
51
|
if cache.version != version
|
|
42
52
|
raise Error::CacheVersionMismatchError.new(cache.version, version)
|
|
43
53
|
end
|
|
44
54
|
|
|
45
55
|
cache
|
|
56
|
+
rescue TypeError, ArgumentError, RangeError
|
|
57
|
+
raise Error::CacheCorruptedError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.validated_data(raw)
|
|
61
|
+
header = MAGIC.bytesize + DIGEST_BYTES
|
|
62
|
+
unless raw.bytesize > header && raw.start_with?(MAGIC)
|
|
63
|
+
raise Error::CacheCorruptedError
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
digest = raw.byteslice(MAGIC.bytesize, DIGEST_BYTES)
|
|
67
|
+
data = raw.byteslice(header, raw.bytesize - header)
|
|
68
|
+
unless Digest::SHA256.digest(data) == digest
|
|
69
|
+
raise Error::CacheCorruptedError
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
data
|
|
46
73
|
end
|
|
47
74
|
end
|
|
48
75
|
end
|
|
@@ -38,6 +38,30 @@ module Expressir
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
# Error raised when a cache file is not a valid cache file or fails integrity validation
|
|
42
|
+
class CacheCorruptedError < ExpressError
|
|
43
|
+
def initialize(message = "Cache file is corrupted or not a valid Expressir cache file")
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Error raised when parallel file parsing loses a worker or its data
|
|
49
|
+
class ParallelParseError < ExpressError
|
|
50
|
+
def initialize(message = "Parallel parsing failed")
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Error raised when streaming parsing is requested but unavailable.
|
|
56
|
+
# The streaming paths require a parsanol release whose
|
|
57
|
+
# parse_with_builder is stable; parse_fresh cannot parse EXPRESS
|
|
58
|
+
# without packrat memoization (see parsanol-ruby#52).
|
|
59
|
+
class StreamingUnsupportedError < ExpressError
|
|
60
|
+
def initialize(message = "Streaming parsing is not supported by the installed parsanol release")
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
41
65
|
# Base class for visitor-related errors
|
|
42
66
|
class VisitorError < ExpressError; end
|
|
43
67
|
|
|
@@ -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
|
";",
|