kapusta 0.14.0 → 0.15.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: e247dd5e305dcac681bcdb9decbf30d8e0628232626f751c2f39fe8d79796e02
4
- data.tar.gz: 6d1e24c986c1301045e469b03a76295e03276cee031939ac87dea34187931251
3
+ metadata.gz: 81b3b1124689c8b7a89734ab0b8a5b584ba8ef6edcffab89fefcd4e86e6a6e2a
4
+ data.tar.gz: f3e83405f8de3298e0bfde66dcc8afd67f38e2fa2733b34085ef00579635c247
5
5
  SHA512:
6
- metadata.gz: 919382905342cffcbf1b6986a232f9050dcacfeb196fab8131d9d72a462d8c741e560fe1b72922998bb980a8fb3eeecbda9c348acd694b3299d890b44022132c
7
- data.tar.gz: 50e77ee9da2df8b1a2f6e694b205dde09e2631d6b49cbc9ddd9cc022e2f86122055abd721d5a82f76af1a2004e78e0f14f2a1b4d02c16cbfb8ee09fb1397a900
6
+ metadata.gz: f2c058a5c9f86be04f3d29ade387ede8ebe818365078a2a31f161a4044eb6b6a0dfd1e65453e681e79898e57dfd2e830281572ef33cc9ca57668ae088e901eb2
7
+ data.tar.gz: fc20f32661cf1979ae93aba6adf7be488e96007d151f8997704f37e6d000fa9affeca6383b8fc749471fa823eb24c4fb2dcd3e6c39fa475cfdf25a7fafa4207d
data/README.md CHANGED
@@ -128,8 +128,8 @@ require_relative "config"
128
128
 
129
129
  For larger programs, organize code with Kapusta's Ruby host forms:
130
130
 
