kapusta 0.2.4 → 0.4.1

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.
data/lib/kapusta.rb CHANGED
@@ -12,10 +12,12 @@ module Kapusta
12
12
  @loaded_kapusta_features = {}
13
13
 
14
14
  def self.eval(source, path: '(eval)', **_opts)
15
+ install!
15
16
  Compiler.run(source, path:)
16
17
  end
17
18
 
18
19
  def self.dofile(path, **_opts)
20
+ install!
19
21
  source = File.read(path)
20
22
  self.eval(source, path:)
21
23
  end
@@ -25,6 +27,7 @@ module Kapusta
25
27
  end
26
28
 
27
29
  def self.require(feature, relative_to: nil)
30
+ install!
28
31
  feature = feature.to_s
29
32
  local_path = resolve_require_path(feature, relative_to:)
30
33
 
@@ -35,12 +38,33 @@ module Kapusta
35
38
  end
36
39
 
37
40
  def self.install!
38
- @install ||= begin
39
- Kernel.require 'rubygems'
40
- true
41
+ return if @installed
42
+
43
+ @installed = true
44
+ Kernel.module_eval do
45
+ def require_relative(path)
46
+ location = caller_locations(1, 1).first
47
+ kap_path = Kapusta.send(:resolve_kap_relative, path, location)
48
+ return Kapusta.send(:require_kapusta_file, kap_path) if kap_path
49
+
50
+ base_file = location&.absolute_path || location&.path
51
+ target = base_file ? File.expand_path(path, File.dirname(base_file)) : path
52
+ Kernel.require(target)
53
+ end
41
54
  end
42
55
  end
43
56
 
57
+ def self.resolve_kap_relative(path, location)
58
+ return unless path.is_a?(String) && location
59
+
60
+ base_file = location.absolute_path || location.path
61
+ return unless base_file
62
+
63
+ full = File.expand_path(path, File.dirname(File.expand_path(base_file)))
64
+ candidates = full.end_with?('.kap') ? [full] : ["#{full}.kap"]
65
+ candidates.find { |c| File.file?(c) }
66
+ end
67
+
44
68
  def self.resolve_require_path(feature, relative_to:)
45
69
  return unless local_feature?(feature)
46
70
 
@@ -88,5 +112,5 @@ module Kapusta
88
112
  end
89
113
 
90
114
  private_class_method :resolve_require_path, :local_feature?, :require_base_dir,
91
- :existing_feature_path, :require_kapusta_file
115
+ :existing_feature_path, :require_kapusta_file, :resolve_kap_relative
92
116
  end
@@ -484,6 +484,10 @@ RSpec.describe 'examples' do
484
484
  expect(run_example('majority-element.kap')).to eq("3\n2\n1\n")
485
485
  end
486
486
 
487
+ it 'manhattan-distance.kap' do
488
+ expect(run_example('manhattan-distance.kap')).to eq("14\n")
489
+ end
490
+
487
491
  it 'plus-one.kap' do
488
492
  expect(run_example('plus-one.kap')).to eq(<<~OUT)
489
493
  [1, 2, 4]
@@ -492,4 +496,59 @@ RSpec.describe 'examples' do
492
496
  [1, 0, 0]
493
497
  OUT
494
498
  end
499
+
500
+ it 'subtract-product-sum.kap' do
501
+ expect(run_example('subtract-product-sum.kap')).to eq("15\n21\n0\n")
502
+ end
503
+
504
+ it 'ugly-number.kap' do
505
+ expect(run_example('ugly-number.kap')).to eq("true\ntrue\nfalse\nfalse\ntrue\n")
506
+ end
507
+
508
+ it 'macros-unless.kap' do
509
+ expect(run_example('macros-unless.kap')).to eq(<<~OUT)
510
+ "shown"
511
+ "also shown"
512
+ OUT
513
+ end
514
+
515
+ it 'macros-swap.kap' do
516
+ expect(run_example('macros-swap.kap')).to eq("2\n1\n")
517
+ end
518
+
519
+ it 'macros-when-let.kap' do
520
+ expect(run_example('macros-when-let.kap')).to eq(<<~OUT)
521
+ "got"
522
+ 3
523
+ "done"
524
+ OUT
525
+ end
526
+
527
+ it 'macros-multi.kap' do
528
+ expect(run_example('macros-multi.kap')).to eq("10\n20\n7\n")
529
+ end
530
+
531
+ it 'macros-thrice-if.kap' do
532
+ expect(run_example('macros-thrice-if.kap')).to eq(<<~OUT)
533
+ "tick"
534
+ 1
535
+ "tick"
536
+ 2
537
+ "tick"
538
+ 3
539
+ "final"
540
+ 3
541
+ OUT
542
+ end
543
+
544
+ it 'macros-dbg.kap' do
545
+ expect(run_example('macros-dbg.kap')).to eq(<<~OUT)
546
+ "dbg"
547
+ 6
548
+ "result"
549
+ 6
550
+ "dbg"
551
+ 50
552
+ OUT
553
+ end
495
554
  end
@@ -210,16 +210,14 @@ RSpec.describe Kapusta::Formatter do
210
210
  end
