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,610 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Eval
|
|
5
|
+
# Pure expression evaluation (TODO.sota/02 step 1): ISO 10303-11
|
|
6
|
+
# ch.12 over the value model. Instance-aware operations (attribute
|
|
7
|
+
# access on entities, TYPEOF/USEDIN/EXISTS) arrive with step 3 and
|
|
8
|
+
# raise Error here.
|
|
9
|
+
module Expression
|
|
10
|
+
V = Expressir::Eval::Values
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def evaluate(node, env)
|
|
15
|
+
case node
|
|
16
|
+
when Model::Literals::Integer then node.value.to_i
|
|
17
|
+
when Model::Literals::Real then node.value.to_f
|
|
18
|
+
when Model::Literals::String then node.value.to_s
|
|
19
|
+
when Model::Literals::Binary then V::Binary.new(bits: node.value.to_s)
|
|
20
|
+
when Model::Literals::Logical then logical_literal(node)
|
|
21
|
+
when Model::Expressions::BinaryExpression then evaluate_binary(node, env)
|
|
22
|
+
when Model::Expressions::UnaryExpression then evaluate_unary(node, env)
|
|
23
|
+
when Model::Expressions::Interval then evaluate_interval(node, env)
|
|
24
|
+
when Model::Expressions::AggregateInitializer
|
|
25
|
+
evaluate_aggregate_initializer(node, env)
|
|
26
|
+
when Model::Expressions::QueryExpression then evaluate_query(node, env)
|
|
27
|
+
when Model::Expressions::FunctionCall then evaluate_call(node, env)
|
|
28
|
+
when Model::References::SimpleReference then evaluate_reference(node, env)
|
|
29
|
+
when Model::References::AttributeReference
|
|
30
|
+
evaluate_attribute_reference(node, env)
|
|
31
|
+
when Model::References::IndexReference then evaluate_index(node, env)
|
|
32
|
+
when Model::References::GroupReference then evaluate_group_reference(node, env)
|
|
33
|
+
else
|
|
34
|
+
raise Error, "unsupported expression #{node.class.name}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def logical_literal(node)
|
|
39
|
+
node.value == "TRUE" ? V::Logical.true_value : V::Logical.false_value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def evaluate_reference(node, env)
|
|
43
|
+
value = env.lookup(node.id)
|
|
44
|
+
return value unless value.nil?
|
|
45
|
+
|
|
46
|
+
raise Error, "unbound identifier '#{node.id}'"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def evaluate_group_reference(node, env)
|
|
50
|
+
base = node.ref
|
|
51
|
+
if base.is_a?(Model::References::SimpleReference) &&
|
|
52
|
+
base.id&.safe_downcase == "self"
|
|
53
|
+
value = env.lookup("self")
|
|
54
|
+
return value if value
|
|
55
|
+
|
|
56
|
+
raise Error, "SELF requires an instance context (step 4)"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
raise Error, "instance access requires a population"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# `color.red` (enumeration members) or `instance.attr`.
|
|
63
|
+
def evaluate_attribute_reference(node, env)
|
|
64
|
+
base = node.ref
|
|
65
|
+
if base.is_a?(Model::References::SimpleReference)
|
|
66
|
+
declaration = find_declared_type(env, base.id)
|
|
67
|
+
if declaration
|
|
68
|
+
return V::Enumeration.new(type: declaration.id,
|
|
69
|
+
item: node.attribute.id)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
owner = evaluate(base, env)
|
|
74
|
+
if owner.is_a?(V::Instance)
|
|
75
|
+
return instance_attribute(owner, node.attribute&.id, env)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
raise Error, "instance attribute access requires a population"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def instance_attribute(instance, attribute_name, env)
|
|
82
|
+
record = instance.record
|
|
83
|
+
record = simple_record_of(record, attribute_name, env) if
|
|
84
|
+
record.is_a?(Expressir::P21::ComplexRecord)
|
|
85
|
+
unless record
|
|
86
|
+
raise Error, "attribute '#{attribute_name}' is not mapped on " \
|
|
87
|
+
"#{record.keyword}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
entity = env.entity_for_keyword(record.keyword)
|
|
91
|
+
raise Error, "unknown entity '#{record.keyword}'" unless entity
|
|
92
|
+
|
|
93
|
+
index = env.slots_for(entity)[attribute_name.safe_downcase]
|
|
94
|
+
unless index
|
|
95
|
+
raise Error, "attribute '#{attribute_name}' is not mapped on " \
|
|
96
|
+
"'#{record.keyword}'"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
instance_value(record.params[index], env)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# P21 record parameters are plain Ruby data; convert them to the
|
|
103
|
+
# evaluator's value model at the instance boundary.
|
|
104
|
+
def instance_value(raw, env)
|
|
105
|
+
case raw
|
|
106
|
+
when :unset, :derived then V::INDETERMINATE
|
|
107
|
+
when Array
|
|
108
|
+
V::Aggregate.of(:list, raw.map { |item| instance_value(item, env) })
|
|
109
|
+
when String
|
|
110
|
+
if raw.start_with?("#")
|
|
111
|
+
env.instance_for(raw) || raw
|
|
112
|
+
else
|
|
113
|
+
raw
|
|
114
|
+
end
|
|
115
|
+
when Expressir::P21::Enumeration
|
|
116
|
+
V::Enumeration.new(type: nil, item: raw.name)
|
|
117
|
+
when Expressir::P21::Binary
|
|
118
|
+
V::Binary.new(bits: raw.hex)
|
|
119
|
+
when Expressir::P21::TypedValue
|
|
120
|
+
instance_value(raw.value, env)
|
|
121
|
+
else raw
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def simple_record_of(complex_record, attribute_name, env)
|
|
126
|
+
Array(complex_record.records).find do |record|
|
|
127
|
+
entity = env.entity_for_keyword(record.keyword)
|
|
128
|
+
entity &&
|
|
129
|
+
env.slots_for(entity).key?(attribute_name.safe_downcase)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def find_declared_type(env, name)
|
|
134
|
+
schema = env.schema
|
|
135
|
+
return nil unless schema
|
|
136
|
+
|
|
137
|
+
Array(schema.types).find do |type|
|
|
138
|
+
type.id&.safe_downcase == name.safe_downcase &&
|
|
139
|
+
type.underlying_type.is_a?(Model::DataTypes::Enumeration)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def evaluate_aggregate_initializer(node, env)
|
|
144
|
+
V::Aggregate.of(:list, Array(node.items).map do |item|
|
|
145
|
+
evaluate(item.respond_to?(:expression) ? item.expression : item, env)
|
|
146
|
+
end)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def evaluate_index(node, env)
|
|
150
|
+
base = evaluate(node.ref, env)
|
|
151
|
+
index1 = evaluate(node.index1, env)
|
|
152
|
+
index2 = node.index2 ? evaluate(node.index2, env) : nil
|
|
153
|
+
case base
|
|
154
|
+
when String then string_slice(base, index1, index2)
|
|
155
|
+
when V::Binary then V::Binary.new(bits: string_slice(base.bits, index1, index2))
|
|
156
|
+
when V::Aggregate
|
|
157
|
+
raise Error, "aggregate indexing selects one element (12.6.1)" if index2
|
|
158
|
+
|
|
159
|
+
base.elements.fetch(index1 - 1)
|
|
160
|
+
else
|
|
161
|
+
raise Error, "cannot index #{describe(base)}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def string_slice(value, index1, index2)
|
|
166
|
+
return value[index1 - 1] unless index2
|
|
167
|
+
|
|
168
|
+
value[index1 - 1, index2 - index1 + 1] || ""
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def evaluate_query(node, env)
|
|
172
|
+
source = evaluate(node.aggregate_source, env)
|
|
173
|
+
unless source.is_a?(V::Aggregate)
|
|
174
|
+
raise Error, "QUERY source shall be an aggregate (12.6.7)"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
kept = source.elements.select do |element|
|
|
178
|
+
inner = env.push({ node.id.safe_downcase => element })
|
|
179
|
+
logical?(evaluate(node.expression, inner))
|
|
180
|
+
end
|
|
181
|
+
V::Aggregate.of(source.kind, kept)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def evaluate_call(node, env)
|
|
185
|
+
callee = node.function
|
|
186
|
+
name = callee.is_a?(Model::References::SimpleReference) ? callee.id : nil
|
|
187
|
+
raise Error, "unsupported call target #{callee.class.name}" unless name
|
|
188
|
+
|
|
189
|
+
arguments = Array(node.parameters).map { |arg| evaluate(arg, env) }
|
|
190
|
+
env.interpreter.call(name, arguments)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def evaluate_unary(node, env)
|
|
194
|
+
operand = evaluate(node.operand, env)
|
|
195
|
+
return V::INDETERMINATE if indeterminate?(operand)
|
|
196
|
+
|
|
197
|
+
case node.operator
|
|
198
|
+
when "PLUS" then to_number!(operand)
|
|
199
|
+
when "MINUS" then -to_number!(operand)
|
|
200
|
+
when "NOT"
|
|
201
|
+
logical = as_logical(operand)
|
|
202
|
+
case logical.value
|
|
203
|
+
when V::LOGICAL_TRUE then V::Logical.false_value
|
|
204
|
+
when V::LOGICAL_FALSE then V::Logical.true_value
|
|
205
|
+
else V::Logical.unknown
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def evaluate_interval(node, env)
|
|
211
|
+
left = relational(evaluate(node.low, env), node.operator1,
|
|
212
|
+
evaluate(node.item, env))
|
|
213
|
+
right = relational(evaluate(node.item, env), node.operator2,
|
|
214
|
+
evaluate(node.high, env))
|
|
215
|
+
evaluate_logical("AND", left, right)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
LOGICAL_OPS = %w[AND OR XOR].freeze
|
|
219
|
+
|
|
220
|
+
def evaluate_binary(node, env)
|
|
221
|
+
operator = node.operator
|
|
222
|
+
left = evaluate(node.operand1, env)
|
|
223
|
+
right = evaluate(node.operand2, env)
|
|
224
|
+
if LOGICAL_OPS.include?(operator)
|
|
225
|
+
return evaluate_logical(operator, left, right)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
return V::Logical.unknown if indeterminate?(left, right)
|
|
229
|
+
|
|
230
|
+
case operator
|
|
231
|
+
when "ADDITION" then evaluate_addition(left, right)
|
|
232
|
+
when "SUBTRACTION" then evaluate_difference(left, right)
|
|
233
|
+
when "MULTIPLICATION" then evaluate_intersection(left, right)
|
|
234
|
+
when "REAL_DIVISION" then to_number!(left).to_f / to_number!(right)
|
|
235
|
+
when "INTEGER_DIVISION", "MODULO" then evaluate_div_mod(operator, left, right)
|
|
236
|
+
when "EXPONENTIATION" then to_number!(left)**to_number!(right)
|
|
237
|
+
when "COMBINE"
|
|
238
|
+
raise Error, "complex entity construction requires instances (step 3)"
|
|
239
|
+
else
|
|
240
|
+
evaluate_relational(operator, left, right)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def evaluate_div_mod(operator, left, right)
|
|
245
|
+
first = truncate(left)
|
|
246
|
+
second = truncate(right)
|
|
247
|
+
operator == "INTEGER_DIVISION" ? first / second : first % second
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# ---- arithmetic (12.1) ----
|
|
251
|
+
|
|
252
|
+
def numeric_pair?(left, right)
|
|
253
|
+
left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def string_pair?(left, right)
|
|
257
|
+
left.is_a?(String) && right.is_a?(String)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def binary_pair?(left, right)
|
|
261
|
+
left.is_a?(V::Binary) && right.is_a?(V::Binary)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def aggregate_pair?(left, right)
|
|
265
|
+
left.is_a?(V::Aggregate) && right.is_a?(V::Aggregate)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def arithmetic(left, right, operator)
|
|
269
|
+
result = to_number!(left).send(operator, to_number!(right))
|
|
270
|
+
both_integer?(left, right) ? result : result.to_f
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def truncate(value)
|
|
274
|
+
value.is_a?(Integer) ? value : to_number!(value).to_f.truncate
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def to_number!(value)
|
|
278
|
+
return value if value.is_a?(Numeric)
|
|
279
|
+
|
|
280
|
+
raise Error, "expected a numeric operand, got #{describe(value)}"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def integer?(value)
|
|
284
|
+
value.is_a?(Integer)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def both_integer?(left, right)
|
|
288
|
+
integer?(left) && integer?(right)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def evaluate_addition(left, right)
|
|
292
|
+
return arithmetic(left, right, :+) if numeric_pair?(left, right)
|
|
293
|
+
return left + right if string_pair?(left, right)
|
|
294
|
+
return V::Binary.new(bits: left.bits + right.bits) if binary_pair?(left, right)
|
|
295
|
+
return union(left, right) if left.is_a?(V::Aggregate) || right.is_a?(V::Aggregate)
|
|
296
|
+
|
|
297
|
+
raise Error,
|
|
298
|
+
"addition requires numeric, string, binary or aggregate " \
|
|
299
|
+
"operands (12.1, 12.3.2, 12.5.2, 12.6)"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def evaluate_difference(left, right)
|
|
303
|
+
return arithmetic(left, right, :-) if numeric_pair?(left, right)
|
|
304
|
+
if left.is_a?(V::Aggregate) || right.is_a?(V::Aggregate)
|
|
305
|
+
return aggregate_difference(left, right)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
raise Error,
|
|
309
|
+
"difference requires numeric or aggregate operands (12.1, 12.6)"
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def evaluate_intersection(left, right)
|
|
313
|
+
return arithmetic(left, right, :*) if numeric_pair?(left, right)
|
|
314
|
+
return aggregate_intersection(left, right) if aggregate_pair?(left, right)
|
|
315
|
+
|
|
316
|
+
raise Error,
|
|
317
|
+
"intersection requires numeric or aggregate operands (12.1, 12.6)"
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# ---- logical operators (12.4) ----
|
|
321
|
+
|
|
322
|
+
def evaluate_logical(operator, left, right)
|
|
323
|
+
left = as_logical(left)
|
|
324
|
+
right = as_logical(right)
|
|
325
|
+
return V::Logical.unknown if [left, right].any? { |l| l.value == :unknown }
|
|
326
|
+
|
|
327
|
+
case operator
|
|
328
|
+
when "AND"
|
|
329
|
+
left.value == V::LOGICAL_TRUE && right.value == V::LOGICAL_TRUE ? V::Logical.true_value : V::Logical.false_value
|
|
330
|
+
when "OR"
|
|
331
|
+
left.value == V::LOGICAL_TRUE || right.value == V::LOGICAL_TRUE ? V::Logical.true_value : V::Logical.false_value
|
|
332
|
+
else # XOR
|
|
333
|
+
left.value == right.value ? V::Logical.false_value : V::Logical.true_value
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def as_logical(value)
|
|
338
|
+
return value if value.is_a?(V::Logical)
|
|
339
|
+
|
|
340
|
+
raise Error, "logical operator requires logical operands (12.4)"
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def logical?(value)
|
|
344
|
+
value.is_a?(V::Logical) && value.value == V::LOGICAL_TRUE
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# ---- relational (12.2) ----
|
|
348
|
+
|
|
349
|
+
def evaluate_relational(operator, left, right)
|
|
350
|
+
case operator
|
|
351
|
+
when "IN" then evaluate_in(left, right)
|
|
352
|
+
when "LIKE" then evaluate_like(left, right)
|
|
353
|
+
when "INSTANCE_EQUAL", "INSTANCE_NOT_EQUAL"
|
|
354
|
+
raise Error, "instance comparison requires instances (step 3)"
|
|
355
|
+
else
|
|
356
|
+
result = relational(left, relational_operator(operator), right)
|
|
357
|
+
operator == "NOT_EQUAL" ? negate(result) : result
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def evaluate_like(left, right)
|
|
362
|
+
hit = like_match?(left, right)
|
|
363
|
+
V::Logical.new(value: hit ? V::LOGICAL_TRUE : V::LOGICAL_FALSE)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def evaluate_in(element, aggregate)
|
|
367
|
+
return V::Logical.unknown if indeterminate?(element, aggregate)
|
|
368
|
+
unless aggregate.is_a?(V::Aggregate)
|
|
369
|
+
raise Error, "membership requires an aggregate right operand (12.2.3)"
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
return V::Logical.true_value if aggregate.elements.any? { |e| values_equal?(element, e) }
|
|
373
|
+
|
|
374
|
+
hole = aggregate.elements.any? { |e| indeterminate?(e) }
|
|
375
|
+
V::Logical.new(value: hole ? V::LOGICAL_UNKNOWN : V::LOGICAL_FALSE)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def relational_operator(operator)
|
|
379
|
+
{ "EQUAL" => :"=", "NOT_EQUAL" => :"=", "LESS_THAN" => :<,
|
|
380
|
+
"LESS_THAN_OR_EQUAL" => :"<=", "GREATER_THAN" => :>,
|
|
381
|
+
"GREATER_THAN_OR_EQUAL" => :">=" }.fetch(operator)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def relational(left, operator, right)
|
|
385
|
+
return V::Logical.unknown if indeterminate?(left, right)
|
|
386
|
+
|
|
387
|
+
ordering = order(left, right)
|
|
388
|
+
result = case operator
|
|
389
|
+
when :"=" then ordering.zero?
|
|
390
|
+
when :< then ordering.negative?
|
|
391
|
+
when :"<=" then ordering <= 0
|
|
392
|
+
when :> then ordering.positive?
|
|
393
|
+
when :">=" then ordering >= 0
|
|
394
|
+
end
|
|
395
|
+
V::Logical.new(value: result ? V::LOGICAL_TRUE : V::LOGICAL_FALSE)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def negate(logical)
|
|
399
|
+
case logical.value
|
|
400
|
+
when V::LOGICAL_TRUE then V::Logical.false_value
|
|
401
|
+
when V::LOGICAL_FALSE then V::Logical.true_value
|
|
402
|
+
else V::Logical.unknown
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# 12.2.1 orderings: numeric, string (code point), binary (bit),
|
|
407
|
+
# logical (false < unknown < true), enumeration (item name;
|
|
408
|
+
# declaration position is the caller's refinement).
|
|
409
|
+
def order(left, right)
|
|
410
|
+
return left <=> right if left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
411
|
+
return left <=> right if left.is_a?(String) && right.is_a?(String)
|
|
412
|
+
return left.bits <=> right.bits if left.is_a?(V::Binary) && right.is_a?(V::Binary)
|
|
413
|
+
return logical_order(left) <=> logical_order(right) if
|
|
414
|
+
left.is_a?(V::Logical) && right.is_a?(V::Logical)
|
|
415
|
+
return left.item <=> right.item if
|
|
416
|
+
left.is_a?(V::Enumeration) && right.is_a?(V::Enumeration)
|
|
417
|
+
|
|
418
|
+
raise Error, "cannot compare #{describe(left)} with #{describe(right)}"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
LOGICAL_ORDER = {
|
|
422
|
+
V::LOGICAL_FALSE => 0,
|
|
423
|
+
V::LOGICAL_UNKNOWN => 1,
|
|
424
|
+
V::LOGICAL_TRUE => 2,
|
|
425
|
+
}.freeze
|
|
426
|
+
|
|
427
|
+
def logical_order(logical)
|
|
428
|
+
LOGICAL_ORDER.fetch(logical.value)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# ---- LIKE (12.2.5, table 11) ----
|
|
432
|
+
|
|
433
|
+
def like_match?(target, pattern)
|
|
434
|
+
unless target.is_a?(String) && pattern.is_a?(String)
|
|
435
|
+
raise Error, "like requires string operands (12.2.5)"
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
match_sequence?(target, pattern.chars, 0)
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def match_sequence?(target, pattern, offset)
|
|
442
|
+
return offset == target.length if pattern.empty?
|
|
443
|
+
|
|
444
|
+
char = pattern[0]
|
|
445
|
+
rest = pattern[1..]
|
|
446
|
+
case char
|
|
447
|
+
when "\\" then match_escaped?(target, rest, offset)
|
|
448
|
+
when "*" then match_star?(target, rest, offset)
|
|
449
|
+
when "&" then match_sequence?(target, rest, target.length)
|
|
450
|
+
when "$" then match_dollar?(target, rest, offset)
|
|
451
|
+
when "!" then match_negation?(target, rest, offset)
|
|
452
|
+
else
|
|
453
|
+
match_single?(target, rest, offset) { |tc| char_matches?(char, tc) }
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def match_escaped?(target, rest, offset)
|
|
458
|
+
char = rest[0]
|
|
459
|
+
return false if char.nil?
|
|
460
|
+
|
|
461
|
+
match_single?(target, rest[1..], offset) { |tc| tc == char }
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def match_star?(target, rest, offset)
|
|
465
|
+
(offset..target.length).any? do |split|
|
|
466
|
+
match_sequence?(target, rest, split)
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def match_dollar?(target, rest, offset)
|
|
471
|
+
index = target.index(" ", offset) || target.length
|
|
472
|
+
prefix = target[offset...index] || ""
|
|
473
|
+
remaining = target[index..] || ""
|
|
474
|
+
match_sequence?(prefix, rest, 0) && match_sequence?(remaining, [], 0)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def match_negation?(target, rest, offset)
|
|
478
|
+
negated = rest[0]
|
|
479
|
+
return false if negated.nil? || offset >= target.length
|
|
480
|
+
|
|
481
|
+
target_char = target[offset]
|
|
482
|
+
matches = case negated
|
|
483
|
+
when "@" then target_char.match?(/[[:alpha:]]/)
|
|
484
|
+
when "^" then target_char.match?(/[[:upper:]]/)
|
|
485
|
+
when "#" then target_char.match?(/[[:digit:]]/)
|
|
486
|
+
when "?" then true
|
|
487
|
+
else target_char == negated
|
|
488
|
+
end
|
|
489
|
+
return false if matches
|
|
490
|
+
|
|
491
|
+
match_sequence?(target, rest[1..], offset + 1)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def match_single?(target, rest, offset)
|
|
495
|
+
return false if offset >= target.length
|
|
496
|
+
|
|
497
|
+
yield(target[offset]) && match_sequence?(target, rest, offset + 1)
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def char_matches?(pattern_char, target_char)
|
|
501
|
+
case pattern_char
|
|
502
|
+
when "@" then target_char.match?(/[[:alpha:]]/)
|
|
503
|
+
when "^" then target_char.match?(/[[:upper:]]/)
|
|
504
|
+
when "#" then target_char.match?(/[[:digit:]]/)
|
|
505
|
+
when "?" then true
|
|
506
|
+
else pattern_char == target_char
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# ---- aggregates (12.6, tables 16-18) ----
|
|
511
|
+
|
|
512
|
+
# Table 17: union results take the promoted kind (bag absorbs
|
|
513
|
+
# everything; set over set/element; list over list/element).
|
|
514
|
+
def union(left, right)
|
|
515
|
+
left_agg = left.is_a?(V::Aggregate) ? left : nil
|
|
516
|
+
right_agg = right.is_a?(V::Aggregate) ? right : nil
|
|
517
|
+
kind = union_kind(left_agg&.kind, right_agg&.kind)
|
|
518
|
+
elements = (left_agg&.elements || [left]) +
|
|
519
|
+
(right_agg&.elements || [right])
|
|
520
|
+
V::Aggregate.of(kind, dedup_if_set(kind, elements))
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def union_kind(left_kind, right_kind)
|
|
524
|
+
return :bag if [left_kind, right_kind].include?(:bag)
|
|
525
|
+
return :set if [left_kind, right_kind].include?(:set)
|
|
526
|
+
|
|
527
|
+
:list
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def dedup_if_set(kind, elements)
|
|
531
|
+
return elements unless kind == :set
|
|
532
|
+
|
|
533
|
+
seen = []
|
|
534
|
+
elements.reject do |element|
|
|
535
|
+
duplicate = seen.any? { |s| values_equal?(s, element) }
|
|
536
|
+
seen << element
|
|
537
|
+
duplicate
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# Tables 18: difference covers bag and set operands; the right
|
|
542
|
+
# operand may be an element singleton. The result keeps the left
|
|
543
|
+
# operand's kind.
|
|
544
|
+
def aggregate_difference(left, right)
|
|
545
|
+
kind = left.is_a?(V::Aggregate) ? left.kind : right.kind
|
|
546
|
+
unless %i[bag set].include?(kind)
|
|
547
|
+
raise Error,
|
|
548
|
+
"difference requires bag or set operands (12.6, table 18)"
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
left_elements = left.is_a?(V::Aggregate) ? left.elements : [left]
|
|
552
|
+
right_elements = right.is_a?(V::Aggregate) ? right.elements : [right]
|
|
553
|
+
remaining = left_elements.reject do |element|
|
|
554
|
+
right_elements.any? { |r| values_equal?(r, element) }
|
|
555
|
+
end
|
|
556
|
+
V::Aggregate.of(kind, remaining)
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# Table 16: intersection covers bag and set operands only;
|
|
560
|
+
# bag∩bag → bag, anything else → set.
|
|
561
|
+
def aggregate_intersection(left, right)
|
|
562
|
+
unless aggregate_pair?(left, right) &&
|
|
563
|
+
%i[bag set].include?(left.kind) &&
|
|
564
|
+
%i[bag set].include?(right.kind)
|
|
565
|
+
raise Error,
|
|
566
|
+
"intersection requires bag or set operands (12.6, table 16)"
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
kind = left.kind == :bag && right.kind == :bag ? :bag : :set
|
|
570
|
+
kept = left.elements.select do |element|
|
|
571
|
+
right.elements.any? { |r| values_equal?(r, element) }
|
|
572
|
+
end
|
|
573
|
+
V::Aggregate.of(kind, dedup_if_set(kind, kept))
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
def values_equal?(one, other)
|
|
577
|
+
return true if one.equal?(other)
|
|
578
|
+
return false if one.class != other.class
|
|
579
|
+
|
|
580
|
+
case one
|
|
581
|
+
when V::Enumeration then one.item == other.item
|
|
582
|
+
when V::Logical then one.value == other.value
|
|
583
|
+
when V::Binary then one.bits == other.bits
|
|
584
|
+
when V::Aggregate
|
|
585
|
+
one.elements.size == other.elements.size &&
|
|
586
|
+
one.elements.zip(other.elements).all? { |x, y| values_equal?(x, y) }
|
|
587
|
+
else one == other
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# ---- indeterminate propagation ----
|
|
592
|
+
|
|
593
|
+
def indeterminate?(*values)
|
|
594
|
+
values.any? { |value| value.equal?(V::INDETERMINATE) }
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def describe(value)
|
|
598
|
+
case value
|
|
599
|
+
when V::Logical then "LOGICAL"
|
|
600
|
+
when V::Enumeration then "ENUMERATION"
|
|
601
|
+
when V::Binary then "BINARY"
|
|
602
|
+
when V::Aggregate then "aggregate"
|
|
603
|
+
when String then "string"
|
|
604
|
+
when Numeric then "number"
|
|
605
|
+
else value.equal?(V::INDETERMINATE) ? "?" : value.class.name
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
end
|