rupkl 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/rupkl/node/amend_expression.rb +48 -0
  4. data/lib/rupkl/node/any.rb +99 -0
  5. data/lib/rupkl/node/base.rb +45 -0
  6. data/lib/rupkl/node/boolean.rb +12 -4
  7. data/lib/rupkl/node/context.rb +27 -0
  8. data/lib/rupkl/node/declared_type.rb +32 -0
  9. data/lib/rupkl/node/dynamic.rb +28 -59
  10. data/lib/rupkl/node/identifier.rb +16 -6
  11. data/lib/rupkl/node/listing.rb +61 -0
  12. data/lib/rupkl/node/mapping.rb +47 -0
  13. data/lib/rupkl/node/member_finder.rb +55 -0
  14. data/lib/rupkl/node/member_reference.rb +44 -21
  15. data/lib/rupkl/node/method_call.rb +64 -0
  16. data/lib/rupkl/node/method_definition.rb +143 -0
  17. data/lib/rupkl/node/node_common.rb +76 -0
  18. data/lib/rupkl/node/null.rb +27 -0
  19. data/lib/rupkl/node/number.rb +136 -6
  20. data/lib/rupkl/node/object.rb +369 -73
  21. data/lib/rupkl/node/operation.rb +97 -60
  22. data/lib/rupkl/node/pkl_module.rb +16 -28
  23. data/lib/rupkl/node/reference_resolver.rb +79 -0
  24. data/lib/rupkl/node/string.rb +388 -18
  25. data/lib/rupkl/node/struct_common.rb +119 -57
  26. data/lib/rupkl/node/this.rb +17 -0
  27. data/lib/rupkl/node/type_common.rb +34 -0
  28. data/lib/rupkl/node/value_common.rb +18 -9
  29. data/lib/rupkl/parser/expression.rb +158 -41
  30. data/lib/rupkl/parser/identifier.rb +2 -2
  31. data/lib/rupkl/parser/literal.rb +18 -12
  32. data/lib/rupkl/parser/method.rb +41 -0
  33. data/lib/rupkl/parser/misc.rb +4 -0
  34. data/lib/rupkl/parser/object.rb +57 -25
  35. data/lib/rupkl/parser/pkl_class.rb +28 -8
  36. data/lib/rupkl/parser/pkl_module.rb +5 -3
  37. data/lib/rupkl/parser/type.rb +28 -0
  38. data/lib/rupkl/parser.rb +8 -0
  39. data/lib/rupkl/pkl_object.rb +26 -6
  40. data/lib/rupkl/version.rb +1 -1
  41. data/lib/rupkl.rb +25 -4
  42. metadata +33 -3
  43. data/lib/rupkl/node/pkl_class.rb +0 -30
@@ -2,25 +2,41 @@
2
2
 
3
3
  module RuPkl
4
4
  module Node
5
- class String
5
+ class String < Any
6
6
  include ValueCommon
7
7
 
8
- def initialize(value, portions, position)
9
- super(value, position)
8
+ uninstantiable_class
9
+
10
+ def initialize(parent, value, portions, position)
11
+ super(parent, value, position)
10
12
  @portions = portions
13
+ portions&.each do |portion|
14
+ portion.is_a?(NodeCommon) && add_child(portion)
15
+ end
11
16
  end
12
17
 
13
18
  attr_reader :portions
14
19
 
15
- def evaluate(scopes)
16
- s = value || evaluate_portions(scopes) || ''
17
- self.class.new(s, nil, position)
20
+ def evaluate(context = nil)
21
+ @value ||= evaluate_portions(context)
22
+ self
18
23
  end
19
24
 
20
- def to_pkl_string(scopes)
21
- super(scopes)
22
- .then { |s| escape(s) }
23
- .then { |s| "\"#{s}\"" }
25
+ def to_pkl_string(context = nil)
26
+ s = to_ruby(context)
27
+ if invalid_string?(s)
28
+ s
29
+ else
30
+ "\"#{escape(s)}\""
31
+ end
32
+ end
33
+
34
+ def copy(parent = nil)
35
+ copied_portions =
36
+ portions&.map do |portion|
37
+ portion.is_a?(NodeCommon) && portion.copy || portion
38
+ end
39
+ self.class.new(parent, nil, copied_portions, position)
24
40
  end
25
41
 
26
42
  def undefined_operator?(operator)
