rupkl 0.2.0 → 0.3.0

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.
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ class Duration < Any
6
+ include Operatable
7
+
8
+ uninstantiable_class
9
+
10
+ def initialize(parent, value, unit, position)
11
+ super(parent, value, position)
12
+ @unit = unit
13
+ end
14
+
15
+ attr_reader :unit
16
+
17
+ def value
18
+ @children[0]
19
+ end
20
+
21
+ def evaluate(_context = nil)
22
+ self
23
+ end
24
+
25
+ def to_ruby(context = nil)
26
+ value.to_ruby(context) * UNIT_FACTOR[unit]
27
+ end
28
+
29
+ def to_pkl_string(context = nil)
30
+ to_string(context)
31
+ end
32
+
33
+ def to_string(context = nil)
34
+ "#{value.to_pkl_string(context)}.#{unit}"
35
+ end
36
+
37
+ def u_op_minus(position)
38
+ Duration.new(nil, value.u_op_minus(position), unit, position)
39
+ end
40
+
41
+ def ==(other)
42
+ other.is_a?(Duration) &&
43
+ begin
44
+ l = calc_second(self)
45
+ r = calc_second(other)
46
+ l.value == r.value
47
+ end
48
+ end
49
+
50
+ [:b_op_lt, :b_op_gt, :b_op_le, :b_op_ge].each do |method_name|
51
+ class_eval(<<~M, __FILE__, __LINE__ + 1)
52
+ # def b_op_lt(r_operand, position)
53
+ # l = calc_second(self)
54
+ # r = calc_second(r_operand)
55
+ # l.b_op_lt(r, position)
56
+ # end
57
+ def #{method_name}(r_operand, position)
58
+ l = calc_second(self)
59
+ r = calc_second(r_operand)
60
+ l.#{method_name}(r, position)
61
+ end
62
+ M
63
+ end
64
+
65
+ [:b_op_add, :b_op_sub].each do |method_name|
66
+ class_eval(<<~M, __FILE__, __LINE__ + 1)
67
+ # def b_op_add(r_operand, position)
68
+ # l, r, u = align_unit(self, r_operand)
69
+ # result = l.b_op_add(r, position)
70
+ # Duration.new(nil, result, u, position)
71
+ # end
72
+ def #{method_name}(r_operand, position)
73
+ l, r, u = align_unit(self, r_operand)
74
+ result = l.#{method_name}(r, position)
75
+ Duration.new(nil, result, u, position)
76
+ end
77
+ M
78
+ end
79
+
80
+ [:b_op_mul, :b_op_rem, :b_op_exp].each do |method_name|
81
+ class_eval(<<~M, __FILE__, __LINE__ + 1)
82
+ # def b_op_mul(r_operand, position)
83
+ # result = value.b_op_mul(r_operand, position)
84
+ # Duration.new(nil, result, unit, position)
85
+ # end
86
+ def #{method_name}(r_operand, position)
87
+ result = value.#{method_name}(r_operand, position)
88
+ Duration.new(nil, result, unit, position)
89
+ end
90
+ M
91
+ end
92
+
93
+ [:b_op_div, :b_op_truncating_div].each do |method_name|
94
+ class_eval(<<~M, __FILE__, __LINE__ + 1)
95
+ # def b_op_div(r_operand, position)
96
+ # if r_operand.is_a?(Duration)
97
+ # l = calc_second(self)
98
+ # r = calc_second(r_operand)
99
+ # l.b_op_div(r, position)
100
+ # else
101
+ # result = value.b_op_div(r_operand, position)
102
+ # Duration.new(nil, result, unit, position)
103
+ # end
104
+ # end
105
+ def #{method_name}(r_operand, position)
106
+ if r_operand.is_a?(Duration)
107
+ l = calc_second(self)
108
+ r = calc_second(r_operand)
109
+ l.#{method_name}(r, position)
110
+ else
111
+ result = value.#{method_name}(r_operand, position)
112
+ Duration.new(nil, result, unit, position)
113
+ end
114
+ end
115
+ M
116
+ end
117
+
118
+ define_builtin_property(:value) do
119
+ value
120
+ end
121
+
122
+ define_builtin_property(:unit) do
123
+ String.new(self, unit.to_s, nil, position)
124
+ end
125
+
126
+ define_builtin_property(:isPositive) do
127
+ result = value.value.zero? || value.value.positive?
128
+ Boolean.new(self, result, position)
129
+ end
130
+
131
+ define_builtin_property(:isoString) do
132
+ unless value.value.finite?
133
+ message = "cannot convert duration '#{to_string(nil)}' to ISO 8601 duration"
134
+ raise EvaluationError.new(message, position)
135
+ end
136
+
137
+ String.new(self, iso8601_string, nil, position)
138
+ end
139
+
140
+ define_builtin_method(
141
+ :isBetween, first: Duration, last: Duration
142
+ ) do |args, parent, position|
143
+ r = calc_second(self)
144
+ f = calc_second(args[:first])
145
+ l = calc_second(args[:last])
146
+ r.execute_builtin_method(:isBetween, { first: f, last: l }, parent, position)
147
+ end
148
+
149
+ define_builtin_method(:toUnit, unit: String) do |args, parent, position|
150
+ unit = unit_symbol(args[:unit], position)
151
+ value = convert_unit(self, unit, position)
152
+ Duration.new(parent, value, unit, position)
153
+ end
154
+
155
+ private
156
+
157
+ UNIT_FACTOR = {
158
+ ns: 10.0**-9, us: 10.0**-6, ms: 10.0**-3,
159
+ s: 1, min: 60, h: 60 * 60, d: 24 * 60 * 60
160
+ }.freeze
161
+
162
+ def unit_factor(unit)
163
+ factor = UNIT_FACTOR[unit]
164
+ if factor >= 1
165
+ Int.new(nil, factor, nil)
166
+ else
167
+ Float.new(nil, factor, nil)
168
+ end
169
+ end
170
+
171
+ def defined_operator?(operator)
172
+ [:[], :'!@', :'&&', :'||'].none?(operator)
173
+ end
174
+
175
+ def valid_r_operand?(operator, operand)
176
+ case operator
177
+ when :*, :%, :** then operand in Number
178
+ when :/, :'~/' then operand in Number | Duration
179
+ else super
180
+ end
181
+ end
182
+
183
+ def calc_second(duration)
184
+ factor = unit_factor(duration.unit)
185
+ duration.value.b_op_mul(factor, nil)
186
+ end
187
+
188
+ def align_unit(l_operand, r_operand)
189
+ unit =
190
+ if UNIT_FACTOR[l_operand.unit] >= UNIT_FACTOR[r_operand.unit]
191
+ l_operand.unit
192
+ else
193
+ r_operand.unit
194
+ end
195
+
196
+ [
197
+ convert_unit(l_operand, unit, position),
198
+ convert_unit(r_operand, unit, position),
199
+ unit
200
+ ]
201
+ end
202
+
203
+ def convert_unit(duration, unit, position)
204
+ return duration.value if duration.unit == unit
205
+
206
+ second = calc_second(duration)
207
+ result = second.b_op_div(unit_factor(unit), position)
208
+ if result.value.to_i == result.value
209
+ Int.new(nil, result.value, position)
210
+ else
211
+ result
212
+ end
213
+ end
214
+
215
+ def iso8601_string
216
+ return 'PT0S' if value.value.zero?
217
+
218
+ parts = value.value.negative? && ['-PT'] || ['PT']
219
+ iso8601_elements.each do |unit, n|
220
+ parts << format_iso8601_element(unit, n)
221
+ end
222
+
223
+ parts.join
224
+ end
225
+
226
+ def iso8601_elements
227
+ result, _ =
228
+ [:h, :min, :s]
229
+ .inject([{}, calc_second(self).value.abs]) do |(elements, sec), unit|
230
+ q, r =
231
+ if unit == :s
232
+ sec
233
+ else
234
+ sec.divmod(UNIT_FACTOR[unit])
235
+ end
236
+ elements[unit] = q if q.positive?
237
+
238
+ [elements, r]
239
+ end
240
+
241
+ result
242
+ end
243
+
244
+ def format_iso8601_element(unit, value)
245
+ s =
246
+ if unit != :s || value.to_i == value
247
+ value
248
+ else
249
+ format('%.12f', value).sub(/0+\Z/, '')
250
+ end
251
+ "#{s}#{unit[0].upcase}"
252
+ end
253
+
254
+ def unit_symbol(string, position)
255
+ symbol = string.value.to_sym
256
+ return symbol if UNIT_FACTOR.key?(symbol)
257
+
258
+ message =
259
+ 'expected value of type ' \
260
+ '"ns"|"us"|"ms"|"s"|"min"|"h"|"d", ' \
261
+ "but got #{string.to_pkl_string(nil)}"
262
+ raise EvaluationError.new(message, position)
263
+ end
264
+ end
265
+ end
266
+ end
@@ -3,12 +3,9 @@
3
3
  module RuPkl
