rupkl 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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