kapusta 0.2.2 → 0.2.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: 693c32746a17e6f3a2aa7ac2a2d23cd6e3b3d981f1ef9355a69c5cd14ea09bbe
4
- data.tar.gz: 7a500a84cbb1c8e3c1937c1fb987aa14db1b835cd63416e2f2e1d9b18309324e
3
+ metadata.gz: ef91159e2623e199fc391fa483440fa9f8ca76f273fbc31caeb0b07e891ee92c
4
+ data.tar.gz: 5468818f0ad4b0509a31635d886f3374009e3e8499a197d82f99da22c53e3a12
5
5
  SHA512:
6
- metadata.gz: fb9f5ac51dbc88892e7d2b2527c6b94d13c972276899cdf86a76f6460e7073c0c4610cbaee2142e66638108226b9a66efe1415e42394e6c8c7abd74bd4ac0d2c
7
- data.tar.gz: 53eac046879c094bd002cb6d0e9c199dfb39680d2140dde5699f62a36a26a8cc5d928ccc02315b1cad568e9b8f383cafeddf2e26cfaa4d07db3f53a29783de7e
6
+ metadata.gz: 7bcc51b164c21357c2aa2ef52e4e544cba0116ca8d8b6b1c02ecb55dc62697508a3cbb22d4c56a0a7cf0da77f53d7500827ecd2debe0760fec34ebe4d992be07
7
+ data.tar.gz: 40b1b709b5e083b94584424f9fb2ad7c3ec6908786ea80051173df8edaa380b9b5b76d0c485a6377f7e77bbed5799633501204d7a39acf0b919eb74197fabcf2
data/examples/anagram.kap CHANGED
@@ -1,8 +1,8 @@
1
1
  (fn normalize-word [word]
2
- (let [lower (word.downcase)
3
- chars (lower.chars)
4
- sorted (chars.sort)]
5
- (sorted.join)))
2
+ (-> word
3
+ (: :downcase)
4
+ (: :chars)
5
+ (: :sort)))
6
6
 
7
7
  (fn anagram? [a b]
8
8
  (= (normalize-word a)
@@ -4,7 +4,7 @@
4
4
  (if (= op "C") (do (-?> (scores.pop) (: :abs)) nil)
5
5
  (= op "D") (scores.push (* 2 (. scores -1)))
6
6
  (= op "+") (scores.push (+ (. scores -1) (. scores -2)))
7
- (scores.push (Integer op))))
7
+ (scores.push (: op :to-i))))
8
8
  scores.sum))
9
9
 
10
10
  (print (cal-points ["5" "2" "C" "D" "+"]))
data/examples/counter.kap CHANGED
@@ -4,8 +4,7 @@
4
4
  (set (ivar n) start))
5
5
 
6
6
  (fn tick []
7
- (set (ivar n) (+ (ivar n) 1))
8
- (ivar n))
7
+ (set (ivar n) (+ (ivar n) 1)))
9
8
 
10
9
  (fn value []
11
10
  (ivar n))
@@ -1,7 +1,7 @@
1
1
  (fn require-score [s]
2
2
  (if (= s "oops")
3
3
  (raise (ArgumentError.new "not a number"))
4
- (Integer s)))
4
+ (: s :to-i)))
5
5
 
6
6
  (fn parse-score [s]
7
7
  (try (require-score s)
@@ -1,6 +1,9 @@
1
1
  (fn length-of-last-word [s]
2
- (let [words (s.strip.split)]
3
- (: (. words -1) :length)))
2
+ (-> s
3
+ (: :strip)
4
+ (: :split)
5
+ (. -1)
6
+ (: :length)))
4
7
 
5
8
  (print (length-of-last-word "Hello World"))
6
9
  (print (length-of-last-word " fly me to the moon "))
@@ -1,7 +1,6 @@
1
1
  (fn palindrome? [s]
2
- (let [lower (s.downcase)
3
- normalized (lower.gsub (ruby "/[^a-z]/") "")]
4
- (= normalized (normalized.reverse))))
2
+ (let [normalized (-> s (: :downcase) (: :gsub (ruby "/[^a-z]/") ""))]
3
+ (= normalized (: normalized :reverse))))
5
4
 