4
4
  module Node
5
5
  class Dynamic < Any
6
+ include Operatable
6
7
  include StructCommon
7
8
 
8
- def properties
9
- @body&.properties(visibility: :object)
10
- end
11
-
12
9
  def entries
13
10
  @body&.entries
14
11
  end
@@ -17,6 +14,10 @@ module RuPkl
17
14
  @body&.elements
18
15
  end
19
16
 
17
+ def to_ruby(context = nil)
18
+ to_pkl_object(context)
19
+ end
20
+
20
21
  def ==(other)
21
22
  other.instance_of?(self.class) &&
22
23
  match_members?(properties, other.properties, false) &&
@@ -25,29 +26,32 @@ module RuPkl
25
26
  end
26
27
 
27
28
  def find_by_key(key)
28
- find_entry(key) || find_element(key)
29
+ (find_entry(key) || find_element(key))&.value
29
30
  end
30
31
 
31
- define_builtin_method(:length) do
32
+ define_builtin_method(:length) do |_, parent, position|
32
33
  result = elements&.size || 0
33
- Int.new(nil, result, position)
34
+ Int.new(parent, result, position)
34
35
  end
35
36
 
36
- define_builtin_method(:hasProperty, name: String) do |name|
37
- result = find_property(name.value.to_sym) && true || false
38
- Boolean.new(nil, result, position)
37
+ define_builtin_method(:hasProperty, name: String) do |args, parent, position|
38
+ name = args[:name].value.to_sym
39
+ result = find_property(name) && true || false
40
+ Boolean.new(parent, result, position)
39
41
  end
