kapusta 0.1.3 → 0.1.4

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.
@@ -37,26 +37,40 @@ module Kapusta
37
37
  end
38
38
 
39
39
  def emit_module_expr(args, env)
40
- emit_module_wrapper(args[0], emit_sequence(args[1..], env, :module, allow_method_definitions: true).first)
40
+ body = emit_sequence(args[1..], env, :module, allow_method_definitions: true, result: false).first
41
+ emit_module_wrapper(args[0], body)
41
42
  end
42
43
 
43
44
  def emit_class_expr(args, env)
44
45
  name_sym, supers, body_forms = split_class_args(args)
46
+ body = emit_sequence(body_forms, env, :class, allow_method_definitions: true, result: false).first
45
47
  emit_class_wrapper(name_sym, supers, env,
46
- emit_sequence(body_forms, env, :class, allow_method_definitions: true).first)
48
+ body)
47
49
  end
48
50
 
49
51
  def emit_module_wrapper(name_sym, body)
50
52
  mod_var = temp('module')
51
- <<~RUBY.chomp
52
- (-> do
53
- #{mod_var} = #{runtime_call(:ensure_module, 'self', name_sym.name.inspect)}
54
- #{mod_var}.module_eval do
55
- #{indent(body)}
56
- end
57
- #{mod_var}
58
- end).call
59
- RUBY
53
+ [
54
+ '(-> do',
55
+ indent("#{mod_var} = #{runtime_call(:ensure_module, 'self', name_sym.name.inspect)}"),
56
+ indent("#{mod_var}.module_eval do"),
57
+ indent(body, 2),
58
+ indent('end'),
59
+ indent(mod_var),
60
+ 'end).call'
61
+ ].join("\n")
62
+ end
63
+
64
+ def emit_direct_module_header(name_sym, body)
65
+ const_name = simple_constant_name(name_sym)
66
+ return nil unless const_name
67
+
68
+ [
69
+ "module #{const_name}",
70
+ indent(body),
71
+ 'end',
72
+ const_name
73
+ ].join("\n")
60
74
  end
61
75
 
62
76
  def emit_class_wrapper(name_sym, supers, env, body)
@@ -67,15 +81,33 @@ module Kapusta
67
81
  else
68
82
  'Object'
69
83
  end
70
- <<~RUBY.chomp
71
- (-> do
72
- #{klass_var} = #{runtime_call(:ensure_class, 'self', name_sym.name.inspect, super_code)}
73
- #{klass_var}.class_eval do
74
- #{indent(body)}
75
- end
76
- #{klass_var}
77
- end).call
78
- RUBY
84
+ [
85
+ '(-> do',
86
+ indent("#{klass_var} = #{runtime_call(:ensure_class, 'self', name_sym.name.inspect, super_code)}"),
87
+ indent("#{klass_var}.class_eval do"),
88
+ indent(body, 2),
89
+ indent('end'),
90
+ indent(klass_var),
91
+ 'end).call'
92
+ ].join("\n")
93
+ end
94
+
95
+ def emit_direct_class_header(name_sym, supers, body)
96
+ const_name = simple_constant_name(name_sym)
97
+ return nil unless const_name && supers.nil?
98
+
99
+ [
100
+ "class #{const_name}",
101
+ indent(body),
102
+ 'end',
103
+ const_name
104
+ ].join("\n")
105
+ end
106
+
107
+ def simple_constant_name(name_sym)
108
+ return nil unless name_sym.is_a?(Sym) && name_sym.name.match?(/\A[A-Z]\w*\z/)
109
+
110
+ name_sym.name
79
111
  end
80
112
 
81
113
  def emit_try(args, env, current_scope)
@@ -103,8 +135,7 @@ module Kapusta
103
135
  lines = ['begin', indent(emit_expr(args[0], env, current_scope))]
104
136
  catches.each do |klass_form, bind_sym, body|
105
137
  rescue_env = env.child
106
- rescue_name = temp(sanitize_local(bind_sym.name))
107
- rescue_env.define(bind_sym.name, rescue_name)
138
+ rescue_name = define_local(rescue_env, bind_sym.name)
108
139
  body_code, = emit_sequence(body, rescue_env, current_scope, allow_method_definitions: false)
109
140
  rescue_line =
110
141
  if klass_form
@@ -176,9 +207,17 @@ module Kapusta
176
207
 
177
208
  def emit_callable_call(callee_code, args, env, current_scope)