@@ -28,27 +44,308 @@ module RuPkl
28
44
  end
29
45
 
30
46
  def invalid_key_operand?(key)
31
- !key.is_a?(Integer)
47
+ !key.is_a?(Int)
32
48
  end
33
49
 
34
50
  def find_by_key(key)
35
51
  index = key.value
36
52
  return nil unless (0...value.length).include?(index)
37
53
 
38
- self.class.new(value[index], nil, portions)
54
+ self.class.new(parent, value[index], nil, position)
55
+ end
56
+
57
+ define_builtin_property(:length) do
58
+ Int.new(self, value.length, position)
59
+ end
60
+
61
+ define_builtin_property(:lastIndex) do
62
+ Int.new(self, value.length - 1, position)
63
+ end
64
+
65
+ define_builtin_property(:isEmpty) do
66
+ Boolean.new(self, value.empty?, position)
67
+ end
68
+
69
+ define_builtin_property(:isBlank) do
70
+ result = /\A\p{White_Space}*\z/.match?(value)
71
+ Boolean.new(self, result, position)
72
+ end
73
+
74
+ define_builtin_property(:md5) do
75
+ hash = Digest::MD5.hexdigest(value)
76
+ String.new(self, hash, nil, position)
77
+ end
78
+
79
+ define_builtin_property(:sha1) do
80
+ hash = Digest::SHA1.hexdigest(value)
81
+ String.new(self, hash, nil, position)
82
+ end
83
+
84
+ define_builtin_property(:sha256) do
85
+ hash = Digest::SHA256.hexdigest(value)
86
+ String.new(self, hash, nil, position)
87
+ end
88
+
89
+ define_builtin_property(:sha256Int) do
90
+ hash = Digest::SHA256.digest(value).unpack1('q')
91
+ Int.new(self, hash, position)
92
+ end
93
+
94
+ define_builtin_property(:base64) do
95
+ result = Base64.urlsafe_encode64(value)
96
+ String.new(self, result, nil, position)
97
+ end
98
+
99
+ define_builtin_property(:base64Decoded) do
100
+ result = Base64.urlsafe_decode64(value)
101
+ String.new(self, result, nil, position)
102
+ rescue ArgumentError
103
+ message = "illegal base64: \"#{value}\""
104
+ raise EvaluationError.new(message, position)
105
+ end
106
+
107
+ define_builtin_method(:getOrNull, index: Int) do |index|
108
+ if (0...value.size).include?(index.value)
109
+ String.new(nil, value[index.value], nil, position)
110
+ else
111
+ Null.new(nil, position)
112
+ end
113
+ end
114
+
115
+ define_builtin_method(:substring, start: Int, exclusive_end: Int) do |s, e|
116
+ check_range(s.value, 0)
117
+ check_range(e.value, s.value)
118
+
119
+ String.new(nil, value[s.value...e.value], nil, position)
120
+ end
121
+
122
+ define_builtin_method(:substringOrNull, start: Int, exclusive_end: Int) do |s, e|
123
+ if inside_range?(s.value, 0) && inside_range?(e.value, s.value)
124
+ String.new(nil, value[s.value...e.value], nil, position)
125
+ else
126
+ Null.new(nil, position)
127
+ end
128
+ end
129
+
130
+ define_builtin_method(:repeat, count: Int) do |count|
131
+ check_positive_number(count)
132
+
133
+ result = value * count.value
134
+ String.new(nil, result, nil, position)
135
+ end
136
+
137
+ define_builtin_method(:contains, pattern: String) do |pattern|
138
+ result = value.include?(pattern.value)
139
+ Boolean.new(nil, result, position)
140
+ end
141
+
142
+ define_builtin_method(:startsWith, pattern: String) do |pattern|
143
+ result = value.start_with?(pattern.value)
144
+ Boolean.new(nil, result, position)
145
+ end
146
+
147
+ define_builtin_method(:endsWith, pattern: String) do |pattern|
148
+ result = value.end_with?(pattern.value)
149
+ Boolean.new(nil, result, position)
150
+ end
151
+
152
+ define_builtin_method(:indexOf, pattern: String) do |pattern|
153
+ index_of(:index, pattern) do
154
+ message = "\"#{pattern.value}\" does not occur in \"#{value}\""
155
+ raise EvaluationError.new(message, position)
156
+ end
157
+ end
158
+
159
+ define_builtin_method(:indexOfOrNull, pattern: String) do |pattern|
160
+ index_of(:index, pattern) do
161
+ Null.new(nil, position)
162
+ end
163
+ end
164
+
165
+ define_builtin_method(:lastIndexOf, pattern: String) do |pattern|
166
+ index_of(:rindex, pattern) do
167
+ message = "\"#{pattern.value}\" does not occur in \"#{value}\""
168
+ raise EvaluationError.new(message, position)
169
+ end
170
+ end
171
+
172
+ define_builtin_method(:lastIndexOfOrNull, pattern: String) do |pattern|
173
+ index_of(:rindex, pattern) do
174
+ Null.new(nil, position)
175
+ end
176
+ end
177
+
178
+ define_builtin_method(:take, n: Int) do |n|
179
+ check_positive_number(n)
180
+
181
+ result = value[0, n.value] || value
182
+ String.new(nil, result, nil, position)
183
+ end
184
+
185
+ define_builtin_method(:takeLast, n: Int) do |n|
186
+ check_positive_number(n)
187
+
188
+ pos = value.size - n.value
189
+ result = pos.negative? && value || value[pos..]
190
+ String.new(nil, result, nil, position)
191
+ end
192
+
193
+ define_builtin_method(:drop, n: Int) do |n|
194
+ check_positive_number(n)
195
+
196
+ result = value[n.value..] || ''
197
+ String.new(nil, result, nil, position)
198
+ end
199
+
200
+ define_builtin_method(:dropLast, n: Int) do |n|
201
+ check_positive_number(n)
202
+
203
+ length = value.size - n.value
204
+ result = length.negative? && '' || value[0, length]
205
+ String.new(nil, result, nil, position)
206
+ end
207
+
208
+ define_builtin_method(
209
+ :replaceFirst,
210
+ pattern: String, replacement: String
211
+ ) do |pattern, replacement|
212
+ result = value.sub(pattern.value, replacement.value)
213
+ String.new(nil, result, nil, position)
214
+ end
215
+
216
+ define_builtin_method(
217
+ :replaceLast,
218
+ pattern: String, replacement: String
219
+ ) do |pattern, replacement|
220
+ result =
221
+ if (index = value.rindex(pattern.value))
222
+ value.dup.tap { |s| s[index, replacement.value.size] = replacement.value }
223
+ else
224
+ value
225
+ end
226
+ String.new(nil, result, nil, position)
227
+ end
228
+
229
+ define_builtin_method(
230
+ :replaceAll,
231
+ pattern: String, replacement: String
232
+ ) do |pattern, replacement|
233
+ result = value.gsub(pattern.value, replacement.value)
234
+ String.new(nil, result, nil, position)
235
+ end
236
+
237
+ define_builtin_method(
238
+ :replaceRange,
239
+ start: Int, exclusive_end: Int, replacement: String
240
+ ) do |start, exclusive_end, replacement|
241
+ check_range(start.value, 0)
242
+ check_range(exclusive_end.value, start.value)
243
+
244
+ range = start.value...exclusive_end.value
245
+ result = value.dup.tap { |s| s[range] = replacement.value }
246
+ String.new(nil, result, nil, position)
247
+ end
248
+
249
+ define_builtin_method(:toUpperCase) do
250
+ String.new(nil, value.upcase, nil, position)
251
+ end
252
+
253
+ define_builtin_method(:toLowerCase) do
254
+ String.new(nil, value.downcase, nil, position)
255
+ end
256
+
257
+ define_builtin_method(:reverse) do
258
+ String.new(nil, value.reverse, nil, position)
259
+ end
260
+
261
+ define_builtin_method(:trim) do
262
+ pattern = /(?:\A\p{White_Space}+)|(?:\p{White_Space}+\z)/
263
+ String.new(nil, value.gsub(pattern, ''), nil, position)
264
+ end
265
+
266
+ define_builtin_method(:trimStart) do
267
+ pattern = /\A\p{White_Space}+/
268
+ String.new(nil, value.sub(pattern, ''), nil, position)
269
+ end
270
+
271
+ define_builtin_method(:trimEnd) do
272
+ pattern = /\p{White_Space}+\z/
273
+ String.new(nil, value.sub(pattern, ''), nil, position)
274
+ end
275
+
276
+ define_builtin_method(:padStart, width: Int, char: String) do |width, char|
277
+ pad(width, char, :pre)
278
+ end
279
+
280
+ define_builtin_method(:padEnd, width: Int, char: String) do |width, char|
281
+ pad(width, char, :post)
282
+ end
283
+
284
+ define_builtin_method(:capitalize) do
285
+ result =
286
+ value.empty? && value || value.dup.tap { |s| s[0] = s[0].upcase }
287
+ String.new(nil, result, nil, position)
288
+ end
289
+
290
+ define_builtin_method(:decapitalize) do
291
+ result =
292
+ value.empty? && value || value.dup.tap { |s| s[0] = s[0].downcase }
293
+ String.new(nil, result, nil, position)
294
+ end
295
+
296
+ define_builtin_method(:toInt) do
297
+ to_int do
298
+ message = "cannot parse string as Int \"#{value}\""
299
+ raise EvaluationError.new(message, position)
300
+ end
301
+ end
302
+
303
+ define_builtin_method(:toIntOrNull) do
304
+ to_int do
305
+ Null.new(nil, position)
306
+ end
307
+ end
308
+
309
+ define_builtin_method(:toFloat) do
310
+ to_float do
311
+ message = "cannot parse string as Float \"#{value}\""
312
+ raise EvaluationError.new(message, position)
313
+ end
314
+ end
315
+
316
+ define_builtin_method(:toFloatOrNull) do
317
+ to_float do
318
+ Null.new(nil, position)
319
+ end
320
+ end
321
+
322
+ define_builtin_method(:toBoolean) do
323
+ to_boolean do
324
+ message = "cannot parse string as Boolean \"#{value}\""
325
+ raise EvaluationError.new(message, position)
326
+ end
327
+ end
328
+
329
+ define_builtin_method(:toBooleanOrNull) do
330
+ to_boolean do
331
+ Null.new(nil, position)
332
+ end
39
333
  end