40
42
 
41
- define_builtin_method(:getProperty, name: String) do |name|
42
- find_property(name.value.to_sym) ||
43
+ define_builtin_method(:getProperty, name: String) do |args, _, position|
44
+ name = args[:name].value.to_sym
45
+ find_property(name)&.value ||
43
46
  begin
44
- m = "cannot find property '#{name.value}'"
47
+ m = "cannot find property '#{name}'"
45
48
  raise EvaluationError.new(m, position)
46
49
  end
47
50
  end
48
51
 
49
- define_builtin_method(:getPropertyOrNull, name: String) do |name|
50
- find_property(name.value.to_sym) || Null.new(nil, position)
52
+ define_builtin_method(:getPropertyOrNull, name: String) do |args, parent, position|
53
+ name = args[:name].value.to_sym
54
+ find_property(name)&.value || Null.new(parent, position)
51
55
  end
52
56
 
53
57
  private
@@ -12,7 +12,7 @@ module RuPkl
12
12
 
13
13
  attr_reader :id
14
14
 
15
- def copy(parent = nil)
15
+ def copy(parent = nil, position = @position)
16
16
  self.class.new(parent, id, position)
17
17
  end
18
18
 
@@ -24,6 +24,10 @@ module RuPkl
24
24
  other
25
25
  end
26
26
  end
27
+
28
+ def to_sym
29
+ id.to_sym
30
+ end
27
31
  end
28
32
  end