131
- 1. Use `module` + `defn` for stateless functions.
132
- 2. Use `class` + `fn` for state or dependencies.
131
+ 1. Use `module Name <<` + `fn` for stateless functions ([`examples/app/args.kap`](https://github.com/evmorov/kapusta/tree/main/examples/app/args.kap)).
132
+ 2. Use `class` + `fn` for state or dependencies ([`examples/use_bank_account.rb`](https://github.com/evmorov/kapusta/tree/main/examples/use_bank_account.rb)).
133
133
  3. Avoid returning hashes of functions as the main app structure.
134
134
 
135
135
  ## Comparison with Fennel
@@ -1,9 +1,9 @@
1
- (module App.Args)
1
+ (module App.Args <<)
2
2
 
3
- (defn parse [argv]
3
+ (fn parse [argv]
4
4
  {:command (: argv 0) :target (: argv 1)})
5
5
 
6
- (defn format-command [parsed]
6
+ (fn format-command [parsed]
7
7
  (.. (: parsed :command) " -> " (: parsed :target)))
8
8
 
9
9
  (end)
@@ -0,0 +1,24 @@
1
+ (module Digits <<
2
+ (fn sum [n]
3
+ (var total 0)
4
+ (var x n)
5
+ (while (> x 0)
6
+ (set total (+ total (% x 10)))
7
+ (set x (/ x 10)))
8
+ total)
9
+
10
+ (fn reverse [n]
11
+ (var result 0)
12
+ (var x n)
13
+ (while (> x 0)
14
+ (set result (+ (* result 10) (% x 10)))
15
+ (set x (/ x 10)))
16
+ result)
17
+
18
+ (fn palindrome? [n]
19
+ (= n (Digits.reverse n))))
20
+
21
+ (print (Digits.sum 12345))
22
+ (print (Digits.reverse 1230))
23
+ (print (Digits.palindrome? 121))
24
+ (print (Digits.palindrome? 123))
@@ -0,0 +1,26 @@
1
+ (module Excel <<)
2
+
3
+ (fn to-number [title]
4
+ (var result 0)
5
+ (each [ch title.chars]
6
+ (set result (+ (* result 26) (- ch.ord 64))))
7
+ result)
8
+
9
+ (fn to-title [number]
10
+ (var n number)
11
+ (var chars [])
12
+ (while (> n 0)
13
+ (set n (- n 1))
14
+ (let [code (+ 65 (% n 26))]
15
+ (chars.unshift code.chr))
16
+ (set n (/ n 26)))
17
+ (chars.join ""))
18
+
19
+ (end)
20
+
21
+ (print (Excel.to-number "A"))
22
+ (print (Excel.to-number "AB"))
23
+ (print (Excel.to-number "ZY"))
24
+ (print (Excel.to-title 1))
25
+ (print (Excel.to-title 28))
26
+ (print (Excel.to-title 701))
@@ -0,0 +1,26 @@
1
+ (require "singleton")
2
+
3
+ (class RateLimiter)
4
+ (include Singleton)
5
+
6
+ (fn initialize []
7
+ (set (ivar last) {}))
8
+
9
+ (fn should-print? [timestamp message]
10
+ (let [last (ivar last)]
11
+ (if (or (not (last.key? message)) (>= timestamp (: last message)))
12
+ (do
13
+ (tset last message (+ timestamp 10))
14
+ true)
15
+ false)))
16
+
17
+ (end)
18
+
19
+ (let [logger (RateLimiter.instance)]
20
+ (print (logger.should-print? 1 "foo"))
21
+ (print (logger.should-print? 2 "bar"))
22
+ (print (logger.should-print? 3 "foo"))
23
+ (print (logger.should-print? 8 "bar"))
24
+ (print (logger.should-print? 10 "foo"))
25
+ (print (logger.should-print? 11 "foo"))
26
+ (print (= logger (RateLimiter.instance))))
@@ -22,11 +22,13 @@ count-effects
22
22
  count-items-matching-rule
23
23
  counter
24
24
  destructure
25
+ digit-tools
25
26
  divisibility-stats
26
27
  doto
27
28
  doto-hygiene
28
29
  egg-count
29
30
  even-squares
31
+ excel-column
30
32
  exceptions
31
33
  factorial
32
34
  falling-drops
@@ -163,6 +163,7 @@ module Kapusta
163
163
  body = with_class_body do
164
164
  emit_sequence(parsed.body, env.child, :module, allow_method_definitions: true, result: false).first
165
165
  end
166
+ body = wrap_singleton(body) if parsed.singleton
166
167
  emit_module_wrapper(parsed.name, body)
167
168
  end
168
169
 
@@ -432,6 +433,9 @@ module Kapusta
432
433
  def emit_self_call(name, args, env, current_scope)
433
434
  positional, kwargs, block_form = split_call_args(args, env, current_scope)
434
435
  snake = Kapusta.kebab_to_snake(name)
436
+ if Language.class_body_declaration?(snake) && kwargs.nil? && block_form.nil?
437
+ return positional.empty? ? snake : "#{snake} #{positional.join(', ')}"
438
+ end
435
439
  if direct_method_name?(snake)
436
440
  return emit_direct_self_call(snake, positional, kwargs, block_form, env, current_scope)
437
441
  end
@@ -95,6 +95,7 @@ module Kapusta
95
95
  body, next_i = with_class_body do
96
96
  emit_form_run(forms, body_start, env.child, :module, header_form: form)
97
97
  end
98
+ body = wrap_singleton(body) if parsed.singleton
98
99
  [emit_direct_module_header(parsed.name, body) || emit_module_wrapper(parsed.name, body), next_i]
99
100
  end
100
101
  else
@@ -157,6 +158,7 @@ module Kapusta
157
158
 
158
159
  name = form.head.name
159
160
  return if special_form?(name)
161
+ return if Language.class_body_declaration?(Kapusta.kebab_to_snake(name))
160
162
  return if form.head.dotted?
161
163
  return if env.lookup_if_defined(name)
162
164
 
@@ -175,6 +177,7 @@ module Kapusta
175
177
  emit_sequence(parsed.body, env.child, :module, allow_method_definitions: true,
176
178
  result: false).first
177
179
  end
180
+ body = wrap_singleton(body) if parsed.singleton
178
181
  emit_direct_module_header(parsed.name, body) || emit_module_wrapper(parsed.name, body)
179
182
  else
180
183
  parsed = Language.parse_class_args(args)
@@ -315,6 +318,10 @@ module Kapusta
315
318
  text.lines.map { |line| line.strip.empty? ? line : "#{prefix}#{line}" }.join
316
319
  end
317
320
 
321
+ def wrap_singleton(body)
322
+ ['class << self', indent(body), 'end'].join("\n")
323
+ end
324
+
318
325
  def temp(prefix)
319
326
  @temp_index += 1
320
327
  "kap_#{prefix}_#{@temp_index}"
@@ -8,7 +8,7 @@ module Kapusta
8
8
 
9
9
  def anonymous? = name.nil?
10
10
  end
11
- ModuleForm = Struct.new(:name, :body, :prefix_length, keyword_init: true)
11
+ ModuleForm = Struct.new(:name, :body, :singleton, :prefix_length, keyword_init: true)
12
12
  ClassForm = Struct.new(:name, :supers, :body, :prefix_length, keyword_init: true)
13
13
  TryForm = Struct.new(:body, :clauses, keyword_init: true)
14
14
  CatchClause = Struct.new(:klass, :bind_sym, :body, keyword_init: true)
@@ -165,6 +165,12 @@ module Kapusta
165
165
  quasi-sym quasi-list quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym
166
166
  ].freeze
167
167
  SPECIAL_FORMS = (CORE_SPECIAL_FORMS + LuaCompat::SPECIAL_FORMS).freeze
168
+ CLASS_BODY_DECLARATIONS = %w[
169
+ include extend prepend
170
+ private public protected
171
+ module_function
172
+ attr_accessor attr_reader attr_writer
173
+ ].freeze
168
174
 
169
175
  module_function
170
176
 
@@ -207,6 +213,8 @@ module Kapusta
207
213
 
208
214
  def special_form?(name) = SPECIAL_FORMS.include?(name)
209
215
 
216
+ def class_body_declaration?(name) = CLASS_BODY_DECLARATIONS.include?(name)
217
+
210
218
  def function_head?(name) = FUNCTION_HEADS.include?(name)
211
219
 
212
220
  def function_definition_head?(name) = FUNCTION_DEFINITION_HEADS.include?(name)
@@ -282,7 +290,10 @@ module Kapusta
282
290
  end
283
291
 
284
292
  def parse_module_args(args)
285
- ModuleForm.new(name: args[0], body: args[1..] || [], prefix_length: 1)
293
+ rest = args[1..] || []
294
+ singleton = rest[0].is_a?(Sym) && rest[0].name == '<<'
295
+ rest = rest.drop(1) if singleton
296
+ ModuleForm.new(name: args[0], body: rest, singleton:, prefix_length: singleton ? 2 : 1)
286
297
  end
287
298
 
288
299
  def parse_class_args(args)
@@ -13,7 +13,7 @@ module Kapusta
13
13
  STDIN_PATH = '-'
14
14
  BODY_ONLY_HEADS = %w[do finally].freeze
15
15
  SINGLE_PREFIX_BODY_HEADS = %w[
16
- while when unless for each icollect collect fcollect accumulate faccumulate module
16
+ while when unless for each icollect collect fcollect accumulate faccumulate
17
17
  ].freeze
18
18
  CASE_HEADS = %w[case match].freeze
19
19
  private_constant :BODY_ONLY_HEADS, :SINGLE_PREFIX_BODY_HEADS, :CASE_HEADS
@@ -188,6 +188,7 @@ module Kapusta
188
188
  when 'try' then render_try(list, indent)
189
189
  when *SINGLE_PREFIX_BODY_HEADS then render_single_prefix_body_form(name, raw_args, indent)
190
190
  when 'class' then render_class(list, indent)
191
+ when 'module' then render_module(list, indent)
191
192
  when 'catch' then render_catch(list, indent)
192
193
  when 'if' then render_if(list, indent)
193
194
  when *CASE_HEADS then render_case_or_match(name, list, raw_args, indent)
@@ -234,6 +235,14 @@ module Kapusta
234
235
  render_prefix_body_form('class', raw_prefix, raw_body, indent)
235
236
  end
236
237
 
238
+ def render_module(list, indent)
239
+ args = list_rest(list)
240
+ raw_args = list_raw_rest(list)
241
+ prefix_length = Compiler::Language.parse_module_args(args).prefix_length
242
+ raw_prefix, raw_body = split_raw_items(raw_args, prefix_length)
243
+ render_prefix_body_form('module', raw_prefix, raw_body, indent)
244
+ end
245
+
237
246
  def render_try(list, indent)
238
247
  args = list_rest(list)
239
248
  return render_sequential_head_form('try', list_raw_rest(list), indent) if contains_comments?(list_raw_rest(list))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kapusta
4
- VERSION = '0.14.0'
4
+ VERSION = '0.15.0'
5
5
  end
@@ -779,6 +779,38 @@ RSpec.describe 'examples' do
779
779
  "alice"
780
780
  OUT
781
781
  end
782
+
783
+ it 'excel-column.kap' do
784
+ expect(run_example('excel-column.kap')).to eq(<<~OUT)
785
+ 1
786
+ 28
787
+ 701
788
+ "A"
789
+ "AB"
790
+ "ZY"
791
+ OUT
792
+ end
793
+
794
+ it 'logger-rate-limiter.kap' do
795
+ expect(run_example('logger-rate-limiter.kap')).to eq(<<~OUT)
796
+ true
797
+ true
798
+ false
799
+ false
800
+ false
801
+ true
802
+ true
803
+ OUT
804
+ end
805
+
806
+ it 'digit-tools.kap' do
807
+ expect(run_example('digit-tools.kap')).to eq(<<~OUT)
808
+ 15
809
+ 321
810
+ true
811
+ false
812
+ OUT
813
+ end
782
814
  end
783
815
 
784
816
  RSpec.describe 'mruby runtime examples' do
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.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov
@@ -53,12 +53,14 @@ files:
53
53
  - examples/count-items-matching-rule.kap
54
54
  - examples/counter.kap
55
55
  - examples/destructure.kap
56
+ - examples/digit-tools.kap
56
57
  - examples/divisibility-stats.kap
57
58
  - examples/doto-hygiene.kap
58
59
  - examples/doto.kap
59
60
  - examples/egg-count.kap
60
61
  - examples/equal-sums.kap
61
62
  - examples/even-squares.kap
63
+ - examples/excel-column.kap
62
64
  - examples/exceptions.kap
63
65
  - examples/factorial.kap
64
66
  - examples/falling-drops.kap
@@ -77,6 +79,7 @@ files:
77
79
  - examples/leap-year.kap
78
80
  - examples/left-right-difference.kap
79
81
  - examples/length-of-last-word.kap
82
+ - examples/logger-rate-limiter.kap
80
83
  - examples/macros-dbg.kap
81
84
  - examples/macros-import-helpers.kap
82
85
  - examples/macros-import-whole.kap