178
209
  positional, kwargs, block = split_call_args(args, env, current_scope)
210
+ return emit_direct_callable_call(callee_code, positional) unless kwargs || block
211
+
179
212
  runtime_call(:call, callee_code, "[#{positional.join(', ')}]", kwargs, block)
180
213
  end
181
214
 
215
+ def emit_direct_callable_call(callee_code, positional)
216
+ rendered_args = positional.join(', ')
217
+ suffix = rendered_args.empty? ? '.call' : ".call(#{rendered_args})"
218
+ "#{parenthesize(callee_code)}#{suffix}"
219
+ end
220
+
182
221
  def emit_multisym_call(head, args, env, current_scope)
183
222
  base_code, segments = multisym_base(head.segments, env)
184
223
  if segments.empty?
@@ -192,10 +231,21 @@ module Kapusta
192
231
  end
193
232
  method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
194
233
  positional, kwargs, block = split_call_args(args, env, current_scope)
234
+ if segments.length == 1 && !kwargs && !block && direct_method_name?(segments.last)
235
+ return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
236
+ end
237
+
195
238
  runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
196
239
  end
197
240
  end
198
241
 
242
+ def emit_direct_method_call(receiver, method_name, positional)
243
+ args = positional.join(', ')
244
+ rendered_receiver = simple_expression?(receiver) ? receiver : parenthesize(receiver)
245
+ suffix = args.empty? ? method_name : "#{method_name}(#{args})"
246
+ "#{rendered_receiver}.#{suffix}"
247
+ end
248
+
199
249
  def emit_self_call(name, args, env, current_scope)
200
250
  positional, kwargs, block = split_call_args(args, env, current_scope)
201
251
  method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
@@ -243,6 +293,13 @@ module Kapusta
243
293
  raise Error, "undefined symbol: #{name}"
244
294
  end
245
295
 
296
+ def emit_gvar(sym)
297
+ ruby_name = global_name(sym.name)
298
+ return "$#{ruby_name}" if direct_global_name?(ruby_name)
299
+
300
+ runtime_call(:get_gvar, sym.name.inspect)
301
+ end
302
+
246
303
  def emit_multisym_value(sym, env)
247
304
  base_code, segments = multisym_base(sym.segments, env)
248
305
  return base_code if segments.empty?
@@ -269,8 +326,34 @@ module Kapusta
269
326
  end
270
327
 
271
328
  def parenthesize(code)
329
+ return code if simple_expression?(code)
330
+
272
331
  "(#{code})"
273
332
  end
333
+
334
+ def direct_method_name?(name)
335
+ Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
336
+ end
337
+
338
+ def direct_global_name?(name)
339
+ name.match?(/\A[a-z_]\w*\z/)
340
+ end
341
+
342
+ def global_name(name)
343
+ Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
344
+ end
345
+
346
+ def simple_expression?(code)
347
+ code.match?(/\A[a-z_]\w*\z/) ||
348
+ code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
349
+ code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
350
+ code.match?(/\A\d+(?:\.\d+)?\z/) ||
351
+ code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?)+\z/) ||
352
+ code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
353
+ code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
354
+ code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
355
+ %w[nil true false self].include?(code)
356
+ end
274
357
  end
275
358
  end
276
359
  end
@@ -10,8 +10,7 @@ module Kapusta
10
10
  if pattern.is_a?(Sym)
11
11
  return ['nil', env] if pattern.name == '_'
12
12
 
13
- ruby_name = temp(sanitize_local(pattern.name))
14
- env.define(pattern.name, ruby_name)
13
+ ruby_name = define_local(env, pattern.name)
15
14
  ["#{ruby_name} = #{value_code}", env]
16
15
  else
17
16
  bindings_var = temp('bindings')
@@ -20,8 +19,7 @@ module Kapusta
20
19
  "#{bindings_var} = #{runtime_call(:destructure, emit_pattern(pattern), value_code)}"
21
20
  ]
22
21
  pattern_names(pattern).each do |name|
23
- ruby_name = temp(sanitize_local(name))
24
- current_env.define(name, ruby_name)
22
+ ruby_name = define_local(current_env, name)
25
23
  lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
26
24
  end
27
25
  [lines.join("\n"), current_env]
@@ -32,8 +30,7 @@ module Kapusta
32
30
  current_env = env
33
31
  lines = []
34
32
  binding_names.each do |name|
35
- ruby_name = temp(sanitize_local(name))
36
- current_env.define(name, ruby_name)
33
+ ruby_name = define_local(current_env, name)
37
34
  lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
