lutaml-lml 0.3.3 → 0.3.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/exe/lutaml-lml-lsp +11 -0
  3. data/lib/lutaml/lml/cli.rb +166 -17
  4. data/lib/lutaml/lml/data_processor/attribute_processing.rb +4 -2
  5. data/lib/lutaml/lml/data_processor/value_declaration_processing.rb +1 -0
  6. data/lib/lutaml/lml/data_processor.rb +8 -1
  7. data/lib/lutaml/lml/diagnostic.rb +149 -0
  8. data/lib/lutaml/lml/document_builder.rb +63 -21
  9. data/lib/lutaml/lml/format/adapter/standard_adapter.rb +22 -23
  10. data/lib/lutaml/lml/formatter/canonical.rb +736 -0
  11. data/lib/lutaml/lml/formatter.rb +2 -0
  12. data/lib/lutaml/lml/grammar/lml.artifact.json +2946 -2512
  13. data/lib/lutaml/lml/grammar/lml.parg +47 -9
  14. data/lib/lutaml/lml/grammar.rb +13 -0
  15. data/lib/lutaml/lml/import_resolver.rb +13 -4
  16. data/lib/lutaml/lml/lsp/conversions.rb +78 -0
  17. data/lib/lutaml/lml/lsp/keywords.rb +43 -0
  18. data/lib/lutaml/lml/lsp/server.rb +378 -0
  19. data/lib/lutaml/lml/lsp/store.rb +68 -0
  20. data/lib/lutaml/lml/lsp/transport.rb +53 -0
  21. data/lib/lutaml/lml/lsp.rb +16 -0
  22. data/lib/lutaml/lml/model_compiler/serialization_mappings.rb +36 -10
  23. data/lib/lutaml/lml/model_compiler.rb +25 -2
  24. data/lib/lutaml/lml/models/association.rb +3 -0
  25. data/lib/lutaml/lml/models/constraint.rb +3 -0
  26. data/lib/lutaml/lml/models/data_type.rb +3 -0
  27. data/lib/lutaml/lml/models/diagram.rb +3 -0
  28. data/lib/lutaml/lml/models/document.rb +10 -3
  29. data/lib/lutaml/lml/models/enum.rb +4 -1
  30. data/lib/lutaml/lml/models/instance.rb +3 -0
  31. data/lib/lutaml/lml/models/instance_collection.rb +3 -0
  32. data/lib/lutaml/lml/models/member_field.rb +2 -0
  33. data/lib/lutaml/lml/models/namespace.rb +2 -0
  34. data/lib/lutaml/lml/models/operation.rb +4 -1
  35. data/lib/lutaml/lml/models/package.rb +3 -0
  36. data/lib/lutaml/lml/models/primitive_type.rb +6 -3
  37. data/lib/lutaml/lml/models/serialization_mapping.rb +2 -0
  38. data/lib/lutaml/lml/models/top_element_attribute.rb +6 -0
  39. data/lib/lutaml/lml/models/uml_class.rb +6 -0
  40. data/lib/lutaml/lml/models/value.rb +3 -0
  41. data/lib/lutaml/lml/pipeline.rb +25 -8
  42. data/lib/lutaml/lml/positioned.rb +13 -0
  43. data/lib/lutaml/lml/preprocessor.rb +44 -13
  44. data/lib/lutaml/lml/quoting.rb +60 -0
  45. data/lib/lutaml/lml/source_position.rb +20 -0
  46. data/lib/lutaml/lml/symbol_index.rb +200 -0
  47. data/lib/lutaml/lml/transform.rb +49 -2
  48. data/lib/lutaml/lml/validator.rb +134 -21
  49. data/lib/lutaml/lml/version.rb +1 -1
  50. data/lib/lutaml/lml.rb +18 -1
  51. metadata +18 -4
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rubygems/version"
4
+
3
5
  module Lutaml
4
6
  module Lml
5
7
  # RS 3001 §Validation rules evaluated over a parsed document:
@@ -8,14 +10,27 @@ module Lutaml
8
10
  # matching declared types) and reference validity (ref: paths resolve
9
11
  # to instances and are acyclic).
10
12
  class Validator
