kapusta 0.1.5 → 0.2.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.
@@ -8,8 +8,11 @@ module Kapusta
8
8
 
9
9
  def emit_lookup(args, env, current_scope)
10
10
  object_code = emit_expr(args[0], env, current_scope)
11
- keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }.join(', ')
12
- runtime_call(:get_path, object_code, "[#{keys}]")
11
+ keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
12
+ return object_code if keys.empty?
13
+
14
+ receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
15
+ "#{receiver}#{keys.map { |k| "[#{k}]" }.join}"
13
16
  end
14
17
 
15
18
  def emit_safe_lookup(args, env, current_scope)
@@ -20,9 +23,16 @@ module Kapusta
20
23
 
21
24
  def emit_colon(args, env, current_scope)
22
25
  receiver = emit_expr(args[0], env, current_scope)
23
- method_name = emit_method_name(args[1], env, current_scope)
26
+ method_form = args[1]
24
27
  positional, kwargs, block = split_call_args(args[2..], env, current_scope)
25
- runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
28
+ literal_name = method_form if method_form.is_a?(Symbol) || method_form.is_a?(String)
29
+ if literal_name && !kwargs && !block && direct_method_name?(literal_name.to_s)
30
+ return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(literal_name.to_s), positional)
31
+ end
32
+
33
+ method_name = emit_method_name(method_form, env, current_scope)
34
+ parts = build_call_args([method_name, *positional], kwargs, block)
35
+ "#{parenthesize(receiver)}.public_send(#{parts})"
26
36
  end
27
37
 
28
38
  def emit_require(arg, env, current_scope)
@@ -33,9 +43,30 @@ module Kapusta
33
43
  when String then arg.inspect
34
44
  else "(#{emit_expr(arg, env, current_scope)}).to_s"
35
45
  end
46
+ if kapusta_require?(arg)
47
+ return [
48
+ 'unless defined?(Kapusta)',
49
+ indent('require "kapusta"'),
50
+ 'end',
51
+ "Kapusta.require(#{path_code}, relative_to: #{@path.inspect})"
52
+ ].join("\n")
53
+ end
54
+
36
55
  "require #{path_code}"
37
56
  end
38
57
 
58
+ def kapusta_require?(arg)
59
+ path =
60
+ case arg
61
+ when Sym then arg.name
62
+ when Symbol then arg.to_s
63
+ when String then arg
64
+ end
65
+ return false unless path
66
+
67
+ path.end_with?('.kap') || path.start_with?('./', '../') || File.absolute_path?(path)
68
+ end
69
+
39
70
  def emit_module_expr(args, env)
40
71
  body = emit_sequence(args[1..], env, :module, allow_method_definitions: true, result: false).first
41
72
  emit_module_wrapper(args[0], body)
@@ -184,6 +215,15 @@ module Kapusta
184
215
  end.join(' && ')
185
216
  end
186
217
 
218
+ def emit_compare_any(args, env, current_scope, operator)
219
+ values = args.map { |arg| emit_expr(arg, env, current_scope) }
220
+ return 'false' if values.length <= 1
221
+
222
+ (0...(values.length - 1)).map do |i|
223
+ "#{parenthesize(values[i])} #{operator} #{parenthesize(values[i + 1])}"
224
+ end.join(' || ')
225
+ end
226
+
187
227
  def emit_reduce(args, env, current_scope, empty_value, operator)
188
228
  return empty_value if args.empty?
189
229
 
@@ -207,9 +247,16 @@ module Kapusta
207
247
 
208
248
  def emit_callable_call(callee_code, args, env, current_scope)
209
249
  positional, kwargs, block = split_call_args(args, env, current_scope)
210
- return emit_direct_callable_call(callee_code, positional) unless kwargs || block
250
+ rendered = build_call_args(positional, kwargs, block)
251
+ suffix = rendered.empty? ? '.call' : ".call(#{rendered})"
252
+ "#{parenthesize(callee_code)}#{suffix}"
253
+ end
211
254
 