40
334
 
41
335
  private
42
336
 
43
- def evaluate_portions(scopes)
44
- portions
45
- &.map { evaluate_portion(scopes, _1) }
46
- &.join
337
+ def evaluate_portions(context)
338
+ evaluated_portions =
339
+ portions&.map do |portion|
340
+ evaluate_portion(portion, context)
341
+ .tap { |s| return s if invalid_string?(s) }
342
+ end
343
+ evaluated_portions&.join || ''
47
344
  end
48
345
 
49
- def evaluate_portion(scopes, portion)
346
+ def evaluate_portion(portion, context)
50
347
  if portion.respond_to?(:to_string)
51
- portion.to_string(scopes)
348
+ portion.to_string(context)
52
349
  else
53
350
  portion
54
351
  end
@@ -61,6 +358,79 @@ module RuPkl
61
358
  }
62
359
  string.gsub(/([\t\n\r"\\])/) { replace[_1] }
63
360
  end
361
+
362
+ def check_range(index, start_index)
363
+ return if inside_range?(index, start_index)
364
+
365
+ message = "index #{index} is out of range " \
366
+ "#{start_index}..#{value.size}: \"#{value}\""
367
+ raise EvaluationError.new(message, position)
368
+ end
369
+
370
+ def inside_range?(index, start_index)
371
+ (start_index..value.size).include?(index)
372
+ end
373
+
374
+ def index_of(method, pattern)
375
+ result = value.__send__(method, pattern.value)
376
+ result && Int.new(nil, result, position) || yield
377
+ end
378
+
379
+ def pad(width, char, pre_post)
380
+ unless char.value.length == 1
381
+ message = "expected a char, but got \"#{char.value}\""
382
+ raise EvaluationError.new(message, position)
383
+ end
384
+
385
+ result =
386
+ pad_prefix_postfix(width, char, pre_post)
387
+ .then { |pre, post| [pre, value, post].join }
388
+ String.new(nil, result, nil, position)
389
+ end
390
+
391
+ def pad_prefix_postfix(width, char, pre_post)
392
+ pad_width = width.value - value.length
393
+ if pad_width <= 0
394
+ [nil, nil]
395
+ elsif pre_post == :pre
396
+ [char.value * pad_width, nil]
397
+ else
398
+ [nil, char.value * pad_width]
399
+ end
400
+ end
401
+
402
+ def to_int
403
+ Int.new(nil, Integer(remove_underscore_from_number(value), 10), position)
404
+ rescue ArgumentError
405
+ yield
406
+ end
407
+
408
+ def to_float
409
+ result =
410
+ case value
411
+ when 'NaN' then ::Float::NAN
412
+ when 'Infinity' then ::Float::INFINITY
413
+ when '-Infinity' then -::Float::INFINITY
414
+ else Float(remove_underscore_from_number(value))
415
+ end
416
+ Float.new(nil, result, position)
417
+ rescue ArgumentError
418
+ yield
419
+ end
420
+
421
+ def remove_underscore_from_number(string)
422
+ string.gsub(/(?:(?<=\d)|(?<=.[eE][+-]))_+/, '')
423
+ end
424
+
425
+ def to_boolean
426
+ result =
427
+ case value
428
+ when /\Atrue\z/i then true
429
+ when /\Afalse\z/i then false
430
+ else return yield
431
+ end
432
+ Boolean.new(nil, result, position)
433
+ end
64
434
  end
65
435
  end
66
436
  end
@@ -3,8 +3,68 @@
3
3
  module RuPkl
4
4
  module Node
5
5
  module StructCommon
6
- def to_string(_scopes)
7
- "new #{self.class.basename} #{to_pkl_string(nil)}"
6
+ include NodeCommon
7
+ include MemberFinder
8
+
9
+ def initialize(parent, body, position)
10
+ super
11
+ @body = body
12
+ body && check_members
13
+ end
14
+
15
+ attr_reader :body
16
+
17
+ def evaluate(context = nil)
18
+ do_evaluation(__method__, context, 1) do |c|
19
+ @body&.evaluate(c)
20
+ end
21
+ self
22
+ end
23
+
24
+ def resolve_structure(context = nil)
25
+ do_evaluation(__method__, context, 1) do |c|
26
+ @body&.resolve_structure(c)
27
+ end
28
+ self
29
+ end
30
+
31
+ def to_ruby(context = nil)
32
+ do_evaluation(__method__, context, 1, PklObject::SELF) do |c|
33
+ create_pkl_object(c)
34
+ end
35
+ end
36
+
37
+ def to_pkl_string(context = nil)
38
+ to_string(context)
39
+ end
40
+
41
+ def to_string(context = nil)
42
+ do_evaluation(__method__, context, 2, invalid_string) do |c|
43
+ to_string_object(c)
44
+ end
45
+ end
46
+
47
+ def current_context
48
+ super&.push_object(self) || Context.new(nil, [self])
49
+ end
50
+
51
+ def structure?
52
+ true
53
+ end
54
+
55
+ def copy(parent = nil)
56
+ self.class.new(parent, @body&.copy, position)
57
+ end
58
+
59
+ def merge!(*bodies)
60
+ return unless @body
61
+
62
+ @body.merge!(*bodies)
63
+ check_members
64
+ end
65
+
66
+ def undefined_operator?(operator)
67
+ [:[], :==, :'!='].none?(operator)
8
68
  end
9
69
 
10
70
  def coerce(_operator, r_operand)
@@ -13,26 +73,48 @@ module RuPkl
13
73
 
14
74
  private
15
75
 
16
- def push_scope(scopes)
17
- yield([*scopes, self])
76
+ def check_members
77
+ message =
78
+ if properties_not_allowed?
79
+ "'#{self.class.basename}' cannot have a property"
80
+ elsif entries_not_allowed?
81
+ "'#{self.class.basename}' cannot have an entry"
82
+ elsif elements_not_allowed?
83
+ "'#{self.class.basename}' cannot have an element"
84
+ end
85
+ message &&
86
+ (raise EvaluationError.new(message, position))
18
87
  end
19
88
 
20
- def add_hash_member(members, member, accessor)
21
- duplicate_member?(members, member, accessor) &&
22
- begin
23
- message = 'duplicate definition of member'
24
- raise EvaluationError.new(message, member.position)
25
- end
26
- members << member
89
+ def properties_not_allowed?
90
+ body.properties
27
91
  end
28
92
 
29
- def duplicate_member?(members, member, accessor)
30
- members
31
- .any? { _1.__send__(accessor) == member.__send__(accessor) }
93
+ def entries_not_allowed?
94
+ body.entries
95
+ end
96
+
97
+ def elements_not_allowed?
98
+ body.elements
99
+ end
100
+
101
+ def do_evaluation(method, context, limit, ifreachlimit = nil)
102
+ return ifreachlimit if reach_limit?(method, limit)
103
+
104
+ call_depth[method] += 1
105
+ result = yield(context&.push_object(self) || current_context)
106
+ call_depth.delete(method)
107
+
108
+ result
32
109
  end
33
110
 
34
- def add_array_member(members, member)
35
- members << member
111
+ def call_depth
112
+ @call_depth ||= Hash.new { |h, k| h[k] = 0 }
113
+ end
114
+
115
+ def reach_limit?(method, limit)
116
+ depth = call_depth[method]
117
+ depth >= 1 && depth >= limit
36
118
  end
37
119
 
38
120
  def match_members?(lhs, rhs, match_order)
@@ -44,58 +126,38 @@ module RuPkl
44
126
  end
45
127
  end
46
128
 
47
- def merge_hash_members(lhs, rhs, accessor)
48
- return nil unless lhs || rhs
49
- return rhs unless lhs
50
-
51
- rhs&.each do |r|
52
- if (index = find_index(lhs, r, accessor))
53
- lhs[index] = r
54
- else
55
- lhs << r
56
- end
129
+ def create_pkl_object(context)
130
+ context.push_scope(@body).then do |c|
131
+ RuPkl::PklObject.new(
132
+ to_ruby_hash(@body.properties, c),
133
+ to_ruby_hash(@body.entries, c),
134
+ to_ruby_array(@body.elements, c)
135
+ )
57
136
  end
58
-
59
- lhs
60
137
  end
61
138
 
62
- def find_index(lhs, rhs, accessor)
63
- lhs.find_index { _1.__send__(accessor) == rhs.__send__(accessor) }
139
+ def to_ruby_hash(members, context)
140
+ members&.to_h { _1.to_ruby(context) }
64
141
  end
65
142
 
66
- def merge_array_members(lhs, rhs)
67
- return nil unless lhs || rhs
68
- return rhs unless lhs
69
- return lhs unless rhs
70
-
71
- lhs.concat(rhs)
143
+ def to_ruby_array(members, context)
144
+ members&.map { _1.to_ruby(context) }
72
145
  end
73
146
 
74
- def create_pkl_object(scopes, properties, entries, elements)
75
- RuPkl::PklObject.new(
76
- to_ruby_hash_members(scopes, properties),
77
- to_ruby_hash_members(scopes, entries),
78
- to_ruby_array_members(scopes, elements)
79
- )
147
+ def to_string_object(context)
148
+ "new #{self.class.basename} #{to_string_members(context)}"
80
149
  end
81
150
 
82
- def to_ruby_hash_members(scopes, members)
83
- members
84
- &.to_h { _1.to_ruby(scopes) }
85
- end
86
-
87
- def to_ruby_array_members(scopes, members)
88
- members
89
- &.map { _1.to_ruby(scopes) }
90
- end
91
-
92
- def to_pkl_string_object(*members)
151
+ def to_string_members(context)
152
+ members = @body.members
93
153
  return '{}' if members.empty?
94
154
 
95
- members
96
- .map { _1.to_pkl_string(nil) }
97
- .join('; ')
98
- .then { "{ #{_1} }" }
155
+ context.push_scope(@body).then do |c|
156
+ members
157
+ .map { _1.to_pkl_string(c) }
158
+ .join('; ')
159
+ .then { "{ #{_1} }" }
160
+ end
99
161
  end
100
162
  end
101
163
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ class This
6
+ include NodeCommon
7
+
8
+ def evaluate(context = nil)
9
+ resolve_reference(context)
10
+ end
11
+
12
+ def resolve_reference(context = nil)
13
+ (context || current_context).objects.last
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ module TypeCommon
6
+ include NodeCommon
7
+
8
+ def check_type(value, context, position)
9
+ klass = value.class
10
+ return if match_type?(klass, context)
11
+
12
+ message =
13
+ "expected type '#{self}', but got type '#{klass.class_name}'"
14
+ raise EvaluationError.new(message, position)
15
+ end
16
+
17
+ private
18
+
19
+ def find_type(type, context)
20
+ exec_on(context) do |c|
21
+ [Base.instance, *c&.objects]
22
+ .reverse_each do |scope|
23
+ next unless scope.respond_to?(:pkl_classes)
24
+
25
+ klass = scope.pkl_classes&.fetch(type.last.id, nil)
26
+ return klass if klass
27
+ end
28
+
29
+ raise EvaluationError.new("cannot find type '#{type.last.id}'", position)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end