expressir 2.2.1 → 2.3.0

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.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.rubocop_todo.yml +253 -81
  4. data/Gemfile +4 -1
  5. data/README.adoc +63 -26
  6. data/benchmark/srl_benchmark.rb +386 -0
  7. data/benchmark/srl_native_benchmark.rb +142 -0
  8. data/benchmark/srl_ruby_benchmark.rb +130 -0
  9. data/expressir.gemspec +3 -2
  10. data/lib/expressir/benchmark.rb +1 -1
  11. data/lib/expressir/changes/item_change.rb +0 -1
  12. data/lib/expressir/changes/mapping_change.rb +0 -1
  13. data/lib/expressir/changes/schema_change.rb +0 -2
  14. data/lib/expressir/changes/version_change.rb +0 -3
  15. data/lib/expressir/changes.rb +5 -6
  16. data/lib/expressir/cli.rb +10 -24
  17. data/lib/expressir/commands/changes.rb +0 -2
  18. data/lib/expressir/commands/changes_import_eengine.rb +0 -3
  19. data/lib/expressir/commands/changes_validate.rb +0 -2
  20. data/lib/expressir/commands/format.rb +1 -1
  21. data/lib/expressir/commands/manifest.rb +0 -7
  22. data/lib/expressir/commands/package.rb +16 -29
  23. data/lib/expressir/commands/validate.rb +0 -2
  24. data/lib/expressir/commands/validate_load.rb +1 -1
  25. data/lib/expressir/commands.rb +20 -0
  26. data/lib/expressir/config.rb +0 -2
  27. data/lib/expressir/coverage.rb +11 -4
  28. data/lib/expressir/eengine/arm_compare_report.rb +1 -4
  29. data/lib/expressir/eengine/changes_section.rb +1 -3
  30. data/lib/expressir/eengine/compare_report.rb +1 -13
  31. data/lib/expressir/eengine/mim_compare_report.rb +1 -4
  32. data/lib/expressir/eengine/modified_object.rb +1 -2
  33. data/lib/expressir/eengine.rb +9 -0
  34. data/lib/expressir/errors.rb +3 -5
  35. data/lib/expressir/express/builder.rb +22 -7
  36. data/lib/expressir/express/builder_registry.rb +411 -0
  37. data/lib/expressir/express/builders/attribute_decl_builder.rb +0 -6
  38. data/lib/expressir/express/builders/built_in_builder.rb +1 -16
  39. data/lib/expressir/express/builders/constant_builder.rb +4 -19
  40. data/lib/expressir/express/builders/declaration_builder.rb +0 -4
  41. data/lib/expressir/express/builders/derive_clause_builder.rb +0 -2
  42. data/lib/expressir/express/builders/derived_attr_builder.rb +0 -2
  43. data/lib/expressir/express/builders/domain_rule_builder.rb +0 -2
  44. data/lib/expressir/express/builders/entity_decl_builder.rb +7 -13
  45. data/lib/expressir/express/builders/explicit_attr_builder.rb +5 -8
  46. data/lib/expressir/express/builders/expression_builder.rb +31 -67
  47. data/lib/expressir/express/builders/function_decl_builder.rb +20 -18
  48. data/lib/expressir/express/builders/interface_builder.rb +0 -20
  49. data/lib/expressir/express/builders/inverse_attr_builder.rb +0 -2
  50. data/lib/expressir/express/builders/inverse_attr_type_builder.rb +0 -6
  51. data/lib/expressir/express/builders/inverse_clause_builder.rb +0 -2
  52. data/lib/expressir/express/builders/literal_builder.rb +1 -15
  53. data/lib/expressir/express/builders/procedure_decl_builder.rb +20 -19
  54. data/lib/expressir/express/builders/qualifier_builder.rb +0 -27
  55. data/lib/expressir/express/builders/reference_builder.rb +1 -10
  56. data/lib/expressir/express/builders/rule_decl_builder.rb +21 -19
  57. data/lib/expressir/express/builders/schema_body_decl_builder.rb +0 -4
  58. data/lib/expressir/express/builders/schema_decl_builder.rb +7 -13
  59. data/lib/expressir/express/builders/schema_version_builder.rb +0 -6
  60. data/lib/expressir/express/builders/simple_id_builder.rb +1 -10
  61. data/lib/expressir/express/builders/statement_builder.rb +4 -32
  62. data/lib/expressir/express/builders/subtype_constraint_builder.rb +6 -30
  63. data/lib/expressir/express/builders/syntax_builder.rb +60 -7
  64. data/lib/expressir/express/builders/type_builder.rb +3 -45
  65. data/lib/expressir/express/builders/type_decl_builder.rb +1 -7
  66. data/lib/expressir/express/builders/unique_clause_builder.rb +1 -3
  67. data/lib/expressir/express/builders/unique_rule_builder.rb +0 -2
  68. data/lib/expressir/express/builders/where_clause_builder.rb +1 -3
  69. data/lib/expressir/express/builders.rb +47 -35
  70. data/lib/expressir/express/error.rb +0 -3
  71. data/lib/expressir/express/formatter.rb +17 -19
  72. data/lib/expressir/express/formatters/data_types_formatter.rb +295 -293
  73. data/lib/expressir/express/formatters/declarations_formatter.rb +617 -615
  74. data/lib/expressir/express/formatters/expressions_formatter.rb +146 -144
  75. data/lib/expressir/express/formatters/literals_formatter.rb +35 -33
  76. data/lib/expressir/express/formatters/references_formatter.rb +34 -32
  77. data/lib/expressir/express/formatters/remark_formatter.rb +176 -209
  78. data/lib/expressir/express/formatters/remark_item_formatter.rb +18 -16
  79. data/lib/expressir/express/formatters/statements_formatter.rb +190 -188
  80. data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +41 -39
  81. data/lib/expressir/express/formatters.rb +22 -0
  82. data/lib/expressir/express/parser.rb +40 -41
  83. data/lib/expressir/express/pretty_formatter.rb +68 -47
  84. data/lib/expressir/express/remark_attacher.rb +210 -147
  85. data/lib/expressir/express/streaming_builder.rb +0 -3
  86. data/lib/expressir/express/transformer/remark_handling.rb +1 -2
  87. data/lib/expressir/express.rb +29 -0
  88. data/lib/expressir/manifest/resolver.rb +0 -3
  89. data/lib/expressir/manifest/validator.rb +0 -3
  90. data/lib/expressir/manifest.rb +6 -0
  91. data/lib/expressir/model/cache.rb +1 -1
  92. data/lib/expressir/model/concerns.rb +19 -0
  93. data/lib/expressir/model/data_types/aggregate.rb +1 -1
  94. data/lib/expressir/model/data_types/array.rb +1 -1
  95. data/lib/expressir/model/data_types/bag.rb +1 -1
  96. data/lib/expressir/model/data_types/binary.rb +1 -1
  97. data/lib/expressir/model/data_types/boolean.rb +1 -1
  98. data/lib/expressir/model/data_types/enumeration.rb +1 -1
  99. data/lib/expressir/model/data_types/enumeration_item.rb +1 -1
  100. data/lib/expressir/model/data_types/generic.rb +1 -1
  101. data/lib/expressir/model/data_types/generic_entity.rb +1 -1
  102. data/lib/expressir/model/data_types/integer.rb +1 -1
  103. data/lib/expressir/model/data_types/list.rb +1 -1
  104. data/lib/expressir/model/data_types/logical.rb +1 -1
  105. data/lib/expressir/model/data_types/number.rb +1 -1
  106. data/lib/expressir/model/data_types/real.rb +1 -1
  107. data/lib/expressir/model/data_types/select.rb +1 -1
  108. data/lib/expressir/model/data_types/set.rb +1 -1
  109. data/lib/expressir/model/data_types/string.rb +1 -1
  110. data/lib/expressir/model/data_types.rb +25 -0
  111. data/lib/expressir/model/declarations/attribute.rb +1 -1
  112. data/lib/expressir/model/declarations/constant.rb +1 -1
  113. data/lib/expressir/model/declarations/derived_attribute.rb +1 -1
  114. data/lib/expressir/model/declarations/entity.rb +4 -1
  115. data/lib/expressir/model/declarations/function.rb +3 -1
  116. data/lib/expressir/model/declarations/informal_proposition_rule.rb +2 -1
  117. data/lib/expressir/model/declarations/interface.rb +1 -1
  118. data/lib/expressir/model/declarations/interface_item.rb +1 -1
  119. data/lib/expressir/model/declarations/interfaced_item.rb +1 -1
  120. data/lib/expressir/model/declarations/inverse_attribute.rb +1 -1
  121. data/lib/expressir/model/declarations/parameter.rb +1 -1
  122. data/lib/expressir/model/declarations/procedure.rb +3 -1
  123. data/lib/expressir/model/declarations/remark_item.rb +1 -1
  124. data/lib/expressir/model/declarations/rule.rb +4 -1
  125. data/lib/expressir/model/declarations/schema.rb +2 -1
  126. data/lib/expressir/model/declarations/schema_version.rb +1 -1
  127. data/lib/expressir/model/declarations/schema_version_item.rb +1 -1
  128. data/lib/expressir/model/declarations/subtype_constraint.rb +1 -1
  129. data/lib/expressir/model/declarations/type.rb +4 -1
  130. data/lib/expressir/model/declarations/unique_rule.rb +1 -1
  131. data/lib/expressir/model/declarations/variable.rb +1 -1
  132. data/lib/expressir/model/declarations/where_rule.rb +1 -1
  133. data/lib/expressir/model/declarations.rb +31 -0
  134. data/lib/expressir/model/dependency_resolver.rb +0 -2
  135. data/lib/expressir/model/exp_file.rb +38 -0
  136. data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -1
  137. data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -1
  138. data/lib/expressir/model/expressions/binary_expression.rb +1 -1
  139. data/lib/expressir/model/expressions/entity_constructor.rb +1 -1
  140. data/lib/expressir/model/expressions/function_call.rb +1 -1
  141. data/lib/expressir/model/expressions/interval.rb +1 -1
  142. data/lib/expressir/model/expressions/query_expression.rb +1 -1
  143. data/lib/expressir/model/expressions/unary_expression.rb +1 -1
  144. data/lib/expressir/model/expressions.rb +18 -0
  145. data/lib/expressir/model/identifier.rb +5 -1
  146. data/lib/expressir/model/indexes.rb +11 -0
  147. data/lib/expressir/model/literals/binary.rb +1 -1
  148. data/lib/expressir/model/literals/integer.rb +1 -1
  149. data/lib/expressir/model/literals/logical.rb +1 -1
  150. data/lib/expressir/model/literals/real.rb +1 -1
  151. data/lib/expressir/model/literals/string.rb +1 -1
  152. data/lib/expressir/model/literals.rb +13 -0
  153. data/lib/expressir/model/model_element.rb +7 -15
  154. data/lib/expressir/model/references/attribute_reference.rb +1 -1
  155. data/lib/expressir/model/references/group_reference.rb +1 -1
  156. data/lib/expressir/model/references/index_reference.rb +1 -1
  157. data/lib/expressir/model/references/simple_reference.rb +1 -1
  158. data/lib/expressir/model/references.rb +12 -0
  159. data/lib/expressir/model/remark_info.rb +1 -7
  160. data/lib/expressir/model/repository.rb +72 -36
  161. data/lib/expressir/model/repository_validator.rb +0 -2
  162. data/lib/expressir/model/search_engine.rb +6 -30
  163. data/lib/expressir/model/statements/alias.rb +1 -1
  164. data/lib/expressir/model/statements/assignment.rb +1 -1
  165. data/lib/expressir/model/statements/case.rb +1 -1
  166. data/lib/expressir/model/statements/case_action.rb +1 -1
  167. data/lib/expressir/model/statements/compound.rb +1 -1
  168. data/lib/expressir/model/statements/escape.rb +1 -1
  169. data/lib/expressir/model/statements/if.rb +1 -1
  170. data/lib/expressir/model/statements/null.rb +1 -1
  171. data/lib/expressir/model/statements/procedure_call.rb +1 -1
  172. data/lib/expressir/model/statements/repeat.rb +1 -1
  173. data/lib/expressir/model/statements/return.rb +1 -1
  174. data/lib/expressir/model/statements/skip.rb +1 -1
  175. data/lib/expressir/model/statements.rb +20 -0
  176. data/lib/expressir/model/supertype_expressions/binary_supertype_expression.rb +1 -1
  177. data/lib/expressir/model/supertype_expressions/oneof_supertype_expression.rb +1 -1
  178. data/lib/expressir/model/supertype_expressions.rb +12 -0
  179. data/lib/expressir/model.rb +28 -4
  180. data/lib/expressir/package/builder.rb +33 -4
  181. data/lib/expressir/package/metadata.rb +0 -1
  182. data/lib/expressir/package/reader.rb +0 -1
  183. data/lib/expressir/package.rb +8 -0
  184. data/lib/expressir/schema_manifest.rb +5 -6
  185. data/lib/expressir/schema_manifest_entry.rb +3 -4
  186. data/lib/expressir/transformer.rb +7 -0
  187. data/lib/expressir/version.rb +1 -1
  188. data/lib/expressir.rb +23 -171
  189. metadata +49 -9
  190. data/lib/expressir/express/builders/token_builder.rb +0 -15
