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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2398c1c908306ac808914717a67e74f8085ee6048deb9c9dd665f4dd8c39cc2f
4
- data.tar.gz: 1022c2df415418d26f1b84b1b16830cc525829182a82e93c1a450d0dd4b5bdd2
3
+ metadata.gz: c136e4c7cc9c5dde0ced8c828ab80882900b278db86010986eb7271a7b45309e
4
+ data.tar.gz: 46ff912715738e99c454727224a5e19f6d1635fbfccb1ade3820a9a51a996d0d
5
5
  SHA512:
6
- metadata.gz: 6a31f0bab47d9c5c0d40720c52422717e0d04a9c03bb03a115ac62c7f541226dc3d9b7e9aa052bf55d574e1193ecab82e97b0610d43bbe1702038024486a1953
7
- data.tar.gz: f20bf20a890d1b23f3ec7d2d113a3340c6255132a367a655cddab52ded9e2276bd236439d242ca0ea46f857096a5f5885620dd9a104f8b0f337c9aba75e50366
6
+ metadata.gz: 9d36ad79125c72a9e804f10ce9e0e5983e852b428f1a1a3718e21ef9394c66f39e57bec366ed81257bd5e62439429cc1cd93039710404db2c8dc0f3d9c09c44a
7
+ data.tar.gz: 0cb236654de0a57184387087c5a7f47dc49d548c1fed0167638439c071b83cda592cd0346f68ffc086c74afccff0b1f08c90ec977238213b9672fa8244052b27
data/README.md CHANGED
@@ -41,6 +41,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
41
41
  | `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
42
42
  | `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
43
43
  | `with-open`, `tail!` | not provided |
44
+ | macros | not provided for now |
44
45
 
45
46
  Kapusta-specific additions:
46
47
 
@@ -0,0 +1,7 @@
1
+ (fn contains-duplicate? [nums]
2
+ (let [seen (collect [n nums] (values n true))]
3
+ (< (length seen) (length nums))))
4
+
5
+ (print (contains-duplicate? [1 2 3 1]))
6
+ (print (contains-duplicate? [1 2 3 4]))
7
+ (print (contains-duplicate? [1 1 1 3 3 4 3 2 4 2]))
@@ -13,9 +13,8 @@ module Kapusta
13
13
  name_sym = args[0]
14
14
  pattern = args[1]
15
15
  body = args[2..]
16
- ruby_name = temp(sanitize_local(name_sym.name))
17
16
  fn_env = env.child
18
- fn_env.define(name_sym.name, ruby_name)
17
+ ruby_name = define_local(fn_env, name_sym.name)
19
18
  <<~RUBY.chomp