212
- runtime_call(:call, callee_code, "[#{positional.join(', ')}]", kwargs, block)
255
+ def build_call_args(positional, kwargs, block)
256
+ parts = positional.dup
257
+ parts << "**#{kwargs}" if kwargs
258
+ parts << "&#{block}" if block
259
+ parts.join(', ')
213
260
  end
214
261
 
215
262
  def emit_bound_call(binding, args, env, current_scope)
@@ -228,30 +275,31 @@ module Kapusta
228
275
  args.empty? ? "#{method_name}()" : "#{method_name}(#{args})"
229
276
  end
230
277
 
231
- def emit_direct_callable_call(callee_code, positional)
232
- rendered_args = positional.join(', ')
233
- suffix = rendered_args.empty? ? '.call' : ".call(#{rendered_args})"
234
- "#{parenthesize(callee_code)}#{suffix}"
235
- end
236
-
237
278
  def emit_multisym_call(head, args, env, current_scope)
238
279
  base_code, segments = multisym_base(head.segments, env)
239
280
  if segments.empty?
240
281
  emit_callable_call(base_code, args, env, current_scope)
241
282
  else
242
- receiver =
243
- if segments.length == 1
244
- base_code
245
- else
246
- runtime_call(:method_path_value, base_code, segments[0...-1].inspect)
247
- end
248
- method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
283
+ receiver = emit_method_path(base_code, segments[0...-1])
249
284
  positional, kwargs, block = split_call_args(args, env, current_scope)
250
- if segments.length == 1 && !kwargs && !block && direct_method_name?(segments.last)
285
+ if !kwargs && !block && direct_method_name?(segments.last)
251
286
  return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
252
287
  end
253
288
 
254
- runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
289
+ method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
290
+ parts = build_call_args([method_name, *positional], kwargs, block)
291
+ "#{receiver}.public_send(#{parts})"
292
+ end
293
+ end
294
+
295
+ def emit_method_path(base_code, segments)
296
+ segments.reduce(base_code) do |acc, segment|
297
+ snake = Kapusta.kebab_to_snake(segment)
298
+ if direct_method_name?(segment)
299
+ "#{acc}.#{snake}"
300
+ else
301
+ "#{acc}.public_send(#{snake.to_sym.inspect})"
302
+ end
255
303
  end
256
304
  end
257
305
 
@@ -265,7 +313,8 @@ module Kapusta
265
313
  def emit_self_call(name, args, env, current_scope)
266
314
  positional, kwargs, block = split_call_args(args, env, current_scope)
267
315
  method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
268
- runtime_call(:invoke_self, 'self', method_name, positional, kwargs, block)
316
+ parts = build_call_args([method_name, *positional], kwargs, block)
317
+ "send(#{parts})"
269
318
  end
270
319
 
271
320
  def split_call_args(args, env, current_scope)
@@ -301,33 +350,31 @@ module Kapusta
301
350
  name = sym.name
302
351
  return 'self' if name == 'self'
303
352
  return 'Float::INFINITY' if name == 'math.huge'
304
- return binding_value_code(env.lookup(name)) if env.defined?(name)
353
+
354
+ if (binding = env.lookup_if_defined(sym))
355
+ return binding_value_code(binding)
356
+ end
305
357
  return emit_multisym_value(sym, env) if sym.dotted?
306
358
  return 'ARGV' if name == 'ARGV'
307
359
  return name if name.match?(/\A[A-Z]/)
308
360
 
309
- raise Error, "undefined symbol: #{name}"
361
+ emit_error!("undefined symbol: #{name}")
310
362
  end
311
363
 
312
364
  def emit_gvar(sym)
313
- ruby_name = global_name(sym.name)
314
- return "$#{ruby_name}" if direct_global_name?(ruby_name)
315
-
316
- runtime_call(:get_gvar, sym.name.inspect)
365
+ "$#{global_name(sym.name)}"
317
366
  end