@@ -1,9 +1,4 @@
1
1
  require "parsanol"
2
- require_relative "error"
3
- require_relative "builder"
4
- require_relative "builders"
5
- require_relative "remark_attacher"
6
- require_relative "streaming_builder"
7
2
 
8
3
  module Expressir
9
4
  module Express
@@ -64,14 +59,22 @@ module Expressir
64
59
  @@cached_grammar_json
65
60
  end
66
61
 
67
- # Parse using native engine (with caching)
62
+ # Parse using native engine with Rust-side transformation (fastest)
63
+ #
64
+ # This method provides ~17x speedup over pure Ruby parsing.
65
+ # The transformation happens in Rust using to_parslet_compatible,
66
+ # producing Parslet-compatible output that Builder.build can consume directly.
67
+ #
68
+ # @param source [String] EXPRESS source code to parse
69
+ # @return [Hash, Array] Transformed AST in Parslet-compatible format
70
+ # @raise [LoadError] If native parser is not available
68
71
  def self.parse_native(source)
69
72
  unless native_available?
70
73
  raise LoadError, "Native parser not available"
71
74
  end
72
75
 
73
- # Use Parsanol 2.0 API - parse returns Slice objects with position info
74
- new.parse(source, mode: :native)
76
+ grammar_atom = cached_parser.syntax
77
+ Parsanol::Native.parse(grammar_atom, source)
75
78
  end