38
35
  end
39
36
  [lines.join("\n"), current_env]
@@ -6,7 +6,7 @@ module Kapusta
6
6
  module Support
7
7
  private
8
8
 
9
- def emit_forms_with_headers(forms, env, current_scope)
9
+ def emit_forms_with_headers(forms, env, current_scope, result: true)
10
10
  i = 0
11
11
  codes = []
12
12
  while i < forms.length
@@ -15,7 +15,9 @@ module Kapusta
15
15
  codes << emit_bodyless_header(form, forms[(i + 1)..], env, current_scope)
16
16
  break
17
17
  else
18
- code, env = emit_form_in_sequence(form, env, current_scope, allow_method_definitions: true)
18
+ code, env = emit_form_in_sequence(form, env, current_scope,
19
+ allow_method_definitions: true,
20
+ result_needed: result && i == forms.length - 1)
19
21
  codes << code
20
22
  i += 1
21
23
  end
@@ -50,25 +52,29 @@ module Kapusta
50
52
  if inner.length == 1 && bodyless_header?(inner[0])
51
53
  emit_bodyless_header(inner[0], remaining_forms, env, :module)
52
54
  else
53
- emit_forms_with_headers(remaining_forms, env, :module)
55
+ emit_forms_with_headers(remaining_forms, env, :module, result: false)
54
56
  end
55
- emit_module_wrapper(name_sym, body)
57
+ emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body)
56
58
  else
57
59
  name_sym, supers, = split_class_args(form.items[1..])
58
- body = emit_forms_with_headers(remaining_forms, env, :class)
59
- emit_class_wrapper(name_sym, supers, env, body)
60
+ body = emit_forms_with_headers(remaining_forms, env, :class, result: false)
61
+ emit_direct_class_header(name_sym, supers, body) || emit_class_wrapper(name_sym, supers, env, body)
60
62
  end
61
63
  end
62
64
 
63
- def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:)
65
+ def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
64
66
  if allow_method_definitions && method_definition_form?(form) && %i[module class].include?(current_scope)
65
67
  [emit_method_definition(form, env), env]
66
68
  elsif named_function_form?(form)
67
69
  emit_named_fn_assignment(form, env, current_scope)
68
70
  elsif local_form?(form)
69
- emit_local_form(form, env, current_scope)
71
+ code, env = emit_local_form(form, env, current_scope)
72
+ code = code.delete_suffix("\nnil") unless result_needed
73
+ [code, env]
70
74
  elsif do_form?(form)
71
- emit_do_form(form.rest, env, current_scope)
75
+ emit_do_form(form.rest, env, current_scope, result_needed:)
76
+ elsif sequence_statement_form?(form)
77
+ emit_sequence_statement_form(form, env, current_scope, result_needed:)
72
78
  elsif set_new_local_form?(form, env)
73
79
  emit_set_form(form, env, current_scope)
74
80
  else
@@ -76,22 +82,51 @@ module Kapusta
76
82
  end
77
83
  end
78
84
 
79
- def emit_do_form(forms, env, current_scope)
80
- body, new_env = emit_sequence(forms, env, current_scope, allow_method_definitions: false)
85
+ def emit_do_form(forms, env, current_scope, result_needed: true)
86
+ body, new_env = emit_sequence(forms, env, current_scope,
87
+ allow_method_definitions: false,
88
+ result: result_needed)
89
+ return [body, new_env] unless result_needed
90
+
81
91
  ["begin\n#{indent(body)}\nend", new_env]
82
92
  end
83
93
 
84
- def emit_sequence(forms, env, current_scope, allow_method_definitions:)
94
+ def emit_sequence(forms, env, current_scope, allow_method_definitions:, result: true)
85
95
  current_env = env
86
96
  codes = []
87
- forms.each do |form|
97
+ forms.each_with_index do |form, index|
88
98
  code, current_env = emit_form_in_sequence(form, current_env, current_scope,
89
- allow_method_definitions:)
99
+ allow_method_definitions:,
100
+ result_needed: result && index == forms.length - 1)
90
101
  codes << code
91
102
  end
92
103
  [codes.join("\n"), current_env]
93
104
  end
94
105
 