318
367
 
319
368
  def emit_multisym_value(sym, env)
320
369
  base_code, segments = multisym_base(sym.segments, env)
321
- return base_code if segments.empty?
322
-
323
- runtime_call(:method_path_value, base_code, segments.inspect)
370
+ emit_method_path(base_code, segments)
324
371
  end
325
372
 
326
373
  def multisym_base(segments, env)
327
374
  if segments[0] == 'self'
328
375
  ['self', segments[1..]]
329
- elsif env.defined?(segments[0])
330
- [binding_value_code(env.lookup(segments[0])), segments[1..]]
376
+ elsif (binding = env.lookup_if_defined(segments[0]))
377
+ [binding_value_code(binding), segments[1..]]
331
378
  else
332
379
  idx = 0
333
380
  const_path = []
@@ -335,7 +382,7 @@ module Kapusta
335
382
  const_path << segments[idx]
336
383
  idx += 1
337
384
  end
338
- raise Error, "bad multisym: #{segments.join('.')}" if const_path.empty?
385
+ emit_error!("bad multisym: #{segments.join('.')}") if const_path.empty?
339
386
 
340
387
  [const_path.join('::'), segments[idx..]]
341
388
  end
@@ -351,10 +398,6 @@ module Kapusta
351
398
  Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
352
399
  end
353
400
 
354
- def direct_global_name?(name)
355
- name.match?(/\A[a-z_]\w*\z/)
356
- end
357
-
358
401
  def global_name(name)
359
402
  Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
360
403
  end
@@ -364,11 +407,19 @@ module Kapusta
364
407
  code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
365
408
  code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
366
409
  code.match?(/\A\d+(?:\.\d+)?\z/) ||
367
- code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?)+\z/) ||
410
+ code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
368
411
  code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
369
412
  code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
370
413
  code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
371
- %w[nil true false self].include?(code)
414
+ %w[nil true false self].include?(code) ||
415
+ negation_simple?(code)
416
+ end
417
+
418
+ def negation_simple?(code)
419
+ return false unless code.start_with?('!') && code.length > 1
420
+
421
+ rest = code[1..]
422
+ simple_expression?(rest) || (rest.start_with?('(') && rest.end_with?(')'))
372
423
  end
373
424
  end
374
425
  end
@@ -10,7 +10,7 @@ module Kapusta
10
10
  if pattern.is_a?(Sym)
11
11
  return ['nil', env] if pattern.name == '_'
12
12
 
13
- ruby_name = define_local(env, pattern.name)
13
+ ruby_name = define_local(env, pattern)
14
14
  ["#{ruby_name} = #{value_code}", env]
15
15
  else
16
16
  bindings_var = temp('bindings')
@@ -61,7 +61,7 @@ module Kapusta
61
61
  elsif or_pattern?(pattern)
62
62
  emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
63
63
  elsif where_pattern?(pattern)
64
- raise Error, '`where` is only valid as a case/match clause head'
64
+ emit_error!('`where` is only valid as a case/match clause head')
65
65
  else
66
66
  emit_sequence_match_pattern(pattern.items, env, mode:, allow_pins:, state:)
67
67
  end
@@ -70,7 +70,7 @@ module Kapusta
70
70
  when Symbol, String, Numeric, true, false
71
71
  "[:lit, #{pattern.inspect}]"
72
72
  else
73
- raise Error, "bad pattern: #{pattern.inspect}"
73
+ emit_error!("bad pattern: #{pattern.inspect}")
74
74
  end
75
75
  end
76
76
 
@@ -88,10 +88,11 @@ module Kapusta
88
88
  end
89
89
 
90
90
  def emit_named_match_pattern(name, env, mode:, state:, allow_nil:, prefer_pin:)
91
+ binding = prefer_pin && mode == :match ? env.lookup_if_defined(name) : nil
91
92
  if state[:bound_names].key?(name)