11
- Violation = Struct.new(:rule, :message, keyword_init: true)
13
+ Violation = Struct.new(:rule, :message, :position, keyword_init: true)
14
+
15
+ RULE_CODES = {
16
+ 'unique_names' => 'validation/LML-V01',
17
+ 'unique_attribute_names' => 'validation/LML-V02',
18
+ 'mandatory_attributes' => 'validation/LML-V03',
19
+ 'type_conformity' => 'validation/LML-V04',
20
+ 'values_set_membership' => 'validation/LML-V05',
21
+ 'unknown_attributes' => 'validation/LML-V06',
22
+ 'reference_validity' => 'validation/LML-V07',
23
+ 'reference_circularity' => 'validation/LML-V08',
24
+ 'pattern_conformity' => 'validation/LML-V09',
25
+ 'pattern_compiles' => 'validation/LML-V10',
26
+ }.freeze
12
27
 
13
28
  PRIMITIVE_CONFORMITY = {
14
29
  'Integer' => /\A-?\d+\z/,
15
30
  'Number' => /\A-?\d+\z/,
16
31
  'Float' => /\A-?\d+\.\d+\z/,
17
32
  'Boolean' => /\A(true|false)\z/,
18
- 'Null' => /\Anull\z/
33
+ 'Null' => /\Anull\z/,
19
34
  }.freeze
20
35
 
21
36
  COLLECTION_KEYS = %i[properties attributes instances].freeze
@@ -24,10 +39,55 @@ module Lutaml
24
39
  new(document).violations
25
40
  end
26
41
 
42
+ # The structured form of the same rules: one Diagnostic per
43
+ # violation, with its code and the source position of the
44
+ # offending member when the parser retained one.
45
+ def self.diagnostics(document)
46
+ new(document).diagnostics
47
+ end
48
+
27
49
  def initialize(document)
28
50
  @document = document
29
51
  end
30
52
 
53
+ def diagnostics
54
+ version_diagnostics + violations.map do |violation|
55
+ Diagnostic.build(
56
+ RULE_CODES.fetch(violation.rule),
57
+ message: violation.message,
58
+ file: @document.source_path,
59
+ line: violation.position&.line,
60
+ column: violation.position&.column,
61
+ length: violation.position&.length,
62
+ )
63
+ end
64
+ end
65
+
66
+ # Version negotiation: a document may declare the language version
67
+ # it speaks with an `lml "x.y"` prologue. A version newer than this
68
+ # build is a warning, not an error (forward compatibility).
69
+ def version_diagnostics
70
+ declared = @document.lml_version.to_s
71
+ return [] if declared.empty?
72
+ return [] unless newer_language_version?(declared)
73
+
74
+ position = @document.source_position
75
+ [Diagnostic.build(
76
+ 'contract/LML-C06',
77
+ message: "document declares LML #{declared}; this build supports #{Grammar.version}",
78
+ file: @document.source_path,
79
+ line: position&.line,
80
+ column: position&.column,
81
+ length: position&.length,
82
+ )]
83
+ end
84
+
85
+ def newer_language_version?(declared)
86
+ Gem::Version.new(declared) > Gem::Version.new(Grammar.version)
87
+ rescue ArgumentError
88
+ false
89
+ end
90
+
31
91
  def violations
32
92
  unique_name_violations +
33
93
  unique_attribute_violations +
@@ -47,7 +107,7 @@ module Lutaml
47
107
  [
48
108
  *Array(@document.classes),
49
109
  *Array(@document.enums),
50
- *Array(@document.data_types)
110
+ *Array(@document.data_types),
51
111
  ]
52
112
  end
53
113
 
@@ -61,10 +121,9 @@ module Lutaml
61
121
 
62
122
  def all_definitions
63
123
  @all_definitions ||= begin
64
- pairs = []
65
- Array(@document.classes).each { |c| pairs << [c.name.to_s, c] }
66
- Array(@document.enums).each { |e| pairs << [e.name.to_s, e] }
67
- Array(@document.data_types).each { |d| pairs << [d.name.to_s, d] }
124
+ pairs = Array(@document.classes).map { |c| [c.name.to_s, c] } +
125
+ Array(@document.enums).map { |e| [e.name.to_s, e] } +
126
+ Array(@document.data_types).map { |d| [d.name.to_s, d] }
68
127
  Array(@document.packages).each { |pkg| collect_package_definitions(pkg, pairs) }
69
128
  pairs
70
129
  end
@@ -91,7 +150,8 @@ module Lutaml
91
150
 
