expressir 2.4.32 → 2.4.34

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.
@@ -127,6 +127,19 @@ module Expressir
127
127
  @formatted_hyperlinked_adoc ||= Expressir::Express::AdocSourceFormatter.format(self)
128
128
  end
129
129
 
130
+ USE_ITEM_KINDS = [
131
+ Expressir::Model::Declarations::Entity,
132
+ Expressir::Model::Declarations::Type,
133
+ ].freeze
134
+
135
+ REFERENCE_ITEM_KINDS = [
136
+ Expressir::Model::Declarations::Constant,
137
+ Expressir::Model::Declarations::Entity,
138
+ Expressir::Model::Declarations::Function,
139
+ Expressir::Model::Declarations::Procedure,
140
+ Expressir::Model::Declarations::Type,
141
+ ].freeze
142
+
130
143
  private
131
144
 
132
145
  # @param [String] id
@@ -142,6 +155,7 @@ module Expressir
142
155
  end
143
156
 
144
157
  # @return [Array<InterfacedItem>]
158
+
145
159
  def interfaced_items
146
160
  return [] unless parent
147
161
 
@@ -151,6 +165,11 @@ module Expressir
151
165
  end.compact
152
166
  end
153
167
 
168
+ # ISO 10303-11 implicit interfacing: USE exposes entities and
169
+ # types only; REFERENCE has no implicit interfacing. Listed
170
+ # items follow eeng resolve-interface: use-from resolves via
171
+ # find-type/entity, reference-from via the 5-kind or-chain.
172
+
154
173
  # Direct items of one interface plus the items visible through
155
174
  # the foreign schema's own USE interfaces (transitive USE
156
175
  # visibility, eeng :use-only → :use-from recursion).
@@ -171,16 +190,23 @@ module Expressir
171
190
  end
172
191
 
173
192
  own = if interface.items.empty?
174
- safe_children.map do |base_item|
175
- create_interfaced_item(base_item.id, base_item)
193
+ if interface.kind == Interface::REFERENCE
194
+ []
195
+ else
196
+ safe_children.filter_map do |base_item|
197
+ next nil unless USE_ITEM_KINDS.any? { |k| base_item.is_a?(k) }
198
+
199
+ create_interfaced_item(base_item.id, base_item)
200
+ end
176
201
  end
177
202
  else
178
203
  interface.items.filter_map do |item|
179
204
  base_item = children_by_id[item.ref.id.safe_downcase]
180
- if base_item
181
- create_interfaced_item(item.id || base_item.id,
182
- base_item)
183
- end
205
+ next nil unless base_item
206
+ next nil unless item_kind_interfacable?(interface.kind, base_item)
207
+
208
+ create_interfaced_item(item.id || base_item.id,
209
+ base_item)
184
210
  end
185
211
  end
186
212
 
@@ -197,6 +223,11 @@ module Expressir
197
223
  end.flatten
198
224
  end
199
225
 
226
+ def item_kind_interfacable?(kind, node)
227
+ kinds = kind == Interface::REFERENCE ? REFERENCE_ITEM_KINDS : USE_ITEM_KINDS
228
+ kinds.any? { |k| node.is_a?(k) }
229
+ end
230
+
200
231
  # Find a foreign schema by name: same file first, then the
201
232
  # enclosing repository (expressir's compiled/batch repository).