6
5
  (print (palindrome? "racecar"))
7
6
  (print (palindrome? "A man, a plan, a canal: Panama"))
data/examples/pangram.kap CHANGED
@@ -1,9 +1,10 @@
1
1
  (fn pangram? [s]
2
- (let [lower (s.downcase)
3
- letters (lower.gsub (ruby "/[^a-z]/") "")
4
- chars (letters.chars)
5
- uniq (chars.uniq)]
6
- (= (uniq.length) 26)))
2
+ (= 26
3
+ (length (-> s
4
+ (: :downcase)
5
+ (: :gsub (ruby "/[^a-z]/") "")
6
+ (: :chars)
7
+ (: :uniq)))))
7
8
 
8
9
  (print (pangram? "The quick brown fox jumps over the lazy dog"))
9
10
  (print (pangram? "Hello, world"))
data/examples/pcall.kap CHANGED
@@ -1,6 +1,9 @@
1
- (let [[ok value] (pcall Integer "12")
2
- [bad-ok error] (pcall Integer "oops")
3
- [handled-ok handled] (xpcall Integer (fn [e] (e.message)) "oops")]
1
+ (fn parse-int [s]
2
+ (: Kernel :Integer s))
3
+
4
+ (let [[ok value] (pcall parse-int "12")
5
+ [bad-ok error] (pcall parse-int "oops")
6
+ [handled-ok handled] (xpcall parse-int (fn [e] (e.message)) "oops")]
4
7
  (print ok)
5
8
  (print value)
6
9
  (print bad-ok)
@@ -0,0 +1,13 @@
1
+ (fn pivot-index [nums]
2
+ (var total 0)
3
+ (each [n nums] (set total (+ total n)))
4
+ (var left 0)
5
+ (var found -1)
6
+ (each [i n (ipairs nums)]
7
+ (when (and (= found -1) (= left (- total left n))) (set found i))
8
+ (set left (+ left n)))
9
+ found)
10
+
11
+ (print (pivot-index [1 7 3 6 5 6]))
12
+ (print (pivot-index [1 2 3]))
13
+ (print (pivot-index [2 1 -1]))
@@ -7,7 +7,7 @@
7
7
  (when (div? n 5) (add-drop "Plang"))
8
8
  (when (div? n 7) (add-drop "Plong"))
9
9
  (if (empty? drops)
10
- (tostring n)
10
+ (: n :to-s)
11
11
  (drops.join)))
12
12
 
13
13
  (print (raindrops 15))
@@ -0,0 +1,7 @@
1
+ (fn single-number [nums]
2
+ (accumulate [acc 0 _ n (ipairs nums)]
3
+ (: acc :^ n)))
4
+
5
+ (print (single-number [2 2 1]))
6
+ (print (single-number [4 1 2 1 2]))
7
+ (print (single-number [1]))
data/kapusta.gemspec CHANGED
@@ -7,13 +7,13 @@ Gem::Specification.new do |spec|
7
7
  spec.version = Kapusta::VERSION
8
8
  spec.authors = ['Evgenii Morozov']
9
9
  spec.homepage = 'https://github.com/evmorov/kapusta'
10
+ spec.license = 'MIT'
10
11
 
11
12
  spec.summary = 'A Lisp for the Ruby runtime'
12
13
  spec.description = 'Kapusta is a Lisp for the Ruby runtime.'
13
14
  spec.required_ruby_version = '>= 3.1'
14
15
 
15
16
  spec.metadata['rubygems_mfa_required'] = 'true'
16
- spec.metadata['homepage_uri'] = spec.homepage
17
17
  spec.metadata['source_code_uri'] = spec.homepage
18
18
  spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
19
19
 
@@ -242,7 +242,7 @@ module Kapusta
242
242
  body_code, = emit_sequence(body, child_env, current_scope,
243
243
  allow_method_definitions: false,
244
244
  result:)
245
- [binding_codes.join("\n"), body_code]
245
+ [binding_codes.reject(&:empty?).join("\n"), body_code]
246
246
  end
247
247
 
248
248
  def join_code(*chunks)
@@ -258,7 +258,7 @@ module Kapusta
258
258
  ["#{ruby_name} = #{value_code}\nnil", env]
259
259
  else
260
260
  bind_code, env = emit_pattern_bind(target, value_code, env)
261
- ["#{bind_code}\nnil", env]
261
+ [join_code(bind_code, 'nil'), env]
262
262
  end
263
263
  end
264
264
 
@@ -106,22 +106,19 @@ module Kapusta
106
106
  if iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
107
107
  case iter_expr.head.name
108
108
  when 'ipairs'
109
- value_var = temp('value')
110
- index_var = temp('index')
111
109
  body_env = env.child
112
- bind_code, body_env = emit_iteration_bindings(
113
- [[binding_pats[0], index_var], [binding_pats[1], value_var]], body_env
114
- )
110
+ value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
111
+ index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
112
+ bind_code = [index_bind, value_bind].compact.join("\n")
115
113
  body_code = yield(body_env)
116
114
  header = "#{emit_expr(iter_expr.items[1], env, current_scope)}" \
117
115
  ".each_with_index do |#{value_var}, #{index_var}|"
118
116
  return iteration_block(header, bind_code, body_code)
119
117
  when 'pairs'
120
- key_var = temp('key')
121
- value_var = temp('value')
122
118
  body_env = env.child
123
- bind_code, body_env = emit_iteration_bindings([[binding_pats[0], key_var], [binding_pats[1], value_var]],
124
- body_env)
119
+ key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
120
+ value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
121
+ bind_code = [key_bind, value_bind].compact.join("\n")
125
122
  body_code = yield(body_env)
126
123
  header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.each do |#{key_var}, #{value_var}|"
127
124
  return iteration_block(header, bind_code, body_code)
@@ -130,11 +127,10 @@ module Kapusta
130
127
 
131
128
  coll_code = emit_expr(iter_expr, env, current_scope)
132
129
  if binding_pats.length == 1
133
- value_var = temp('value')
134
130
  body_env = env.child
135
- bind_code, body_env = emit_iteration_bindings([[binding_pats[0], value_var]], body_env)
131
+ value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
136
132
  body_code = yield(body_env)
137
- iteration_block("#{coll_code}.each do |#{value_var}|", bind_code, body_code)
133
+ iteration_block("#{coll_code}.each do |#{value_var}|", bind_code || '', body_code)
138
134
  else
139
135
  parts_var = temp('parts')
140
136
  body_env = env.child
@@ -145,6 +141,17 @@ module Kapusta
145
141
  end
146
142
  end
147
143
 
144
+ def bind_iteration_param(pattern, fallback_name, env)
145
+ if pattern.is_a?(Sym) && !pattern.dotted?
146
+ ruby_name = pattern.name == '_' ? '_' : define_local(env, pattern.name)
147
+ [ruby_name, nil]
148
+ else
149
+ tmp = temp(fallback_name)
150
+ bind_code, _new_env = emit_iteration_bindings([[pattern, tmp]], env)
151
+ [tmp, bind_code.empty? ? nil : bind_code]
152
+ end
153
+ end
154
+
148
155
  def iteration_block(header, bind_code, body_code)
149
156
  [header, indent(join_code(bind_code, body_code)), 'end'].join("\n")
150
157
  end
@@ -156,7 +163,7 @@ module Kapusta
156
163
 
157
164
  code, current_env = emit_pattern_bind(pattern, value_code, current_env)
158
165
  code
159
- end.compact
166
+ end.compact.reject(&:empty?)
160
167
  [codes.join("\n"), current_env]
161
168
  end
162
169
 
@@ -189,11 +196,9 @@ module Kapusta
189
196
  end
190
197
 
191
198
  def emit_sequence_value_assignment(target_var, body_code)
192
- <<~RUBY.chomp
193
- #{target_var} = begin
194
- #{indent(body_code)}
195
- end
196
- RUBY
199
+ return "#{target_var} = #{body_code}" unless body_code.include?("\n")
200
+
201
+ ["#{target_var} = begin", indent(body_code), 'end'].join("\n")
197
202
  end
198
203
  end
199
204
  end
@@ -21,11 +21,17 @@ module Kapusta
21
21
  runtime_call(:qget_path, object_code, "[#{keys}]")
22
22
  end
23
23
 
24
+ BINARY_OPERATOR_METHODS = %w[<=> ** << >> & | ^ === =~].freeze
25
+ private_constant :BINARY_OPERATOR_METHODS
26
+
24
27
  def emit_colon(args, env, current_scope)
25
28
  receiver = emit_expr(args[0], env, current_scope)
26
29
  method_form = args[1]
27
30
  positional, kwargs, block_form = split_call_args(args[2..], env, current_scope)
28
31
  literal_name = method_form if method_form.is_a?(Symbol) || method_form.is_a?(String)
32
+ if literal_name && binary_operator_call?(literal_name.to_s, positional, kwargs, block_form)
33
+ return emit_binary_operator_call(receiver, literal_name.to_s, positional[0])
34
+ end
29
35
  if literal_name && direct_method_name?(literal_name.to_s)
30
36
  return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(literal_name.to_s),
31
37
  positional, kwargs, block_form, env, current_scope)
