kapusta 0.3.0 → 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.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/bin/check-all +19 -0
- data/bin/fennel-parity +157 -0
- data/examples/macros-dbg.kap +9 -0
- data/examples/macros-multi.kap +12 -0
- data/examples/macros-swap.kap +9 -0
- data/examples/macros-thrice-if.kap +18 -0
- data/examples/macros-unless.kap +7 -0
- data/examples/macros-when-let.kap +7 -0
- data/examples/packet-router.kap +2 -5
- data/examples/tic-tac-toe.kap +4 -9
- data/examples/ugly-number.kap +22 -0
- data/lib/kapusta/ast.rb +42 -0
- data/lib/kapusta/compiler/emitter/expressions.rb +34 -0
- data/lib/kapusta/compiler/emitter/patterns.rb +7 -11
- data/lib/kapusta/compiler/emitter/support.rb +2 -1
- data/lib/kapusta/compiler/macro_expander.rb +256 -0
- data/lib/kapusta/compiler.rb +8 -0
- data/lib/kapusta/formatter.rb +216 -87
- data/lib/kapusta/reader.rb +46 -10
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +5 -5
- data/spec/examples_spec.rb +51 -0
- data/spec/formatter_spec.rb +8 -10
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 01cc980a37ff63b56db021edb64f7ed9b4381569bb82e8e4ada099a2b17ba19e
|
|
4
|
+
data.tar.gz: 035f0f4a38d5d9fad65f4a53e8343bbcad4b681bd33dfa975f7c9c638a83ccb4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: caaeb31630a5c80607fe6c2907548c2874f177add0bdbd3555e72039dd598aaf481f91778043f6b81cd511c9ac8609e48f61c474766dac9b3e8a2fa41d408970
|
|
7
|
+
data.tar.gz: 5417ad8088be602618c3662660af820be6dbf15eb27d8300b1c542e3889fbfe1813d51ca7306f988a630616d1857f333141bf20b964d0869c2caa8c3eba869e3
|
data/README.md
CHANGED
|
@@ -49,7 +49,7 @@ See `examples/bank-account.kap` and `examples/use_bank_account.rb`.
|
|
|
49
49
|
|
|
50
50
|
## Examples
|
|
51
51
|
|
|
52
|
-
See [`examples/`](https://github.com/evmorov/kapusta/tree/main/examples).
|
|
52
|
+
See [`examples/`](https://github.com/evmorov/kapusta/tree/main/examples/) and [`examples-compiled/`](https://github.com/evmorov/kapusta/tree/main/examples-compiled/).
|
|
53
53
|
|
|
54
54
|
```fennel
|
|
55
55
|
(fn ack [m n]
|
|
@@ -90,7 +90,6 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
|
|
|
90
90
|
| `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
|
|
91
91
|
| `(print x)` is Lua's `print` (bare) | `(print x)` is Ruby's `p` (inspect-style) |
|
|
92
92
|
| `with-open`, `tail!` | not provided |
|
|
93
|
-
| macros | not provided for now |
|
|
94
93
|
|
|
95
94
|
Kapusta-specific additions:
|
|
96
95
|
|
data/bin/check-all
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
echo "== rspec =="
|
|
5
|
+
bundle exec rspec
|
|
6
|
+
|
|
7
|
+
echo "== rubocop -A =="
|
|
8
|
+
bundle exec rubocop -A
|
|
9
|
+
|
|
10
|
+
echo "== kapfmt =="
|
|
11
|
+
for file in examples/*.kap; do ./exe/kapfmt --fix "$file"; done
|
|
12
|
+
|
|
13
|
+
echo "== fennel parity =="
|
|
14
|
+
bin/fennel-parity
|
|
15
|
+
|
|
16
|
+
echo "== compile examples =="
|
|
17
|
+
bin/compile-examples
|
|
18
|
+
|
|
19
|
+
echo "== Success! =="
|
data/bin/fennel-parity
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
ROOT = File.expand_path('..', __dir__)
|
|
7
|
+
EXAMPLES = File.join(ROOT, 'examples')
|
|
8
|
+
KAPUSTA = File.join(ROOT, 'exe', 'kapusta')
|
|
9
|
+
KAPFMT = File.join(ROOT, 'exe', 'kapfmt')
|
|
10
|
+
|
|
11
|
+
COMPATIBLE = %w[
|
|
12
|
+
ackermann.kap
|
|
13
|
+
anonymous-greeter.kap
|
|
14
|
+
climbing-stairs.kap
|
|
15
|
+
describe.kap
|
|
16
|
+
destructure.kap
|
|
17
|
+
factorial.kap
|
|
18
|
+
fib.kap
|
|
19
|
+
fizzbuzz.kap
|
|
20
|
+
gcd.kap
|
|
21
|
+
hashfn.kap
|
|
22
|
+
leap-year.kap
|
|
23
|
+
macros-dbg.kap
|
|
24
|
+
macros-multi.kap
|
|
25
|
+
macros-swap.kap
|
|
26
|
+
macros-thrice-if.kap
|
|
27
|
+
macros-unless.kap
|
|
28
|
+
macros-when-let.kap
|
|
29
|
+
match.kap
|
|
30
|
+
min-max.kap
|
|
31
|
+
or-patterns.kap
|
|
32
|
+
packet-router.kap
|
|
33
|
+
points.kap
|
|
34
|
+
primes.kap
|
|
35
|
+
safe-lookup.kap
|
|
36
|
+
shapes.kap
|
|
37
|
+
squares.kap
|
|
38
|
+
sum.kap
|
|
39
|
+
tic-tac-toe.kap
|
|
40
|
+
underscore-patterns.kap
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
def run(cmd, file)
|
|
44
|
+
out, err, status = Open3.capture3(cmd, file, chdir: EXAMPLES)
|
|
45
|
+
[out, err, status]
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
['', e.message, nil]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def strip_outer_quotes(line)
|
|
51
|
+
if line.length >= 2 && line.start_with?('"') && line.end_with?('"')
|
|
52
|
+
line[1..-2]
|
|
53
|
+
else
|
|
54
|
+
line
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def normalize_kapusta(out)
|
|
59
|
+
out.lines.map { |l| strip_outer_quotes(l.chomp) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def normalize_fennel(out)
|
|
63
|
+
# Lua's `print` joins multiple args with TAB on one line; Ruby's `p`
|
|
64
|
+
# prints each on its own line. Split tabs so the two layouts line up.
|
|
65
|
+
out.lines.flat_map { |l| l.chomp.split("\t") }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def unified_diff(label_kap, kap_lines, label_fnl, fnl_lines)
|
|
69
|
+
max = [kap_lines.length, fnl_lines.length].max
|
|
70
|
+
rows = []
|
|
71
|
+
(0...max).each do |i|
|
|
72
|
+
la = kap_lines[i]
|
|
73
|
+
lb = fnl_lines[i]
|
|
74
|
+
next if la == lb
|
|
75
|
+
|
|
76
|
+
rows << format(' line %<n>3d %<a>s: %<la>s',
|
|
77
|
+
n: i + 1, a: label_kap, la: la.inspect)
|
|
78
|
+
rows << format(' %<b>s: %<lb>s',
|
|
79
|
+
b: label_fnl, lb: lb.inspect)
|
|
80
|
+
end
|
|
81
|
+
rows.join("\n")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def check_format(path)
|
|
85
|
+
k_out, k_err, k_status = run(KAPFMT, path)
|
|
86
|
+
f_out, f_err, f_status = run('fnlfmt', path)
|
|
87
|
+
|
|
88
|
+
return "kapfmt exited #{k_status&.exitstatus}: #{k_err.strip}" if k_status.nil? || !k_status.success?
|
|
89
|
+
return "fnlfmt exited #{f_status&.exitstatus}: #{f_err.strip}" if f_status.nil? || !f_status.success?
|
|
90
|
+
return if k_out == f_out
|
|
91
|
+
|
|
92
|
+
k_lines = k_out.lines.map(&:chomp)
|
|
93
|
+
f_lines = f_out.lines.map(&:chomp)
|
|
94
|
+
"format differs:\n#{unified_diff('kapfmt', k_lines, 'fnlfmt', f_lines)}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def check_run(path)
|
|
98
|
+
k_out, k_err, k_status = run(KAPUSTA, path)
|
|
99
|
+
f_out, f_err, f_status = run('fennel', path)
|
|
100
|
+
|
|
101
|
+
return "kapusta exited #{k_status&.exitstatus}: #{k_err.strip}" if k_status.nil? || !k_status.success?
|
|
102
|
+
return "fennel exited #{f_status&.exitstatus}: #{f_err.strip}" if f_status.nil? || !f_status.success?
|
|
103
|
+
|
|
104
|
+
k_lines = normalize_kapusta(k_out)
|
|
105
|
+
f_lines = normalize_fennel(f_out)
|
|
106
|
+
return if k_lines == f_lines
|
|
107
|
+
|
|
108
|
+
if k_lines.length == f_lines.length
|
|
109
|
+
"output differs:\n#{unified_diff('kapusta', k_lines, 'fennel', f_lines)}"
|
|
110
|
+
else
|
|
111
|
+
"line count differs (kapusta=#{k_lines.length}, fennel=#{f_lines.length})"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def check(name)
|
|
116
|
+
path = File.join(EXAMPLES, name)
|
|
117
|
+
return [:miss, "missing file: #{path}"] unless File.exist?(path)
|
|
118
|
+
|
|
119
|
+
fmt_reason = check_format(path)
|
|
120
|
+
return [:fail, fmt_reason] if fmt_reason
|
|
121
|
+
|
|
122
|
+
run_reason = check_run(path)
|
|
123
|
+
return [:fail, run_reason] if run_reason
|
|
124
|
+
|
|
125
|
+
[:ok, nil]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
puts "Checking #{COMPATIBLE.size} compatible examples (kapfmt vs fnlfmt, kapusta vs fennel)...\n\n"
|
|
129
|
+
|
|
130
|
+
failures = []
|
|
131
|
+
COMPATIBLE.each do |name|
|
|
132
|
+
status, reason = check(name)
|
|
133
|
+
case status
|
|
134
|
+
when :ok
|
|
135
|
+
puts "OK #{name}"
|
|
136
|
+
when :miss
|
|
137
|
+
failures << [name, reason]
|
|
138
|
+
puts "MISS #{name}"
|
|
139
|
+
when :fail
|
|
140
|
+
failures << [name, reason]
|
|
141
|
+
puts "FAIL #{name}"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
puts
|
|
146
|
+
if failures.empty?
|
|
147
|
+
puts "All #{COMPATIBLE.size} compatible examples produce matching output."
|
|
148
|
+
exit 0
|
|
149
|
+
else
|
|
150
|
+
puts "#{failures.size} of #{COMPATIBLE.size} examples failed:\n\n"
|
|
151
|
+
failures.each do |name, reason|
|
|
152
|
+
puts " * #{name}"
|
|
153
|
+
reason.each_line { |l| puts " #{l.rstrip}" }
|
|
154
|
+
puts
|
|
155
|
+
end
|
|
156
|
+
exit 1
|
|
157
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
(macro thrice-if [condition result]
|
|
2
|
+
(fn step [i]
|
|
3
|
+
(if (< 0 i)
|
|
4
|
+
`(if ,condition
|
|
5
|
+
(do
|
|
6
|
+
,result
|
|
7
|
+
,(step (- i 1))))))
|
|
8
|
+
|
|
9
|
+
(step 3))
|
|
10
|
+
|
|
11
|
+
(var counter 0)
|
|
12
|
+
|
|
13
|
+
(fn ready? [] (< counter 5))
|
|
14
|
+
|
|
15
|
+
(fn tick [] (set counter (+ counter 1)) (print "tick" counter))
|
|
16
|
+
|
|
17
|
+
(thrice-if (ready?) (tick))
|
|
18
|
+
(print "final" counter)
|
data/examples/packet-router.kap
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
(fn inbox-line [user event]
|
|
2
2
|
(match event
|
|
3
3
|
[:score user points] (.. "score:" points)
|
|
4
|
-
[:profile user ?city] (if city (.. "city:" city) "city:nil")
|
|
4
|
+
[:profile user ?city] (if ?city (.. "city:" ?city) "city:nil")
|
|
5
5
|
_ "other"))
|
|
6
6
|
|
|
7
7
|
(fn score-delta [user event]
|
|
8
8
|
(case event
|
|
9
|
-
(where (or [:bonus (= user)
|
|
10
|
-
(> points 0)
|
|
11
|
-
(< points 10))
|
|
12
|
-
points
|
|
9
|
+
(where (or [:bonus (= user) p] [:score (= user) p]) (> p 0) (< p 10)) p
|
|
13
10
|
_ 0))
|
|
14
11
|
|
|
15
12
|
(fn packet-kind [packet]
|
data/examples/tic-tac-toe.kap
CHANGED
|
@@ -6,13 +6,8 @@
|
|
|
6
6
|
[["O" _ _] ["O" _ _] ["O" _ _]] "O"
|
|
7
7
|
_ "draw"))
|
|
8
8
|
|
|
9
|
-
(each
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
(ipairs [
|
|
14
|
-
[["X" "X" "X"] ["O" "" ""] ["" "O" ""]]
|
|
15
|
-
[["O" "X" "X"] ["O" "" "X"] ["O" "" ""]]
|
|
16
|
-
[["X" "O" ""] ["" "X" "O"] ["" "" "X"]]
|
|
17
|
-
[["X" "O" "X"] ["O" "X" "O"] ["O" "X" "O"]]])]
|
|
9
|
+
(each [_ board (ipairs [[["X" "X" "X"] ["O" "" ""] ["" "O" ""]]
|
|
10
|
+
[["O" "X" "X"] ["O" "" "X"] ["O" "" ""]]
|
|
11
|
+
[["X" "O" ""] ["" "X" "O"] ["" "" "X"]]
|
|
12
|
+
[["X" "O" "X"] ["O" "X" "O"] ["O" "X" "O"]]])]
|
|
18
13
|
(print (winner board)))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(macro floor-div [a b]
|
|
2
|
+
`(: (/ ,a ,b) :floor))
|
|
3
|
+
|
|
4
|
+
(macro divide-out! [v d]
|
|
5
|
+
`(while (= 0 (% ,v ,d))
|
|
6
|
+
(set ,v (floor-div ,v ,d))))
|
|
7
|
+
|
|
8
|
+
(fn ugly? [n]
|
|
9
|
+
(var x n)
|
|
10
|
+
(if (<= n 0)
|
|
11
|
+
false
|
|
12
|
+
(do
|
|
13
|
+
(divide-out! x 2)
|
|
14
|
+
(divide-out! x 3)
|
|
15
|
+
(divide-out! x 5)
|
|
16
|
+
(= x 1))))
|
|
17
|
+
|
|
18
|
+
(print (ugly? 6))
|
|
19
|
+
(print (ugly? 1))
|
|
20
|
+
(print (ugly? 14))
|
|
21
|
+
(print (ugly? 0))
|
|
22
|
+
(print (ugly? 30))
|
data/lib/kapusta/ast.rb
CHANGED
|
@@ -9,6 +9,8 @@ module Kapusta
|
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
BlankLine = Class.new
|
|
13
|
+
|
|
12
14
|
class Sym
|
|
13
15
|
attr_reader :name
|
|
14
16
|
|
|
@@ -74,17 +76,25 @@ module Kapusta
|
|
|
74
76
|
|
|
75
77
|
class Vec
|
|
76
78
|
attr_reader :items
|
|
79
|
+
attr_accessor :multiline_source
|
|
77
80
|
|
|
78
81
|
def initialize(items)
|
|
79
82
|
@items = items
|
|
83
|
+
@multiline_source = false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def to_ary
|
|
87
|
+
@items
|
|
80
88
|
end
|
|
81
89
|
end
|
|
82
90
|
|
|
83
91
|
class HashLit
|
|
84
92
|
attr_reader :entries
|
|
93
|
+
attr_accessor :multiline_source
|
|
85
94
|
|
|
86
95
|
def initialize(entries)
|
|
87
96
|
@entries = entries
|
|
97
|
+
@multiline_source = false
|
|
88
98
|
end
|
|
89
99
|
|
|
90
100
|
def pairs
|
|
@@ -98,9 +108,11 @@ module Kapusta
|
|
|
98
108
|
|
|
99
109
|
class List
|
|
100
110
|
attr_reader :items
|
|
111
|
+
attr_accessor :multiline_source
|
|
101
112
|
|
|
102
113
|
def initialize(items)
|
|
103
114
|
@items = items
|
|
115
|
+
@multiline_source = false
|
|
104
116
|
end
|
|
105
117
|
|
|
106
118
|
def head
|
|
@@ -115,4 +127,34 @@ module Kapusta
|
|
|
115
127
|
@items.empty?
|
|
116
128
|
end
|
|
117
129
|
end
|
|
130
|
+
|
|
131
|
+
class AutoGensym < Sym
|
|
132
|
+
def inspect
|
|
133
|
+
"#<AutoGensym #{@name}#>"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class Quasiquote
|
|
138
|
+
attr_reader :form
|
|
139
|
+
|
|
140
|
+
def initialize(form)
|
|
141
|
+
@form = form
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
class Unquote
|
|
146
|
+
attr_reader :form
|
|
147
|
+
|
|
148
|
+
def initialize(form)
|
|
149
|
+
@form = form
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class UnquoteSplice
|
|
154
|
+
attr_reader :form
|
|
155
|
+
|
|
156
|
+
def initialize(form)
|
|
157
|
+
@form = form
|
|
158
|
+
end
|
|
159
|
+
end
|
|
118
160
|
end
|
|
@@ -96,11 +96,45 @@ module Kapusta
|
|
|
96
96
|
when '/' then emit_div(args, env, current_scope)
|
|
97
97
|
when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
|
|
98
98
|
when 'print' then emit_print(args, env, current_scope)
|
|
99
|
+
when 'quasi-sym' then "Kapusta::Sym.new(#{emit_expr(args[0], env, current_scope)})"
|
|
100
|
+
when 'quasi-list' then "Kapusta::List.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
101
|
+
when 'quasi-list-tail' then emit_quasi_list_tail(args, env, current_scope)
|
|
102
|
+
when 'quasi-vec' then "Kapusta::Vec.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
103
|
+
when 'quasi-vec-tail' then emit_quasi_vec_tail(args, env, current_scope)
|
|
104
|
+
when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
|
|
105
|
+
when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
|
|
106
|
+
when 'macro', 'macros', 'import-macros'
|
|
107
|
+
emit_error!("#{name} must appear at the top level and is consumed by the macro expander")
|
|
99
108
|
else
|
|
100
109
|
emit_error!("unknown special form: #{name}")
|
|
101
110
|
end
|
|
102
111
|
end
|
|
103
112
|
|
|
113
|
+
def emit_quasi_list_tail(args, env, current_scope)
|
|
114
|
+
head_items = args[0]
|
|
115
|
+
tail_expr = emit_expr(args[1], env, current_scope)
|
|
116
|
+
head_code = head_items.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')
|
|
117
|
+
"Kapusta::List.new([#{head_code}, *#{parenthesize(tail_expr)}])"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def emit_quasi_vec_tail(args, env, current_scope)
|
|
121
|
+
head_items = args[0]
|
|
122
|
+
tail_expr = emit_expr(args[1], env, current_scope)
|
|
123
|
+
head_code = head_items.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')
|
|
124
|
+
"Kapusta::Vec.new([#{head_code}, *#{parenthesize(tail_expr)}])"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def emit_quasi_gensym(arg, env, current_scope)
|
|
128
|
+
"Kapusta::Compiler::MacroExpander.fresh_gensym(#{emit_expr(arg, env, current_scope)})"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def emit_quasi_hash(args, env, current_scope)
|
|
132
|
+
pairs = args.each_slice(2).map do |key, value|
|
|
133
|
+
"[#{emit_expr(key, env, current_scope)}, #{emit_expr(value, env, current_scope)}]"
|
|
134
|
+
end
|
|
135
|
+
"Kapusta::HashLit.new([#{pairs.join(', ')}])"
|
|
136
|
+
end
|
|
137
|
+
|
|
104
138
|
def emit_concat(args, env, current_scope)
|
|
105
139
|
return '""' if args.empty?
|
|
106
140
|
|
|
@@ -79,8 +79,7 @@ module Kapusta
|
|
|
79
79
|
if sub.is_a?(Sym)
|
|
80
80
|
raise PatternNotTranslatable if sub.name == '_'
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
ruby_name = define_local(current_env, bind_name)
|
|
82
|
+
ruby_name = define_local(current_env, sub.name)
|
|
84
83
|
lines << "#{ruby_name} = #{access}"
|
|
85
84
|
else
|
|
86
85
|
sub_code, current_env = try_emit_native_pattern_bind(sub, access, current_env) ||
|
|
@@ -96,8 +95,7 @@ module Kapusta
|
|
|
96
95
|
when Sym
|
|
97
96
|
return ['_', env, nil] if pattern.name == '_'
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
ruby_name = define_local(env, bind_name)
|
|
98
|
+
ruby_name = define_local(env, pattern.name)
|
|
101
99
|
[ruby_name, env, nil]
|
|
102
100
|
when Vec
|
|
103
101
|
inner = []
|
|
@@ -137,8 +135,7 @@ module Kapusta
|
|
|
137
135
|
|
|
138
136
|
return '*' if sym.name == '_'
|
|
139
137
|
|
|
140
|
-
|
|
141
|
-
"*#{define_local(env, bind_name)}"
|
|
138
|
+
"*#{define_local(env, sym.name)}"
|
|
142
139
|
end
|
|
143
140
|
|
|
144
141
|
class PatternNotTranslatable < StandardError; end
|
|
@@ -179,12 +176,11 @@ module Kapusta
|
|
|
179
176
|
return '_' if name == '_'
|
|
180
177
|
|
|
181
178
|
if nil_allowing_pattern_name?(name)
|
|
182
|
-
|
|
183
|
-
raise PatternNotTranslatable if state[:bound_names].key?(bind_name)
|
|
179
|
+
raise PatternNotTranslatable if state[:bound_names].key?(name)
|
|
184
180
|
|
|
185
|
-
state[:bound_names][
|
|
186
|
-
state[:binding_names] <<
|
|
187
|
-
sanitize_local(
|
|
181
|
+
state[:bound_names][name] = true
|
|
182
|
+
state[:binding_names] << name
|
|
183
|
+
sanitize_local(name)
|
|
188
184
|
else
|
|
189
185
|
binding = mode == :match ? env.lookup_if_defined(name) : nil
|
|
190
186
|
if state[:bound_names].key?(name)
|
|
@@ -263,7 +263,8 @@ module Kapusta
|
|
|
263
263
|
|
|
264
264
|
def sanitize_local(name)
|
|
265
265
|
base = Kapusta.kebab_to_snake(name.respond_to?(:name) ? name.name : name)
|
|
266
|
-
base = base.
|
|
266
|
+
base = base.sub(/\A\?/, 'q_').gsub('?', '_q')
|
|
267
|
+
base = base.sub(/\A!/, 'bang_').gsub('!', '_bang')
|
|
267
268
|
base = base.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
268
269
|
if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
|
|
269
270
|
base = "_#{base}"
|