202
233
  def foreign_schema(name)
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module P21
5
+ # Turns a {Grammar} capture tree into a {Population}.
6
+ module Builder
7
+ module_function
8
+
9
+ def build(tree)
10
+ header = build_header(tree[:header_section])
11
+ instances = build_instances(tree[:data_section])
12
+ Population.new(header: header, instances: instances)
13
+ end
14
+
15
+ def build_header(section)
16
+ Array(section[:header_entity]).each_with_object({}) do |entry, hash|
17
+ record = entry[:simple_record]
18
+ hash[record_keyword(record)] = record_params(record)
19
+ end
20
+ end
21
+
22
+ def build_instances(section)
23
+ list = section[:instance_list]
24
+ entries = list.is_a?(Array) ? list : [list]
25
+ entries.each_with_object({}) do |entry, hash|
26
+ if (complex = entry[:complex_entity_instance])
27
+ hash[instance_id(complex)] = build_complex(complex)
28
+ elsif (simple = entry[:simple_entity_instance])
29
+ record = simple[:simple_record]
30
+ hash[instance_id(simple)] = Record.new(record_keyword(record),
31
+ record_params(record))
32
+ end
33
+ end
34
+ end
35
+
36
+ def instance_id(body)
37
+ "##{body[:entity_instance_name].to_s.delete_prefix('#')}"
38
+ end
39
+
40
+ def build_complex(body)
41
+ subs = body[:subsuper_record]
42
+ subs = [subs] unless subs.is_a?(Array)
43
+ records = subs.map do |sub|
44
+ record = sub[:simple_record]
45
+ Record.new(record_keyword(record), record_params(record))
46
+ end
47
+ ComplexRecord.new(records)
48
+ end
49
+
50
+ def record_keyword(record)
51
+ record[:keyword].to_s.delete_prefix("!")
52
+ end
53
+
54
+ # A parameter_list capture is a hash when there is a single parameter
55
+ # and an array of {parameter: ...} entries otherwise.
56
+ def parameter_list_values(capture)
57
+ return [] if capture.nil?
58
+
59
+ entries = capture.is_a?(Array) ? capture : [capture]
60
+ entries.map { |entry| build_value(entry[:parameter]) }
61
+ end
62
+
63
+ def record_params(record)
64
+ parameter_list_values(record[:parameter_list])
65
+ end
66
+
67
+ def build_value(param)
68
+ if (typed = param[:typed_parameter])
69
+ TypedValue.new(typed[:keyword].to_s.delete_prefix("!"),
70
+ parameter_list_values(typed[:parameter_list]))
71
+ elsif param[:parameter_list]
72
+ parameter_list_values(param[:parameter_list])
73
+ elsif param[:entity_instance_name]
74
+ "##{param[:entity_instance_name].to_s.delete_prefix('#')}"
75
+ elsif param[:number_literal]
76
+ text = param[:number_literal].to_s
77
+ text.match?(/[.Ee]/) ? text.to_f : text.to_i
78
+ elsif param[:enumeration]
79
+ Enumeration.new(param[:enumeration].to_s.delete_prefix(".").delete_suffix("."))
80
+ elsif param[:binary_literal]
81
+ Binary.new(param[:binary_literal].to_s.delete_prefix('"').delete_suffix('"'))
82
+ elsif param[:string_literal]
83
+ param[:string_literal].to_s[1..-2].gsub("''", "'")
84
+ elsif param[:unset]
85
+ :unset
86
+ elsif param[:derived]
87
+ :derived
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parsanol"
4
+
5
+ module Expressir
6
+ module P21
7
+ # ISO 10303-21 exchange-structure grammar (TODO.eengine/06 step 1).
8
+ # Parses the HEADER section and the DATA section's instance list
9
+ # into a capture tree; {Expressir::P21::PopulationBuilder} turns
10
+ # the tree into a {Expressir::P21::Population}.
11
+ #
12
+ # Reference: ISO 10303-21 exchange structure grammar
13
+ # (`~/src/mn/iso-10303-detached-docs/sources/iso-10303-21*/`);
14
+ # scope ladder in TODO.eengine/06 — the supported subset is the
15
+ # header FILE_* entities plus simple/complex data instances with
16
+ # lists, typed parameters, references, enumerations, binaries,
17
+ # strings, unset ($) and derived (*).
18
+ class Grammar < Parsanol::Parser
19
+ root(:exchange_structure)
20
+
21
+ rule(:comment) { str("/*") >> (str("*/").absent? >> any).repeat >> str("*/") }
22
+ rule(:ws?) { (match[" \t\r\n"] | comment).repeat }
23
+ rule(:ws1) { (match[" \t\r\n"] | comment).repeat(1) }
24
+ rule(:keyword) { match["!"].maybe >> match["A-Za-z0-9_"].repeat(1) }
25
+ rule(:entity_instance_name) { str("#") >> match["0-9"].repeat(1) }
26
+
27
+ rule(:string_literal) do
28
+ # ISO 10303-21: doubled quotes are escaped quotes
29
+ str("'") >> ((str("'").absent? >> any) | str("''")).repeat >> str("'")
30
+ end
31
+ rule(:binary_literal) { str('"') >> match["0-9A-Fa-f"].repeat >> str('"') }
32
+ rule(:enumeration) { str(".") >> match["A-Za-z0-9_"].repeat(1) >> str(".") }
33
+ rule(:unset) { str("$") }
34
+ rule(:derived) { str("*") }
35
+
36
+ rule(:parameter_list) do
37
+ str("(") >> ws? >>
38
+ (parameter.as(:parameter) >>
39
+ (str(",") >> ws? >> parameter.as(:parameter)).repeat).maybe >>
40
+ str(")") >> ws?
41
+ end
42
+
43
+ rule(:typed_parameter) do
44
+ keyword.as(:keyword) >> ws? >> parameter_list.as(:parameter_list)
45
+ end
46
+ rule(:list_parameter) { parameter_list }
47
+
48
+ rule(:number_literal) do
49
+ match["+-"].maybe >> match["0-9"].repeat(1) >>
50
+ (str(".") >> match["0-9"].repeat(1)).maybe >>
51
+ (match["Ee"] >> match["+-"].maybe >> match["0-9"].repeat(1)).maybe
52
+ end
53
+
54
+ rule(:parameter) do
55
+ typed_parameter.as(:typed_parameter) |
56
+ list_parameter.as(:parameter_list) |
57
+ entity_instance_name.as(:entity_instance_name) |
58
+ number_literal.as(:number_literal) |
59
+ enumeration.as(:enumeration) |
60
+ binary_literal.as(:binary_literal) |
61
+ string_literal.as(:string_literal) |
62
+ unset.as(:unset) | derived.as(:derived)
63
+ end
64
+
65
+ rule(:simple_record) do
66
+ keyword.as(:keyword) >> ws? >> parameter_list.as(:parameter_list)
67
+ end
68
+
69
+ rule(:subsuper_record) do
70
+ str("(") >> ws? >> simple_record.as(:simple_record).repeat(1) >>
71
+ str(")") >> ws?
72
+ end
73
+
74
+ rule(:simple_entity_instance) do
75
+ (entity_instance_name.as(:entity_instance_name) >> ws? >>
76
+ str("=") >> ws? >> simple_record.as(:simple_record) >>
77
+ str(";") >> ws?).as(:simple_entity_instance)
78
+ end
79
+ rule(:complex_entity_instance) do
80
+ (entity_instance_name.as(:entity_instance_name) >> ws? >>
81
+ str("=") >> ws? >> subsuper_record.as(:subsuper_record) >>
82
+ str(";") >> ws?).as(:complex_entity_instance)
83
+ end
84
+ rule(:entity_instance) { complex_entity_instance | simple_entity_instance }
85
+
86
+ rule(:header_entity) do
87
+ simple_record.as(:simple_record) >> str(";") >> ws?
88
+ end
89
+ rule(:header_section) do
90
+ str("HEADER;") >> ws? >> header_entity.repeat(1).as(:header_entity) >>
91
+ str("ENDSEC;") >> ws?
92
+ end
93
+ rule(:data_section) do
94
+ str("DATA;") >> ws? >>
95
+ (str("(") >> ws? >> parameter >> str(");") >> ws?).maybe >>
96
+ entity_instance.repeat.as(:instance_list) >>
97
+ str("ENDSEC;") >> ws?
98
+ end
99
+ rule(:exchange_structure) do
100
+ ws? >> str("ISO-10303-21;") >> ws? >>
101
+ header_section.as(:header_section) >>
102
+ data_section.as(:data_section) >>
103
+ str("END-ISO-10303-21;") >> ws?
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module P21
5
+ # Facade: parse an ISO 10303-21 exchange structure into a
6
+ # {Population} (TODO.eengine/06 step 1).
7
+ module Parser
8
+ module_function
9
+
10
+ def parse(source)
11
+ tree = Grammar.new.parse(source)
12
+ Builder.build(tree)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module P21
5
+ # A parsed ISO 10303-21 exchange structure (TODO.eengine/06).
6
+ # `header` — Hash: keyword => parameters (Array of values).
7
+ # `instances`— Hash: "#id" => Record.
8
+ #
9
+ # Values: String (string literal), Integer (reference id, keyed as
10
+ # "#id" in instances), Expressir::P21::Enumeration,
11
+ # Expressir::P21::Binary, Expressir::P21::TypedValue,
12
+ # Expressir::P21::ComplexRecord, :unset, :derived, or nested
13
+ # Arrays for lists.
14
+ class Population
15
+ attr_reader :header, :instances
16
+
17
+ def initialize(header: {}, instances: {})
18
+ @header = header
19
+ @instances = instances
20
+ end
21
+ end
22
+
23
+ # A data-section instance: `#12 = KEYWORD(param, ...) ;`
24
+ Record = Struct.new(:keyword, :params)
25
+
26
+ # `#21 = (A(...) B(...))` — complex entity instance.
27
+ ComplexRecord = Struct.new(:records)
28
+
29
+ TypedValue = Struct.new(:keyword, :value)
30
+
31
+ Enumeration = Struct.new(:name)
32
+
33
+ Binary = Struct.new(:hex)
34
+ end
35
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module P21
5
+ # Validates a {Population} against a loaded schema
6
+ # (TODO.eengine/06 step 2). Single-schema scope: instance keywords
7
+ # must be declared in the given schema; USE/REFERENCE visibility is
8
+ # a later ladder step.
9
+ #
10
+ # Mapping follows ISO 10303-21 clause 12: derived attributes are
11
+ # not mapped (a supertype attribute redeclared as DERIVED keeps its
12
+ # slot, valued `*`), inverse attributes are not mapped, and the
13
+ # value order is supertype attributes first.
14
+ module Validator
15
+ Note = Struct.new(:id, :severity, :instance, :message, keyword_init: true)
16
+
17
+ module_function
18
+
19
+ # `short_names` maps exchange-structure short names (ISO 10303-21,
20
+ # clause 12.3) to schema entity names, e.g. {"CPT" => "cartesian_point"}.
21
+ def validate(population, schema, short_names: nil)
22
+ short_names = short_names&.transform_keys(&:downcase)
23
+ notes = []
24
+ check_file_schema(notes, population, schema)
25
+ population.instances.each do |id, instance|
26
+ if instance.is_a?(ComplexRecord)
27
+ check_complex(notes, schema, population, id, instance, short_names)
28
+ else
29
+ check_simple(notes, schema, population, id, instance, short_names)
30
+ end
31
+ end
32
+ notes
33
+ end
34
+
35
+ def errors(notes)
36
+ notes.select { |note| note.severity == :error }
37
+ end
38
+
39
+ def warnings(notes)
40
+ notes.select { |note| note.severity == :warning }
41
+ end
42
+
43
+ def valid?(notes)
44
+ errors(notes).empty?
45
+ end
46
+
47
+ def note!(notes, id, severity, instance, message)
48
+ notes << Note.new(id: id, severity: severity, instance: instance,
49
+ message: message)
50
+ end
51
+
52
+ def check_file_schema(notes, population, schema)
53
+ declared = Array(population.header["FILE_SCHEMA"]).flatten.first
54
+ return unless declared && schema.id
55
+ return if declared.downcase == schema.id.downcase
56
+
57
+ note!(notes, :p21_schema_mismatch, :warning, nil,
58
+ "FILE_SCHEMA '#{declared}' does not match schema '#{schema.id}'")
59
+ end
60
+
61
+ def check_simple(notes, schema, population, id, record, short_names)
62
+ entity = find_entity(schema, record.keyword, short_names)
63
+ unless entity
64
+ note!(notes, :p21_unknown_entity, :error, id,
65
+ "#{record.keyword} is not declared in schema '#{schema.id}'")
66
+ return
67
+ end
68
+
69
+ check_params(notes, schema, population, id, record.params,
70
+ mapped_slots(entity))
71
+ end
72
+
73
+ def check_complex(notes, schema, population, id, complex, short_names)
74
+ pairs = complex.records.filter_map do |record|
75
+ entity = find_entity(schema, record.keyword, short_names)
76
+ unless entity
77
+ note!(notes, :p21_unknown_entity, :error, id,
78
+ "#{record.keyword} is not declared in schema '#{schema.id}'")
79
+ next nil
80
+ end
81
+
82
+ [record, entity]
83
+ end
84
+ return if pairs.empty?
85
+
86
+ check_clique(notes, id, pairs.map { |_, entity| entity })
87
+ pairs.each do |record, entity|
88
+ slots = explicit_attributes(entity).map { |attr| [attr, false] }
89
+ check_params(notes, schema, population, id, record.params, slots)
90
+ end
91
+ end
92
+
93
+ # A complex instance's records must form a valid supertype graph
94
+ # path: they share at least one common supertype.
95
+ def check_clique(notes, id, entities)
96
+ common = closure_keys(entities.first)
97
+ entities.drop(1).each { |entity| common &= closure_keys(entity) }
98
+ return unless common.empty?
99
+
100
+ note!(notes, :p21_complex_clique, :error, id,
101
+ "#{id}: complex records share no common supertype")
102
+ end
103
+
104
+ def closure_keys(entity)
105
+ keys = entity.supertype_closure.filter_map { |e| e.id&.downcase }
106
+ keys << entity.id&.downcase
107
+ end
108
+
109
+ # Part 21 value order: supertype attributes first; an attribute
110
+ # redeclared as DERIVED in the entity keeps its inherited slot,
111
+ # valued `*` (12.2.6).
112
+ def mapped_slots(entity)
113
+ own_derived_keys = own_derived_attributes(entity)
114
+ .filter_map { |attr| attr.id&.downcase }
115
+ inherited = entity.supertype_closure.flat_map do |supertype|
116
+ explicit_attributes(supertype)
117
+ end
118
+ inherited.map do |attr|
119
+ [attr, own_derived_keys.include?(attr.id&.downcase)]
120
+ end + explicit_attributes(entity).map { |attr| [attr, false] }
121
+ end
122
+
123
+ def check_params(notes, schema, population, id, params, slots)
124
+ if params.size != slots.size
125
+ note!(notes, :p21_arity_mismatch, :error, id,
126
+ "#{id}: #{params.size} values for #{slots.size} mapped attributes")
127
+ end
128
+
129
+ slots.zip(params).each do |(attr, derived_slot), value|
130
+ next if value.nil?
131
+
132
+ if derived_slot
133
+ next if value == :derived
134
+
135
+ note!(notes, :p21_derived_slot, :error, id,
136
+ "#{id}: attribute '#{attr.id}' redeclared DERIVED must be '*'")
137
+ next
138
+ end
139
+
140
+ check_value(notes, schema, population, id, attr, value)
141
+ end
142
+ end
143
+
144
+ def check_value(notes, schema, population, id, attr, value)
145
+ case value
146
+ when String
147
+ return unless value.match?(/\A#\d+\z/)
148
+
149
+ return if population.instances.key?(value)
150
+
151
+ note!(notes, :p21_unknown_reference, :error, id,
152
+ "#{id}: reference #{value} has no instance")
153
+ when :derived
154
+ note!(notes, :p21_derived_value, :error, id,
155
+ "#{id}: '*' outside a derived slot")
156
+ when TypedValue
157
+ return if find_entity(schema, value.keyword) || find_type(schema, value.keyword)
158
+
159
+ note!(notes, :p21_unknown_typed_keyword, :error, id,
160
+ "#{id}: typed parameter '#{value.keyword}' does not resolve")
161
+ when Enumeration
162
+ items = enumeration_items(schema, attr)
163
+ return unless items
164
+
165
+ names = items.filter_map { |item| item.id&.downcase }
166
+ return if names.include?(value.name.downcase)
167
+
168
+ note!(notes, :p21_enum_value, :error, id,
169
+ "#{id}: .#{value.name}. is not in attribute '#{attr.id}' enumeration")
170
+ when Array
171
+ value.each { |item| check_value(notes, schema, population, id, attr, item) }
172
+ end
173
+ end
174
+
175
+ def explicit_attributes(entity)
176
+ Array(entity.attributes)
177
+ .select { |attr| attr.instance_of?(Model::Declarations::Attribute) }
178
+ end
179
+
180
+ def own_derived_attributes(entity)
181
+ Array(entity.attributes)
182
+ .select { |attr| attr.instance_of?(Model::Declarations::DerivedAttribute) }
183
+ end
184
+
185
+ def find_entity(schema, keyword, short_names = nil)
186
+ key = keyword.downcase
187
+ Array(schema.entities).find { |entity| entity.id&.downcase == key } ||
188
+ resolve_short_name(schema, key, short_names)
189
+ end
190
+
191
+ def resolve_short_name(schema, key, short_names)
192
+ return nil unless short_names.is_a?(Hash) && short_names[key]
193
+
194
+ find_entity(schema, short_names[key], nil)
195
+ end
196
+
197
+ def find_type(schema, keyword)
198
+ Array(schema.types).find { |type| type.id&.downcase == keyword.downcase }
199
+ end
200
+
201
+ def enumeration_items(schema, attr)
202
+ type = attr&.type
203
+ return Array(type.items) if type.is_a?(Model::DataTypes::Enumeration)
204
+
205
+ return nil unless type.is_a?(Model::References::SimpleReference)
206
+
207
+ declaration = find_type(schema, type.id.to_s)
208
+ underlying = declaration&.underlying_type
209
+ underlying.is_a?(Model::DataTypes::Enumeration) ? Array(underlying.items) : nil
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ # ISO 10303-21 exchange-structure support (TODO.eengine/06).
5
+ module P21
6
+ autoload :Population, "#{__dir__}/p21/population"
7
+ autoload :Record, "#{__dir__}/p21/population"
8
+ autoload :ComplexRecord, "#{__dir__}/p21/population"
9
+ autoload :TypedValue, "#{__dir__}/p21/population"
10
+ autoload :Enumeration, "#{__dir__}/p21/population"
11
+ autoload :Binary, "#{__dir__}/p21/population"
12
+ autoload :Grammar, "#{__dir__}/p21/grammar"
13
+ autoload :Builder, "#{__dir__}/p21/builder"
14
+ autoload :Validator, "#{__dir__}/p21/validator"
15
+ autoload :Parser, "#{__dir__}/p21/parser"
16
+ end
17
+ end
@@ -3,6 +3,6 @@ module Expressir
3
3
  # autoload it (Ruby autoload only works for module/class constants, not
4
4
  # for string constants like `Expressir::VERSION`).
5
5
  module Version
6
- VERSION = "2.4.32".freeze
6
+ VERSION = "2.4.34".freeze
7
7
  end
8
8
  end
data/lib/expressir.rb CHANGED
@@ -30,6 +30,7 @@ module Expressir
30
30
 
31
31
  autoload :Version, "#{__dir__}/expressir/version"
32
32
  autoload :Mapping, "#{__dir__}/expressir/mapping"
33
+ autoload :P21, "#{__dir__}/expressir/p21"
33
34
 
34
35
  # Error classes — autoload the same errors.rb file from each name.
35
36
  # The first reference triggers the load; the file defines all 21 classes.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expressir
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.32
4
+ version: 2.4.34
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-14 00:00:00.000000000 Z
11
+ date: 2026-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys
@@ -303,6 +303,16 @@ files:
303
303
  - TODO.bugs/29-validate-ascii-god-class.md
304
304
  - TODO.bugs/30-unicode-map-extraction.md
305
305
  - TODO.bugs/README.md
306
+ - TODO.eengine/00-overview.md
307
+ - TODO.eengine/01-resolution-diagnostics.md
308
+ - TODO.eengine/02-check-catalog-completion.md
309
+ - TODO.eengine/03-entity-computations.md
310
+ - TODO.eengine/04-shtolo-rule-audit.md
311
+ - TODO.eengine/05-shtolo-longform-flatten.md
312
+ - TODO.eengine/06-population-p21.md
313
+ - TODO.eengine/07-schema-compare-patch.md
314
+ - TODO.eengine/08-official-corpus-gate.md
315
+ - TODO.eengine/09-mim-mapping-parser.md
306
316
  - TODO.max-perf/01-restore-ci-green.md
307
317
  - TODO.max-perf/02-streaming-parse-path.md
308
318
  - TODO.max-perf/03-cli-parallel-opt-in.md
@@ -552,6 +562,7 @@ files:
552
562
  - lib/expressir/express/remark_scanner.rb
553
563
  - lib/expressir/express/resolve_references_model_visitor.rb
554
564
  - lib/expressir/express/schema_block_scanner.rb
565
+ - lib/expressir/express/schema_compare.rb
555
566
  - lib/expressir/express/schema_head_formatter.rb
556
567
  - lib/expressir/express/schema_plain_source_formatter.rb
557
568
  - lib/expressir/express/schema_source_formatter.rb
@@ -664,6 +675,12 @@ files:
664
675
  - lib/expressir/model/supertype_expressions.rb
665
676
  - lib/expressir/model/supertype_expressions/binary_supertype_expression.rb
666
677
  - lib/expressir/model/supertype_expressions/oneof_supertype_expression.rb
678
+ - lib/expressir/p21.rb
679
+ - lib/expressir/p21/builder.rb
680
+ - lib/expressir/p21/grammar.rb
681
+ - lib/expressir/p21/parser.rb
682
+ - lib/expressir/p21/population.rb
683
+ - lib/expressir/p21/validator.rb
667
684
  - lib/expressir/package.rb
668
685
  - lib/expressir/package/builder.rb
669
686
  - lib/expressir/package/metadata.rb