92
151
  Violation.new(
93
152
  rule: 'unique_names',
94
- message: "'#{name}' is defined #{count} times in #{scope_label}"
153
+ message: "'#{name}' is defined #{count} times in #{scope_label}",
154
+ position: definitions.find { |definition| definition.name.to_s == name }&.source_position,
95
155
  )
96
156
  end
97
157
  end
@@ -105,7 +165,8 @@ module Lutaml
105
165
 
106
166
  Violation.new(
107
167
  rule: 'unique_attribute_names',
108
- message: "#{name}: duplicate attribute(s) #{dupes.inspect}"
168
+ message: "#{name}: duplicate attribute(s) #{dupes.inspect}",
169
+ position: definition.source_position,
109
170
  )
110
171
  end
111
172
  end
@@ -114,11 +175,58 @@ module Lutaml
114
175
  Array(definition.attributes).map(&:name).map(&:to_s)
115
176
  end
116
177
 
178
+ def entry_position(instance, name)
179
+ entry = Array(instance.attributes).find { |attribute| attribute.name.to_s == name }
180
+ entry&.source_position || instance.source_position
181
+ end
182
+
183
+ # RS 3001 par. Attribute: a `pattern` declaration constrains
184
+ # string values; a pattern that does not compile is itself a
185
+ # definition error.
186
+ def pattern_violations(instance)
187
+ definition = type_definitions[definition_type(instance)]
188
+ return [] unless definition.is_a?(UmlClass) || definition.is_a?(DataType)
189
+
190
+ instance_attribute_pairs(instance).filter_map do |name, value|
191
+ attr = Array(definition.attributes).find { |a| a.name.to_s == name }
192
+ next unless attr&.pattern
193
+
194
+ regexp = compile_pattern(attr.pattern)
195
+ next invalid_pattern_violation(definition_type(instance), name, attr) unless regexp
196
+
197
+ literal = value.respond_to?(:value) ? value.value : value
198
+ next unless literal.is_a?(String)
199
+ next if regexp.match?(literal)
200
+
201
+ Violation.new(
202
+ rule: 'pattern_conformity',
203
+ message: "instance '#{instance_name(instance)}': attribute '#{name}' " \
204
+ "does not match #{attr.pattern.inspect}, got #{literal.inspect}",
205
+ position: entry_position(instance, name),
206
+ )
207
+ end
208
+ end
209
+
210
+ def compile_pattern(pattern)
211
+ Regexp.new(pattern)
212
+ rescue RegexpError
213
+ nil
214
+ end
215
+
216
+ def invalid_pattern_violation(type, name, attr)
217
+ Violation.new(
218
+ rule: 'pattern_compiles',
219
+ message: "#{type}: attribute '#{name}' has an invalid pattern #{attr.pattern.inspect}",
220
+ position: attr.source_position,
221
+ )
222
+ end
223
+
117
224
  def instance_violations
118
225
  instances.flat_map do |instance|
119
226
  mandatory_violations(instance) +
120
227
  type_conformity_violations(instance) +
121
228
  values_set_violations(instance) +
229
+ pattern_violations(instance) +
122
230
  unknown_attribute_violations(instance) +
123
231
  reference_violations(instance)
124
232
  end.compact
@@ -140,7 +248,8 @@ module Lutaml
140
248
  Violation.new(
141
249
  rule: 'values_set_membership',
142
250
  message: "instance '#{instance_name(instance)}': attribute '#{name}' " \
143
- "must be one of #{attr.value_set.inspect}, got #{literal.to_s.inspect}"
251
+ "must be one of #{attr.value_set.inspect}, got #{literal.to_s.inspect}",
252
+ position: entry_position(instance, name),
144
253
  )
145
254
  end
146
255
  end
@@ -151,12 +260,13 @@ module Lutaml
151
260
 
152
261
  declared = attribute_names(definition)
153
262
  instance_attribute_pairs(instance)
154
- .reject { |name, _value| declared.include?(name) }
263
+ .reject { |pair| declared.include?(pair[0]) }
155
264
  .map do |name, _value|
156
265
  Violation.new(
157
266
  rule: 'unknown_attributes',
158
267
  message: "instance '#{instance_name(instance)}' (#{definition_type(instance)}): " \
159
- "unknown attribute '#{name}'"
268
+ "unknown attribute '#{name}'",
269
+ position: entry_position(instance, name),
160
270
  )
161
271
  end
162
272
  end