92
93
  "[:ref, #{name.inspect}]"
93
- elsif prefer_pin && mode == :match && env.defined?(name)
94
- "[:pin, #{binding_value_code(env.lookup(name))}]"
94
+ elsif binding
95
+ "[:pin, #{binding_value_code(binding)}]"
95
96
  else
96
97
  state[:bound_names][name] = true
97
98
  state[:binding_names] << name
@@ -115,13 +116,15 @@ module Kapusta
115
116
  end
116
117
 
117
118
  def emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
118
- raise Error, 'pin patterns are only supported inside `case` guards' unless allow_pins && mode == :case
119
+ emit_error!('pin patterns are only supported inside `case` guards') unless allow_pins && mode == :case
119
120
 
120
121
  name_sym = pattern.items[1]
121
- raise Error, "bad pin pattern: #{pattern.inspect}" unless name_sym.is_a?(Sym)
122
- raise Error, "cannot pin undefined name: #{name_sym.name}" unless env.defined?(name_sym.name)
122
+ emit_error!("bad pin pattern: #{pattern.inspect}") unless name_sym.is_a?(Sym)
123
123
 
124
- "[:pin, #{binding_value_code(env.lookup(name_sym.name))}]"
124
+ binding = env.lookup_if_defined(name_sym.name)
125
+ emit_error!("cannot pin undefined name: #{name_sym.name}") unless binding
126
+
127
+ "[:pin, #{binding_value_code(binding)}]"
125
128
  end
126
129
 
127
130
  def emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
@@ -136,7 +139,7 @@ module Kapusta
136
139
  compiled = emit_match_pattern(subpattern, env, mode:, allow_pins:, state: alt_state)
137
140
  alt_names = alt_state[:binding_names][initial_names..]
138
141
  canonical_names ||= alt_names
139
- raise Error, 'all `or` patterns must bind the same names' if canonical_names.sort != alt_names.sort
142
+ emit_error!('all `or` patterns must bind the same names') if canonical_names.sort != alt_names.sort
140
143
 
141
144
  compiled
142
145
  end
@@ -174,7 +177,7 @@ module Kapusta
174
177
  when Symbol, String, Numeric, true, false
175
178
  "[:lit, #{pattern.inspect}]"
176
179
  else
177
- raise Error, "bad pattern: #{pattern.inspect}"
180
+ emit_error!("bad pattern: #{pattern.inspect}")
178
181
  end
179
182
  end
180
183
 
@@ -6,6 +6,10 @@ module Kapusta
6
6
  module Support
7
7
  private
8
8
 
9
+ def emit_error!(message)
10
+ raise Error, "#{@path}: #{message}"
11
+ end
12
+
9
13
  def emit_forms_with_headers(forms, env, current_scope, result: true)
10
14
  i = 0
11
15
  codes = []
@@ -191,7 +195,7 @@ module Kapusta
191
195
 
192
196
  def local_name(source_name, env, shadow:)
193
197
  base = sanitize_local(source_name)
194
- base = "user_#{base}" if reserved_generated_name?(base)
198
+ base = "user_#{base}" if !generated_symbol?(source_name) && reserved_generated_name?(base)
195
199
  return base unless ruby_name_defined?(env, base, shadow:)
196
200
 
197
201
  index = 2
@@ -211,6 +215,10 @@ module Kapusta
211
215
  name.start_with?('kap_', '__kap_')
212
216
  end
213
217
 
218
+ def generated_symbol?(source_name)
219
+ source_name.is_a?(GeneratedSym)
220
+ end
221
+
214
222
  def runtime_helper(name)
215
223
  helper = name.to_sym
216
224
  @runtime_helpers << helper unless @runtime_helpers.include?(helper)
@@ -255,24 +263,18 @@ module Kapusta
255
263
 
256
264
  def emit_counted_loop(ruby_name:, start_code:, finish_code:, step_code:,
257
265
  until_form:, loop_env:, current_scope:, body_code:)