@@ -37,6 +43,15 @@ module Kapusta
37
43
  "#{parenthesize(receiver)}.public_send(#{parts})"
38
44
  end
39
45
 
46
+ def binary_operator_call?(name, positional, kwargs, block_form)
47
+ BINARY_OPERATOR_METHODS.include?(name) &&
48
+ positional.length == 1 && !kwargs && !block_form
49
+ end
50
+
51
+ def emit_binary_operator_call(receiver, operator, arg_code)
52
+ "#{parenthesize(receiver)} #{operator} #{parenthesize(arg_code)}"
53
+ end
54
+
40
55
  def emit_require(arg, env, current_scope)
41
56
  path_code =
42
57
  case arg
@@ -218,6 +233,9 @@ module Kapusta
218
233
  def emit_compare(args, env, current_scope, operator)
219
234
  values = args.map { |arg| emit_expr(arg, env, current_scope) }
220
235
  return 'true' if values.length <= 1
236
+ if (nil_pred = nil_predicate(args, values, operator, negate: false))
237
+ return nil_pred
238
+ end
221
239
 
222
240
  (0...(values.length - 1)).map do |i|
223
241
  "#{parenthesize(values[i])} #{operator} #{parenthesize(values[i + 1])}"
@@ -227,12 +245,27 @@ module Kapusta
227
245
  def emit_compare_any(args, env, current_scope, operator)