@@ -176,7 +286,8 @@ module Lutaml
176
286
  Violation.new(
177
287
  rule: 'mandatory_attributes',
178
288
  message: "instance '#{instance_name(instance)}' (#{definition_type(instance)}): " \
179
- "mandatory attribute '#{attr.name}' is not populated"
289
+ "mandatory attribute '#{attr.name}' is not populated",
290
+ position: instance.source_position,
180
291
  )
181
292
  end
182
293
  end
@@ -185,8 +296,6 @@ module Lutaml
185
296
  attr.cardinality && attr.cardinality.min == '1'
186
297
  end
187
298
 
188
-
189
-
190
299
  def type_conformity_violations(instance)
191
300
  definition = type_definitions[definition_type(instance)]
192
301
  return [] unless definition.respond_to?(:attributes)
@@ -207,7 +316,8 @@ module Lutaml
207
316
  Violation.new(
208
317
  rule: 'type_conformity',
209
318
  message: "instance '#{instance_name(instance)}': attribute '#{name}' " \
210
- "expects #{attr.type}, got #{value.to_s.inspect}"
319
+ "expects #{attr.type}, got #{value.to_s.inspect}",
320
+ position: entry_position(instance, name),
211
321
  )
212
322
  end
213
323
 
@@ -218,7 +328,8 @@ module Lutaml
218
328
  next Violation.new(
219
329
  rule: 'reference_validity',
220
330
  message: "instance '#{instance_name(instance)}': attribute '#{name}' " \
221
- "references unknown path '#{path}'"
331
+ "references unknown path '#{path}'",
332
+ position: entry_position(instance, name),
222
333
  )
223
334
  end
224
335
 
@@ -241,7 +352,7 @@ module Lutaml
241
352
  @collection_names ||= [@document.instances&.name].compact
242
353
  end
243
354
 
244
- def attribute_violation(instance, _name, path, target)
355
+ def attribute_violation(instance, name, path, target)
245
356
  segment = path.to_s.split('.')[1]
246
357
  target_instance = instances.find { |i| instance_name(i) == target }
247
358
  return if segment.nil? || target_instance.nil? ||
@@ -250,7 +361,8 @@ module Lutaml
250
361
  Violation.new(
251
362
  rule: 'reference_validity',
252
363
  message: "instance '#{instance_name(instance)}': reference '#{path}' " \
253
- "does not resolve (no attribute '#{segment}' on '#{target}')"
364
+ "does not resolve (no attribute '#{segment}' on '#{target}')",
365
+ position: entry_position(instance, name),
254
366
  )
255
367
  end
256
368
 
@@ -306,7 +418,8 @@ module Lutaml
306
418
  cycles.map do |cycle|
307
419
  Violation.new(
308
420
  rule: 'reference_circularity',
309
- message: "circular reference: #{cycle.join(' -> ')}"
421
+ message: "circular reference: #{cycle.join(' -> ')}",
422
+ position: instances.find { |instance| instance_name(instance) == cycle.first }&.source_position,
310
423
  )
311
424
  end
312
425
  end
@@ -317,7 +430,7 @@ module Lutaml
317
430
  path = walk_for_cycle(graph, start)
318
431
  cycles << path if path
319
432
  end
320
- cycles.uniq { |cycle| cycle.sort }
433
+ cycles.uniq(&:sort)
321
434
  end
322
435
 
323
436
  def walk_for_cycle(graph, start)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Lml
5
- VERSION = "0.3.3"
5
+ VERSION = "0.3.5"
6
6
  end
7
7
  end
data/lib/lutaml/lml.rb CHANGED
@@ -17,9 +17,20 @@ module Lutaml
17
17
  # and renders diagrams. See the entry-point contract under .parse /
18
18
  # .parse_document below.
19
19
  module Lml
20
- class Error < Lutaml::Error; end
20
+ # Base error for LML failures; carries an optional Diagnostic.
21
+ class Error < Lutaml::Error
22
+ # Structured record for the failure, when one could be built.
23
+ attr_reader :diagnostic
24
+
25
+ def initialize(message = nil, diagnostic: nil)
26
+ @diagnostic = diagnostic
27
+ super(message)
28
+ end
29
+ end
30
+
21
31
  class ParsingError < Error; end
22
32
  class ImportError < Error; end
33
+ class HydrationError < Error; end
23
34
 
24
35
  def self.compile(input, namespace: nil)
25
36
  ModelCompiler.new(namespace: namespace).compile(input)
@@ -64,8 +75,14 @@ module Lutaml
64
75
  autoload :EntityTypes, "lutaml/lml/entity_types"
