expressir 2.4.13 → 2.4.15
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/lib/expressir/commands/parity_inputs.rb +30 -5
- data/lib/expressir/express/checker.rb +84 -3
- data/lib/expressir/express/shtolo.rb +13 -7
- data/lib/expressir/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 714b13d808744a329002460e108fdb35f4a8fcf0d3095517cd241d411bce215e
|
|
4
|
+
data.tar.gz: 8cf66567212cc463ab351cd6e0cbb70ee63ec11ae2c0b31036cd3d0aff264384
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c13e1c4cdb0a551e86e2d557c6df01fc2a1b36e1ff536cd4afc6a2255c922d0bcb8252debdc600479514963b38b8482d41de118fb55933472ed173cca77e2aa
|
|
7
|
+
data.tar.gz: 3f4107ac13feea2a5fdc9d5659312e44af1c7288d957132a119fb1799df396f709ed5c9d82c970669ceda5e64238506abe412a4850c310b4e173f347c67cc31d
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "find"
|
|
2
|
+
require "thor"
|
|
2
3
|
|
|
3
4
|
module Expressir
|
|
4
5
|
module Commands
|
|
@@ -42,8 +43,9 @@ module Expressir
|
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
unless missing.empty?
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
raise Thor::Error,
|
|
47
|
+
"expressir: schema(s) not found: #{missing.uniq.join(', ')}" \
|
|
48
|
+
"#{resolver_hint(manifest, stepmod)}"
|
|
47
49
|
end
|
|
48
50
|
seen.values.compact.uniq
|
|
49
51
|
end
|
|
@@ -90,14 +92,37 @@ module Expressir
|
|
|
90
92
|
return manifest_index(manifest) if manifest
|
|
91
93
|
return stepmod_index(stepmod) if stepmod
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
# Default: the file's own directory plus, when the root sits
|
|
96
|
+
# inside a STEPmod checkout, its enclosing `schemas/` tree —
|
|
97
|
+
# the layout wg12-step modules live in.
|
|
98
|
+
index = same_dir_index(root_path)
|
|
99
|
+
ancestor = ancestor_schemas_dir(root_path)
|
|
100
|
+
index.update(stepmod_index(File.dirname(ancestor))) if ancestor
|
|
101
|
+
index
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Nearest ancestor directory NAMED `schemas`, or nil.
|
|
105
|
+
def ancestor_schemas_dir(root_path)
|
|
106
|
+
dir = File.dirname(File.expand_path(root_path))
|
|
107
|
+
until dir == "/"
|
|
108
|
+
return dir if File.basename(dir) == "schemas"
|
|
109
|
+
|
|
110
|
+
dir = File.dirname(dir)
|
|
111
|
+
end
|
|
112
|
+
nil
|
|
94
113
|
end
|
|
95
114
|
|
|
96
115
|
def same_dir_index(root_path)
|
|
97
116
|
dir = File.dirname(File.expand_path(root_path))
|
|
98
|
-
|
|
99
|
-
|
|
117
|
+
h = {}
|
|
118
|
+
Dir.glob(File.join(dir, "*.exp")).each do |path|
|
|
119
|
+
# index by DECLARED schema name first (a file named arm.exp
|
|
120
|
+
# may declare schema m_arm), basename as fallback
|
|
121
|
+
declared = File.read(path)[/\bSCHEMA\s+(\w+)/i, 1]
|
|
122
|
+
h[declared.downcase] = path if declared
|
|
123
|
+
h[File.basename(path, ".exp").downcase] ||= path
|
|
100
124
|
end
|
|
125
|
+
h
|
|
101
126
|
end
|
|
102
127
|
|
|
103
128
|
# Interface names declared by +source+. Remark text is stripped
|
|
@@ -97,6 +97,11 @@ module Expressir
|
|
|
97
97
|
check_where_rules(schema, t, t.where_rules)
|
|
98
98
|
end
|
|
99
99
|
schema.rules.each { |r| check_where_rules(schema, r, r.where_rules) }
|
|
100
|
+
schema.entities.each do |entity|
|
|
101
|
+
check_aggregate_bounds(schema, entity)
|
|
102
|
+
check_attrib_name_fun(schema, entity)
|
|
103
|
+
end
|
|
104
|
+
check_string_references(schema)
|
|
100
105
|
check_unresolved_refs(schema)
|
|
101
106
|
end
|
|
102
107
|
|
|
@@ -285,7 +290,7 @@ module Expressir
|
|
|
285
290
|
end
|
|
286
291
|
end
|
|
287
292
|
|
|
288
|
-
def each_reference(node, &)
|
|
293
|
+
def each_reference(node, &block)
|
|
289
294
|
case node
|
|
290
295
|
when Model::References::SimpleReference then yield node
|
|
291
296
|
when Model::ModelElement
|
|
@@ -294,8 +299,8 @@ module Expressir
|
|
|
294
299
|
|
|
295
300
|
value = node.public_send(attr)
|
|
296
301
|
case value
|
|
297
|
-
when Array then value.each { |item| each_reference(item, &) }
|
|
298
|
-
when Model::ModelElement then each_reference(value, &)
|
|
302
|
+
when Array then value.each { |item| each_reference(item, &block) }
|
|
303
|
+
when Model::ModelElement then each_reference(value, &block)
|
|
299
304
|
end
|
|
300
305
|
end
|
|
301
306
|
end
|
|
@@ -342,6 +347,82 @@ module Expressir
|
|
|
342
347
|
:type if find_type(schema, id)
|
|
343
348
|
end
|
|
344
349
|
|
|
350
|
+
# eeng check-agg-type: a two-bound aggregate whose lower bound
|
|
351
|
+
# exceeds its upper bound is invalid.
|
|
352
|
+
def check_aggregate_bounds(schema, entity)
|
|
353
|
+
entity.attributes.to_a.each do |attr|
|
|
354
|
+
type = attr.type
|
|
355
|
+
next unless type.respond_to?(:bound1) && type.respond_to?(:bound2)
|
|
356
|
+
|
|
357
|
+
lower = integer_bound(type.bound1)
|
|
358
|
+
upper = integer_bound(type.bound2)
|
|
359
|
+
next if lower.nil? || upper.nil?
|
|
360
|
+
next if lower <= upper
|
|
361
|
+
|
|
362
|
+
note!(:check_agg_type, :error, schema,
|
|
363
|
+
"ENTITY #{entity.id}: aggregate '#{attr.id}' bounds " \
|
|
364
|
+
"[#{lower}:#{upper}] are inverted", attr)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def integer_bound(bound)
|
|
369
|
+
return nil unless bound.is_a?(Model::Literals::Integer)
|
|
370
|
+
|
|
371
|
+
bound.value.to_i
|
|
372
|
+
rescue StandardError
|
|
373
|
+
nil
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# eeng check-attrib-name-fun: an attribute sharing its name with
|
|
377
|
+
# a function of the same schema can never be referenced cleanly.
|
|
378
|
+
def check_attrib_name_fun(schema, entity)
|
|
379
|
+
funs = schema.functions.to_a.map { |f| f.id.safe_downcase }
|
|
380
|
+
entity.attributes.to_a.each do |attr|
|
|
381
|
+
next unless attr.id && funs.include?(attr.id.safe_downcase)
|
|
382
|
+
|
|
383
|
+
note!(:check_attrib_name_fun, :warning, schema,
|
|
384
|
+
"ENTITY #{entity.id}: attribute '#{attr.id}' shadows a " \
|
|
385
|
+
"function of the same name", attr)
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# eeng check-string-no-entity: string literals of the shape
|
|
390
|
+
# 'SCHEMA.ITEM' whose SCHEMA is known in the repository but whose
|
|
391
|
+
# ITEM is not declared in it (the TYPEOF idiom).
|
|
392
|
+
def check_string_references(schema)
|
|
393
|
+
known = @by_name
|
|
394
|
+
each_string(schema) do |literal, value|
|
|
395
|
+
match = /\A(\w+)\.(\w+)\z/.match(value)
|
|
396
|
+
next unless match
|
|
397
|
+
|
|
398
|
+
schema_id = match[1].safe_downcase
|
|
399
|
+
item_id = match[2].safe_downcase
|
|
400
|
+
target = known[schema_id]&.find { |s| s.id.safe_downcase == schema_id }
|
|
401
|
+
next unless target
|
|
402
|
+
next if Expressir::Express::SelfSchemaReference.declared?(target, item_id)
|
|
403
|
+
|
|
404
|
+
note!(:check_string_no_entity, :warning, schema,
|
|
405
|
+
"string '#{value}' references '#{item_id}' which is not " \
|
|
406
|
+
"declared in schema '#{target.id}'", literal)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def each_string(node, &block)
|
|
411
|
+
case node
|
|
412
|
+
when Model::Literals::String then yield node, node.value
|
|
413
|
+
when Model::ModelElement
|
|
414
|
+
node.class.attributes.each_key do |attr|
|
|
415
|
+
next if Model::ModelElement::SKIP_ATTRIBUTES.include?(attr) || attr == :parent
|
|
416
|
+
|
|
417
|
+
value = node.public_send(attr)
|
|
418
|
+
case value
|
|
419
|
+
when Array then value.each { |item| each_string(item, &block) }
|
|
420
|
+
when Model::ModelElement then each_string(value, &block)
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
345
426
|
def check_where_rules(schema, owner, rules)
|
|
346
427
|
Array(rules).each do |wr|
|
|
347
428
|
next if wr.id.nil? || wr.id.match?(WHERE_LABEL)
|
|
@@ -351,13 +351,17 @@ module Expressir
|
|
|
351
351
|
end
|
|
352
352
|
end
|
|
353
353
|
|
|
354
|
-
# Every identifier a call site names, across the
|
|
355
|
-
# surviving declarations: FunctionCall for functions
|
|
356
|
-
# ProcedureCall statements for procedures
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
354
|
+
# Every identifier a call site or reference names, across the
|
|
355
|
+
# artifact's surviving declarations: FunctionCall for functions,
|
|
356
|
+
# ProcedureCall statements for procedures, and ANY SimpleReference
|
|
357
|
+
# naming a schema function/procedure — a zero-argument function
|
|
358
|
+
# used bare (`v < limit_value`) parses as a plain reference, not
|
|
359
|
+
# a FunctionCall, and dropping it would leave the longform with a
|
|
360
|
+
# dangling call. Over-retaining is safe: prune only decides what
|
|
361
|
+
# may be dropped.
|
|
360
362
|
def called_ids(schema)
|
|
363
|
+
invocable = (schema.functions.to_a + schema.procedures.to_a)
|
|
364
|
+
.map { |d| d.id.safe_downcase }
|
|
361
365
|
called = {}
|
|
362
366
|
each_node(schema) do |node|
|
|
363
367
|
id = nil
|
|
@@ -366,8 +370,10 @@ module Expressir
|
|
|
366
370
|
id = node.function.is_a?(String) ? node.function : node.function&.id
|
|
367
371
|
when Expressir::Model::Statements::ProcedureCall
|
|
368
372
|
id = node.procedure.is_a?(String) ? node.procedure : node.procedure&.id
|
|
373
|
+
when Model::References::SimpleReference
|
|
374
|
+
id = node.id
|
|
369
375
|
end
|
|
370
|
-
called[id.safe_downcase] = true if id
|
|
376
|
+
called[id.safe_downcase] = true if id && invocable.include?(id.safe_downcase)
|
|
371
377
|
end
|
|
372
378
|
called
|
|
373
379
|
end
|
data/lib/expressir/version.rb
CHANGED