expressir 2.4.31-arm-linux → 2.4.36-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.eengine/00-overview.md +39 -0
- data/TODO.eengine/01-resolution-diagnostics.md +30 -0
- data/TODO.eengine/02-check-catalog-completion.md +53 -0
- data/TODO.eengine/03-entity-computations.md +22 -0
- data/TODO.eengine/04-shtolo-rule-audit.md +32 -0
- data/TODO.eengine/05-shtolo-longform-flatten.md +12 -0
- data/TODO.eengine/06-population-p21.md +34 -0
- data/TODO.eengine/07-schema-compare-patch.md +25 -0
- data/TODO.eengine/08-official-corpus-gate.md +7 -0
- data/TODO.eengine/09-mim-mapping-parser.md +9 -0
- data/TODO.parity-ee/00-overview.md +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/TODO.suma-improvements/07-part28-xml.md +10 -13
- 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 +25 -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/smrl_xml_index.rb +25 -0
- data/lib/expressir/commands/validate.rb +3 -0
- data/lib/expressir/commands.rb +3 -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/builder.rb +17 -5
- data/lib/expressir/express/builders/entity_decl_builder.rb +17 -5
- data/lib/expressir/express/builders/expression_builder.rb +9 -1
- data/lib/expressir/express/checker.rb +970 -5
- data/lib/expressir/express/dialect.rb +44 -0
- data/lib/expressir/express/formatters/declarations_formatter.rb +21 -14
- data/lib/expressir/express/formatters/expressions_formatter.rb +4 -1
- data/lib/expressir/express/parser.rb +9 -4
- data/lib/expressir/express/pretty_formatter.rb +9 -14
- data/lib/expressir/express/schema_compare.rb +194 -0
- data/lib/expressir/express/shtolo.rb +194 -63
- data/lib/expressir/express.rb +3 -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/model/declarations/entity.rb +132 -0
- data/lib/expressir/model/declarations/schema.rb +37 -6
- data/lib/expressir/p21/builder.rb +92 -0
- data/lib/expressir/p21/grammar.rb +107 -0
- data/lib/expressir/p21/parser.rb +16 -0
- data/lib/expressir/p21/population.rb +35 -0
- data/lib/expressir/p21/validator.rb +213 -0
- data/lib/expressir/p21.rb +17 -0
- 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 +5 -0
- data/lib/tasks/conformance.rake +27 -0
- data/lib/tasks/reference.rake +25 -0
- metadata +68 -4
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Lsp
|
|
5
|
+
# The cross-document view used by the server: open documents, schema
|
|
6
|
+
# resolution through USE/REFERENCE interfaces, diagnostics, navigation
|
|
7
|
+
# and rename computation.
|
|
8
|
+
class Workspace
|
|
9
|
+
SEVERITIES = { error: 1, warning: 2, info: 3, hint: 4 }.freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :documents
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@documents = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def open(uri:, text:, path: nil)
|
|
18
|
+
@documents[uri] = Document.new(uri: uri, text: text, path: path)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def change(uri:, text:)
|
|
22
|
+
document = @documents[uri]
|
|
23
|
+
return document.update(text) if document
|
|
24
|
+
|
|
25
|
+
self.open(uri: uri, text: text)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def close(uri)
|
|
29
|
+
@documents.delete(uri)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def document(uri)
|
|
33
|
+
@documents[uri]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def symbols(uri)
|
|
37
|
+
document(uri)&.symbols || []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def diagnostics(uri)
|
|
41
|
+
document = @documents[uri]
|
|
42
|
+
return [] unless document
|
|
43
|
+
|
|
44
|
+
return [parse_diagnostic(document)] if document.parse_error
|
|
45
|
+
|
|
46
|
+
checker_notes.filter_map { |note| diagnostic_for(document, note) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def definition(uri, position)
|
|
50
|
+
target = target_at(uri, position)
|
|
51
|
+
return nil unless target
|
|
52
|
+
|
|
53
|
+
location(target[:doc], target[:node])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def references(uri, position, include_declaration: true)
|
|
57
|
+
target = target_at(uri, position)
|
|
58
|
+
return [] unless target
|
|
59
|
+
|
|
60
|
+
declaration = target[:node]
|
|
61
|
+
results = reference_locations(declaration)
|
|
62
|
+
results << location(target[:doc], declaration) if include_declaration
|
|
63
|
+
dedupe_locations(results)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def hover(uri, position)
|
|
67
|
+
target = target_at(uri, position)
|
|
68
|
+
return nil unless target
|
|
69
|
+
|
|
70
|
+
value = Express::PrettyFormatter.new.format(target[:node])
|
|
71
|
+
{
|
|
72
|
+
"contents" => {
|
|
73
|
+
"kind" => "markdown",
|
|
74
|
+
"value" => "```express\n#{value}\n```",
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
rescue StandardError
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def prepare_rename(uri, position)
|
|
82
|
+
target = target_at(uri, position)
|
|
83
|
+
return nil unless target
|
|
84
|
+
|
|
85
|
+
document = target[:doc]
|
|
86
|
+
node = target[:node]
|
|
87
|
+
{ "range" => document.range_for(node),
|
|
88
|
+
"placeholder" => node.id.to_s }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the URI→edits map for a rename, after applying the edits
|
|
92
|
+
# in memory and verifying that every affected document still parses.
|
|
93
|
+
def rename(uri, position, new_name)
|
|
94
|
+
target = target_at(uri, position)
|
|
95
|
+
return nil unless target
|
|
96
|
+
|
|
97
|
+
edits = rename_edits(target[:doc], target[:node], new_name)
|
|
98
|
+
verify_rename!(edits, target[:node], new_name)
|
|
99
|
+
{ "changes" => edits }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def checker_notes
|
|
105
|
+
key = @documents.each_value.map { |doc| doc.text.hash }
|
|
106
|
+
return @checker_notes if key == @checker_notes_key
|
|
107
|
+
|
|
108
|
+
files = @documents.each_value.filter_map(&:file)
|
|
109
|
+
notes = if files.empty?
|
|
110
|
+
[]
|
|
111
|
+
else
|
|
112
|
+
repository = Model::Repository.new(files: files)
|
|
113
|
+
Express::Checker.new(repository).check.notes
|
|
114
|
+
end
|
|
115
|
+
@checker_notes = notes
|
|
116
|
+
@checker_notes_key = key
|
|
117
|
+
notes
|
|
118
|
+
rescue StandardError
|
|
119
|
+
[]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def diagnostic_for(document, note)
|
|
123
|
+
schema = document.schemas.find do |candidate|
|
|
124
|
+
candidate.id.to_s.casecmp?(note.schema.to_s)
|
|
125
|
+
end
|
|
126
|
+
return nil unless schema
|
|
127
|
+
return nil if resolved_note?(document, note)
|
|
128
|
+
|
|
129
|
+
node = note.node
|
|
130
|
+
range = if node&.source_offset
|
|
131
|
+
document.range(node.source_offset,
|
|
132
|
+
node.respond_to?(:id) && node.id ? node.id.to_s.bytesize : 1)
|
|
133
|
+
else
|
|
134
|
+
document.range_for(schema)
|
|
135
|
+
end
|
|
136
|
+
{
|
|
137
|
+
"range" => range,
|
|
138
|
+
"severity" => SEVERITIES.fetch(note.severity, 3),
|
|
139
|
+
"code" => note.id,
|
|
140
|
+
"source" => "expressir",
|
|
141
|
+
"message" => note.message.to_s,
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The checker sees one file at a time; an open document that supplies
|
|
146
|
+
# the referenced schema or interfaced declaration is not unresolved.
|
|
147
|
+
def resolved_note?(document, note)
|
|
148
|
+
return false unless note.id == :check_unresolved_ref
|
|
149
|
+
return false unless note.node.is_a?(Model::References::SimpleReference)
|
|
150
|
+
|
|
151
|
+
!resolve_reference(document, note.node).nil? ||
|
|
152
|
+
!schema_by_id(note.node.id).nil?
|
|
153
|
+
rescue StandardError
|
|
154
|
+
false
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def parse_diagnostic(document)
|
|
158
|
+
error = document.parse_error
|
|
159
|
+
message = error.message.to_s
|
|
160
|
+
line = message[/line (\d+)/, 1].to_i
|
|
161
|
+
character = message[/char (\d+)/, 1].to_i
|
|
162
|
+
line = 1 if line.zero?
|
|
163
|
+
character = 1 if character.zero?
|
|
164
|
+
start = { "line" => line - 1, "character" => character - 1 }
|
|
165
|
+
{
|
|
166
|
+
"range" => { "start" => start,
|
|
167
|
+
"end" => start.merge("character" => start["character"] + 1) },
|
|
168
|
+
"severity" => 1,
|
|
169
|
+
"source" => "expressir",
|
|
170
|
+
"message" => message.lines.first.to_s.strip,
|
|
171
|
+
}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def target_at(uri, position)
|
|
175
|
+
document = @documents[uri]
|
|
176
|
+
return nil unless document&.file
|
|
177
|
+
|
|
178
|
+
token = document.token_at(position)
|
|
179
|
+
return nil unless token
|
|
180
|
+
|
|
181
|
+
declaration = document.declaration_nodes.find do |node|
|
|
182
|
+
document.name_offset(node) == token.start_byte &&
|
|
183
|
+
node.id.to_s.casecmp?(token.text)
|
|
184
|
+
end
|
|
185
|
+
return { doc: document, node: declaration, token: token } if declaration
|
|
186
|
+
|
|
187
|
+
reference = document.reference_nodes.find do |node|
|
|
188
|
+
node.source_offset == token.start_byte
|
|
189
|
+
end
|
|
190
|
+
return nil unless reference
|
|
191
|
+
|
|
192
|
+
resolved = resolve_reference(document, reference)
|
|
193
|
+
resolved && { doc: resolved[:doc], node: resolved[:node], token: token }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def resolve_reference(document, node)
|
|
197
|
+
schema = schema_of(node)
|
|
198
|
+
return nil unless schema
|
|
199
|
+
|
|
200
|
+
local = node.find(node.id)
|
|
201
|
+
if local
|
|
202
|
+
target_doc = document_for_node(local) || document
|
|
203
|
+
return { doc: target_doc, node: local }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
resolve_interface(schema, node.id)
|
|
207
|
+
rescue StandardError
|
|
208
|
+
nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def resolve_interface(schema, name)
|
|
212
|
+
Array(schema.interfaces).each do |interface|
|
|
213
|
+
interface_schema = schema_by_id(interface.schema&.id)
|
|
214
|
+
next unless interface_schema
|
|
215
|
+
|
|
216
|
+
items = Array(interface.items)
|
|
217
|
+
if items.empty?
|
|
218
|
+
node = interface_schema[:schema].find(name)
|
|
219
|
+
return { doc: interface_schema[:doc], node: node } if node
|
|
220
|
+
else
|
|
221
|
+
items.each do |item|
|
|
222
|
+
local_name = item.id || item.ref&.id
|
|
223
|
+
next unless local_name.to_s.casecmp?(name.to_s)
|
|
224
|
+
|
|
225
|
+
node = interface_schema[:schema].find(item.ref&.id)
|
|
226
|
+
return { doc: interface_schema[:doc], node: node } if node
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def schema_by_id(id)
|
|
234
|
+
return nil unless id
|
|
235
|
+
|
|
236
|
+
@documents.each_value do |document|
|
|
237
|
+
document.schemas.each do |schema|
|
|
238
|
+
next unless schema.id.to_s.casecmp?(id.to_s)
|
|
239
|
+
|
|
240
|
+
return { doc: document, schema: schema }
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
nil
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def document_for_node(node)
|
|
247
|
+
schema = schema_of(node)
|
|
248
|
+
return nil unless schema
|
|
249
|
+
|
|
250
|
+
@documents.each_value do |document|
|
|
251
|
+
return document if document.schemas.any? { |candidate| candidate.equal?(schema) }
|
|
252
|
+
end
|
|
253
|
+
nil
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def schema_of(node)
|
|
257
|
+
current = node
|
|
258
|
+
current = current.parent while current && !current.is_a?(Model::Declarations::Schema)
|
|
259
|
+
current
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def location(document, node)
|
|
263
|
+
{ "uri" => document.uri, "range" => document.range_for(node) }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def reference_locations(declaration)
|
|
267
|
+
declaring_schema = schema_of(declaration)
|
|
268
|
+
@documents.each_value.flat_map do |document|
|
|
269
|
+
document.reference_nodes.filter_map do |reference|
|
|
270
|
+
resolved = resolve_reference(document, reference)
|
|
271
|
+
next unless resolved && same_declaration?(resolved[:node], declaration)
|
|
272
|
+
|
|
273
|
+
location(document, reference)
|
|
274
|
+
end + interface_locations(document, declaring_schema, declaration)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def interface_locations(document, declaring_schema, declaration)
|
|
279
|
+
document.schemas.flat_map do |schema|
|
|
280
|
+
Array(schema.interfaces).flat_map do |interface|
|
|
281
|
+
next [] unless interface.schema&.id.to_s.casecmp?(declaring_schema.id.to_s)
|
|
282
|
+
|
|
283
|
+
Array(interface.items).filter_map do |item|
|
|
284
|
+
next unless item.ref&.id.to_s.casecmp?(declaration.id.to_s)
|
|
285
|
+
|
|
286
|
+
location(document, item.ref)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def same_declaration?(left, right)
|
|
293
|
+
left.path.to_s.casecmp?(right.path.to_s)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def rename_edits(declaring_document, declaration, new_name)
|
|
297
|
+
edits = {}
|
|
298
|
+
add_edit(edits, declaring_document, declaration, new_name)
|
|
299
|
+
|
|
300
|
+
@documents.each_value do |document|
|
|
301
|
+
document.schemas.each do |schema|
|
|
302
|
+
rename_references(document, schema, declaration, new_name, edits)
|
|
303
|
+
rename_interface_items(document, schema, declaration, new_name, edits)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
edits
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def rename_references(document, schema, declaration, new_name, edits)
|
|
310
|
+
document.reference_nodes.each do |reference|
|
|
311
|
+
next unless schema_of(reference).equal?(schema)
|
|
312
|
+
next unless reference.id.to_s.casecmp?(declaration.id.to_s)
|
|
313
|
+
|
|
314
|
+
resolved = resolve_reference(document, reference)
|
|
315
|
+
next unless resolved && same_declaration?(resolved[:node], declaration)
|
|
316
|
+
|
|
317
|
+
add_edit(edits, document, reference, new_name)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def rename_interface_items(document, schema, declaration, new_name, edits)
|
|
322
|
+
Array(schema.interfaces).each do |interface|
|
|
323
|
+
next unless interface.schema&.id.to_s.casecmp?(schema_of(declaration).id.to_s)
|
|
324
|
+
|
|
325
|
+
Array(interface.items).each do |item|
|
|
326
|
+
next unless item.ref&.id.to_s.casecmp?(declaration.id.to_s)
|
|
327
|
+
|
|
328
|
+
add_edit(edits, document, item.ref, new_name)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def add_edit(edits, document, node, new_name)
|
|
334
|
+
range = document.range_for(node)
|
|
335
|
+
list = (edits[document.uri] ||= [])
|
|
336
|
+
return if list.any? { |edit| edit["range"] == range }
|
|
337
|
+
|
|
338
|
+
list << { "range" => range, "newText" => new_name }
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def verify_rename!(edits, declaration, new_name)
|
|
342
|
+
declaring_schema = schema_of(declaration)
|
|
343
|
+
edits.each do |uri, document_edits|
|
|
344
|
+
document = @documents[uri]
|
|
345
|
+
text = apply_edits(document.text, document_edits)
|
|
346
|
+
probe = Document.new(uri: uri, text: text, path: document.path)
|
|
347
|
+
raise "rename produced invalid EXPRESS in #{uri}" if probe.parse_error
|
|
348
|
+
|
|
349
|
+
verify_schema_after_rename(document, probe, declaring_schema,
|
|
350
|
+
declaration, new_name, uri)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# The renamed declaration may disappear under its old path; every
|
|
355
|
+
# other removal is a structure loss and aborts the rename.
|
|
356
|
+
def verify_schema_after_rename(document, probe, declaring_schema,
|
|
357
|
+
declaration, _new_name, uri)
|
|
358
|
+
index = document.schemas.index(declaring_schema)
|
|
359
|
+
return unless index
|
|
360
|
+
|
|
361
|
+
probe_schema = probe.schemas[index]
|
|
362
|
+
return unless probe_schema
|
|
363
|
+
|
|
364
|
+
old_path = "#{declaring_schema.id}.#{declaration.id}".downcase
|
|
365
|
+
unexpected = Express::SchemaCompare
|
|
366
|
+
.compare(declaring_schema, probe_schema)
|
|
367
|
+
.select do |delta|
|
|
368
|
+
delta.op == :removed && delta.path.to_s.downcase != old_path
|
|
369
|
+
end
|
|
370
|
+
raise "rename removed unrelated declarations in #{uri}" if unexpected.any?
|
|
371
|
+
|
|
372
|
+
formatted = Express::Formatter.format(probe_schema)
|
|
373
|
+
formatted_probe = Document.new(uri: uri, text: formatted,
|
|
374
|
+
path: document.path)
|
|
375
|
+
raise "renamed schema does not format in #{uri}" if formatted_probe.parse_error
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def apply_edits(text, edits)
|
|
379
|
+
scratch = Document.new(uri: "file:///nonexistent", text: text)
|
|
380
|
+
source = text.b
|
|
381
|
+
offsets = edits.map do |edit|
|
|
382
|
+
start_byte = scratch.byte_offset(edit["range"]["start"]["line"],
|
|
383
|
+
edit["range"]["start"]["character"])
|
|
384
|
+
end_byte = scratch.byte_offset(edit["range"]["end"]["line"],
|
|
385
|
+
edit["range"]["end"]["character"])
|
|
386
|
+
[start_byte, end_byte, edit["newText"].b]
|
|
387
|
+
end
|
|
388
|
+
offsets.sort_by { |start_byte, _, _| -start_byte }.each do |start_byte, end_byte, replacement|
|
|
389
|
+
source[start_byte...end_byte] = replacement
|
|
390
|
+
end
|
|
391
|
+
source.force_encoding(Encoding::UTF_8)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def dedupe_locations(locations)
|
|
395
|
+
locations.uniq do |location|
|
|
396
|
+
[location["uri"], location["range"]["start"]["line"],
|
|
397
|
+
location["range"]["start"]["character"]]
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
# Language Server Protocol support: stdio JSON-RPC transport, open
|
|
5
|
+
# documents, and the workspace view that resolves across schemas.
|
|
6
|
+
module Lsp
|
|
7
|
+
autoload :Document, "#{__dir__}/lsp/document"
|
|
8
|
+
autoload :Workspace, "#{__dir__}/lsp/workspace"
|
|
9
|
+
autoload :Server, "#{__dir__}/lsp/server"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -56,6 +56,138 @@ module Expressir
|
|
|
56
56
|
*remark_items,
|
|
57
57
|
]
|
|
58
58
|
end
|
|
59
|
+
|
|
60
|
+
# eeng compute-inheritance: the full supertype closure, nearest
|
|
61
|
+
# supertype first, cycle-safe. Cross-schema supertypes resolve
|
|
62
|
+
# through the enclosing repository.
|
|
63
|
+
# @return [Array<Entity>]
|
|
64
|
+
def supertype_closure
|
|
65
|
+
closure = []
|
|
66
|
+
visited = { id&.safe_downcase => true }
|
|
67
|
+
queue = Array(subtype_of).map { |ref| ref.respond_to?(:id) ? ref.id : ref }
|
|
68
|
+
while (name = queue.shift)
|
|
69
|
+
key = name&.safe_downcase
|
|
70
|
+
next if key.nil? || visited.key?(key)
|
|
71
|
+
|
|
72
|
+
supertype = find_supertype_entity(key)
|
|
73
|
+
next unless supertype
|
|
74
|
+
|
|
75
|
+
visited[key] = true
|
|
76
|
+
closure << supertype
|
|
77
|
+
queue.concat(Array(supertype.subtype_of).map do |ref|
|
|
78
|
+
ref.respond_to?(:id) ? ref.id : ref
|
|
79
|
+
end)
|
|
80
|
+
end
|
|
81
|
+
closure
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Explicit attributes inherited from the supertype closure,
|
|
85
|
+
# nearest supertype first, skipping ids this entity (or a
|
|
86
|
+
# nearer ancestor) redeclares.
|
|
87
|
+
# @return [Array<ExplicitAttribute>]
|
|
88
|
+
def inherited_explicit_attributes
|
|
89
|
+
claimed = explicit_only(Array(attributes)).filter_map(&:id).map(&:safe_downcase)
|
|
90
|
+
closure = supertype_closure
|
|
91
|
+
closure.flat_map do |supertype|
|
|
92
|
+
explicit_only(Array(supertype.attributes)).filter_map do |attr|
|
|
93
|
+
next nil unless attr.id
|
|
94
|
+
|
|
95
|
+
key = attr.id.safe_downcase
|
|
96
|
+
next nil if claimed.include?(key)
|
|
97
|
+
|
|
98
|
+
claimed << key
|
|
99
|
+
attr
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Derived attributes accumulated from the supertype closure.
|
|
105
|
+
# @return [Array<DerivedAttribute>]
|
|
106
|
+
def inherited_derived_attributes
|
|
107
|
+
closure = supertype_closure
|
|
108
|
+
seen = {}
|
|
109
|
+
closure.flat_map do |supertype|
|
|
110
|
+
derived_attribute_items(supertype).filter_map do |attr|
|
|
111
|
+
next nil unless attr.id
|
|
112
|
+
|
|
113
|
+
key = attr.id.safe_downcase
|
|
114
|
+
next nil if seen.key?(key)
|
|
115
|
+
|
|
116
|
+
seen[key] = true
|
|
117
|
+
attr
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Inverse attributes accumulated from the supertype closure.
|
|
123
|
+
# @return [Array<InverseAttribute>]
|
|
124
|
+
def inherited_inverse_attributes
|
|
125
|
+
closure = supertype_closure
|
|
126
|
+
seen = {}
|
|
127
|
+
closure.flat_map do |supertype|
|
|
128
|
+
inverse_attribute_items(supertype).filter_map do |attr|
|
|
129
|
+
next nil unless attr.id
|
|
130
|
+
|
|
131
|
+
key = attr.id.safe_downcase
|
|
132
|
+
next nil if seen.key?(key)
|
|
133
|
+
|
|
134
|
+
seen[key] = true
|
|
135
|
+
attr
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# All explicit attributes: own + inherited (eeng
|
|
141
|
+
# explicit-attrs materialization).
|
|
142
|
+
# @return [Array<ExplicitAttribute>]
|
|
143
|
+
def all_explicit_attributes
|
|
144
|
+
[*explicit_only(Array(attributes)), *inherited_explicit_attributes]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# Entity#attributes carries every attribute kind; the explicit
|
|
150
|
+
# kind is Expressir::Model::Declarations::Attribute.
|
|
151
|
+
# DerivedAttribute/InverseAttribute subclass Attribute, so the
|
|
152
|
+
# explicit kind is the exact base class.
|
|
153
|
+
def explicit_only(attrs)
|
|
154
|
+
attrs.select { |a| a.instance_of?(Expressir::Model::Declarations::Attribute) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# The Ruby parse path parks every attribute kind in `attributes`
|
|
158
|
+
# (typed by class); the core path fills derived_attributes/
|
|
159
|
+
# inverse_attributes instead. Both layouts resolve to the same
|
|
160
|
+
# items here.
|
|
161
|
+
def derived_attribute_items(entity)
|
|
162
|
+
own = Array(entity.derived_attributes)
|
|
163
|
+
return own if own.any?
|
|
164
|
+
|
|
165
|
+
Array(entity.attributes).grep(Expressir::Model::Declarations::DerivedAttribute)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def inverse_attribute_items(entity)
|
|
169
|
+
own = Array(entity.inverse_attributes)
|
|
170
|
+
return own if own.any?
|
|
171
|
+
|
|
172
|
+
Array(entity.attributes).grep(Expressir::Model::Declarations::InverseAttribute)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def find_supertype_entity(key)
|
|
176
|
+
container = parent
|
|
177
|
+
container = container.parent while container &&
|
|
178
|
+
!container.is_a?(Model::Repository) &&
|
|
179
|
+
!container.is_a?(Model::ExpFile)
|
|
180
|
+
return nil unless container.respond_to?(:schemas)
|
|
181
|
+
|
|
182
|
+
container.schemas.each do |foreign|
|
|
183
|
+
next unless foreign.respond_to?(:entities)
|
|
184
|
+
|
|
185
|
+
foreign.entities.each do |candidate|
|
|
186
|
+
return candidate if candidate.id&.safe_downcase == key
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
nil
|
|
190
|
+
end
|
|
59
191
|
end
|
|
60
192
|
end
|
|
61
193
|
end
|
|
@@ -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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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)
|