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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52369f7a4cd658385e7ddc69f552477d94b55340cb9f796ac635550a7def6e6a
4
- data.tar.gz: 6d673c9c387b52b0e4cd9e55b8ae2cf1d0a7e2c5f741dc7705f405b0ddaf25c6
3
+ metadata.gz: 53512e4cdebbb07a50f1bff53338574994140e5b16ddc5c1fd05e27b8cbc8a20
4
+ data.tar.gz: c7907d4901ecfb193c4df0c0b09bca2869babab65f6327d542c59a88a9edff51
5
5
  SHA512:
6
- metadata.gz: 53510507ba41a4d67ec73184eaef665fee182220407046eb9db64cf05cae7d28a315b5b8cf4ace6071b359a83801bd85d7e45ce5e4aefd3c608fef614f116fee
7
- data.tar.gz: ff1b88fc11dc10ccffc1459686b17754bd39dc6a941756d7cab304253aec3b63bc36967c9edcf50cf4c29eadee3ae2afb0467383b6428dcb74f41c452173ab4a
6
+ metadata.gz: c185f6d33f64071522cef35498fb7310f47d4091d3873868361294ff40edb51e836cdb2c0168dac1f599bc9ccd7c230d34d276fa6ab1a3fca479a4a4c40b9025
7
+ data.tar.gz: dcf0acd60680784bb38685305f5454466e4c36358d08a2994e2ca988e5fa19d32fa6eafdb48bb1054681e5699daaddc4b9ebf73477f0450d8dc78482cfad0147
data/README.md CHANGED
@@ -29,6 +29,18 @@ exe/kapusta --compile examples/fizzbuzz.kap > examples/fizzbuzz.rb
29
29
  ruby examples/fizzbuzz.rb
30
30
  ```
31
31
 
32
+ ## Using from Ruby
33
+
34
+ Ruby can require a `.kap` file and use it directly.
35
+
36
+ ```
37
+ require 'kapusta'
38
+ Kapusta.require('./bank-account', relative_to: __FILE__)
39
+ account = BankAccount.new('Ada', 100)
40
+ ```
41
+
42
+ See `examples/bank-account.kap` and `examples/use_bank_account.rb`.
43
+
32
44
  ## Comparison with Fennel
33
45
 
34
46
  Kapusta keeps most core Fennel forms. The main differences come from Ruby's runtime and object model.
@@ -40,6 +52,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
40
52
  | `(. xs 1)` is the first element | `(. xs 0)` is the first element |
41
53
  | `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