29
33
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ class IfExpression
6
+ include NodeCommon
7
+
8
+ def condition
9
+ @children[0]
10
+ end
11
+
12
+ def if_expression
13
+ @children[1]
14
+ end
15
+
16
+ def else_expression
17
+ @children[2]
18
+ end
19
+
20
+ def evaluate(context = nil)
21
+ resolve_structure(context).evaluate(context)
22
+ end
23
+
24
+ def resolve_structure(context = nil)
25
+ @result ||=
26
+ if evaluate_condition(context)
27
+ if_expression
28
+ else
29
+ else_expression
30
+ end
31
+ @result.resolve_structure(context)
32
+ end
33
+
34
+ private
35
+
36
+ def evaluate_condition(context)
37
+ evaluated = condition.evaluate(context)
38
+ return evaluated.value if evaluated.is_a?(Boolean)
39
+
40
+ message = "expected type 'Boolean', but got type '#{evaluated.class_name}'"
41
+ raise EvaluationError.new(message, position)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ class IntSeq < Any
6
+ include Operatable
7
+
8
+ uninstantiable_class
9
+
10
+ def initialize(parent, start, last, step, position)
11
+ super(parent, *[start, last, step].compact, position)
12
+ end
13
+
14
+ def start
15
+ children[0]
16
+ end
17
+
18
+ def last
19
+ children[1]
20
+ end
21
+
22
+ alias_method :end, :last
23
+
24
+ def step
25
+ children[2]
26
+ end
27
+
28
+ def evaluate(_context = nil)
29
+ self
30
+ end
31
+
32
+ def to_ruby(_context = nil)
33
+ to_array(self)
34
+ end
35
+
36
+ def to_pkl_string(context = nil)
37
+ to_string(context)
38
+ end
39
+
40
+ def to_string(context = nil)
41
+ start_value, last_value, step_value = children.map { _1.to_pkl_string(context) }
42
+ if step && step.value != 1
43
+ "IntSeq(#{start_value}, #{last_value}).step(#{step_value})"
44
+ else
45
+ "IntSeq(#{start_value}, #{last_value})"
46
+ end
47
+ end
48
+
49
+ def ==(other)
50
+ other.is_a?(self.class) &&
51
+ to_array(self) == to_array(other)
52
+ end
53
+
54
+ define_builtin_property(:start) do
55
+ Int.new(self, start.value, position)
56
+ end
57
+
58
+ define_builtin_property(:end) do
59
+ Int.new(self, last.value, position)
60
+ end
61
+
62
+ define_builtin_property(:step) do
63
+ Int.new(self, step&.value || 1, position)
64
+ end
65
+
66
+ define_builtin_method(:step, step: Int) do |args, parent, position|
67
+ step = args[:step]
68
+ if step.value.zero?
69
+ message = "expected a non zero number, but got '#{step.value}'"
70
+ raise EvaluationError.new(message, position)
71
+ end
72
+
73
+ IntSeq.new(parent, start, last, step, position)
74
+ end
75
+
76
+ private
77
+
78
+ def to_array(intseq)
79
+ start_value, last_value, step_value = intseq.children.map(&:value)
80
+ Range.new(start_value, last_value).step(step_value || 1).to_a
81
+ end
82
+ end
83
+ end
84
+ end
@@ -3,19 +3,26 @@
3
3
  module RuPkl
4
4
  module Node
5
5
  class Listing < Any
6
+ include Operatable
6
7
  include StructCommon
7
8
 
8
9
  def elements
9
10
  @body&.elements
10
11
  end
11
12
 
13
+ def to_ruby(context = nil)
14
+ to_ruby_object(context) do |_properties, _entries, elements|
15
+ replace_self_array(elements, elements) || []
16
+ end
17
+ end
18
+
12
19
  def ==(other)
13
20
  other.instance_of?(self.class) &&
14
21
  match_members?(elements, other.elements, true)
15
22
  end
16
23
 
17
24
  def find_by_key(key)
18
- find_element(key)
25
+ find_element(key)&.value
19
26
  end
20
27
 
21
28
  define_builtin_property(:isEmpty) do
@@ -43,12 +50,12 @@ module RuPkl
43
50
  Listing.new(self, body, position)
44
51
  end
45
52
 
46
- define_builtin_method(:join, separator: String) do |separator|
53
+ define_builtin_method(:join, separator: String) do |args, parent, position|
47
54
  result =
48
55
  elements
49
56
  &.map { _1.value.to_string }
50
- &.join(separator.value)
51
- String.new(nil, result || '', nil, position)
57
+ &.join(args[:separator].value)
58
+ String.new(parent, result || '', nil, position)
52
59
  end
53
60
 