258
- finish_var = temp('finish')
259
- step_var = temp('step')
260
- cmp_var = temp('cmp')
261
266
  until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
262
- body = [until_code, body_code, "#{ruby_name} += #{step_var}"].compact.reject(&:empty?).join("\n")
267
+ body = [until_code, body_code].compact.reject(&:empty?).join("\n")
268
+ step_part = step_code == '1' ? '' : ", #{step_code}"
263
269
  [
264
- "#{ruby_name} = #{start_code}",
265
- "#{finish_var} = #{finish_code}",
266
- "#{step_var} = #{step_code}",
267
- "#{cmp_var} = #{step_var} >= 0 ? :<= : :>=",
268
- "while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})",
270
+ "#{parenthesize(start_code)}.step(#{finish_code}#{step_part}) do |#{ruby_name}|",
269
271
  indent(body),
270
272
  'end'
271
273
  ].join("\n")
272
274
  end
273
275
 
274
276
  def sanitize_local(name)
275
- base = Kapusta.kebab_to_snake(name)
277
+ base = Kapusta.kebab_to_snake(name.respond_to?(:name) ? name.name : name)
276
278
  base = base.gsub('?', '_q').gsub('!', '_bang')
277
279
  base = base.gsub(/[^a-zA-Z0-9_]/, '_')
278
280
  if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
@@ -34,11 +34,7 @@ module Kapusta
34
34
  env = Env.new
35
35
  body = emit_forms_with_headers(forms, env, :toplevel)
36
36
  helpers = Runtime.helper_source(@runtime_helpers)
37
- [
38
- '# frozen_string_literal: true',
39
- helpers,
40
- body
41
- ].reject(&:empty?).join("\n\n") << "\n"
37
+ [helpers, body].reject(&:empty?).join("\n\n") << "\n"
42
38
  end
43
39
  end
44
40
  end
@@ -38,11 +38,11 @@ module Kapusta
38
38
  when 'when'
39
39
  cond = items[1]
40
40
  body = wrap_do(items[2..])
41
- List.new([Sym.new('if'), cond, body, nil])
41
+ List.new([Sym.new('if'), cond, body])
42
42
  when 'unless'
43
43
  cond = items[1]
44
44
  body = wrap_do(items[2..])
45
- List.new([Sym.new('if'), cond, nil, body])
45
+ List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body])
46
46
  when 'tset'
47
47
  List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]])
48
48
  when 'pcall'
@@ -85,29 +85,48 @@ module Kapusta
85
85
  short = %w[-?> -?>>].include?(kind)
86
86
  position = %w[-> -?>].include?(kind) ? :first : :last
87
87
 
88
+ return thread_short(forms, position) if short
89
+
88
90
  forms[1..].reduce(value) do |memo, form|
89
- threaded =
90
- if form.is_a?(List)
91
- if position == :first
92
- List.new([form.items[0], memo, *form.items[1..]])
93
- else
94
- List.new([*form.items, memo])
95
- end
96
- else
97
- List.new([form, memo])
98
- end
99
-
100
- if short
101
- List.new([Sym.new('if'), List.new([Sym.new('='), memo, nil]), nil, threaded])
91
+ thread_step(memo, form, position)
92
+ end
93
+ end
94
+
95
+ def thread_short(forms, position)
96
+ forms[1..].reduce(forms.first) do |memo, form|
97
+ temp = thread_temp
98
+ List.new([
99
+ Sym.new('let'),
100
+ Vec.new([temp, memo]),
101
+ List.new([
102
+ Sym.new('if'),
103
+ List.new([Sym.new('='), temp, nil]),
104
+ nil,
105
+ thread_step(temp, form, position)
106
+ ])
107
+ ])
108
+ end
109
+ end
110
+
111
+ def thread_step(memo, form, position)
112
+ if form.is_a?(List)
113
+ if position == :first
114
+ List.new([form.items[0], memo, *form.items[1..]])
102
115
  else