42
54
  | `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
55
+ | `(print x)` is Lua's `print` (bare) | `(print x)` is Ruby's `p` (inspect-style) |
43
56
  | `with-open`, `tail!` | not provided |
44
57
  | macros | not provided for now |
45
58
 
@@ -0,0 +1,21 @@
1
+ (class BankAccount)
2
+
3
+ (fn initialize [owner balance]
4
+ (set (ivar owner) owner)
5
+ (set (ivar balance) balance))
6
+
7
+ (fn owner []
8
+ (ivar owner))
9
+
10
+ (fn balance []
11
+ (ivar balance))
12
+
13
+ (fn deposit [amount]
14
+ (set (ivar balance)
15
+ (+ (ivar balance) amount))
16
+ self)
17
+
18
+ (fn withdraw [amount]
19
+ (set (ivar balance)
20
+ (- (ivar balance) amount))
21
+ self)
@@ -0,0 +1,11 @@
1
+ (fn cal-points [ops]
2
+ (let [scores []]
3
+ (each [op ops]
4
+ (if (= op "C") (do (-?> (scores.pop) (: :abs)) nil)
5
+ (= op "D") (scores.push (* 2 (. scores -1)))
6
+ (= op "+") (scores.push (+ (. scores -1) (. scores -2)))
7
+ (scores.push (Integer op))))
8
+ scores.sum))
9
+
10
+ (print (cal-points ["5" "2" "C" "D" "+"]))
11
+ (print (cal-points ["5" "-2" "4" "C" "D" "9" "+" "+"]))
@@ -0,0 +1,13 @@
1
+ (fn climb-stairs [n]
2
+ (var prev 1)
3
+ (var curr 1)
4
+ (for [_ 2 n]
5
+ (let [next (+ prev curr)]
6
+ (set prev curr)
7
+ (set curr next)))
8
+ curr)
9
+
10
+ (print (climb-stairs 2))
11
+ (print (climb-stairs 3))
12
+ (print (climb-stairs 5))
13
+ (print (climb-stairs 10))
@@ -0,0 +1,5 @@
1
+ (let [__doto__ 99
2
+ xs []]
3
+ (doto xs
4
+ (: :push __doto__))
5
+ (print (xs.inspect)))
@@ -0,0 +1,20 @@
1
+ (fn sum-of-squares [n]
2
+ (var x n)
3
+ (var total 0)
4
+ (while (> x 0)
5
+ (let [d (% x 10)]
6
+ (set total (+ total (* d d)))
7
+ (set x (: (/ x 10) :floor))))
8
+ total)
9
+
10
+ (fn happy? [n]
11
+ (let [seen {}]
12
+ (var x n)
13
+ (while (and (not= x 1) (not (seen.key? x)))
14
+ (tset seen x true)
15
+ (set x (sum-of-squares x)))
16
+ (= x 1)))
17
+
18
+ (print (happy? 19))
19
+ (print (happy? 2))
20
+ (print (happy? 1))
@@ -0,0 +1,7 @@
1
+ (fn length-of-last-word [s]
2
+ (let [words (s.strip.split)]
3
+ (: (. words -1) :length)))
4
+
5
+ (print (length-of-last-word "Hello World"))
6
+ (print (length-of-last-word " fly me to the moon "))
7
+ (print (length-of-last-word "luffy is still joyboy"))
@@ -0,0 +1,12 @@
1
+ (fn max-subarray [nums]
2
+ (var best (. nums 0))
3
+ (var curr (. nums 0))
4
+ (for [i 1 (- (length nums) 1)]
5
+ (let [n (. nums i)]
6
+ (set curr (if (> (+ curr n) n) (+ curr n) n))
7
+ (when (> curr best) (set best curr))))
8
+ best)
9
+
10
+ (print (max-subarray [-2 1 -3 4 -1 2 1 -5 4]))
11
+ (print (max-subarray [1]))
12
+ (print (max-subarray [5 4 -1 7 8]))
@@ -0,0 +1,13 @@
1
+ (fn move-zeroes [nums]
2
+ (var write 0)
3
+ (for [read 0 (- (length nums) 1)]
4
+ (when (not= (. nums read) 0)
5
+ (tset nums write (. nums read))
6
+ (set write (+ write 1))))
7
+ (for [i write (- (length nums) 1)]
8
+ (tset nums i 0))
9
+ nums)
10
+
11
+ (print (move-zeroes [0 1 0 3 12]))
12
+ (print (move-zeroes [0]))
13
+ (print (move-zeroes [1 2 3]))
data/examples/stack.kap CHANGED
@@ -1,19 +1,36 @@
1
- (class Stack)
1
+ (class MinStack)
2
2
 
3
3
  (fn initialize []
4
- (set (ivar xs) []))
4
+ (set (ivar xs) [])
5
+ (set (ivar mins) []))
5
6
 
6
- (fn push! [x]
7
- (let [xs (ivar xs)]
8
- (xs.push x))
7
+ (fn push [x]
8
+ (let [xs (ivar xs)
9
+ mins (ivar mins)
10
+ current-min (if (mins.empty?) x (< x (. mins -1)) x (. mins -1))]
11
+ (xs.push x)
12
+ (mins.push current-min))
9
13
  self)
10
14
 
11
- (fn pop! []
15
+ (fn pop []
16
+ (let [mins (ivar mins)]
17
+ (mins.pop))
12
18
  (let [xs (ivar xs)]
13
19
  (xs.pop)))
14
20
 
15
- (fn empty? []
16
- (= 0 (length (ivar xs))))
21
+ (fn top []
22
+ (. (ivar xs) -1))
23
+
24
+ (fn get-min []
25
+ (. (ivar mins) -1))
26
+
27
+ (let [s (MinStack.new)]
28
+ (s.push -2)
29
+ (s.push 0)
30
+ (s.push -3)
31
+ (print (s.get-min))
32
+ (s.pop)
33
+ (print (s.top))
34
+ (print (s.get-min)))
17
35
 
18
- (fn size []
19
- (length (ivar xs)))
36
+ (print (= MinStack.superclass Object))
@@ -0,0 +1,17 @@
1
+ (let [
2
+ two-sum-hash
3
+ (fn [nums target seen]
4
+ (var i 0)
5
+ (var answer nil)
6
+ (while (and (< i (length nums)) (= answer nil))
7
+ (let [n (. nums i)
8
+ complement (- target n)]
9
+ (if (seen.key? complement)
10
+ (set answer [(. seen complement) i])
11
+ (tset seen n i)))
12
+ (set i (+ i 1)))
13
+ answer)
14
+ ]
15
+ (print (two-sum-hash [2 7 11 15] 9 {}))
16
+ (print (two-sum-hash [3 2 4] 6 {}))
17
+ (print (two-sum-hash [1 2 3] 10 {})))
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'kapusta'
5
+
6
+ Kapusta.require('./bank-account', relative_to: __FILE__)
7
+
8
+ account = BankAccount.new('Ada', 100)
9
+ account.deposit(50)
10
+ account.withdraw(30)
11
+
12
+ puts "Owner: #{account.owner}"
13
+ puts "Balance: #{account.balance}"
@@ -0,0 +1,19 @@
1
+ (class ValidParenthesesSolution)
2
+
3
+ (fn initialize []
4
+ (set (ivar pairs)
5
+ {")" "(" "]" "[" "}" "{"}))
6
+
7
+ (fn valid? [s]
8
+ (let [pairs (ivar pairs)
9
+ stack []
10
+ chars (s.chars)]
11
+ (var i 0)
12
+ (var ok true)
13
+ (while (and ok (< i (length chars)))
14
+ (let [ch (. chars i)]
15
+ (if (pairs.key? ch)
16
+ (if (-?> (stack.pop) (= (. pairs ch))) nil (set ok false))
17
+ (stack.push ch)))
18
+ (set i (+ i 1)))
19
+ (and ok (stack.empty?))))
@@ -0,0 +1,8 @@
1
+ (require "./valid-parentheses-1")
2
+
3
+ (let [solution (ValidParenthesesSolution.new)]
4
+ (print (solution.valid? "()"))
5
+ (print (solution.valid? "()[]{}"))
6
+ (print (solution.valid? "([])"))
7
+ (print (solution.valid? "(]"))
8
+ (print (solution.valid? "([)]")))
data/lib/kapusta/ast.rb CHANGED
@@ -25,12 +25,16 @@ module Kapusta
25
25
  end
26
26
 
27
27
  def ==(other)
28
- other.is_a?(Sym) && other.name == @name
28
+ other.instance_of?(self.class) && other.name == @name
29
29
  end
30
30
  alias eql? ==
31
31
 
32
32
  def hash
33
- @name.hash
33
+ [self.class, @name].hash
34
+ end
35
+
36
+ def binding_key
37
+ @name
34
38
  end
35
39
 
36
40
  def dotted?
@@ -42,6 +46,32 @@ module Kapusta
42
46
  end
43
47
  end
44
48
 
49
+ class GeneratedSym < Sym
50
+ attr_reader :id
51
+
52
+ def initialize(name, id)
53
+ super(name)
54
+ @id = id
55
+ end
56
+
57
+ def inspect
58
+ "#<GeneratedSym #{@name} #{@id}>"
59
+ end
60
+
61
+ def ==(other)
62
+ other.is_a?(GeneratedSym) && other.id == @id
63
+ end
64
+ alias eql? ==
65
+
66
+ def hash
67
+ [self.class, @id].hash
68
+ end
69
+
70
+ def binding_key
71
+ [self.class, @id]
72
+ end
73
+ end
74
+
45
75
  class Vec
46
76
  attr_reader :items
47
77
 
@@ -62,7 +92,7 @@ module Kapusta
62
92
  end
63
93
 
64
94
  def all_sym_keys?
65
- pairs.all? { |key, _| key.is_a?(Symbol) }
95
+ pairs.any? && pairs.all? { |key, _| key.is_a?(Symbol) }
66
96
  end
67
97
  end
68
98
 
@@ -42,9 +42,7 @@ module Kapusta
42
42
  end
43
43
 
44
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)
45
+ params, body_code = build_simple_block_parts(pattern, body, env, current_scope)
48
46
  header = params.empty? ? 'proc do' : "proc do |#{params.join(', ')}|"
49
47
  [
50
48
  header,
@@ -53,6 +51,13 @@ module Kapusta
53
51
  ].join("\n")
54
52
  end
55
53
 
54
+ def build_simple_block_parts(pattern, body, env, current_scope)
55
+ body_env = env.child
56
+ params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
57
+ body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
58
+ [params, body_code]
59
+ end
60
+
56
61
  def simple_parameter_pattern?(pattern)
57
62
  pattern.is_a?(Vec) && pattern.items.all? { |item| item.is_a?(Sym) && !item.dotted? && item.name != '&' }
58
63
  end
@@ -172,9 +177,7 @@ module Kapusta
172
177
  end
173
178
 
174
179
  def emit_simple_method_body(pattern, body, env)
175
- body_env = env.child
176
- params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
177
- body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
180
+ params, body_code = build_simple_block_parts(pattern, body, env, :toplevel)
178
181
  [params.empty? ? 'do' : "do |#{params.join(', ')}|", body_code]
179
182
  end
180
183
 
@@ -200,9 +203,12 @@ module Kapusta
200
203
 
201
204
  def sym_captures_outer_binding?(sym, env, local_names)
202
205
  name = sym.dotted? ? sym.segments.first : sym.name
203
- return false if local_names.include?(name) || !env.defined?(name)
206
+ return false if local_names.include?(name)
207
+
208
+ binding = env.lookup_if_defined(name)
209
+ return false if binding.nil?
204
210
 
205
- !method_binding?(env.lookup(name))
211
+ !method_binding?(binding)
206
212
  end
207
213
 
208
214
  def emit_let(args, env, current_scope)
@@ -266,10 +272,10 @@ module Kapusta
266
272
  value_code = emit_expr(form.items[2], env, current_scope)
267
273
 
268
274
  if target.is_a?(Sym) && !target.dotted?
275
+ binding = env.lookup_if_defined(target.name)
269
276
  ruby_name =
270
- if env.defined?(target.name)
271
- binding = env.lookup(target.name)
272
- raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
277
+ if binding
278
+ emit_error!("cannot set method binding: #{target.name}") if method_binding?(binding)
273
279
 
274
280
  binding
275
281
  else
@@ -292,10 +298,17 @@ module Kapusta
292
298
  when Sym
293
299
  if target.dotted?
294
300
  base_code, segments = multisym_base(target.segments, env)
295
- runtime_call(:set_method_path, base_code, segments.inspect, value_code)
301
+ receiver = emit_method_path(base_code, segments[0...-1])
302
+ last = segments.last
303
+ snake = Kapusta.kebab_to_snake(last)
304
+ if direct_method_name?(last)
305
+ "#{receiver}.#{snake} = #{value_code}"
306
+ else
307
+ "#{receiver}.public_send(:\"#{snake}=\", #{value_code})"
308
+ end
296
309
  else
297
310
  binding = env.lookup(target.name)
298
- raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
311
+ emit_error!("cannot set method binding: #{target.name}") if method_binding?(binding)
299
312
 
300
313
  "#{binding} = #{value_code}"
301
314
  end
@@ -303,24 +316,21 @@ module Kapusta
303
316
  head = target.head
304
317
  if head.is_a?(Sym) && head.name == '.'
305
318
  object_code = emit_expr(target.items[1], env, current_scope)
306
- keys_code = "[#{target.items[2..].map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
307
- runtime_call(:set_path, object_code, keys_code, value_code)
319
+ keys = target.items[2..].map { |item| emit_expr(item, env, current_scope) }
320
+ receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
321
+ prefix = keys[0...-1].map { |k| "[#{k}]" }.join
322
+ "#{receiver}#{prefix}[#{keys.last}] = #{value_code}"
308
323
  elsif head.is_a?(Sym) && head.name == 'ivar'
309
- runtime_call(:set_ivar, 'self', target.items[1].name.inspect, value_code)
324
+ "@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
310
325
  elsif head.is_a?(Sym) && head.name == 'cvar'
311
- runtime_call(:set_cvar, 'self', target.items[1].name.inspect, value_code)
326
+ "@@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
312
327
  elsif head.is_a?(Sym) && head.name == 'gvar'
313
- ruby_name = global_name(target.items[1].name)
314
- if direct_global_name?(ruby_name)
315
- "$#{ruby_name} = #{value_code}"
316
- else
317
- runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
318
- end
328
+ "$#{global_name(target.items[1].name)} = #{value_code}"
319
329
  else
320
- raise Error, "bad set target: #{target.inspect}"
330
+ emit_error!("bad set target: #{target.inspect}")
321
331
  end
322
332
  else
323
- raise Error, "bad set target: #{target.inspect}"
333
+ emit_error!("bad set target: #{target.inspect}")
324
334
  end
325
335
  end
326
336
  end
@@ -82,12 +82,12 @@ module Kapusta
82
82
  arm_env = env.child
83
83
  assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
84
84
  body_code = emit_expr(body, arm_env, current_scope)
85
+ arm_body = [assign_code, body_code].reject(&:empty?).join("\n")
85
86
  <<~RUBY.chomp
86
87
  #{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
87
88
  if #{match_var}[0]
88
89
  #{bindings_var} = #{match_var}[1]
89
- #{assign_code}
90
- #{body_code}
90
+ #{arm_body}
91
91
  else
92
92
  #{indent(else_code)}
93
93
  end
@@ -104,11 +104,11 @@ module Kapusta
104
104
  assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
105
105
  guard_code = emit_case_guards(guards, arm_env, current_scope)
106
106
  body_code = emit_expr(body, arm_env, current_scope)
107
+ bindings_line = assign_code.empty? ? '' : "\n #{assign_code}"
107
108
  <<~RUBY.chomp
108
109
  #{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
109
110
  if #{match_var}[0]
110
- #{bindings_var} = #{match_var}[1]
111
- #{assign_code}
111
+ #{bindings_var} = #{match_var}[1]#{bindings_line}
112
112
  if #{guard_code}
113
113
  #{body_code}
114
114
  else
@@ -17,7 +17,7 @@ module Kapusta
17
17
  when List then emit_list(form, env, current_scope)
18
18
  when String, Symbol, Numeric, true, false, nil then form.inspect
19
19
  else
20
- raise Error, "cannot emit form: #{form.inspect}"
20
+ emit_error!("cannot emit form: #{form.inspect}")
21
21
  end
22
22
  end
23
23
 
@@ -36,7 +36,9 @@ module Kapusta
36
36
  if head.is_a?(Sym)
37
37
  return emit_special(head.name, args, env, current_scope) if special_form?(head.name)
38
38
  return emit_multisym_call(head, args, env, current_scope) if head.dotted?
39
- return emit_bound_call(env.lookup(head.name), args, env, current_scope) if env.defined?(head.name)
39
+ if (binding = env.lookup_if_defined(head.name))
40
+ return emit_bound_call(binding, args, env, current_scope)
41
+ end
40
42
 
41
43
  return emit_self_call(head.name, args, env, current_scope)
42
44
  end
@@ -69,21 +71,21 @@ module Kapusta
69
71
  when '?.' then emit_safe_lookup(args, env, current_scope)
70
72
  when ':' then emit_colon(args, env, current_scope)
71
73
  when '..' then emit_concat(args, env, current_scope)
72
- when 'length' then "(#{emit_expr(args[0], env, current_scope)}).length"
74
+ when 'length' then "#{parenthesize(emit_expr(args[0], env, current_scope))}.length"
73
75
  when 'require' then emit_require(args[0], env, current_scope)
74
76
  when 'module' then emit_module_expr(args, env)
75
77
  when 'class' then emit_class_expr(args, env)
76
78
  when 'try' then emit_try(args, env, current_scope)
77
79
  when 'raise' then emit_raise(args, env, current_scope)
78
- when 'ivar' then runtime_call(:get_ivar, 'self', args[0].name.inspect)
79
- when 'cvar' then runtime_call(:get_cvar, 'self', args[0].name.inspect)
80
+ when 'ivar' then "@#{Kapusta.kebab_to_snake(args[0].name)}"
81
+ when 'cvar' then "@@#{Kapusta.kebab_to_snake(args[0].name)}"
80
82
  when 'gvar' then emit_gvar(args[0])
81
83
  when 'ruby' then "Kernel.eval(#{emit_expr(args[0], env, current_scope)})"
82
84
  when 'and' then emit_and(args, env, current_scope)
83
85
  when 'or' then emit_or(args, env, current_scope)
84
- when 'not' then "(!#{emit_expr(args[0], env, current_scope)})"
86
+ when 'not' then "!#{parenthesize(emit_expr(args[0], env, current_scope))}"
85
87
  when '=' then emit_compare(args, env, current_scope, '==')
86
- when 'not=' then "(!#{emit_special('=', args, env, current_scope)})"
88
+ when 'not=' then emit_compare_any(args, env, current_scope, '!=')
87
89
  when '<' then emit_compare(args, env, current_scope, '<')
88
90
  when '<=' then emit_compare(args, env, current_scope, '<=')
89
91
  when '>' then emit_compare(args, env, current_scope, '>')
@@ -95,7 +97,7 @@ module Kapusta
95
97
  when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
96
98
  when 'print' then emit_print(args, env, current_scope)
97
99
  else
98
- raise Error, "unknown special form: #{name}"
100
+ emit_error!("unknown special form: #{name}")
99
101
  end
100
102
  end
101
103
 
@@ -106,17 +108,15 @@ module Kapusta
106
108
  end
107
109
 
108
110
  def emit_print(args, env, current_scope)
109
- return '$stdout.puts("")' if args.empty?
111
+ return 'p' if args.empty?
110
112
 
111
- values = args.map { |arg| emit_string_part(arg, env, current_scope) }
112
- output = values.length == 1 ? values.first : "[#{values.join(', ')}].join(\"\\t\")"
113
- "$stdout.puts(#{output})"
113
+ "p(#{args.map { |arg| emit_expr(arg, env, current_scope) }.join(', ')})"
114
114
  end
115
115
 
116
116
  def emit_string_part(arg, env, current_scope)
117
117
  return arg.inspect if arg.is_a?(String)
118
118
 
119
- runtime_call(:stringify, emit_expr(arg, env, current_scope))
119
+ "(#{emit_expr(arg, env, current_scope)}).to_s"
120
120
  end
121
121
  end
122
122
  end