76
79
 
77
80
  def cts(atom)
@@ -218,7 +221,7 @@ module Expressir
218
221
  (attributeDecl >> op_colon >> parameterType >> op_decl >> expression >> op_delim).as(:derivedAttr)
219
222
  end
220
223
  rule(:digit) { match["0-9"] }
221
- rule(:digits) { (digit >> digit.repeat) }
224
+ rule(:digits) { digit >> digit.repeat }
222
225
  rule(:domainRule) do
223
226
  ((ruleLabelId >> op_colon).maybe >> expression).as(:domainRule)
224
227
  end
@@ -228,7 +231,7 @@ module Expressir
228
231
  rule(:embeddedRemark) do
229
232
  (str("(*") >> (str("*)").absent? >> (embeddedRemark | any)).repeat >> str("*)")).as(:embeddedRemark)
230
233
  end
231
- rule(:encodedCharacter) { (octet >> octet >> octet >> octet) }
234
+ rule(:encodedCharacter) { octet >> octet >> octet >> octet }
232
235
  rule(:encodedStringLiteral) do
233
236
  cts((str('"') >> encodedCharacter.repeat(1) >> str('"')).as(:str)).as(:encodedStringLiteral)
234
237
  end
@@ -667,7 +670,7 @@ module Expressir
667
670
  # @param [String] file Express file path