103
- threaded
116
+ List.new([*form.items, memo])
104
117
  end
118
+ else
119
+ List.new([form, memo])
105
120
  end
106
121
  end
107
122
 
123
+ def thread_temp
124
+ gensym('kap_thread')
125
+ end
126
+
108
127
  def doto(forms)
109
128
  value = forms.first
110
- temp = Sym.new('__doto__')
129
+ temp = gensym('kap_doto')
111
130
  body = forms[1..].map do |form|
112
131
  if form.is_a?(List)
113
132
  List.new([form.items[0], temp, *form.items[1..]])
@@ -117,6 +136,11 @@ module Kapusta
117
136
  end
118
137
  List.new([Sym.new('let'), Vec.new([temp, value]), *body, temp])
119
138
  end
139
+
140
+ def gensym(prefix)
141
+ @gensym_index = (@gensym_index || 0) + 1
142
+ GeneratedSym.new("#{prefix}_#{@gensym_index}", @gensym_index)
143
+ end
120
144
  end
121
145
  end
122
146
  end
@@ -4,106 +4,11 @@ module Kapusta
4
4
  module Compiler
5
5
  module Runtime
6
6
  HELPER_DEPENDENCIES = {
7
- stringify: %i[repr],
8
- print_values: %i[stringify],
9
- concat: %i[stringify],
10
- method_path_value: %i[kebab_to_snake],
11
- set_method_path: %i[kebab_to_snake],
12
- get_ivar: %i[kebab_to_snake],
13
- set_ivar: %i[kebab_to_snake],
14
- get_cvar: %i[current_class_scope kebab_to_snake],
15
- set_cvar: %i[current_class_scope kebab_to_snake],
16
- get_gvar: %i[kebab_to_snake],
17
- set_gvar: %i[kebab_to_snake],
18
7
  destructure: %i[destructure_into],
19
8
  match_pattern: %i[match_pattern_into]
20
9
  }.freeze
21
10
 
