expressir 2.4.35-arm-linux → 2.4.37-arm-linux

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/conformance.yml +34 -0
  3. data/.gitignore +3 -0
  4. data/Gemfile +1 -1
  5. data/TODO.sota/00-overview.md +57 -0
  6. data/TODO.sota/01-type-checking.md +115 -0
  7. data/TODO.sota/02-evaluator.md +86 -0
  8. data/TODO.sota/03-conformance.md +53 -0
  9. data/TODO.sota/04-lsp.md +70 -0
  10. data/TODO.sota/05-interop.md +55 -0
  11. data/TODO.sota/06-dialects.md +57 -0
  12. data/TODO.sota/07-reference-artifacts.md +55 -0
  13. data/docs/_guides/interop/index.adoc +25 -0
  14. data/docs/_guides/interop/json-schema.adoc +88 -0
  15. data/docs/_guides/interop/owl-rdf.adoc +87 -0
  16. data/docs/_guides/reference/checker-notes.adoc +55 -0
  17. data/docs/_guides/reference/checker.adoc +36 -0
  18. data/docs/_guides/reference/evaluator.adoc +40 -0
  19. data/docs/_guides/reference/grammar-crosswalk.adoc +299 -0
  20. data/docs/_guides/reference/index.adoc +22 -0
  21. data/docs/_guides/reference/lsp.adoc +41 -0
  22. data/docs/_guides/reference/p21.adoc +30 -0
  23. data/docs/_guides/reference/shtolo.adoc +36 -0
  24. data/exe/expressir-lsp +6 -0
  25. data/expressir.gemspec +2 -2
  26. data/lib/expressir/3.2/expressir_core.so +0 -0
  27. data/lib/expressir/3.3/expressir_core.so +0 -0
  28. data/lib/expressir/3.4/expressir_core.so +0 -0
  29. data/lib/expressir/4.0/expressir_core.so +0 -0
  30. data/lib/expressir/cli.rb +18 -0
  31. data/lib/expressir/commands/check.rb +3 -1
  32. data/lib/expressir/commands/export_json_schema.rb +29 -0
  33. data/lib/expressir/commands/export_owl.rb +26 -0
  34. data/lib/expressir/commands/validate.rb +3 -0
  35. data/lib/expressir/commands.rb +2 -0
  36. data/lib/expressir/conformance/trend.rb +100 -0
  37. data/lib/expressir/conformance.rb +8 -0
  38. data/lib/expressir/eval/environment.rb +83 -0
  39. data/lib/expressir/eval/expression.rb +610 -0
  40. data/lib/expressir/eval/interpreter.rb +316 -0
  41. data/lib/expressir/eval/rules.rb +88 -0
  42. data/lib/expressir/eval/values.rb +74 -0
  43. data/lib/expressir/eval.rb +17 -0
  44. data/lib/expressir/express/builders/expression_builder.rb +9 -1
  45. data/lib/expressir/express/checker.rb +779 -2
  46. data/lib/expressir/express/dialect.rb +44 -0
  47. data/lib/expressir/express/formatters/expressions_formatter.rb +4 -1
  48. data/lib/expressir/express/parser.rb +9 -4
  49. data/lib/expressir/express.rb +1 -0
  50. data/lib/expressir/interop/json_schema.rb +195 -0
  51. data/lib/expressir/interop/owl.rb +216 -0
  52. data/lib/expressir/interop.rb +13 -0
  53. data/lib/expressir/lsp/document.rb +226 -0
  54. data/lib/expressir/lsp/server.rb +184 -0
  55. data/lib/expressir/lsp/workspace.rb +402 -0
  56. data/lib/expressir/lsp.rb +11 -0
  57. data/lib/expressir/mapping/refpath.rb +65 -4
  58. data/lib/expressir/reference/grammar_crosswalk.rb +122 -0
  59. data/lib/expressir/reference/grammar_crosswalk.yml +800 -0
  60. data/lib/expressir/reference/note_catalog.rb +54 -0
  61. data/lib/expressir/reference.rb +10 -0
  62. data/lib/expressir/version.rb +1 -1
  63. data/lib/expressir.rb +4 -0
  64. data/lib/tasks/conformance.rake +27 -0
  65. data/lib/tasks/reference.rake +25 -0
  66. metadata +50 -4
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Express
5
+ # Edition awareness for ISO 10303-11. The grammar accepts the union
6
+ # of both editions' text syntax; edition gating is reported by the
7
+ # Checker so a 1994 consumer sees exactly which declarations use
8
+ # 2004-only constructs, and a 2004 consumer sees the edition-1
9
+ # syntax the 2004 edition deprecates (Annex K).
10
+ module Dialect
11
+ EDITION_1994 = :"10303-11:1994"
12
+ EDITION_2004 = :"10303-11:2004"
13
+ DIALECTS = [EDITION_1994, EDITION_2004].freeze
14
+ DEFAULT = EDITION_2004
15
+
16
+ # The audited change list lives in
17
+ # spec/fixtures/dialects/iso-10303-11-2004-changes.yaml; every
18
+ # construct there maps to a note id below.
19
+ NOTE_IDS = %i[
20
+ check_dialect_2004_only
21
+ check_dialect_deprecated
22
+ check_dialect_mixed
23
+ ].freeze
24
+
25
+ module_function
26
+
27
+ def normalize(value)
28
+ return DEFAULT if value.nil?
29
+
30
+ candidate = value.to_s
31
+ dialect = DIALECTS.find do |entry|
32
+ entry.to_s == candidate || entry.to_s.end_with?(":#{candidate}")
33
+ end
34
+ return dialect if dialect
35
+
36
+ raise Expressir::InvalidOptionError.new(
37
+ "dialect",
38
+ value,
39
+ valid_options: DIALECTS.map(&:to_s),
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
@@ -154,7 +154,10 @@ module Expressir
154
154
 
155
155
  def format_expressions_unary_expression(node)
156
156
  needs_parens = node.operand.is_a?(Model::Expressions::BinaryExpression) ||
157
- node.operand.is_a?(Model::Expressions::Interval)
157
+ node.operand.is_a?(Model::Expressions::Interval) ||
158
+ # Nested unary must stay parenthesized: "- -x" is not a
159
+ # simple factor, and "--x" opens a comment.
160
+ node.operand.is_a?(Model::Expressions::UnaryExpression)
158
161
 
159
162
  [
160
163
  case node.operator
@@ -56,8 +56,11 @@ module Expressir
56
56
  # @raise [Error::SchemaParseFailure] if the schema file fails to parse
57
57
  def self.from_file(file, skip_references: nil, include_source: nil,
58
58
  root_path: nil, use_native: nil,
59
- use_core: nil) # rubocop:disable Metrics/AbcSize
60
- use_core = Core::NATIVE_AVAILABLE if use_core.nil?
59
+ use_core: nil, dialect: nil) # rubocop:disable Metrics/AbcSize
60
+ Dialect.normalize(dialect)
61
+ # use_native: false pins the Ruby grammar path; the core path
62
+ # must not silently take over that request.
63
+ use_core = Core::NATIVE_AVAILABLE if use_core.nil? && use_native != false
61
64
  if use_core && Core::NATIVE_AVAILABLE
62
65
  return from_file_core(file, skip_references: skip_references,
63
66
  include_source: include_source,
@@ -115,7 +118,8 @@ module Expressir
115
118
  # @return [Model::Repository] Repository containing all parsed ExpFiles
116
119
  def self.from_files(files, skip_references: nil, include_source: nil,
117
120
  root_path: nil, use_native: nil, max_processes: nil,
118
- compiled_set: nil, &progress)
121
+ compiled_set: nil, dialect: nil, &progress)
122
+ Dialect.normalize(dialect)
119
123
  set_path = compiled_set || ENV.fetch("EXPRESSIR_COMPILED_SET", nil)
120
124
  if set_path && core_set_available?
121
125
  repo = from_compiled_set(set_path, files,
@@ -457,7 +461,8 @@ include_source: nil, root_path: nil, use_native: nil, &block)
457
461
  # @return [Model::ExpFile] Parsed ExpFile
458
462
  # @raise [Error::SchemaParseFailure] if the content fails to parse
459
463
  def self.from_exp(content, skip_references: nil, include_source: nil,
460
- use_native: nil, use_streaming: false)
464
+ use_native: nil, use_streaming: false, dialect: nil)
465
+ Dialect.normalize(dialect)
461
466
  content = strip_bom(content)