228
246
  values = args.map { |arg| emit_expr(arg, env, current_scope) }
229
247
  return 'false' if values.length <= 1
248
+ if (nil_pred = nil_predicate(args, values, operator, negate: true))
249
+ return nil_pred
250
+ end
230
251
 
231
252
  (0...(values.length - 1)).map do |i|
232
253
  "#{parenthesize(values[i])} #{operator} #{parenthesize(values[i + 1])}"
233
254
  end.join(' || ')
234
255
  end
235
256
 
257
+ def nil_predicate(args, values, operator, negate:)
258
+ return unless args.length == 2
259
+ return unless (operator == '==' && !negate) || (operator == '!=' && negate)
260
+
261
+ nil_idx = args.find_index(&:nil?)
262
+ return unless nil_idx
263
+
264
+ other = values[1 - nil_idx]
265
+ receiver = simple_expression?(other) ? other : parenthesize(other)
266
+ "#{'!' if negate}#{receiver}.nil?"
267
+ end
268
+
236
269
  def emit_reduce(args, env, current_scope, empty_value, operator)
237
270
  return empty_value if args.empty?
238
271
 
@@ -292,6 +325,9 @@ module Kapusta
292
325
  else
293
326
  receiver = emit_method_path(base_code, segments[0...-1])
294
327
  positional, kwargs, block_form = split_call_args(args, env, current_scope)
328
+ if binary_operator_call?(segments.last, positional, kwargs, block_form)
329
+ return emit_binary_operator_call(receiver, segments.last, positional[0])
330
+ end
295
331
  if direct_method_name?(segments.last)
296
332
  return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last),
297
333
  positional, kwargs, block_form, env, current_scope)
@@ -328,10 +364,23 @@ module Kapusta
328
364
 
329
365
  def emit_self_call(name, args, env, current_scope)
330
366
  positional, kwargs, block_form = split_call_args(args, env, current_scope)