54
61
  private
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ module Node
5
+ class Map < Any
6
+ MapEntry = Struct.new(:key, :value) do
7
+ def match_key?(other_key)
8
+ key == other_key
9
+ end
10
+ end
11
+
12
+ include Operatable
13
+ include MemberFinder
14
+ undef_method :pkl_method
15
+
16
+ uninstantiable_class
17
+
18
+ def initialize(parent, entries, position)
19
+ @entries = compose_entries(entries, position)
20
+ super(parent, *@entries&.flat_map(&:to_a), position)
21
+ end
22
+
23
+ attr_reader :entries
24
+
25
+ def evaluate(_context = nil)
26
+ self
27
+ end
28
+
29
+ def to_ruby(context = nil)
30
+ entries&.to_h { |e| e.map { _1.to_ruby(context) } } || {}
31
+ end
32
+
33
+ def to_pkl_string(context = nil)
34
+ to_string(context)
35
+ end
36
+
37
+ def to_string(context = nil)
38
+ entry_string =
39
+ entries
40
+ &.flat_map { |entry| entry.map { _1.to_pkl_string(context) } }
41
+ &.join(', ')
42
+ "Map(#{entry_string})"
43
+ end
44
+
45
+ def b_op_add(r_operand, position)
46
+ result =
47
+ if entries && r_operand.entries
48
+ entries + r_operand.entries
49
+ else
50
+ entries || r_operand.entries
51
+ end
52
+ self.class.new(nil, result&.flat_map(&:to_a), position)
53
+ end
54
+
55
+ def ==(other)
56
+ return false unless other.is_a?(self.class)
57
+ return false unless entries&.size == other.entries&.size
58
+ return true unless entries
59
+
60
+ entries
61
+ .all? { |entry| entry.value == other.find_by_key(entry.key) }
62
+ end
63
+
64
+ def find_by_key(key)
65
+ find_entry(key)&.value
66
+ end
67
+
68
+ define_builtin_property(:isEmpty) do
69
+ result = entries.nil? || entries.empty?
70
+ Boolean.new(self, result, position)
71
+ end
72
+
73
+ define_builtin_property(:length) do
74
+ result = entries&.length || 0
75
+ Int.new(self, result, position)
76
+ end
77
+
78
+ define_builtin_property(:keys) do
79
+ keys = entries&.map(&:key)
80
+ Set.new(self, keys, position)
81
+ end
82
+
83
+ define_builtin_property(:values) do
84
+ values = entries&.map(&:value)
85
+ List.new(self, values, position)
86
+ end
87
+
88
+ define_builtin_property(:entries) do
89
+ key_value_pairs =
90
+ entries&.map { |e| Pair.new(nil, e.key, e.value, position) }
91
+ List.new(self, key_value_pairs, position)
92
+ end
93
+
94
+ private
95
+
96
+ def compose_entries(entries, position)
97
+ return if entries.nil? || entries.empty?
98
+
99
+ check_arity(entries, position)
100
+ entries.each_slice(2)&.with_object([]) do |(k, v), result|
101
+ index =
102
+ result.find_index { _1.match_key?(k) } ||
103
+ result.size
104
+ result[index] = MapEntry.new(k, v)
105
+ end
106
+ end
107
+
108
+ def check_arity(entries, position)
109
+ return if ((entries&.size || 0) % 2).zero?
110
+
111
+ m = 'number of arguments must be a multiple of two'
112
+ raise EvaluationError.new(m, position)
113
+ end
114
+
115
+ def defined_operator?(operator)
116
+ [:[], :+].any?(operator)
117
+ end
118
+ end
119
+ end
120
+ end
@@ -3,19 +3,26 @@
3
3
  module RuPkl
4
4
  module Node
5
5
  class Mapping < Any
6
+ include Operatable
6
7
  include StructCommon
7
8
 
8
9
  def entries
9
10
  @body&.entries
10
11
  end
11
12
 
13
+ def to_ruby(context = nil)
14
+ to_ruby_object(context) do |_properties, entries, _elements|
15
+ replace_self_hash(entries, entries) || {}
16
+ end
17
+ end
18
+
12
19
  def ==(other)
13
20
  other.instance_of?(self.class) &&
14
21
  match_members?(entries, other.entries, false)
15
22
  end
16
23
 
17
24
  def find_by_key(key)
18
- find_entry(key)
25
+ find_entry(key)&.value
19
26
  end
20
27
 
21
28
  define_builtin_property(:isEmpty) do
@@ -28,13 +35,13 @@ module RuPkl
28
35
  Int.new(self, result, position)
29
36
  end
30
37
 
31
- define_builtin_method(:containsKey, key: Any) do |key|
32
- result = find_entry(key) && true || false
33
- Boolean.new(nil, result, position)
38
+ define_builtin_method(:containsKey, key: Any) do |args, parent, position|
39
+ result = find_entry(args[:key]) && true || false
40
+ Boolean.new(parent, result, position)
34
41
  end
35
42
 
36
- define_builtin_method(:getOrNull, key: Any) do |key|
37
- find_entry(key) || Null.new(nil, position)
43
+ define_builtin_method(:getOrNull, key: Any) do |args, parent, position|
44
+ find_entry(args[:key])&.value || Null.new(parent, position)
38
45
  end
39
46
 
40
47
  private