kapusta 0.1.0 → 0.1.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 -1
- data/examples/accumulator.kap +1 -1
- data/examples/anagram.kap +3 -3
- data/examples/binary-search.kap +2 -2
- data/examples/block-sort.kap +1 -1
- data/examples/blocks-and-kwargs.kap +20 -0
- data/examples/calc.kap +1 -1
- data/examples/counter.kap +1 -1
- data/examples/doto.kap +1 -1
- data/examples/even-squares.kap +1 -1
- data/examples/exceptions.kap +2 -2
- data/examples/files.kap +13 -0
- data/examples/greet.kap +1 -1
- data/examples/inheritance.kap +13 -0
- data/examples/kwargs.kap +1 -1
- data/examples/match.kap +3 -3
- data/examples/module-header.kap +2 -1
- data/examples/palindrome.kap +3 -3
- data/examples/pangram.kap +2 -2
- data/examples/pcall.kap +6 -6
- data/examples/pipeline.kap +4 -1
- data/examples/points.kap +1 -1
- data/examples/record.kap +1 -1
- data/examples/regex.kap +3 -1
- data/examples/ruby-eval.kap +1 -1
- data/examples/safe-lookup.kap +2 -2
- data/examples/scopes.kap +4 -4
- data/examples/threading.kap +28 -0
- data/examples/tset.kap +2 -2
- data/examples/two-sum.kap +3 -3
- data/kapusta.gemspec +4 -0
- data/lib/kapusta/ast.rb +16 -4
- data/lib/kapusta/cli.rb +8 -2
- data/lib/kapusta/formatter.rb +262 -110
- data/lib/kapusta/reader.rb +51 -24
- data/lib/kapusta/version.rb +1 -1
- data/spec/cli_spec.rb +15 -0
- data/spec/examples_spec.rb +25 -0
- data/spec/formatter_spec.rb +114 -5
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25c55d7f1c7a4b1dcd3cca0e7a513a74591905e9dfc97e96f015b695d75905a2
|
|
4
|
+
data.tar.gz: '0940fc49053103149667dd8bf515e159d87a10182bb3fbd5eb152ba63f487e06'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3a8cb442fcb6d5157dcf3885e9d6a7323735418e488715e35baea0006b098e8822b8252013eea2636f96a35665db8173556507af293188f632108827f8119d5
|
|
7
|
+
data.tar.gz: d0f19cbc23c569524cb64c0b70b14d4ea5ed149291acad18528611a7b12c2ff1b4d9c27ce1a239ec07e9c19bb36c5907f294fbca4b0b783bfc2991c880b93fc7
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Kapusta is a Lisp for the Ruby runtime.
|
|
4
4
|
|
|
5
|
-
It is inspired by Fennel. It is not intended to be production-ready like Clojure: that would be a lot of work, and Ruby is already a rich, elegant language.
|
|
5
|
+
It is inspired by [Fennel](https://fennel-lang.org). It is not intended to be production-ready like Clojure: that would be a lot of work, and Ruby is already a rich, elegant language.
|
|
6
6
|
|
|
7
7
|
Instead, Kapusta aims to bring some of the simplicity and joy of Lisp to Ruby. Where Lua is intentionally minimal, and Fennel follows that design for good reason, Kapusta exists mostly for fun. You can use it for small apps, LeetCode, DragonRuby, or maybe even Rails.
|
|
8
8
|
|
data/examples/accumulator.kap
CHANGED
data/examples/anagram.kap
CHANGED
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
(= (normalize-word a)
|
|
9
9
|
(normalize-word b)))
|
|
10
10
|
|
|
11
|
-
(
|
|
12
|
-
(
|
|
13
|
-
(
|
|
11
|
+
(print (anagram? "listen" "silent"))
|
|
12
|
+
(print (anagram? "apple" "papel"))
|
|
13
|
+
(print (anagram? "hello" "world"))
|
data/examples/binary-search.kap
CHANGED
data/examples/block-sort.kap
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
(require "fileutils")
|
|
2
|
+
|
|
3
|
+
(let [tmp-dir (File.expand_path "tmp" Dir.pwd)
|
|
4
|
+
path (File.join tmp-dir "blocks-and-kwargs.txt")]
|
|
5
|
+
(try
|
|
6
|
+
(do
|
|
7
|
+
(FileUtils.mkdir_p tmp-dir)
|
|
8
|
+
(File.open path
|
|
9
|
+
"w"
|
|
10
|
+
{:encoding "UTF-8"}
|
|
11
|
+
(fn [io] (: io :write "Ada\nLin\n")))
|
|
12
|
+
(File.open path
|
|
13
|
+
"r"
|
|
14
|
+
{:encoding "UTF-8"}
|
|
15
|
+
(fn [io]
|
|
16
|
+
(let [content (: io :read)]
|
|
17
|
+
(print (content.strip))
|
|
18
|
+
(print (length (content.lines)))))))
|
|
19
|
+
(finally
|
|
20
|
+
(FileUtils.rm_f path))))
|
data/examples/calc.kap
CHANGED
data/examples/counter.kap
CHANGED
data/examples/doto.kap
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
(let [xs (doto [] (: :push 1) (: :push 2) (: :push 3))]
|
|
2
|
-
(
|
|
2
|
+
(print (xs.join ", ")))
|
data/examples/even-squares.kap
CHANGED
data/examples/exceptions.kap
CHANGED
data/examples/files.kap
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
(require "fileutils")
|
|
2
|
+
|
|
3
|
+
(let [tmp-dir (File.expand_path "tmp" Dir.pwd)
|
|
4
|
+
path (File.join tmp-dir "file-io-example.txt")
|
|
5
|
+
body "Ada\nLin"]
|
|
6
|
+
(try
|
|
7
|
+
(do
|
|
8
|
+
(FileUtils.mkdir_p tmp-dir)
|
|
9
|
+
(File.write path body)
|
|
10
|
+
(print (File.read path))
|
|
11
|
+
(print (length (File.readlines path))))
|
|
12
|
+
(finally
|
|
13
|
+
(FileUtils.rm_f path))))
|
data/examples/greet.kap
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
(let [name (or (. ARGV 0) "world")]
|
|
2
|
-
(
|
|
2
|
+
(print (.. "Hello, " name "!")))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
(let [animal-class (class Zoo.Animal
|
|
2
|
+
(fn initialize [name] (set (ivar name) name))
|
|
3
|
+
(fn name [] (ivar name))
|
|
4
|
+
(fn kingdom [] "animalia")
|
|
5
|
+
(fn label [] (.. (self.name) " the animal")))
|
|
6
|
+
dog-class (class Zoo.Dog [Zoo.Animal]
|
|
7
|
+
(fn label [] (.. (self.name) " the dog"))
|
|
8
|
+
(fn bark [] "woof"))
|
|
9
|
+
dog (dog-class.new "Poppy")]
|
|
10
|
+
(print (= dog-class.superclass animal-class)
|
|
11
|
+
(dog.kingdom)
|
|
12
|
+
(dog.label)
|
|
13
|
+
(dog.bark)))
|
data/examples/kwargs.kap
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(
|
|
1
|
+
(print (format "%<name>s has %<count>d tasks" {:name "Ada" :count 3}))
|
data/examples/match.kap
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
{: name} (.. name ": no score")
|
|
5
5
|
_ "unknown"))
|
|
6
6
|
|
|
7
|
-
(
|
|
8
|
-
(
|
|
9
|
-
(
|
|
7
|
+
(print (describe-user {:name "Ada" :stats {:score 9}}))
|
|
8
|
+
(print (describe-user {:name "Lin"}))
|
|
9
|
+
(print (describe-user {}))
|
data/examples/module-header.kap
CHANGED
data/examples/palindrome.kap
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
normalized (lower.gsub (ruby "/[^a-z]/") "")]
|
|
4
4
|
(= normalized (normalized.reverse))))
|
|
5
5
|
|
|
6
|
-
(
|
|
7
|
-
(
|
|
8
|
-
(
|
|
6
|
+
(print (palindrome? "racecar"))
|
|
7
|
+
(print (palindrome? "A man, a plan, a canal: Panama"))
|
|
8
|
+
(print (palindrome? "kapusta"))
|
data/examples/pangram.kap
CHANGED
data/examples/pcall.kap
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
(let [[ok value] (pcall Integer "12")
|
|
2
2
|
[bad-ok error] (pcall Integer "oops")
|
|
3
3
|
[handled-ok handled] (xpcall Integer (fn [e] (e.message)) "oops")]
|
|
4
|
-
(
|
|
5
|
-
(
|
|
6
|
-
(
|
|
7
|
-
(
|
|
8
|
-
(
|
|
9
|
-
(
|
|
4
|
+
(print ok)
|
|
5
|
+
(print value)
|
|
6
|
+
(print bad-ok)
|
|
7
|
+
(print (error.class))
|
|
8
|
+
(print handled-ok)
|
|
9
|
+
(print handled))
|
data/examples/pipeline.kap
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
; Pipelines stay readable as stages stack up.
|
|
1
2
|
(let [words ["red" "green" "blue" "black" "olive"]]
|
|
3
|
+
; Filter first so later stages stay focused.
|
|
2
4
|
(-> words
|
|
5
|
+
; Keep the shorter words before transforming them.
|
|
3
6
|
(: :select (fn [w] (< (length w) 5)))
|
|
4
7
|
(: :map (fn [w] (w.upcase)))
|
|
5
8
|
(: :sort)
|
|
6
|
-
(: :each (fn [w] (
|
|
9
|
+
(: :each (fn [w] (print w)))))
|
data/examples/points.kap
CHANGED
data/examples/record.kap
CHANGED
data/examples/regex.kap
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
(fn parse-date [s]
|
|
2
2
|
(let [re (ruby "/\\A(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})\\z/")]
|
|
3
|
+
; Match YYYY-MM-DD and expose the named captures.
|
|
3
4
|
(case (re.match s)
|
|
4
5
|
nil nil
|
|
5
6
|
m (m.named-captures))))
|
|
6
7
|
|
|
7
8
|
(each [_ s (ipairs ["2026-04-23" "hello" "1999-12-31"])]
|
|
9
|
+
; Show both successful and failed parses.
|
|
8
10
|
(let [parsed (parse-date s)]
|
|
9
|
-
(
|
|
11
|
+
(print (.. s " -> " parsed))))
|
data/examples/ruby-eval.kap
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(
|
|
1
|
+
(print (ruby "[1, 2, 3].map { _1 * 10 }.join('-')"))
|
data/examples/safe-lookup.kap
CHANGED
data/examples/scopes.kap
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
(let [a (ScopeCounter.new)
|
|
14
14
|
b (ScopeCounter.new)]
|
|
15
|
-
(
|
|
16
|
-
(
|
|
17
|
-
(
|
|
18
|
-
(
|
|
15
|
+
(print (a.add! 5))
|
|
16
|
+
(print (b.add! 4))
|
|
17
|
+
(print (ScopeCounter.total))
|
|
18
|
+
(print (gvar last-total)))
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
(fn append [suffix value]
|
|
2
|
+
(.. value suffix))
|
|
3
|
+
|
|
4
|
+
(fn wrap [left right value]
|
|
5
|
+
(.. left value right))
|
|
6
|
+
|
|
7
|
+
(fn fetch-name [user]
|
|
8
|
+
(?. user :profile :name))
|
|
9
|
+
|
|
10
|
+
(let [thread-last (->> "Ada"
|
|
11
|
+
(append " Lovelace")
|
|
12
|
+
(wrap "[" "]")
|
|
13
|
+
(append "!"))
|
|
14
|
+
maybe-name (-?>> {:profile {:name "Ada"}}
|
|
15
|
+
(fetch-name)
|
|
16
|
+
(append "!")
|
|
17
|
+
(wrap "<" ">"))
|
|
18
|
+
missing-name (-?>> {:profile nil}
|
|
19
|
+
(fetch-name)
|
|
20
|
+
(append "!")
|
|
21
|
+
(wrap "<" ">"))
|
|
22
|
+
thread-first (-?> "kapusta"
|
|
23
|
+
(: :upcase)
|
|
24
|
+
(: :reverse))
|
|
25
|
+
missing-first (-?> nil
|
|
26
|
+
(: :upcase)
|
|
27
|
+
(: :reverse))]
|
|
28
|
+
(print thread-last maybe-name missing-name thread-first missing-first))
|
data/examples/tset.kap
CHANGED
data/examples/two-sum.kap
CHANGED
|
@@ -12,6 +12,6 @@
|
|
|
12
12
|
(let [first-pair (two-sum [2 7 11 15] 9)
|
|
13
13
|
second-pair (two-sum [3 2 4] 6)
|
|
14
14
|
missing-pair (two-sum [1 2 3] 10)]
|
|
15
|
-
(
|
|
16
|
-
(
|
|
17
|
-
(
|
|
15
|
+
(print first-pair)
|
|
16
|
+
(print second-pair)
|
|
17
|
+
(print missing-pair))
|
data/kapusta.gemspec
CHANGED
|
@@ -6,12 +6,16 @@ Gem::Specification.new do |spec|
|
|
|
6
6
|
spec.name = 'kapusta'
|
|
7
7
|
spec.version = Kapusta::VERSION
|
|
8
8
|
spec.authors = ['Evgenii Morozov']
|
|
9
|
+
spec.homepage = 'https://github.com/evmorov/kapusta'
|
|
9
10
|
|
|
10
11
|
spec.summary = 'A Lisp for the Ruby runtime'
|
|
11
12
|
spec.description = 'Kapusta is a Lisp for the Ruby runtime.'
|
|
12
13
|
spec.required_ruby_version = '>= 3.1'
|
|
13
14
|
|
|
14
15
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
|
|
15
19
|
|
|
16
20
|
spec.files = Dir.chdir(__dir__) do
|
|
17
21
|
`git ls-files -z`.split("\x0").select do |path|
|
data/lib/kapusta/ast.rb
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Kapusta
|
|
4
|
+
class Comment
|
|
5
|
+
attr_reader :text
|
|
6
|
+
|
|
7
|
+
def initialize(text)
|
|
8
|
+
@text = text
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
4
12
|
class Sym
|
|
5
13
|
attr_reader :name
|
|
6
14
|
|
|
@@ -43,14 +51,18 @@ module Kapusta
|
|
|
43
51
|
end
|
|
44
52
|
|
|
45
53
|
class HashLit
|
|
46
|
-
attr_reader :
|
|
54
|
+
attr_reader :entries
|
|
55
|
+
|
|
56
|
+
def initialize(entries)
|
|
57
|
+
@entries = entries
|
|
58
|
+
end
|
|
47
59
|
|
|
48
|
-
def
|
|
49
|
-
@
|
|
60
|
+
def pairs
|
|
61
|
+
@entries.grep(Array)
|
|
50
62
|
end
|
|
51
63
|
|
|
52
64
|
def all_sym_keys?
|
|
53
|
-
|
|
65
|
+
pairs.all? { |key, _| key.is_a?(Symbol) }
|
|
54
66
|
end
|
|
55
67
|
end
|
|
56
68
|
|
data/lib/kapusta/cli.rb
CHANGED
|
@@ -5,7 +5,7 @@ require 'optparse'
|
|
|
5
5
|
|
|
6
6
|
module Kapusta
|
|
7
7
|
class CLI
|
|
8
|
-
Options = Struct.new(:compile, :help, keyword_init: true)
|
|
8
|
+
Options = Struct.new(:compile, :help, :version, keyword_init: true)
|
|
9
9
|
|
|
10
10
|
def self.start(argv = ARGV)
|
|
11
11
|
args = argv.dup
|
|
@@ -16,6 +16,11 @@ module Kapusta
|
|
|
16
16
|
return
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
if options.version
|
|
20
|
+
$stdout.puts "kapusta #{Kapusta::VERSION}"
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
19
24
|
if options.compile
|
|
20
25
|
compile_file(args)
|
|
21
26
|
else
|
|
@@ -24,12 +29,13 @@ module Kapusta
|
|
|
24
29
|
end
|
|
25
30
|
|
|
26
31
|
def self.parse_options(args)
|
|
27
|
-
options = Options.new(compile: false, help: false)
|
|
32
|
+
options = Options.new(compile: false, help: false, version: false)
|
|
28
33
|
|
|
29
34
|
OptionParser.new do |parser|
|
|
30
35
|
parser.banner = usage
|
|
31
36
|
parser.on('-c', '--compile', 'Compile .kap to Ruby') { options.compile = true }
|
|
32
37
|
parser.on('-h', '--help', 'Show this help') { options.help = true }
|
|
38
|
+
parser.on('-v', '--version', 'Show version') { options.version = true }
|
|
33
39
|
end.order!(args)
|
|
34
40
|
|
|
35
41
|
options
|