211
211
 
212
212
  expect(output).to eq(<<~KAP)
213
- (let
214
- [
215
- profile
216
- {
217
- :name "Ada"
218
- ; active user
219
- :active true}
220
- ; next binding
221
- role
222
- "Engineer"]
213
+ (let [
214
+ profile
215
+ {:name "Ada"
216
+ ; active user
217
+ :active true}
218
+ ; next binding
219
+ role
220
+ "Engineer"]
223
221
  (print profile role))
224
222
  KAP
225
223
  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.4
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov
@@ -20,8 +20,10 @@ files:
20
20
  - Gemfile
21
21
  - README.md
22
22
  - Rakefile
23
+ - bin/check-all
23
24
  - bin/compile-examples
24
25
  - bin/console
26
+ - bin/fennel-parity
25
27
  - bin/setup
26
28
  - examples/accumulator.kap
27
29
  - examples/ackermann.kap
@@ -56,7 +58,14 @@ files:
56
58
  - examples/kwargs.kap
57
59
  - examples/leap-year.kap
58
60
  - examples/length-of-last-word.kap
61
+ - examples/macros-dbg.kap
62
+ - examples/macros-multi.kap
63
+ - examples/macros-swap.kap
64
+ - examples/macros-thrice-if.kap
65
+ - examples/macros-unless.kap
66
+ - examples/macros-when-let.kap
59
67
  - examples/majority-element.kap
68
+ - examples/manhattan-distance.kap
60
69
  - examples/match.kap
61
70
  - examples/maximum-subarray.kap
62
71
  - examples/min-max.kap
@@ -84,12 +93,14 @@ files:
84
93
  - examples/single-number.kap
85
94
  - examples/squares.kap
86
95
  - examples/stack.kap
96
+ - examples/subtract-product-sum.kap
87
97
  - examples/sum.kap
88
98
  - examples/threading.kap
89
99
  - examples/tic-tac-toe.kap
90
100
  - examples/tset.kap
91
101
  - examples/two-sum-hash.kap
92
102
  - examples/two-sum.kap
103
+ - examples/ugly-number.kap
93
104
  - examples/underscore-patterns.kap
94
105
  - examples/use_bank_account.rb
95
106
  - examples/valid-parentheses-1.kap
@@ -111,8 +122,8 @@ files:
111
122
  - lib/kapusta/compiler/emitter/interop.rb
112
123
  - lib/kapusta/compiler/emitter/patterns.rb
113
124
  - lib/kapusta/compiler/emitter/support.rb
125
+ - lib/kapusta/compiler/macro_expander.rb
114
126
  - lib/kapusta/compiler/normalizer.rb
115
- - lib/kapusta/compiler/runtime.rb
116
127
  - lib/kapusta/env.rb
117
128
  - lib/kapusta/error.rb
118
129
  - lib/kapusta/formatter.rb
@@ -1,226 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kapusta
4
- module Compiler
5
- module Runtime
6
- HELPER_DEPENDENCIES = {
7
- destructure: %i[destructure_into],
8
- match_pattern: %i[match_pattern_into]
9
- }.freeze
10
-
11
- HELPER_SOURCES = {
12
- qget_path: <<~RUBY.chomp,
13
- def kap_qget_path(obj, keys)
14
- keys.each do |key|
15
- return if obj.nil?
16
-
17
- obj = obj[key]
18
- end
19
- obj
20
- end
21
- RUBY
22
- ensure_module: <<~RUBY.chomp,
23
- def kap_ensure_module(holder, path)
24
- segments = path.split('.')
25
- last = segments.pop
26
- scope = holder.is_a?(Module) ? holder : Object
27
- segments.each do |segment|
28
- scope =
29
- if scope.const_defined?(segment, false)
30
- scope.const_get(segment, false)
31
- else
32
- mod = Module.new
33
- scope.const_set(segment, mod)
34
- mod
35
- end
36
- end
37
- if scope.const_defined?(last, false)
38
- scope.const_get(last, false)
39
- else
40
- mod = Module.new
41
- scope.const_set(last, mod)
42
- mod
43
- end
44
- end
45
- RUBY
46
- ensure_class: <<~RUBY.chomp,
47
- def kap_ensure_class(holder, path, super_class)
48
- segments = path.split('.')
49
- last = segments.pop
50
- scope = holder.is_a?(Module) ? holder : Object
51
- segments.each do |segment|
52
- scope =
53
- if scope.const_defined?(segment, false)
54
- scope.const_get(segment, false)
55
- else
56
- mod = Module.new
57
- scope.const_set(segment, mod)
58
- mod
59
- end
60
- end
61
- if scope.const_defined?(last, false)
62
- scope.const_get(last, false)
63
- else
64
- klass = Class.new(super_class)
65
- scope.const_set(last, klass)
66
- klass
67
- end
68
- end
69
- RUBY
70
- destructure: <<~RUBY.chomp,
71
- def kap_destructure(pattern, value)
72
- bindings = {}
73
- kap_destructure_into(pattern, value, bindings)
74
- bindings
75
- end
76
- RUBY
77
- destructure_into: <<~'RUBY'.chomp,
78
- def kap_destructure_into(pattern, value, bindings)
79
- case pattern[0]
80
- when :sym
81
- name = pattern[1]
82
- bindings[name] = value unless name == '_'
83
- when :vec
84
- items = pattern[1]
85
- rest_idx = items.index { |item| item.is_a?(Array) && item[0] == :rest }
86
- if rest_idx
87
- before = items[0...rest_idx]
88
- rest_pattern = items[rest_idx][1]
89
- before.each_with_index do |item, i|
90
- kap_destructure_into(item, value ? value[i] : nil, bindings)
91
- end
92
- rest_value = value ? (value[rest_idx..] || []) : []
93
- kap_destructure_into(rest_pattern, rest_value, bindings)
94
- else
95
- items.each_with_index do |item, i|
96
- kap_destructure_into(item, value ? value[i] : nil, bindings)
97
- end
98
- end
99
- when :hash
100
- pattern[1].each do |key, subpattern|
101
- kap_destructure_into(subpattern, value ? value[key] : nil, bindings)
102
- end
103
- when :ignore
104
- nil
105
- else
106
- raise "unknown destructure pattern: #{pattern.inspect}"
107
- end
108
- end
109
- RUBY
110
- match_pattern: <<~RUBY.chomp,
111
- def kap_match_pattern(pattern, value)
112
- bindings = {}
113
- [kap_match_pattern_into(pattern, value, bindings), bindings]
114
- end
115
- RUBY
116
- match_pattern_into: <<~'RUBY'.chomp
117
- def kap_match_pattern_into(pattern, value, bindings)
118
- case pattern[0]
119
- when :bind
120
- name = pattern[1]
121
- allow_nil = pattern[2]
122
- return false if value.nil? && !allow_nil
123
-
124
- bindings[name] = value
125
- true
126
- when :ref
127
- bindings.key?(pattern[1]) && bindings[pattern[1]] == value
128
- when :wild
129
- true
130
- when :vec
131
- return false unless value.is_a?(Array) || value.respond_to?(:to_ary)
132
-
133
- array = value.is_a?(Array) ? value : value.to_ary
134
- items = pattern[1]
135
- rest_idx = items.index { |item| item.is_a?(Array) && item[0] == :rest }
136
- if rest_idx
137
- before = items[0...rest_idx]
138
- rest_pattern = items[rest_idx][1]
139
- return false if array.length < before.length
140
-
141
- before.each_with_index do |item, i|
142
- return false unless kap_match_pattern_into(item, array[i], bindings)
143
- end
144
- kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
145
- else
146
- return false unless array.length >= items.length
147
-
148
- items.each_with_index do |item, i|
149
- return false unless kap_match_pattern_into(item, array[i], bindings)
150
- end
151
- true
152
- end
153
- when :hash
154
- return false unless value.is_a?(Hash)
155
-
156
- pattern[1].each do |key, subpattern|
157
- return false unless value.key?(key)
158
- return false unless kap_match_pattern_into(subpattern, value[key], bindings)
159
- end
160
- true
161
- when :lit
162
- value == pattern[1]
163
- when :pin
164
- value == pattern[1]
165
- when :or
166
- pattern[1].any? do |option|
167
- option_bindings = bindings.dup
168
- next false unless kap_match_pattern_into(option, value, option_bindings)
169
-
170
- bindings.replace(option_bindings)
171
- true
172
- end
173
- else
174
- raise "bad pattern: #{pattern.inspect}"
175
- end
176
- end
177
- RUBY
178
- }.transform_values(&:freeze).freeze
179
-
180
- module_function
181
-
182
- def helper_name(name)
183
- "kap_#{name}"
184
- end
185
-
186
- def helper_source(helpers)
187
- ordered = []
188
- seen = {}
189
- helpers.each { |name| append_helper_source(name.to_sym, ordered, seen) }
190
- return '' if ordered.empty?
191
-
192
- [
193
- ordered.map { |name| HELPER_SOURCES.fetch(name) }.join("\n\n"),
194
- "private #{ordered.map { |name| ":#{helper_name(name)}" }.join(', ')}"
195
- ].join("\n\n")
196
- end
197
-
198
- def append_helper_source(name, ordered, seen)
199
- return if seen[name]
200
-
201
- HELPER_DEPENDENCIES.fetch(name, []).each do |dependency|
202
- append_helper_source(dependency, ordered, seen)
203
- end
204
- ordered << name
205
- seen[name] = true
206
- end
207
-
208
- HELPER_SOURCES.each_value do |source|
209
- module_eval(source, __FILE__, __LINE__)
210
- end
211
-
212
- helper_methods = []
213
-
214
- HELPER_SOURCES.each_key do |name|
215
- helper_method = :"kap_#{name}"
216
- body = instance_method(helper_method)
217
- define_singleton_method(helper_method, body)
218
- define_singleton_method(name, body)
219
- helper_methods << helper_method
220
- end
221
-
222
- private_class_method(*helper_methods)
223
- send(:private, *helper_methods)
224
- end
225
- end
226
- end