106
+ def sequence_statement_form?(form)
107
+ return false unless form.is_a?(List) && form.head.is_a?(Sym)
108
+
109
+ %w[let while for each].include?(form.head.name)
110
+ end
111
+
112
+ def emit_sequence_statement_form(form, env, current_scope, result_needed:)
113
+ case form.head.name
114
+ when 'let'
115
+ return [emit_let_statement(form.rest, env, current_scope), env] unless result_needed
116
+ when 'while'
117
+ return [emit_while_statement(form.rest, env, current_scope), env]
118
+ when 'for'
119
+ return [emit_for_statement(form.rest, env, current_scope), env]
120
+ when 'each'
121
+ code = emit_each_statement(form.rest, env, current_scope)
122
+ return ["#{code}\nnil", env] if result_needed
123
+
124
+ return [code, env]
125
+ end
126
+
127
+ [emit_expr(form, env, current_scope), env]
128
+ end
129
+
95
130
  def special_form?(name)
96
131
  Compiler::SPECIAL_FORMS.include?(name)
97
132
  end
@@ -140,7 +175,35 @@ module Kapusta
140
175
 
141
176
  def temp(prefix)
142
177
  @temp_index += 1
143
- "__kap_#{prefix}_#{@temp_index}"
178
+ "kap_#{prefix}_#{@temp_index}"
179
+ end
180
+
181
+ def define_local(env, source_name, shadow: false)
182
+ ruby_name = local_name(source_name, env, shadow:)
183
+ env.define(source_name, ruby_name)
184
+ ruby_name
185
+ end
186
+
187
+ def local_name(source_name, env, shadow:)
188
+ base = sanitize_local(source_name)
189
+ base = "user_#{base}" if reserved_generated_name?(base)
190
+ return base unless ruby_name_defined?(env, base, shadow:)
191
+
192
+ index = 2
193
+ loop do
194
+ candidate = "#{base}_#{index}"
195
+ return candidate unless ruby_name_defined?(env, candidate, shadow:)
196
+
197
+ index += 1
198
+ end
199
+ end
200
+
201
+ def ruby_name_defined?(env, name, shadow:)
202
+ shadow ? env.local_ruby_name_defined?(name) : env.ruby_name_defined?(name)
203
+ end
204
+
205
+ def reserved_generated_name?(name)
206
+ name.start_with?('kap_', '__kap_')
144
207
  end
145
208
 
146
209
  def runtime_helper(name)
@@ -158,9 +221,8 @@ module Kapusta
158
221
 
159
222
  def parse_counted_for_bindings(bindings, env, current_scope)
160
223
  name_sym = bindings[0]
161
- ruby_name = temp(sanitize_local(name_sym.name))
162
224
  loop_env = env.child
163
- loop_env.define(name_sym.name, ruby_name)
225
+ ruby_name = define_local(loop_env, name_sym.name)
164
226
  start_code = emit_expr(bindings[1], env, current_scope)
165
227
  finish_code = emit_expr(bindings[2], env, current_scope)
166
228
  step_code = '1'
@@ -184,24 +246,25 @@ module Kapusta
184
246
  step_var = temp('step')
185
247
  cmp_var = temp('cmp')
186
248
  until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
187
- <<~RUBY.chomp
188
- #{ruby_name} = #{start_code}
189
- #{finish_var} = #{finish_code}
190
- #{step_var} = #{step_code}
191
- #{cmp_var} = #{step_var} >= 0 ? :<= : :>=
192
- while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})
193
- #{until_code}
194
- #{indent(body_code)}
195
- #{ruby_name} += #{step_var}
196
- end
197
- RUBY
249
+ body = [until_code, body_code, "#{ruby_name} += #{step_var}"].compact.reject(&:empty?).join("\n")
250
+ [
251
+ "#{ruby_name} = #{start_code}",
252
+ "#{finish_var} = #{finish_code}",
253
+ "#{step_var} = #{step_code}",
254
+ "#{cmp_var} = #{step_var} >= 0 ? :<= : :>=",
255
+ "while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})",
256
+ indent(body),
257
+ 'end'
258
+ ].join("\n")
198
259
  end
199
260
 
200
261
  def sanitize_local(name)
201
262
  base = Kapusta.kebab_to_snake(name)
202
263
  base = base.gsub('?', '_q').gsub('!', '_bang')
203
264
  base = base.gsub(/[^a-zA-Z0-9_]/, '_')
204
- base = "_#{base}" if base.empty? || base.match?(/\A\d/) || self.class::RUBY_KEYWORDS.include?(base)
265
+ if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
266
+ base = "_#{base}"
267
+ end
205
268
  base
206
269
  end
207
270
  end