65
76
  autoload :AssociationLabelResolver, "lutaml/lml/association_label_resolver"
66
77
  autoload :Executor, "lutaml/lml/executor"
78
+ autoload :Lsp, "lutaml/lml/lsp"
79
+ autoload :SymbolIndex, "lutaml/lml/symbol_index"
67
80
  autoload :YamlParser, "lutaml/lml/yaml_parser"
68
81
  autoload :HasAttributes, "lutaml/lml/has_attributes"
82
+ autoload :Quoting, "lutaml/lml/quoting"
83
+ autoload :Diagnostic, "lutaml/lml/diagnostic"
84
+ autoload :SourcePosition, "lutaml/lml/source_position"
85
+ autoload :Positioned, "lutaml/lml/positioned"
69
86
  autoload :VERSION, "lutaml/lml/version"
70
87
 
71
88
  # Type classes (Lutaml::Model::Type::Value subclasses, not models)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-lml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-19 00:00:00.000000000 Z
11
+ date: 2026-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.8.114
19
+ version: 0.8.156
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.8.114
26
+ version: 0.8.156
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: parsanol
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -73,10 +73,12 @@ email:
73
73
  - open.source@ribose.com
74
74
  executables:
75
75
  - lutaml-lml
76
+ - lutaml-lml-lsp
76
77
  extensions: []
77
78
  extra_rdoc_files: []
78
79
  files:
79
80
  - exe/lutaml-lml
81
+ - exe/lutaml-lml-lsp
80
82
  - lib/lutaml/lml.rb
81
83
  - lib/lutaml/lml/association_label_resolver.rb
82
84
  - lib/lutaml/lml/cli.rb
@@ -87,6 +89,7 @@ files:
87
89
  - lib/lutaml/lml/data_processor/value_declaration_processing.rb
88
90
  - lib/lutaml/lml/data_processor/value_processing.rb
89
91
  - lib/lutaml/lml/data_processor/view_processing.rb
92
+ - lib/lutaml/lml/diagnostic.rb
90
93
  - lib/lutaml/lml/document_builder.rb
91
94
  - lib/lutaml/lml/entity_types.rb
92
95
  - lib/lutaml/lml/executor.rb
@@ -102,6 +105,7 @@ files:
102
105
  - lib/lutaml/lml/format/adapter/transform.rb
103
106
  - lib/lutaml/lml/formatter.rb
104
107
  - lib/lutaml/lml/formatter/base.rb
108
+ - lib/lutaml/lml/formatter/canonical.rb
105
109
  - lib/lutaml/lml/formatter/elk.rb
106
110
  - lib/lutaml/lml/formatter/graphviz.rb
107
111
  - lib/lutaml/lml/formatter/graphviz/document_formatter.rb
@@ -123,6 +127,12 @@ files:
123
127
  - lib/lutaml/lml/layout/engine.rb
124
128
  - lib/lutaml/lml/layout/graph_viz_engine.rb
125
129
  - lib/lutaml/lml/literal_value.rb
130
+ - lib/lutaml/lml/lsp.rb
131
+ - lib/lutaml/lml/lsp/conversions.rb
132
+ - lib/lutaml/lml/lsp/keywords.rb
133
+ - lib/lutaml/lml/lsp/server.rb
134
+ - lib/lutaml/lml/lsp/store.rb
135
+ - lib/lutaml/lml/lsp/transport.rb
126
136
  - lib/lutaml/lml/model_compiler.rb
127
137
  - lib/lutaml/lml/model_compiler/enums.rb
128
138
  - lib/lutaml/lml/model_compiler/hydration.rb
@@ -163,8 +173,12 @@ files:
163
173
  - lib/lutaml/lml/models/view_import.rb
164
174
  - lib/lutaml/lml/parser.rb
165
175
  - lib/lutaml/lml/pipeline.rb
176
+ - lib/lutaml/lml/positioned.rb
166
177
  - lib/lutaml/lml/preprocessor.rb
178
+ - lib/lutaml/lml/quoting.rb
167
179
  - lib/lutaml/lml/source.rb
180
+ - lib/lutaml/lml/source_position.rb
181
+ - lib/lutaml/lml/symbol_index.rb
168
182
  - lib/lutaml/lml/transform.rb
169
183
  - lib/lutaml/lml/types.rb
170
184
  - lib/lutaml/lml/types/text_type.rb