668
671
  # @param [Boolean] skip_references skip resolving references
669
672
  # @param [Boolean] include_source attach original source code to model elements
670
- # @return [Model::Repository]
673
+ # @return [Model::ExpFile] ExpFile containing parsed schemas
671
674
  # @raise [SchemaParseFailure] if the schema file fails to parse
672
675
  def self.from_file(file, skip_references: nil, include_source: nil, root_path: nil) # rubocop:disable Metrics/AbcSize
673
676
  Expressir::Benchmark.measure_file(file) do
@@ -683,10 +686,12 @@ module Expressir
683
686
  raise Error::SchemaParseFailure.new(schema_file, e)
684
687
  end
685
688
 
686
- @repository = Builder.build_with_remarks(ast, source: source,
689
+ @exp_file = ::Expressir::Express::Builder.build_with_remarks(ast, source: source,
687
690
  include_source: include_source)
688
691
 
689
- @repository.schemas.each do |schema|
692
+ # Set file path on the ExpFile and propagate to schemas
693
+ @exp_file.path = schema_file
694
+ @exp_file.schemas.each do |schema|
690
695
  schema.file = schema_file
691
696
  schema.file_basename = File.basename(schema_file, ".exp")
692
697
  schema.formatted = schema.to_s(no_remarks: true)
@@ -695,11 +700,11 @@ module Expressir
695
700
  unless skip_references
696
701
  Expressir::Benchmark.measure_references do
697
702
  @resolve_references_model_visitor = ResolveReferencesModelVisitor.new
698
- @resolve_references_model_visitor.visit(@repository)
703
+ @resolve_references_model_visitor.visit(@exp_file)
699
704
  end
700
705
  end
701
706
 
702
- @repository
707
+ @exp_file
703
708
  end
704
709
  end
705
710
 
@@ -711,19 +716,16 @@ module Expressir
711
716
  # @yieldparam filename [String] Name of the file being processed
712
717
  # @yieldparam schemas [Array, nil] Array of parsed schemas (nil if parsing failed)
713
718
  # @yieldparam error [Exception, nil] Error that occurred (nil if parsing succeeded)
714
- # @return [Model::Repository]
715
- def self.from_files(files, skip_references: nil, include_source: nil,
716
- root_path: nil)
717
- all_schemas = []
719
+ # @return [Model::Repository] Repository containing all parsed ExpFiles
720
+ def self.from_files(files, skip_references: nil, include_source: nil, root_path: nil)
721
+ all_exp_files = []
718
722
 
719
723
  files.each do |file|
720
- repository = from_file(file, skip_references: true,
721
- root_path: root_path)
722
- file_schemas = repository.schemas
723
- all_schemas.concat(file_schemas)
724
+ exp_file = from_file(file, skip_references: true, root_path: root_path)
725
+ all_exp_files << exp_file
724
726
 
725
727
  # Call the progress block if provided
726
- yield(file, file_schemas, nil) if block_given?
728
+ yield(file, exp_file&.schemas, nil) if block_given?
727
729
  rescue StandardError => e
728
730
  # Call the progress block with the error if provided
729
731
  yield(file, nil, e) if block_given?
@@ -733,9 +735,7 @@ root_path: nil)
733
735
  raise unless e.is_a?(Error::SchemaParseFailure)