462
467
  if use_streaming
463
468
  raise Error::StreamingUnsupportedError
@@ -15,6 +15,7 @@ module Expressir
15
15
  autoload :Checker, "#{__dir__}/express/checker"
16
16
  autoload :Concatenator, "#{__dir__}/express/concatenator"
17
17
  autoload :Core, "#{__dir__}/express/core"
18
+ autoload :Dialect, "#{__dir__}/express/dialect"
18
19
  autoload :RemarkOverlay, "#{__dir__}/express/remark_overlay"
19
20
  autoload :ModelTraversal, "#{__dir__}/express/model_traversal"
20
21
  autoload :RefsOverlay, "#{__dir__}/express/refs_overlay"
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Interop
5
+ # JSON Schema 2020-12 rendering of an EXPRESS schema. Decisions
6
+ # (docs/interop/json-schema.md): entities become object definitions
7
+ # composed by allOf over their subtype_of supertypes; explicit
8
+ # attributes are properties in declaration order; OPTIONAL
9
+ # attributes leave `required` and admit null; aggregates become
10
+ # arrays with bounds; SELECT becomes anyOf; ENUMERATION becomes a
11
+ # string enum; derived and inverse attributes are not exported
12
+ # (they are not part of the Part 21 exchange structure).
13
+ class JsonSchema
14
+ DRAFT = "https://json-schema.org/draft/2020-12/schema"
15
+ BASE_IRI = "https://expressir.org/json-schema"
16
+
17
+ def self.export(schema)
18
+ new(schema).export
19
+ end
20
+
21
+ attr_reader :schema
22
+
23
+ def initialize(schema)
24
+ @schema = schema
25
+ end
26
+
27
+ def export
28
+ result = {
29
+ "$schema" => DRAFT,
30
+ "$id" => "#{BASE_IRI}/#{schema.id}",
31
+ "title" => schema.id.to_s,
32
+ "$defs" => definitions,
33
+ }
34
+ entities = Array(schema.entities)
35
+ result["anyOf"] = entities.map { |entity| reference(entity.id) } unless entities.empty?
36
+ result
37
+ end
38
+
39
+ def to_json(pretty: true)
40
+ pretty ? JSON.pretty_generate(export) : JSON.generate(export)
41
+ end
42
+
43
+ private
44
+
45
+ def definitions
46
+ result = {}
47
+ Array(schema.types).each do |type|
48
+ result[type.id] = defined_type_schema(type)
49
+ end
50
+ Array(schema.entities).each do |entity|
51
+ result[entity.id] = entity_schema(entity)
52
+ end
53
+ result
54
+ end
55
+
56
+ def defined_type_schema(type)
57
+ case type.underlying_type
58
+ when Model::DataTypes::Enumeration
59
+ enumeration_schema(type.underlying_type)
60
+ when Model::DataTypes::Select
61
+ select_schema(type.underlying_type)
62
+ else
63
+ type_schema(type.underlying_type)
64
+ end
65
+ end
66
+
67
+ def entity_schema(entity)
68
+ own = {
69
+ "type" => "object",
70
+ "properties" => explicit_attributes(entity).to_h do |attribute|
71
+ [attribute.id, attribute_schema(attribute)]
72
+ end,
73
+ }
74
+ required = explicit_attributes(entity).reject(&:optional)
75
+ .map(&:id)
76
+ own["required"] = required unless required.empty?
77
+
78
+ supertypes = Array(entity.subtype_of).map do |supertype|
79
+ reference(supertype.id)
80
+ end
81
+ return own.merge("title" => entity.id) if supertypes.empty?
82
+
83
+ { "title" => entity.id, "allOf" => [*supertypes, own] }
84
+ end
85
+
86
+ def explicit_attributes(entity)
87
+ Array(entity.attributes)
88
+ .select { |attribute| attribute.instance_of?(Model::Declarations::Attribute) }
89
+ end
90
+
91
+ def attribute_schema(attribute)
92
+ base = type_schema(attribute.type)
93
+ return base unless attribute.optional
94
+
95
+ { "anyOf" => [base, { "type" => "null" }] }
96
+ end
97
+
98
+ def type_schema(type)
99
+ case type
100
+ when Model::DataTypes::Integer then { "type" => "integer" }
101
+ when Model::DataTypes::Real, Model::DataTypes::Number
102
+ { "type" => "number" }
103
+ when Model::DataTypes::String then { "type" => "string" }
104
+ when Model::DataTypes::Boolean then { "type" => "boolean" }
105
+ when Model::DataTypes::Logical
106
+ { "enum" => [true, false, nil] }
107
+ when Model::DataTypes::Binary
108
+ { "type" => "string", "pattern" => "\\A[01]*\\z" }
109
+ when Model::DataTypes::Enumeration
110
+ enumeration_schema(type)
111
+ when Model::DataTypes::Select
112
+ select_schema(type)
113
+ when Model::DataTypes::List, Model::DataTypes::Array,
114
+ Model::DataTypes::Set, Model::DataTypes::Bag
115
+ aggregate_schema(type)
116
+ when Model::References::SimpleReference then reference(type.id)
117
+ when nil then {}
118
+ else { "type" => "object" }
119
+ end
120
+ end
121
+
122
+ def enumeration_schema(enumeration)
123
+ { "type" => "string",
124
+ "enum" => enumeration_values(enumeration).map(&:to_s) }
125
+ end
126
+
127
+ def enumeration_values(enumeration, seen = [])
128
+ return [] if enumeration.nil? || seen.include?(enumeration.object_id)
129
+
130
+ seen += [enumeration.object_id]
131
+ values = []
132
+ base_type = resolve_reference(enumeration.based_on)
133
+ if base_type&.underlying_type.is_a?(Model::DataTypes::Enumeration)
134
+ values.concat(enumeration_values(base_type.underlying_type, seen))
135
+ end
136
+ values.concat(Array(enumeration.items).filter_map(&:id))
137
+ values.uniq
138
+ end
139
+
140
+ def select_schema(select)
141
+ { "anyOf" => select_schemas(select) }
142
+ end
143
+
144
+ def select_schemas(select, seen = [])
145
+ return [] if select.nil? || seen.include?(select.object_id)
146
+
147
+ seen += [select.object_id]
148
+ refs = []
149
+ base_type = resolve_reference(select.based_on)
150
+ if base_type&.underlying_type.is_a?(Model::DataTypes::Select)
151
+ refs.concat(select_schemas(base_type.underlying_type, seen))
152
+ end
153
+ refs.concat(Array(select.items).map { |item| type_schema(item) })
154
+ refs.uniq
155
+ end
156
+
157
+ def aggregate_schema(type)
158
+ result = { "type" => "array",
159
+ "items" => type_schema(type.base_type) }
160
+ lower, upper = aggregate_bounds(type)
161
+ result["minItems"] = lower if lower
162
+ result["maxItems"] = upper if upper
163
+ result["uniqueItems"] = true if unique?(type)
164
+ result
165
+ end
166
+
167
+ def aggregate_bounds(type)
168
+ [bound_value(type.bound1), bound_value(type.bound2)]
169
+ end
170
+
171
+ def bound_value(bound)
172
+ return nil unless bound.is_a?(Model::Literals::Integer)
173
+
174
+ bound.value.to_i
175
+ end
176
+
177
+ def unique?(type)
178
+ type.is_a?(Model::DataTypes::Set) ||
179
+ (type.is_a?(Model::DataTypes::List) && type.unique)
180
+ end
181
+
182
+ def resolve_reference(reference)
183
+ return nil unless reference.respond_to?(:id) && reference.id
184
+
185
+ schema.find(reference.id)
186
+ rescue StandardError
187
+ nil
188
+ end
189
+
190
+ def reference(id)
191
+ { "$ref" => "#/$defs/#{id}" }
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Interop
5
+ # OWL 2 / RDF Turtle rendering of an EXPRESS schema, in the ifcOWL
6
+ # lineage generalized to arbitrary EXPRESS (docs/interop/owl-rdf.md).
7
+ # Entities become owl:Class with rdfs:subClassOf per supertype;
8
+ # explicit attributes become properties with rdfs:domain/rdfs:range;
9
+ # SELECT becomes an owl:unionOf equivalent class; ENUMERATION items
10
+ # become individuals of the enumeration class. WHERE rules are not
11
+ # exported (documented scope limit).
12
+ class Owl
13
+ PREFIXES = {
14
+ "owl" => "http://www.w3.org/2002/07/owl#",
15
+ "rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
16
+ "rdfs" => "http://www.w3.org/2000/01/rdf-schema#",
17
+ "xsd" => "http://www.w3.org/2001/XMLSchema#",
18
+ }.freeze
19
+
20
+ PRIMITIVE_RANGES = {
21
+ Model::DataTypes::Integer => "xsd:integer",
22
+ Model::DataTypes::Real => "xsd:double",
23
+ Model::DataTypes::Number => "xsd:double",
24
+ Model::DataTypes::String => "xsd:string",
25
+ Model::DataTypes::Boolean => "xsd:boolean",
26
+ Model::DataTypes::Logical => "xsd:boolean",
27
+ Model::DataTypes::Binary => "xsd:hexBinary",
28
+ }.freeze
29
+
30
+ def self.export(schema, base_iri: nil)
31
+ new(schema, base_iri: base_iri).export
32
+ end
33
+
34
+ attr_reader :schema, :base_iri
35
+
36
+ def initialize(schema, base_iri: nil)
37
+ @schema = schema
38
+ @base_iri = base_iri || "https://expressir.org/owl/#{schema.id}#"
39
+ end
40
+
41
+ def export
42
+ blocks = [ontology_block]
43
+ Array(schema.types).each { |type| blocks.concat(type_blocks(type)) }
44
+ Array(schema.entities).each { |entity| blocks.concat(entity_blocks(entity)) }
45
+ ([prefix_block, ""] + blocks).join("\n")
46
+ end
47
+
48
+ private
49
+
50
+ def prefix_block
51
+ prefixes = { "ex" => base_iri }.merge(PREFIXES)
52
+ prefixes.map { |name, iri| "@prefix #{name}: <#{iri}> ." }.join("\n")
53
+ end
54
+
55
+ def ontology_block
56
+ block(["a owl:Ontology", "rdfs:label #{quote(schema.id.to_s)}"],
57
+ subject: "<#{base_iri}>")
58
+ end
59
+
60
+ def entity_blocks(entity)
61
+ lines = ["a owl:Class", "rdfs:label #{quote(entity.id.to_s)}"]
62
+ supertypes = Array(entity.subtype_of)
63
+ if supertypes.any?
64
+ parents = supertypes.map { |parent| "ex:#{parent.id}" }
65
+ lines << "rdfs:subClassOf #{parents.join(', ')}"
66
+ end
67
+ lines << "rdfs:comment #{quote('EXPRESS ABSTRACT SUPERTYPE')}" if entity.abstract
68
+ [block(lines, subject: "ex:#{entity.id}")] + attribute_blocks(entity)
69
+ end
70
+
71
+ def attribute_blocks(entity)
72
+ explicit_attributes(entity).map do |attribute|
73
+ range = range_for(attribute.type)
74
+ lines = ["a #{property_kind(attribute.type)}",
75
+ "rdfs:domain ex:#{entity.id}",
76
+ "rdfs:range #{range}"]
77
+ lines << "rdfs:comment #{quote('OPTIONAL')}" if attribute.optional
78
+ block(lines, subject: "ex:#{attribute.id}")
79
+ end
80
+ end
81
+
82
+ def explicit_attributes(entity)
83
+ Array(entity.attributes)
84
+ .select { |attribute| attribute.instance_of?(Model::Declarations::Attribute) }
85
+ end
86
+
87
+ def type_blocks(type)
88
+ underlying = type.underlying_type
89
+ case underlying
90
+ when Model::DataTypes::Enumeration
91
+ enumeration_blocks(type.id, underlying)
92
+ when Model::DataTypes::Select
93
+ [select_block(type.id, underlying)]
94
+ else
95
+ [defined_type_block(type.id, underlying)]
96
+ end
97
+ end
98
+
99
+ def enumeration_blocks(name, enumeration)
100
+ values = enumeration_values(enumeration)
101
+ blocks = [block(["a owl:Class", "rdfs:label #{quote(name.to_s)}"],
102
+ subject: "ex:#{name}")]
103
+ values.each do |value|
104
+ blocks << "ex:#{value} a owl:NamedIndividual, ex:#{name} ."
105
+ end
106
+ blocks
107
+ end
108
+
109
+ def enumeration_values(enumeration, seen = [])
110
+ return [] if enumeration.nil? || seen.include?(enumeration.object_id)
111
+
112
+ seen += [enumeration.object_id]
113
+ values = []
114
+ base_type = resolve_reference(enumeration.based_on)
115
+ if base_type&.underlying_type.is_a?(Model::DataTypes::Enumeration)
116
+ values.concat(enumeration_values(base_type.underlying_type, seen))
117
+ end
118
+ values.concat(Array(enumeration.items).filter_map(&:id))
119
+ values.uniq
120
+ end
121
+
122
+ def select_block(name, select)
123
+ items = select_items(select).map { |item| "ex:#{item}" }
124
+ block(
125
+ ["a owl:Class",
126
+ "rdfs:label #{quote(name.to_s)}",
127
+ "owl:equivalentClass [ a owl:Class ; owl:unionOf ( #{items.join(' ')} ) ]"],
128
+ subject: "ex:#{name}",
129
+ )
130
+ end
131
+
132
+ def select_items(select, seen = [])
133
+ return [] if select.nil? || seen.include?(select.object_id)
134
+
135
+ seen += [select.object_id]
136
+ names = []
137
+ base_type = resolve_reference(select.based_on)
138
+ if base_type&.underlying_type.is_a?(Model::DataTypes::Select)
139
+ names.concat(select_items(base_type.underlying_type, seen))
140
+ end
141
+ names.concat(Array(select.items).filter_map(&:id))
142
+ names.uniq
143
+ end
144
+
145
+ def defined_type_block(name, underlying)
146
+ primitive = PRIMITIVE_RANGES[underlying.class]
147
+ lines = ["a #{primitive ? 'rdfs:Datatype' : 'owl:Class'}",
148
+ "rdfs:label #{quote(name.to_s)}"]
149
+ lines << if primitive
150
+ "owl:equivalentClass #{primitive}"
151
+ elsif underlying.is_a?(Model::References::SimpleReference)
152
+ "rdfs:subClassOf ex:#{underlying.id}"
153
+ else
154
+ "rdfs:subClassOf rdf:List"
155
+ end
156
+ block(lines, subject: "ex:#{name}")
157
+ end
158
+
159
+ def property_kind(type)
160
+ object_property?(type) ? "owl:ObjectProperty" : "owl:DatatypeProperty"
161
+ end
162
+
163
+ def object_property?(type, seen = [])
164
+ case type
165
+ when Model::DataTypes::List, Model::DataTypes::Array,
166
+ Model::DataTypes::Set, Model::DataTypes::Bag,
167
+ Model::DataTypes::Select, Model::DataTypes::Enumeration
168
+ true
169
+ when Model::References::SimpleReference
170
+ return false if seen.include?(type.id)
171
+
172
+ target = resolve_reference(type)
173
+ case target
174
+ when Model::Declarations::Entity then true
175
+ when Model::Declarations::Type
176
+ object_property?(target.underlying_type, seen + [type.id])
177
+ else false
178
+ end
179
+ else false
180
+ end
181
+ end
182
+
183
+ def range_for(type)
184
+ case type
185
+ when Model::DataTypes::List, Model::DataTypes::Array,
186
+ Model::DataTypes::Set, Model::DataTypes::Bag
187
+ "rdf:List"
188
+ when Model::DataTypes::Enumeration, Model::DataTypes::Select
189
+ "rdfs:Resource"
190
+ when Model::References::SimpleReference
191
+ "ex:#{type.id}"
192
+ else
193
+ PRIMITIVE_RANGES[type.class] || "rdfs:Resource"
194
+ end
195
+ end
196
+
197
+ def resolve_reference(reference)
198
+ return nil unless reference.respond_to?(:id) && reference.id
199
+
200
+ schema.find(reference.id)
201
+ rescue StandardError
202
+ nil
203
+ end
204
+
205
+ def block(predicates, subject: nil)
206
+ head = subject ? "#{subject} #{predicates.first}" : predicates.first
207
+ rest = predicates.drop(1).map { |line| " #{line}" }
208
+ "#{([head] + rest).join(" ;\n")} ."
209
+ end
210
+
211
+ def quote(text)
212
+ JSON.generate(text)
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Expressir
6
+ # Mappings from EXPRESS to other modeling notations. These are
7
+ # contributions, not ISO mappings; every decision is documented under
8
+ # docs/interop/.
9
+ module Interop
10
+ autoload :JsonSchema, "#{__dir__}/interop/json_schema"
11
+ autoload :Owl, "#{__dir__}/interop/owl"
12
+ end
13
+ end