expressir 2.4.35 → 2.4.37
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/ext/expressir_core/Cargo.toml +1 -1
- 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,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)
|