734
736
  end
735
737
 
736
- @repository = Model::Repository.new(
737
- schemas: all_schemas,
738
- )
738
+ @repository = Model::Repository.new(files: all_exp_files)
739
739
 
740
740
  unless skip_references
741
741
  Expressir::Benchmark.measure_references do
@@ -753,7 +753,7 @@ root_path: nil)
753
753
  # @param [Boolean] include_source attach original source code to model elements
754
754
  # @param [Boolean] use_native use native parser if available (default: false - AST format differs slightly)
755
755
  # @param [Boolean] use_streaming use streaming builder for maximum performance (default: false)
756
- # @return [Model::Repository] Parsed repository
756
+ # @return [Model::ExpFile] Parsed ExpFile
757
757
  # @raise [SchemaParseFailure] if the content fails to parse
758
758
  def self.from_exp(content, skip_references: nil, include_source: nil,
759
759
  use_native: false, use_streaming: false)
@@ -774,10 +774,10 @@ root_path: nil)
774
774
  raise Error::SchemaParseFailure.new("(from string)", e)
775
775
  end
776
776
 
777
- repository = Builder.build_with_remarks(ast, source: content,
778
- include_source: include_source)
777
+ exp_file = ::Expressir::Express::Builder.build_with_remarks(ast, source: content,
778
+ include_source: include_source)
779
779
 
780
- repository.schemas.each do |schema|
780
+ exp_file.schemas.each do |schema|
781
781
  schema.file = nil
782
782
  schema.file_basename = nil
783
783
  schema.formatted = schema.to_s(no_remarks: true)
@@ -786,33 +786,32 @@ root_path: nil)
786
786
  unless skip_references
