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.
- checksums.yaml +4 -4
- data/.github/workflows/conformance.yml +34 -0
- data/.gitignore +3 -0
- data/Gemfile +1 -1
- data/TODO.sota/00-overview.md +57 -0
- data/TODO.sota/01-type-checking.md +115 -0
- data/TODO.sota/02-evaluator.md +86 -0
- data/TODO.sota/03-conformance.md +53 -0
- data/TODO.sota/04-lsp.md +70 -0
- data/TODO.sota/05-interop.md +55 -0
- data/TODO.sota/06-dialects.md +57 -0
- data/TODO.sota/07-reference-artifacts.md +55 -0
- data/docs/_guides/interop/index.adoc +25 -0
- data/docs/_guides/interop/json-schema.adoc +88 -0
- data/docs/_guides/interop/owl-rdf.adoc +87 -0
- data/docs/_guides/reference/checker-notes.adoc +55 -0
- data/docs/_guides/reference/checker.adoc +36 -0
- data/docs/_guides/reference/evaluator.adoc +40 -0
- data/docs/_guides/reference/grammar-crosswalk.adoc +299 -0
- data/docs/_guides/reference/index.adoc +22 -0
- data/docs/_guides/reference/lsp.adoc +41 -0
- data/docs/_guides/reference/p21.adoc +30 -0
- data/docs/_guides/reference/shtolo.adoc +36 -0
- data/exe/expressir-lsp +6 -0
- data/expressir.gemspec +2 -2
- data/lib/expressir/3.2/expressir_core.so +0 -0
- data/lib/expressir/3.3/expressir_core.so +0 -0
- data/lib/expressir/3.4/expressir_core.so +0 -0
- data/lib/expressir/4.0/expressir_core.so +0 -0
- data/lib/expressir/cli.rb +18 -0
- data/lib/expressir/commands/check.rb +3 -1
- data/lib/expressir/commands/export_json_schema.rb +29 -0
- data/lib/expressir/commands/export_owl.rb +26 -0
- data/lib/expressir/commands/validate.rb +3 -0
- data/lib/expressir/commands.rb +2 -0
- data/lib/expressir/conformance/trend.rb +100 -0
- data/lib/expressir/conformance.rb +8 -0
- data/lib/expressir/eval/environment.rb +83 -0
- data/lib/expressir/eval/expression.rb +610 -0
- data/lib/expressir/eval/interpreter.rb +316 -0
- data/lib/expressir/eval/rules.rb +88 -0
- data/lib/expressir/eval/values.rb +74 -0
- data/lib/expressir/eval.rb +17 -0
- data/lib/expressir/express/builders/expression_builder.rb +9 -1
- data/lib/expressir/express/checker.rb +779 -2
- data/lib/expressir/express/dialect.rb +44 -0
- data/lib/expressir/express/formatters/expressions_formatter.rb +4 -1
- data/lib/expressir/express/parser.rb +9 -4
- data/lib/expressir/express.rb +1 -0
- data/lib/expressir/interop/json_schema.rb +195 -0
- data/lib/expressir/interop/owl.rb +216 -0
- data/lib/expressir/interop.rb +13 -0
- data/lib/expressir/lsp/document.rb +226 -0
- data/lib/expressir/lsp/server.rb +184 -0
- data/lib/expressir/lsp/workspace.rb +402 -0
- data/lib/expressir/lsp.rb +11 -0
- data/lib/expressir/mapping/refpath.rb +65 -4
- data/lib/expressir/reference/grammar_crosswalk.rb +122 -0
- data/lib/expressir/reference/grammar_crosswalk.yml +800 -0
- data/lib/expressir/reference/note_catalog.rb +54 -0
- data/lib/expressir/reference.rb +10 -0
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +4 -0
- data/lib/tasks/conformance.rake +27 -0
- data/lib/tasks/reference.rake +25 -0
- 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
|
-
|
|
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
|
data/lib/expressir/express.rb
CHANGED
|
@@ -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
|