22
11
  HELPER_SOURCES = {
23
- kebab_to_snake: <<~RUBY.chomp,
24
- def kap_kebab_to_snake(name)
25
- name.tr('-', '_')
26
- end
27
- RUBY
28
- call: <<~'RUBY'.chomp,
29
- def kap_call(callee, positional, kwargs = nil, block = nil)
30
- raise "not callable: #{callee.inspect}" unless callee.respond_to?(:call)
31
-
32
- if block
33
- kwargs ? callee.call(*positional, **kwargs, &block) : callee.call(*positional, &block)
34
- else
35
- kwargs ? callee.call(*positional, **kwargs) : callee.call(*positional)
36
- end
37
- end
38
- RUBY
39
- send_call: <<~RUBY.chomp,
40
- def kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
41
- if block
42
- if kwargs
43
- receiver.public_send(method_name, *positional, **kwargs, &block)
44
- else
45
- receiver.public_send(method_name, *positional, &block)
46
- end
47
- elsif kwargs
48
- receiver.public_send(method_name, *positional, **kwargs)
49
- else
50
- receiver.public_send(method_name, *positional)
51
- end
52
- end
53
- RUBY
54
- invoke_self: <<~RUBY.chomp,
55
- def kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
56
- if block
57
- if kwargs
58
- receiver.send(method_name, *positional, **kwargs, &block)
59
- else
60
- receiver.send(method_name, *positional, &block)
61
- end
62
- else
63
- kwargs ? receiver.send(method_name, *positional, **kwargs) : receiver.send(method_name, *positional)
64
- end
65
- end
66
- RUBY
67
- stringify: <<~RUBY.chomp,
68
- def kap_stringify(value)
69
- case value
70
- when nil then 'nil'
71
- when Array, Hash then kap_repr(value)
72
- else value.to_s
73
- end
74
- end
75
- RUBY
76
- repr: <<~'RUBY'.chomp,
77
- def kap_repr(value)
78
- case value
79
- when nil then 'nil'
80
- when true, false then value.to_s
81
- when String, Symbol then value.inspect
82
- when Array
83
- "[#{value.map { |item| kap_repr(item) }.join(', ')}]"
84
- when Hash
85
- "{#{value.map { |key, item| "#{kap_repr(key)}=>#{kap_repr(item)}" }.join(', ')}}"
86
- else
87
- value.inspect
88
- end
89
- end
90
- RUBY
91
- print_values: <<~'RUBY'.chomp,
92
- def kap_print_values(*values)
93
- $stdout.puts(values.map { |value| kap_stringify(value) }.join("\t"))
94
- nil
95
- end
96
- RUBY
97
- concat: <<~RUBY.chomp,
98
- def kap_concat(values)
99
- values.map { |value| kap_stringify(value) }.join
100
- end
101
- RUBY
102
- get_path: <<~RUBY.chomp,
103
- def kap_get_path(obj, keys)
104
- keys.reduce(obj) { |acc, key| acc[key] }
105
- end
106
- RUBY
107
12
  qget_path: <<~RUBY.chomp,
108
13
  def kap_qget_path(obj, keys)
109
14
  keys.each do |key|
@@ -114,63 +19,6 @@ module Kapusta
114
19
  obj
115
20
  end
116
21
  RUBY
117
- set_path: <<~RUBY.chomp,
118
- def kap_set_path(obj, keys, value)
119
- target = obj
120
- keys[0...-1].each { |key| target = target[key] }
121
- target[keys.last] = value
122
- end
123
- RUBY
124
- method_path_value: <<~RUBY.chomp,
125
- def kap_method_path_value(base, segments)
126
- segments.reduce(base) { |obj, segment| obj.public_send(kap_kebab_to_snake(segment).to_sym) }
127
- end
128
- RUBY
129
- set_method_path: <<~'RUBY'.chomp,
130
- def kap_set_method_path(base, segments, value)
131
- target = base
132
- segments[0...-1].each do |segment|
133
- target = target.public_send(kap_kebab_to_snake(segment).to_sym)
134
- end
135
- setter = "#{kap_kebab_to_snake(segments.last)}="
136
- target.public_send(setter.to_sym, value)
137
- end
138
- RUBY
139
- current_class_scope: <<~RUBY.chomp,
140
- def kap_current_class_scope(receiver)
141
- receiver.is_a?(Module) ? receiver : receiver.class
142
- end
143
- RUBY
144
- get_ivar: <<~'RUBY'.chomp,
145
- def kap_get_ivar(receiver, name)
146
- receiver.instance_variable_get("@#{kap_kebab_to_snake(name)}")
147
- end
148
- RUBY
149
- set_ivar: <<~'RUBY'.chomp,
150
- def kap_set_ivar(receiver, name, value)
151
- receiver.instance_variable_set("@#{kap_kebab_to_snake(name)}", value)
152
- end
153
- RUBY
154
- get_cvar: <<~'RUBY'.chomp,
155
- def kap_get_cvar(receiver, name)
156
- kap_current_class_scope(receiver).class_variable_get("@@#{kap_kebab_to_snake(name)}")
157
- end
158
- RUBY
159
- set_cvar: <<~'RUBY'.chomp,
160
- def kap_set_cvar(receiver, name, value)
161
- kap_current_class_scope(receiver).class_variable_set("@@#{kap_kebab_to_snake(name)}", value)
162
- end
163
- RUBY
164
- get_gvar: <<~'RUBY'.chomp,
165
- def kap_get_gvar(name)
166
- Kernel.eval("$#{kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
167
- end
168
- RUBY
169
- set_gvar: <<~'RUBY'.chomp,
170
- def kap_set_gvar(name, value)
171
- Kernel.eval("$#{kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
172
- end
173
- RUBY
174
22
  ensure_module: <<~RUBY.chomp,
175
23
  def kap_ensure_module(holder, path)
176
24
  segments = path.split('.')