787
787
  Expressir::Benchmark.measure_references do
788
788
  resolve_references_model_visitor = ResolveReferencesModelVisitor.new
789
- resolve_references_model_visitor.visit(repository)
789
+ resolve_references_model_visitor.visit(exp_file)
790
790
  end
791
791
  end
792
792
 
793
- repository
793
+ exp_file
794
794
  end
795
795
 
796
796
  # Parse using streaming builder (maximum performance)
797
797
  # @param [String] content Express content as string
798
798
  # @param [Boolean] skip_references skip resolving references
799
799
  # @param [Boolean] include_source attach original source code to model elements
800
- # @return [Model::Repository] Parsed repository
800
+ # @return [Model::ExpFile] Parsed ExpFile
801
801
  # @raise [SchemaParseFailure] if the content fails to parse
802
- def self.from_exp_streaming(content, skip_references: nil,
803
- include_source: nil)
802
+ def self.from_exp_streaming(content, skip_references: nil, include_source: nil)
804
803
  grammar_json = Parser.cached_grammar_json
805
- builder = StreamingBuilder.new(source: content,
804
+ builder = ::Expressir::Express::StreamingBuilder.new(source: content,
806
805
  include_source: include_source)
807
806
 
808
807
  begin
809
- repository = Parsanol::Native.parse_with_builder(grammar_json,
808
+ exp_file = Parsanol::Native.parse_with_builder(grammar_json,
810
809
  content, builder)
811
810
  rescue StandardError => e
812
811
  raise Error::SchemaParseFailure.new("(streaming)", e)
813
812
  end
814
813
 
815
- repository.schemas.each do |schema|
814
+ exp_file.schemas.each do |schema|
816
815
  schema.file = nil
817
816
  schema.file_basename = nil
818
817
  schema.formatted = schema.to_s(no_remarks: true)
@@ -821,11 +820,11 @@ include_source: nil)
821
820
  unless skip_references
822
821
  Expressir::Benchmark.measure_references do
823
822
  resolve_references_model_visitor = ResolveReferencesModelVisitor.new
824
- resolve_references_model_visitor.visit(repository)
823
+ resolve_references_model_visitor.visit(exp_file)
825
824
  end
826
825
  end
827
826
 
828
- repository
827
+ exp_file
829
828
  end
830
829
  private_class_method :from_exp_streaming
831
830
  end
@@ -1,5 +1,3 @@
1
- require_relative "formatter"
2
-
3
1
  module Expressir
4
2
  module Express
5
3
  # Pretty formatter for EXPRESS schemas following ELF specification
@@ -100,22 +98,16 @@ module Expressir
100
98
  remark = node.untagged_remarks.first
101
99
  return "" if remark.nil?
102
100
 
103
- # Handle both RemarkInfo and String
104
- if remark.is_a?(Model::RemarkInfo)
105
- text = remark.text
106
- return "" if text.nil? || text.empty?
101
+ return "" unless remark.is_a?(Model::RemarkInfo)
107
102
 
108
- # Include tag if present
109
- formatted_text = remark.tagged? ? "\"#{remark.tag}\" #{text}" : text
103
+ text = remark.text
104
+ return "" if text.nil? || text.empty?
110
105
 
111
- # Use format from RemarkInfo
112
- remark.tail? ? " -- #{formatted_text}" : " (* #{formatted_text} *)"
113
- else
114
- # Legacy string - default to tail format
115
- return "" if remark.empty?
106
+ # Include tag if present
107
+ formatted_text = remark.tagged? ? "\"#{remark.tag}\" #{text}" : text
116
108
 
117
- " -- #{remark}"
118
- end
109
+ # Use format from RemarkInfo
110
+ remark.tail? ? " -- #{formatted_text}" : " (* #{formatted_text} *)"
119
111
  end
120
112
 
121
113
  # Override to preserve tail remarks on attributes
@@ -254,33 +246,27 @@ module Expressir
254
246
  end
255
247
 
256
248
  # Format a single preamble remark
257
- # @param remark [String, RemarkInfo] Remark text or RemarkInfo object
249
+ # @param remark [RemarkInfo] RemarkInfo object
258
250
  # @param indent_str [String] Indentation to use (optional)
259
251
  # @return [String] Formatted remark
260
252
  def format_preamble_remark(remark, _indent_str = "")
261
- # Handle both String (legacy) and RemarkInfo
262
- if remark.is_a?(Model::RemarkInfo)
263
- text = remark.text
264
-
265
- # Include tag if present
266
- text = "\"#{remark.tag}\" #{text}" if remark.tagged?
267
-
268
- # Use format from RemarkInfo
269
- if remark.tail?
270
- "-- #{text}"
271
- elsif text.include?("\n")
272
- # Embedded remark - always use embedded format for preamble
273
- ["(*", text,
274
- "*)"].join("\n")
275
- else
276
- "(* #{text} *)"
277
- end
278
- elsif remark.include?("\n")
279
- # Legacy string handling
280
- ["(*", remark,
253
+ return "" unless remark.is_a?(Model::RemarkInfo)
254
+
255
+ text = remark.text
256
+ return "" if text.nil? || text.empty?
257
+
258
+ # Include tag if present
259
+ text = "\"#{remark.tag}\" #{text}" if remark.tagged?
260
+
261
+ # Use format from RemarkInfo
262
+ if remark.tail?
263
+ "-- #{text}"
264
+ elsif text.include?("\n")
265
+ # Embedded remark - always use embedded format for preamble
266
+ ["(*", text,
281
267
  "*)"].join("\n")
282
268
  else
283
- "(* #{remark} *)"
269
+ "(* #{text} *)"
284
270
  end
285
271
  end
286
272
 
@@ -307,18 +293,53 @@ module Expressir
307
293
  def format_repository(node)
308
294
  result = []
309
295
 
310
- # Add preamble if source has remarks
311
- # Use begin/rescue for duck typing (RBS-safe)
312
- source_remarks = begin
313
- node.source_remarks if node.is_a?(Model::Repository)
314
- rescue NoMethodError
315
- nil
296
+ # Format files if present
297
+ node.files&.each do |exp_file|
298
+ file_output = format_exp_file(exp_file)
299
+ result << file_output if file_output && !file_output.empty?
300
+ end
301
+
302
+ # Handle schemas directly added to repository (not via files)
303
+ direct_schemas = node.schemas.select do |s|
304
+ # Schema is direct if it's not in any file
305
+ node.files.nil? || node.files.none? { |f| f.schemas&.include?(s) }
316
306
  end
317
- if source_remarks
318
- result.concat(format_preamble(source_remarks))
307
+
308
+ if direct_schemas.any?
309
+ # Add preamble if repository has untagged remarks
310
+ if node.untagged_remarks && !node.untagged_remarks.empty?
311
+ result.concat(format_preamble(node.untagged_remarks))
312
+ end
313
+
314
+ # Add provenance
315
+ provenance = format_provenance
316
+ result << provenance if provenance
317
+ result << "" if provenance
318
+
319
+ # Add schemas
320
+ schemas_output = direct_schemas.map { |x| format(x) }.join("\n\n")
321
+ result << schemas_output if schemas_output
322
+ elsif result.empty?
323
+ # Empty repository - just add provenance
324
+ provenance = format_provenance
325
+ result << provenance if provenance
326
+ end
327
+
328
+ result.any? ? "#{result.join("\n")}\n" : ""
329
+ end
330
+
331
+ # Format ExpFile with file-level preamble
332
+ # @param node [Model::ExpFile] ExpFile node
333
+ # @return [String] Formatted file
334
+ def format_exp_file(node)
335
+ result = []
336
+
337
+ # Add file-level preamble if present
338
+ if node.untagged_remarks && !node.untagged_remarks.empty?
339
+ result.concat(format_preamble(node.untagged_remarks))
319
340
  end
320
341
 
321
- # Add provenance
342
+ # Add provenance (only for first file or single file)
322
343
  provenance = format_provenance
323
344
  result << provenance if provenance
324
345
  result << "" if provenance
@@ -327,7 +348,7 @@ module Expressir
327
348
  schemas = node.schemas&.map { |x| format(x) }&.join("\n\n")
328
349
  result << schemas if schemas
329
350
 
330
- "#{result.join("\n")}\n"
351
+ result.empty? ? "" : "#{result.join("\n")}\n"
331
352
  end
332
353
 
333
354
  # Format a block of constants with aligned colons and assignments