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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3cf38ff1a09f95805cc5a6dc8c074832e47eeb8ceb7e62c79f503d521d775ac
4
- data.tar.gz: a308605c1d7c97d33f766d08e8262e6e2039104ff7362e36a0fa5bd8e3839ccf
3
+ metadata.gz: 714b13d808744a329002460e108fdb35f4a8fcf0d3095517cd241d411bce215e
4
+ data.tar.gz: 8cf66567212cc463ab351cd6e0cbb70ee63ec11ae2c0b31036cd3d0aff264384
5
5
  SHA512:
6
- metadata.gz: 4c97b4d258e50604fba0bdc0365c3f8272d6944dc225755e6408c6b585324b917448134352a4ffb4da445ca21ef910858283931abef43625d0b4138c90ecf694
7
- data.tar.gz: d268fd6aef2e11bdafe5f81845931e91742dcd33ff48f3a2f6bb0076a6f9bad440045609a501b81fcc2130fef6dcd565d972ebc6634861d0341f9efa2649b5b5
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
- warn "expressir: schema(s) not found: #{missing.uniq.join(', ')}" \
46
- "#{resolver_hint(manifest, stepmod)}"
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
- same_dir_index(root_path)
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
- Dir.glob(File.join(dir, "*.exp")).to_h do |p|
99
- [File.basename(p, ".exp").downcase, p]
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 artifact's
355
- # surviving declarations: FunctionCall for functions and
356
- # ProcedureCall statements for procedures. (A zero-argument
357
- # function reference written bare is not represented as a
358
- # FunctionCall by the parser and is counted via any SimpleReference
359
- # miss — accepted v1 blind spot, see tranche notes.)
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
@@ -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.13".freeze
6
+ VERSION = "2.4.15".freeze
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expressir
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.13
4
+ version: 2.4.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.