20
19
  (-> do
21
20
  #{ruby_name} = nil
@@ -27,22 +26,40 @@ module Kapusta
27
26
  end
28
27
 
29
28
  def emit_lambda(pattern, body, env, current_scope)
29
+ return emit_simple_lambda(pattern, body, env, current_scope) if simple_parameter_pattern?(pattern)
30
+
30
31
  args_var = temp('args')
31
32
  body_env = env.child
32
33
  bindings_code, body_env = emit_pattern_bind(pattern, args_var, body_env)
33
34
  body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
34
- <<~RUBY.chomp
35
- ->(*#{args_var}) do
36
- #{bindings_code}
37
- #{body_code}
38
- end
39
- RUBY
35
+ block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
36
+ block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
37
+ [
38
+ "->(*#{args_var}#{block_locals_clause}) do",
39
+ indent(join_code(bindings_code, body_code)),
40
+ 'end'
41
+ ].join("\n")
42
+ end
43
+
44
+ def emit_simple_lambda(pattern, body, env, current_scope)
45
+ body_env = env.child
46
+ params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
47
+ body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
48
+ header = params.empty? ? 'proc do' : "proc do |#{params.join(', ')}|"
49
+ [
50
+ header,
51
+ indent(body_code),
52
+ 'end'
53
+ ].join("\n")
54
+ end
55
+
56
+ def simple_parameter_pattern?(pattern)
57
+ pattern.is_a?(Vec) && pattern.items.all? { |item| item.is_a?(Sym) && !item.dotted? && item.name != '&' }
40
58
  end
41
59
 
42
60
  def emit_named_fn_assignment(form, env, current_scope)
43
61
  name_sym = form.items[1]
44
- ruby_name = temp(sanitize_local(name_sym.name))
45
- env.define(name_sym.name, ruby_name)
62
+ ruby_name = define_local(env, name_sym.name)
46
63
  fn_env = env.child
47
64
  fn_env.define(name_sym.name, ruby_name)
48
65
  lambda_code = emit_lambda(form.items[2], form.items[3..], fn_env, current_scope)
@@ -53,31 +70,59 @@ module Kapusta
53
70
  name_sym = form.items[1]
54
71
  pattern = form.items[2]
55
72
  body = form.items[3..]
56
- method_env = env.child
57
- args_var = temp('args')
58
- bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
59
- body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
73
+ block_header, body_code = emit_method_body(pattern, body, env)
60
74
 
61
75
  if name_sym.name.start_with?('self.')
62
76
  ruby_name = Kapusta.kebab_to_snake(name_sym.name.delete_prefix('self.')).to_sym.inspect
63
- <<~RUBY.chomp
64
- define_singleton_method(#{ruby_name}) do |*#{args_var}|
65
- #{bindings_code}
66
- #{body_code}
67
- end
68
- RUBY
77
+ [
78
+ "define_singleton_method(#{ruby_name}) #{block_header}",
79
+ indent(body_code),
80
+ 'end'
81
+ ].join("\n")
69
82
  else
70
83
  ruby_name = Kapusta.kebab_to_snake(name_sym.name).to_sym.inspect
71
- <<~RUBY.chomp
72
- define_method(#{ruby_name}) do |*#{args_var}|
73
- #{bindings_code}
74
- #{body_code}
75
- end
76
- RUBY
84
+ [
85
+ "define_method(#{ruby_name}) #{block_header}",
86
+ indent(body_code),
87
+ 'end'
88
+ ].join("\n")
77
89
  end
78
90
  end
79
91
 
92
+ def emit_method_body(pattern, body, env)
93
+ return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
94
+
95
+ args_var = temp('args')
96
+ method_env = env.child
97
+ bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
98
+ body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
99
+ block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
100
+ block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
101
+ ["do |*#{args_var}#{block_locals_clause}|", join_code(bindings_code, body_code)]
102
+ end
103
+
104
+ def emit_simple_method_body(pattern, body, env)
105
+ body_env = env.child
106
+ params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
107
+ body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
108
+ [params.empty? ? 'do' : "do |#{params.join(', ')}|", body_code]
109
+ end
110
+
80
111
  def emit_let(args, env, current_scope)
112
+ binding_code, body_code = emit_let_parts(args, env, current_scope, result: true)
113
+ <<~RUBY.chomp
114
+ (-> do
115
+ #{indent(join_code(binding_code, body_code))}
116
+ end).call
117
+ RUBY
118
+ end
119
+
120
+ def emit_let_statement(args, env, current_scope)
121
+ binding_code, body_code = emit_let_parts(args, env, current_scope, result: false)
122
+ join_code(binding_code, body_code)
123
+ end
124
+
125
+ def emit_let_parts(args, env, current_scope, result:)
81
126
  bindings = args[0]
82
127
  body = args[1..]
83
128
  child_env = env.child
@@ -91,13 +136,14 @@ module Kapusta
91
136
  binding_codes << bind_code
92
137
  i += 2
93
138
  end
94
- body_code, = emit_sequence(body, child_env, current_scope, allow_method_definitions: false)
95
- <<~RUBY.chomp
96
- (-> do
97
- #{binding_codes.join("\n")}
98
- #{body_code}
99
- end).call
100
- RUBY
139
+ body_code, = emit_sequence(body, child_env, current_scope,
140
+ allow_method_definitions: false,
141
+ result:)
142
+ [binding_codes.join("\n"), body_code]
143
+ end
144
+
145
+ def join_code(*chunks)
146
+ chunks.reject(&:empty?).join("\n")
101
147
  end
102
148
 
103
149
  def emit_local_form(form, env, current_scope)
@@ -105,8 +151,7 @@ module Kapusta
105
151
  value_code = emit_expr(form.items[2], env, current_scope)
106
152
 
107
153
  if target.is_a?(Sym)
108
- ruby_name = temp(sanitize_local(target.name))
109
- env.define(target.name, ruby_name)
154
+ ruby_name = define_local(env, target.name)
110
155
  ["#{ruby_name} = #{value_code}\nnil", env]
111
156
  else
112
157
  bind_code, env = emit_pattern_bind(target, value_code, env)
@@ -128,9 +173,7 @@ module Kapusta
128
173
  if env.defined?(target.name)
129
174
  env.lookup(target.name)
130
175
  else
131
- fresh = temp(sanitize_local(target.name))
132
- env.define(target.name, fresh)
133
- fresh
176
+ define_local(env, target.name)
134
177
  end
135
178
  ["#{ruby_name} = #{value_code}", env]
136
179
  else
@@ -164,7 +207,12 @@ module Kapusta
164
207
  elsif head.is_a?(Sym) && head.name == 'cvar'
165
208
  runtime_call(:set_cvar, 'self', target.items[1].name.inspect, value_code)
166
209
  elsif head.is_a?(Sym) && head.name == 'gvar'
167
- runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
210
+ ruby_name = global_name(target.items[1].name)
211
+ if direct_global_name?(ruby_name)
212
+ "$#{ruby_name} = #{value_code}"
213
+ else
214
+ runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
215
+ end
168
216
  else
169
217
  raise Error, "bad set target: #{target.inspect}"
170
218
  end
@@ -10,75 +10,39 @@ module Kapusta
10
10
  result_var = temp('result')
11
11
  iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
12
12
  body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
13
- <<~RUBY.chomp
14
- __kap_value = begin
15
- #{indent(body)}
16
- end
17
- #{result_var} << __kap_value unless __kap_value.nil?
18
- RUBY
13
+ emit_array_collection_step(result_var, body)
19
14
  end
20
- <<~RUBY.chomp
21
- (-> do
22
- #{result_var} = []
23
- #{iter_code}
24
- #{result_var}
25
- end).call
26
- RUBY
15
+ emit_collection_result(result_var, '[]', iter_code)
27
16
  end
28
17
 
29
18
  def emit_collect(args, env, current_scope)
30
19
  result_var = temp('result')
31
20
  iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
32
21
  body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
33
- <<~RUBY.chomp
34
- __kap_pair = begin
35
- #{indent(body)}
36
- end
37
- if __kap_pair.is_a?(Array) && __kap_pair.length == 2 && !__kap_pair[0].nil? && !__kap_pair[1].nil?
38
- #{result_var}[__kap_pair[0]] = __kap_pair[1]
39
- end
40
- RUBY
22
+ emit_hash_collection_step(result_var, body)
41
23
  end
42
- <<~RUBY.chomp
43
- (-> do
44
- #{result_var} = {}
45
- #{iter_code}
46
- #{result_var}
47
- end).call
48
- RUBY
24
+ emit_collection_result(result_var, '{}', iter_code)
49
25
  end
50
26
 
51
27
  def emit_fcollect(args, env, current_scope)
52
28
  result_var = temp('result')
53
29
  parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
54
30
  body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope, allow_method_definitions: false)
55
- collecting_body = <<~RUBY.chomp
56
- __kap_value = begin
57
- #{indent(body_code)}
58
- end
59
- #{result_var} << __kap_value unless __kap_value.nil?
60
- RUBY
31
+ collecting_body = emit_array_collection_step(result_var, body_code)
61
32
  loop_code = emit_counted_loop(**parsed, current_scope:, body_code: collecting_body)
62
- <<~RUBY.chomp
63
- (-> do
64
- #{result_var} = []
65
- #{indent(loop_code)}
66
- #{result_var}
67
- end).call
68
- RUBY
33
+ emit_collection_result(result_var, '[]', loop_code)
69
34
  end
70
35
 
71
36
  def emit_accumulate(args, env, current_scope)
72
37
  bindings = args[0].items
73
38
  acc_name = bindings[0]
74
- acc_var = temp(sanitize_local(acc_name.name))
75
39
  iter_bindings = Vec.new(bindings[2..])
76
40
  loop_env = env.child
77
- loop_env.define(acc_name.name, acc_var)
41
+ acc_var = define_local(loop_env, acc_name.name)
78
42
  iter_code = emit_iteration(iter_bindings, loop_env, current_scope) do |iter_env|
79
43
  iter_env.define(acc_name.name, acc_var)
80
44
  emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first.then do |body|
81
- "#{acc_var} = begin\n#{indent(body)}\nend"
45
+ emit_sequence_value_assignment(acc_var, body)
82
46
  end
83
47
  end
84
48
  <<~RUBY.chomp
@@ -93,14 +57,12 @@ module Kapusta
93
57
  def emit_faccumulate(args, env, current_scope)
94
58
  bindings = args[0].items
95
59
  acc_name = bindings[0]
96
- acc_var = temp(sanitize_local(acc_name.name))
97
60
  loop_name = bindings[2]
98
- loop_var = temp(sanitize_local(loop_name.name))
99
61
  loop_env = env.child
100
- loop_env.define(acc_name.name, acc_var)
101
- loop_env.define(loop_name.name, loop_var)
62
+ acc_var = define_local(loop_env, acc_name.name)
63
+ loop_var = define_local(loop_env, loop_name.name)
102
64
  body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
103
- accumulating_body = "#{acc_var} = begin\n#{indent(body_code)}\nend"
65
+ accumulating_body = emit_sequence_value_assignment(acc_var, body_code)
104
66
  loop_code = emit_counted_loop(
105
67
  ruby_name: loop_var,
106
68
  start_code: emit_expr(bindings[3], env, current_scope),
@@ -210,6 +172,42 @@ module Kapusta
210
172
  end.compact
211
173
  [codes.join("\n"), current_env]
212
174
  end
175
+
176
+ def emit_collection_result(result_var, initial_code, iter_code)
177
+ <<~RUBY.chomp
178
+ (-> do
179
+ #{result_var} = #{initial_code}
180
+ #{indent(iter_code)}
181
+ #{result_var}
182
+ end).call
183
+ RUBY
184
+ end
185
+
186
+ def emit_array_collection_step(result_var, body_code)
187
+ value_var = temp('value')
188
+ <<~RUBY.chomp
189
+ #{emit_sequence_value_assignment(value_var, body_code)}
190
+ #{result_var} << #{value_var} unless #{value_var}.nil?
191
+ RUBY
192
+ end
193
+
194
+ def emit_hash_collection_step(result_var, body_code)
195
+ pair_var = temp('pair')
196
+ <<~RUBY.chomp
197
+ #{emit_sequence_value_assignment(pair_var, body_code)}
198
+ if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 && !#{pair_var}[0].nil? && !#{pair_var}[1].nil?
199
+ #{result_var}[#{pair_var}[0]] = #{pair_var}[1]
200
+ end
201
+ RUBY
202
+ end
203
+
204
+ def emit_sequence_value_assignment(target_var, body_code)
205
+ <<~RUBY.chomp
206
+ #{target_var} = begin
207
+ #{indent(body_code)}
208
+ end
209
+ RUBY
210
+ end
213
211
  end
214
212
  end
215
213
  end
@@ -16,14 +16,35 @@ module Kapusta
16
16
 
17
17
  cond = emit_expr(args[0], env, current_scope)
18
18
  truthy = emit_expr(args[1], env, current_scope)
19
- falsy = build_if(args[2..], env, current_scope)
20
- <<~RUBY.chomp
21
- if #{cond}
22
- #{truthy}
23
- else
24
- #{indent(falsy)}
25
- end
26
- RUBY
19
+ lines = ["if #{cond}", indent(truthy)]
20
+ append_else_lines(lines, args[2..], env, current_scope)
21
+ lines << 'end'
22
+ lines.join("\n")
23
+ end
24
+
25
+ def append_else_lines(lines, args, env, current_scope)
26
+ return if args.empty?
27
+
28
+ if args.length == 1 && if_form?(args[0])
29
+ append_elsif_lines(lines, args[0].rest, env, current_scope)
30
+ elsif args.length >= 2
31
+ append_elsif_lines(lines, args, env, current_scope)
32
+ else
33
+ lines << 'else'
34
+ lines << indent(emit_expr(args[0], env, current_scope))
35
+ end
36
+ end
37
+
38
+ def append_elsif_lines(lines, args, env, current_scope)
39
+ return append_else_lines(lines, args, env, current_scope) if args.length < 2
40
+
41
+ lines << "elsif #{emit_expr(args[0], env, current_scope)}"
42
+ lines << indent(emit_expr(args[1], env, current_scope))
43
+ append_else_lines(lines, args[2..], env, current_scope)
44
+ end
45
+
46
+ def if_form?(form)
47
+ form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'if'
27
48
  end
28
49
 
29
50
  def emit_case(args, env, current_scope, mode)
@@ -106,21 +127,16 @@ module Kapusta
106
127
  end
107
128
 
108
129
  def emit_while(args, env, current_scope)
109
- body_code, = emit_sequence(args[1..], env, current_scope, allow_method_definitions: false)
110
130
  <<~RUBY.chomp
111
131
  (-> do
112
- while #{emit_expr(args[0], env, current_scope)}
113
- #{indent(body_code)}
114
- end
132
+ #{indent(emit_while_statement(args, env, current_scope))}
115
133
  nil
116
134
  end).call
117
135
  RUBY
118
136
  end
119
137
 
120
138
  def emit_for(args, env, current_scope)
121
- parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
122
- body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope, allow_method_definitions: false)
123
- loop_code = emit_counted_loop(**parsed, current_scope:, body_code:)
139
+ loop_code = emit_for_statement(args, env, current_scope)
124
140
  <<~RUBY.chomp
125
141
  (-> do
126
142
  #{indent(loop_code)}
@@ -130,9 +146,7 @@ module Kapusta
130
146
  end
131
147
 
132
148
  def emit_each(args, env, current_scope)
133
- iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
134
- emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
135
- end
149
+ iter_code = emit_each_statement(args, env, current_scope)
136
150
  <<~RUBY.chomp
137
151
  (-> do
138
152
  #{iter_code}
@@ -140,6 +154,33 @@ module Kapusta
140
154
  end).call
141
155
  RUBY
142
156
  end
157
+
158
+ def emit_while_statement(args, env, current_scope)
159
+ body_code, = emit_sequence(args[1..], env, current_scope,
160
+ allow_method_definitions: false,
161
+ result: false)
162
+ [
163
+ "while #{emit_expr(args[0], env, current_scope)}",
164
+ indent(body_code),
165
+ 'end'
166
+ ].join("\n")
167
+ end
168
+
169
+ def emit_for_statement(args, env, current_scope)
170
+ parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
171
+ body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope,
172
+ allow_method_definitions: false,
173
+ result: false)
174
+ emit_counted_loop(**parsed, current_scope:, body_code:)
175
+ end
176
+
177
+ def emit_each_statement(args, env, current_scope)
178
+ emit_iteration(args[0], env, current_scope) do |iter_env|
179
+ emit_sequence(args[1..], iter_env, current_scope,
180
+ allow_method_definitions: false,
181
+ result: false).first
182
+ end
183
+ end
143
184
  end
144
185
  end
145
186
  end
@@ -79,7 +79,7 @@ module Kapusta
79
79
  when 'raise' then emit_raise(args, env, current_scope)
80
80
  when 'ivar' then runtime_call(:get_ivar, 'self', args[0].name.inspect)
81
81
  when 'cvar' then runtime_call(:get_cvar, 'self', args[0].name.inspect)
82
- when 'gvar' then runtime_call(:get_gvar, args[0].name.inspect)
82
+ when 'gvar' then emit_gvar(args[0])
83
83
  when 'ruby' then "Kernel.eval(#{emit_expr(args[0], env, current_scope)})"
84
84
  when 'and' then emit_and(args, env, current_scope)
85
85
  when 'or' then emit_or(args, env, current_scope)