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,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Eval
5
+ # Function and statement interpretation (TODO.sota/02 step 2):
6
+ # ISO 10303-11 ch.13 executable statements over the value model,
7
+ # with the pure builtin functions of ch.15 set A (math/string/
8
+ # aggregate). Instance-aware builtins (TYPEOF, USEDIN, EXISTS over
9
+ # instances) arrive with step 3.
10
+ class Interpreter
11
+ V = Expressir::Eval::Values
12
+ EX = Expressir::Eval::Expression
13
+
14
+ BUILTIN_CONSTANTS = {
15
+ "PI" => Math::PI,
16
+ "E" => Math::E,
17
+ "CONST_E" => Math::E,
18
+ }.freeze
19
+
20
+ def initialize(schema:, population: nil)
21
+ @schema = schema
22
+ @population = population
23
+ end
24
+
25
+ def function(name)
26
+ key = name.to_s.safe_downcase
27
+ Array(@schema&.functions).find { |f| f.id&.safe_downcase == key }
28
+ end
29
+
30
+ def call(name, arguments)
31
+ function = function(name)
32
+ return execute_function(function, arguments) if function
33
+
34
+ call_builtin(name, arguments)
35
+ end
36
+
37
+ def execute_function(function, arguments)
38
+ env = Environment.new(schema: @schema, population: @population)
39
+ parameters = Array(function.parameters)
40
+ parameters.each_with_index do |param, index|
41
+ env = env.define(param.id, arguments.fetch(index, V::INDETERMINATE))
42
+ end
43
+
44
+ Array(function.variables).each do |variable|
45
+ value = variable.expression ? EX.evaluate(variable.expression, env) : V::INDETERMINATE
46
+ env = env.define(variable.id, value)
47
+ end
48
+
49
+ result = catch(:eval_return) do
50
+ execute_statements(function, Array(function.statements), env)
51
+ nil
52
+ end
53
+ return result.first if result.is_a?(Array) && result.size == 1
54
+ return result unless result.nil?
55
+
56
+ raise Error, "function '#{function.id}' ended without RETURN (13.7)"
57
+ end
58
+
59
+ def execute_statements(owner, statements, env)
60
+ current = env
61
+ Array(statements).each do |statement|
62
+ current = execute_statement(owner, statement, current)
63
+ end
64
+ current
65
+ end
66
+
67
+ def execute_statement(owner, statement, env)
68
+ case statement
69
+ when Model::Statements::Null then env
70
+ when Model::Statements::Assignment then execute_assignment(statement, env)
71
+ when Model::Statements::If then execute_if(owner, statement, env)
72
+ when Model::Statements::Case then execute_case(owner, statement, env)
73
+ when Model::Statements::Repeat then execute_repeat(owner, statement, env)
74
+ when Model::Statements::Return then throw(:eval_return, [evaluate_return(statement, env)])
75
+ when Model::Statements::Escape then throw(:eval_escape, env)
76
+ when Model::Statements::Skip then throw(:eval_skip, env)
77
+ when Model::Statements::ProcedureCall
78
+ raise Error, "procedure calls arrive with TODO.sota/02 step 3+"
79
+ when Model::Statements::Alias
80
+ raise Error, "ALIAS statements arrive with TODO.sota/02 step 3+"
81
+ else
82
+ raise Error, "unsupported statement #{statement.class.name}"
83
+ end
84
+ end
85
+
86
+ def evaluate_return(statement, env)
87
+ statement.expression ? EX.evaluate(statement.expression, env) : nil
88
+ end
89
+
90
+ def execute_assignment(statement, env)
91
+ value = EX.evaluate(statement.expression, env)
92
+ target = statement.ref
93
+ case target
94
+ when Model::References::SimpleReference
95
+ env.define(target.id, value)
96
+ when Model::References::IndexReference
97
+ execute_indexed_assignment(target, value, env)
98
+ else
99
+ raise Error, "assignment to instance attributes requires a " \
100
+ "population (step 3)"
101
+ end
102
+ end
103
+
104
+ def execute_indexed_assignment(target, value, env)
105
+ raise Error, "aggregate element assignment expects one index" if target.index2
106
+
107
+ base = target.ref
108
+ unless base.is_a?(Model::References::SimpleReference)
109
+ raise Error, "nested indexed assignment requires a population (step 3)"
110
+ end
111
+
112
+ aggregate = env.lookup(base.id)
113
+ unless aggregate.is_a?(V::Aggregate)
114
+ raise Error, "indexed assignment requires an aggregate variable"
115
+ end
116
+
117
+ index = EX.evaluate(target.index1, env) - 1
118
+ elements = aggregate.elements.dup
119
+ elements[index] = value
120
+ env.define(base.id, V::Aggregate.of(aggregate.kind, elements))
121
+ end
122
+
123
+ def execute_if(owner, statement, env)
124
+ condition = EX.evaluate(statement.expression, env)
125
+ truth = logical_truth(condition)
126
+ branch = truth == V::LOGICAL_TRUE ? statement.statements : statement.else_statements
127
+ execute_statements(owner, Array(branch), env)
128
+ end
129
+
130
+ def execute_case(owner, statement, env)
131
+ selector = EX.evaluate(statement.expression, env)
132
+ action = Array(statement.actions).find do |case_action|
133
+ Array(case_action.labels).any? do |label|
134
+ EX.values_equal?(selector, EX.evaluate(label, env))
135
+ end
136
+ end
137
+ body = action ? action.statement : statement.otherwise_statement
138
+ return env unless body
139
+
140
+ execute_statement(owner, body, env)
141
+ end
142
+
143
+ def execute_repeat(owner, statement, env)
144
+ if statement.while_expression
145
+ execute_while(owner, statement, env)
146
+ elsif statement.until_expression
147
+ execute_until(owner, statement, env)
148
+ else
149
+ execute_increment(owner, statement, env)
150
+ end
151
+ end
152
+
153
+ def execute_while(owner, statement, env)
154
+ escaped = catch(:eval_escape) do
155
+ loop do
156
+ condition = EX.evaluate(statement.while_expression, env)
157
+ break unless logical_truth(condition) == V::LOGICAL_TRUE
158
+
159
+ env = execute_statements(owner, Array(statement.statements), env)
160
+ end
161
+ nil
162
+ end
163
+ escaped || env
164
+ end
165
+
166
+ def execute_until(owner, statement, env)
167
+ escaped = catch(:eval_escape) do
168
+ loop do
169
+ env = execute_statements(owner, Array(statement.statements), env)
170
+ condition = EX.evaluate(statement.until_expression, env)
171
+ break if logical_truth(condition) == V::LOGICAL_TRUE
172
+ end
173
+ nil
174
+ end
175
+ escaped || env
176
+ end
177
+
178
+ def execute_increment(owner, statement, env)
179
+ first = EX.evaluate(statement.bound1, env)
180
+ last = EX.evaluate(statement.bound2, env)
181
+ step = statement.increment ? EX.evaluate(statement.increment, env) : 1
182
+ raise Error, "repeat increment must not be zero (13.6.1)" if step.zero?
183
+
184
+ control = statement.id
185
+ value = first
186
+ current = env
187
+ escaped = catch(:eval_escape) do
188
+ loop do
189
+ break if step.positive? ? value > last : value < last
190
+
191
+ iteration_env = current.define(control, value)
192
+ current = catch(:eval_skip) do
193
+ execute_statements(owner, Array(statement.statements),
194
+ iteration_env)
195
+ end || iteration_env
196
+ value += step
197
+ end
198
+ nil
199
+ end
200
+ escaped || current
201
+ end
202
+
203
+ # 12.4: only true selects the then-branch; unknown/false/indeterminate
204
+ # take the else branch.
205
+ def logical_truth(value)
206
+ return :unknown if value.equal?(V::INDETERMINATE)
207
+
208
+ value.is_a?(V::Logical) ? value.value : :unknown
209
+ end
210
+
211
+ # ---- builtin functions (ch.15, set A) ----
212
+
213
+ def call_builtin(name, arguments)
214
+ dispatch_builtin(name.to_s.upcase, arguments)
215
+ end
216
+
217
+ def dispatch_builtin(name, arguments)
218
+ case name
219
+ when "ABS" then arguments[0].abs
220
+ when "SIN" then Math.sin(arguments[0])
221
+ when "COS" then Math.cos(arguments[0])
222
+ when "TAN" then Math.tan(arguments[0])
223
+ when "ASIN" then Math.asin(arguments[0])
224
+ when "ACOS" then Math.acos(arguments[0])
225
+ when "ATAN" then Math.atan(arguments[0])
226
+ when "ATAN2" then Math.atan2(arguments[0], arguments[1])
227
+ when "SQRT" then Math.sqrt(arguments[0])
228
+ when "EXP" then Math.exp(arguments[0])
229
+ when "LOG" then Math.log(arguments[0])
230
+ when "LOG2" then Math.log2(arguments[0])
231
+ when "LOG10" then Math.log10(arguments[0])
232
+ when "ODD" then V::Logical.new(value: arguments[0].odd? ? V::LOGICAL_TRUE : V::LOGICAL_FALSE)
233
+ when "LENGTH" then arguments[0].to_s.length
234
+ when "BLENGTH" then arguments[0].bits.length
235
+ when "SIZEOF", "HIINDEX", "HIBOUND" then arguments[0].elements.size
236
+ when "LOINDEX", "LOBOUND" then 1
237
+ when "EXISTS" then V::Logical.new(value: exists?(arguments[0]) ? V::LOGICAL_TRUE : V::LOGICAL_FALSE)
238
+ when "TYPEOF" then typeof_value(arguments[0])
239
+ when "USEDIN" then usedin_value(arguments[0], arguments[1])
240
+ when "NVL"
241
+ arguments[0].equal?(V::INDETERMINATE) ? arguments[1] : arguments[0]
242
+ when "VALUE"
243
+ return arguments[0] if arguments[0].is_a?(Numeric)
244
+
245
+ text = arguments[0].to_s
246
+ text.match?(/\A[+-]?\d+\z/) ? text.to_i : text.to_f
247
+ else
248
+ raise Error, "builtin '#{name}' is not implemented yet"
249
+ end
250
+ end
251
+
252
+ def exists?(value)
253
+ !value.equal?(V::INDETERMINATE) && !value.nil?
254
+ end
255
+
256
+ # TYPEOF (15.x): the type names of an instance — the record
257
+ # keyword plus its supertype closure, unqualified and
258
+ # schema-qualified (corpus rules use the qualified form).
259
+ def typeof_value(value)
260
+ return V::Aggregate.of(:set, []) unless value.is_a?(V::Instance)
261
+
262
+ keywords = if value.record.is_a?(Expressir::P21::ComplexRecord)
263
+ Array(value.record.records).map(&:keyword)
264
+ else
265
+ [value.record.keyword]
266
+ end
267
+ names = keywords.flat_map { |keyword| typeof_names(keyword) }
268
+ V::Aggregate.of(:set, names.uniq)
269
+ end
270
+
271
+ def typeof_names(keyword)
272
+ entity = Array(@schema&.entities).find do |candidate|
273
+ candidate.id&.safe_downcase == keyword&.safe_downcase
274
+ end
275
+ return [keyword.to_s.upcase] unless entity
276
+
277
+ ([entity] + entity.supertype_closure).flat_map do |candidate|
278
+ [candidate.id.upcase, "#{@schema.id.upcase}.#{candidate.id.upcase}"]
279
+ end.uniq
280
+ end
281
+
282
+ # USEDIN (15.x): instances that reference the argument.
283
+ def usedin_value(value, _role)
284
+ return V::Aggregate.of(:bag, []) unless
285
+ value.is_a?(V::Instance) && @population
286
+
287
+ referencing = @population.instances.filter_map do |id, record|
288
+ next unless record_references?(record, value.id)
289
+
290
+ V::Instance.new(id: id.delete_prefix("#"), record: record)
291
+ end
292
+ V::Aggregate.of(:bag, referencing)
293
+ end
294
+
295
+ def record_references?(record, target_id)
296
+ records = record.is_a?(Expressir::P21::ComplexRecord) ? record.records : [record]
297
+ reference = "##{target_id}"
298
+ records.any? do |partial|
299
+ params_reference?(partial.params, reference)
300
+ end
301
+ end
302
+
303
+ def params_reference?(params, reference)
304
+ Array(params).any? do |param|
305
+ case param
306
+ when String then param == reference
307
+ when Array then params_reference?(param, reference)
308
+ when Expressir::P21::TypedValue
309
+ params_reference?(param.value, reference)
310
+ else false
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Eval
5
+ # WHERE-rule checking over a population (TODO.sota/02 step 4):
6
+ # every instance's entity domain rules evaluate with SELF and the
7
+ # entity's attributes bound. Per ISO 10303-11 9.4 a rule is
8
+ # violated only when it evaluates to false; unknown/indeterminate
9
+ # is not a violation.
10
+ module Rules
11
+ V = Expressir::Eval::Values
12
+ EX = Expressir::Eval::Expression
13
+
14
+ RuleResult = Struct.new(:instance, :rule_id, :status, :message,
15
+ keyword_init: true)
16
+
17
+ module_function
18
+
19
+ def check(population, schema)
20
+ population.instances.flat_map do |id, record|
21
+ entity_for(schema, record)
22
+ entity = entity_for(schema, record)
23
+ next [] unless entity
24
+
25
+ Array(entity.where_rules).map do |rule|
26
+ evaluate_rule(population, schema, id, record, entity, rule)
27
+ end
28
+ end
29
+ end
30
+
31
+ def failures(results)
32
+ results.select { |result| result.status == :fail }
33
+ end
34
+
35
+ def violated?(results)
36
+ failures(results).any?
37
+ end
38
+
39
+ def evaluate_rule(population, schema, id, record, entity, rule)
40
+ env = environment_for(population, schema, id, record, entity)
41
+ value = EX.evaluate(rule.expression, env)
42
+ status = status_of(value)
43
+ RuleResult.new(
44
+ instance: id,
45
+ rule_id: rule.id,
46
+ status: status,
47
+ message: status == :fail ? "#{id}: #{entity.id}.#{rule.id}" : nil,
48
+ )
49
+ rescue Expressir::Eval::Error => e
50
+ RuleResult.new(instance: id, rule_id: rule.id, status: :error,
51
+ message: "#{id}: #{entity.id}.#{rule.id}: #{e.message}")
52
+ end
53
+
54
+ def environment_for(population, schema, id, record, entity)
55
+ env = Environment.new(schema: schema, population: population)
56
+ instance = V::Instance.new(id: id.delete_prefix("#"), record: record)
57
+ env = env.define("self", instance)
58
+ env.slots_for(entity).each_key do |name|
59
+ next unless name
60
+
61
+ value = EX.instance_attribute(instance, name, env)
62
+ env = env.define(name, value)
63
+ end
64
+ env
65
+ end
66
+
67
+ def status_of(value)
68
+ return :unknown if value.equal?(V::INDETERMINATE)
69
+ return :unknown unless value.is_a?(V::Logical)
70
+
71
+ case value.value
72
+ when V::LOGICAL_TRUE then :pass
73
+ when V::LOGICAL_FALSE then :fail
74
+ else :unknown
75
+ end
76
+ end
77
+
78
+ def entity_for(schema, record)
79
+ if record.is_a?(Expressir::P21::ComplexRecord)
80
+ record = record.records.first
81
+ end
82
+ Array(schema.entities).find do |entity|
83
+ entity.id&.safe_downcase == record.keyword&.safe_downcase
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Eval
5
+ # EXPRESS value model (ISO 10303-11 ch.8/12): indeterminate,
6
+ # logical, enumeration, binary and aggregates are dedicated
7
+ # values; NUMBER-family members, strings map to Ruby natives.
8
+ #
9
+ # Values are frozen; aggregate operators return new values.
10
+ module Values
11
+ # The indeterminate value `?` (ISO 10303-11 8.6? — the
12
+ # indeterminate sign). A singleton: comparisons and arithmetic
13
+ # propagate it per clause instead of treating it as nil.
14
+ INDETERMINATE = Object.new.freeze
15
+
16
+ # EXPRESS logical values are the symbols true/false/unknown as
17
+ # values, not Ruby booleans (ISO 10303-11 8.2).
18
+ LOGICAL_TRUE = :true # rubocop:disable Lint/BooleanSymbol
19
+ LOGICAL_FALSE = :false # rubocop:disable Lint/BooleanSymbol
20
+ LOGICAL_UNKNOWN = :unknown
21
+
22
+ Logical = Struct.new(:value, keyword_init: true) do
23
+ # The logical values are ISO 10303-11 logical literals
24
+ # (true/false/unknown as values), not Ruby booleans.
25
+ def self.true_value # rubocop:disable Lint/BooleanSymbol
26
+ new(value: LOGICAL_TRUE).freeze
27
+ end
28
+
29
+ def self.false_value # rubocop:disable Lint/BooleanSymbol
30
+ new(value: LOGICAL_FALSE).freeze
31
+ end
32
+
33
+ def self.unknown
34
+ new(value: LOGICAL_UNKNOWN).freeze
35
+ end
36
+
37
+ def to_s
38
+ value.to_s.upcase
39
+ end
40
+ end
41
+
42
+ Enumeration = Struct.new(:type, :item, keyword_init: true) do
43
+ def to_s
44
+ ".#{item}."
45
+ end
46
+ end
47
+
48
+ Binary = Struct.new(:bits, keyword_init: true) do
49
+ def to_s
50
+ "%#{bits}"
51
+ end
52
+ end
53
+
54
+ # A Part 21 instance: the population record plus its #id.
55
+ Instance = Struct.new(:id, :record, keyword_init: true) do
56
+ def to_s
57
+ "##{id}"
58
+ end
59
+ end
60
+
61
+ # kind: :list, :set, :bag or :array — union/difference/
62
+ # intersection semantics depend on it (12.6).
63
+ Aggregate = Struct.new(:kind, :elements, keyword_init: true) do
64
+ def self.of(kind, elements)
65
+ new(kind: kind, elements: elements.freeze).freeze
66
+ end
67
+
68
+ def size
69
+ elements.size
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ # The EXPRESS evaluator (TODO.sota/02): runs the language's
5
+ # expressions, functions and rules over values and — from step 3 —
6
+ # Part 21 populations. The evaluator consumes the same model the
7
+ # parser produces; nothing here re-parses.
8
+ module Eval
9
+ class Error < StandardError; end
10
+
11
+ autoload :Values, "#{__dir__}/eval/values"
12
+ autoload :Environment, "#{__dir__}/eval/environment"
13
+ autoload :Expression, "#{__dir__}/eval/expression"
14
+ autoload :Interpreter, "#{__dir__}/eval/interpreter"
15
+ autoload :Rules, "#{__dir__}/eval/rules"
16
+ end
17
+ end
@@ -99,7 +99,15 @@ module Expressir
99
99
  def build_factor(ast_data)
100
100
  return nil unless ast_data[:simple_factor]
101
101
 
102
- Builder.build_node(:simple_factor, ast_data[:simple_factor])
102
+ base = Builder.build_node(:simple_factor, ast_data[:simple_factor])
103
+ rhs = ast_data[:rhs]
104
+ return base unless rhs
105
+
106
+ Model::Expressions::BinaryExpression.new(
107
+ operator: Model::Expressions::BinaryExpression::EXPONENTIATION,
108
+ operand1: base,
109
+ operand2: Builder.build_node(:simple_factor, rhs[:simple_factor]),
110
+ )
103
111
  end
104
112
 
105
113
  def build_simple_factor(ast_data)