367
+ snake = Kapusta.kebab_to_snake(name)
368
+ if direct_method_name?(snake)
369
+ return emit_direct_self_call(snake, positional, kwargs, block_form, env, current_scope)
370
+ end
371
+
331
372
  block = emit_block_proc(block_form, env, current_scope)
332
- method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
373
+ method_name = snake.to_sym.inspect
333
374
  parts = build_call_args([method_name, *positional], kwargs, block)
334
- "send(#{parts})"
375
+ "public_send(#{parts})"
376
+ end
377
+
378
+ def emit_direct_self_call(method_name, positional, kwargs, block_form, env, current_scope)
379
+ attached = block_form && emit_attached_block(block_form, env, current_scope)
380
+ block = block_form && !attached ? emit_block_proc(block_form, env, current_scope) : nil
381
+ parts = build_call_args(positional, kwargs, block)
382
+ call = parts.empty? ? "#{method_name}()" : "#{method_name}(#{parts})"
383
+ attached ? "#{call} #{attached}" : call
335
384
  end
336
385
 
337
386
  def split_call_args(args, env, current_scope)
@@ -442,7 +491,7 @@ module Kapusta
442
491
  code.match?(/\A\$[a-zA-Z_]\w*\z/) ||
443
492
  code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
444
493
  code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
445
- code.match?(/\A\d+(?:\.\d+)?\z/) ||
494
+ code.match?(/\A-?\d+(?:\.\d+)?\z/) ||
446
495
  code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
447
496
  code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
448
497
  code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
@@ -8,7 +8,7 @@ module Kapusta
8
8
 
9
9
  def emit_pattern_bind(pattern, value_code, env)
10
10
  if pattern.is_a?(Sym)
11
- return ['nil', env] if pattern.name == '_'
11
+ return ['', env] if pattern.name == '_'
12
12
 
13
13
  ruby_name = define_local(env, pattern)
14
14
  ["#{ruby_name} = #{value_code}", env]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kapusta
4
- VERSION = '0.2.2'
4
+ VERSION = '0.2.4'
5
5
  end
@@ -280,6 +280,10 @@ RSpec.describe 'examples' do
280
280
  OUT
281
281
  end
282
282
 
283
+ it 'pivot-index.kap' do
284
+ expect(run_example('pivot-index.kap')).to eq("3\n-1\n0\n")
285
+ end
286
+
283
287
  it 'points.kap' do
284
288
  expect(run_example('points.kap')).to eq(<<~OUT)
285
289
  "origin"
@@ -399,6 +403,10 @@ RSpec.describe 'examples' do
399
403
  expect(run_example('shapes.kap')).to eq("78.5\n9\n8\n0\n")
400
404
  end
401
405
 
406
+ it 'single-number.kap' do
407
+ expect(run_example('single-number.kap')).to eq("1\n4\n1\n")
408
+ end
409
+
402
410
  it 'squares.kap' do
403
411
  expect(run_example('squares.kap')).to eq("1\n4\n9\n16\n25\n")
404
412
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kapusta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov
@@ -68,6 +68,7 @@ files:
68
68
  - examples/pangram.kap
69
69
  - examples/pcall.kap
70
70
  - examples/pipeline.kap
71
+ - examples/pivot-index.kap
71
72
  - examples/plus-one.kap
72
73
  - examples/points.kap
73
74
  - examples/primes.kap
@@ -80,6 +81,7 @@ files:
80
81
  - examples/safe-lookup.kap
81
82
  - examples/scopes.kap
82
83
  - examples/shapes.kap
84
+ - examples/single-number.kap
83
85
  - examples/squares.kap
84
86
  - examples/stack.kap
85
87
  - examples/sum.kap
@@ -123,10 +125,10 @@ files:
123
125
  - spec/reader_spec.rb
124
126
  - spec/spec_helper.rb
125
127
  homepage: https://github.com/evmorov/kapusta
126
- licenses: []
128
+ licenses:
129
+ - MIT
127
130
  metadata:
128
131
  rubygems_mfa_required: 'true'
129
- homepage_uri: https://github.com/evmorov/kapusta
130
132
  source_code_uri: https://github.com/evmorov/kapusta
131
133
  bug_tracker_uri: https://github.com/evmorov/kapusta/issues
132
134
  rdoc_options: []