kapusta 0.1.0 → 0.1.2

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: f08d47c985c44cde4016b3f4188f1a59a4a4b72ae752d3876af8ab7c66c12069
4
- data.tar.gz: d9b96c75f4a99de5bbdb9e7bdbdf258bfda5bf7791d13ffe2d115a655627f8b0
3
+ metadata.gz: 62ac0b0f6d3ce6116b82110191b5c36783ed1689e2567076dbe68fc66e6ab3f7
4
+ data.tar.gz: 6b705edf9d441c4f753e8420b0e8bc6a2327d91f6ab9041a603c3afe6546487b
5
5
  SHA512:
6
- metadata.gz: f936f5546667301b2bf9a96f0a16f8b61f6ea0b6fefc6c10a87c8a794b60b376274d2672b24e2a23b269adc3d932d418442dfdbce6d8957569a6ae426d0a3a4f
7
- data.tar.gz: fb523b8572d114ee9e5b87c4a993607b9ab610a82f3aa0886a1465064877d956768fde168f7e58e2cfe2f31cddc8efddf82473d6addeab97f7c112d8d1fadcbb
6
+ metadata.gz: 243b1e47fd6a5684f7b4d2a9bf46e27d6b96883fb3cf71bf4f90edb4d48f0e5afa006c2da0191f5fc3cb116e37b506243b0f7030b90a6c6096629173e98074de
7
+ data.tar.gz: 825b21b71cb0eacb24623beff28dc2fcb1b542cf50268d78ebcfba2b156bf634992975fb1f1a3a0e27f407c51e54c8eb96683d544f861de5f5cc51731fc819c3
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
 
@@ -10,6 +10,14 @@ For more information about Kapusta, see the official Fennel documentation and tu
10
10
 
11
11
  ## Usage
12
12
 
13
+ ```
14
+ gem install kapusta
15
+ kapfmt --fix examples/fizzbuzz.kap
16
+ kapusta examples/fizzbuzz.kap
17
+ ```
18
+
19
+ or
20
+
13
21
  ```
14
22
  exe/kapusta examples/fizzbuzz.kap
15
23
  ```
@@ -37,7 +45,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
37
45
  Kapusta-specific additions:
38
46
 
39
47
  - `module` and `class` for Ruby host structure, including file-header forms
40
- - `ivar` / `cvar` / `gvar` escape hatches
48
+ - `ivar` (`@var`) / `cvar` (`@@var`) / `gvar` (`$var`) escape hatches
41
49
  - `try` / `catch` / `finally` plus `raise` for exceptions
42
50
  - `(ruby "...")` raw host escape hatch
43
51
  - a trailing symbol-keyed hash is emitted as Ruby keyword arguments
@@ -13,4 +13,4 @@
13
13
  (let [acc (Accumulator.new 10)]
14
14
  (acc.add! 5)
15
15
  (acc.add! 7)
16
- (puts (acc.value)))
16
+ (print (acc.value)))
data/examples/anagram.kap CHANGED
@@ -8,6 +8,6 @@
8
8
  (= (normalize-word a)
9
9
  (normalize-word b)))
10
10
 
11
- (puts (anagram? "listen" "silent"))
12
- (puts (anagram? "apple" "papel"))
13
- (puts (anagram? "hello" "world"))
11
+ (print (anagram? "listen" "silent"))
12
+ (print (anagram? "apple" "papel"))
13
+ (print (anagram? "hello" "world"))
@@ -12,5 +12,5 @@
12
12
 
13
13
  (let [found (binary-search [1 3 5 7 9 11] 7)
14
14
  missing (binary-search [1 3 5 7 9 11] 2)]
15
- (puts found)
16
- (puts (missing.inspect)))
15
+ (print found)
16
+ (print missing))
@@ -1,3 +1,3 @@
1
1
  (let [xs [3 1 2]
2
2
  sorted (xs.sort (fn [a b] (b.<=> a)))]
3
- (puts (sorted.join ", ")))
3
+ (print (sorted.join ", ")))
@@ -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
@@ -7,4 +7,4 @@
7
7
  [:div a b] (/ (eval-expr a) (eval-expr b))
8
8
  _ (raise (ArgumentError.new (.. "unknown op: " (expr.inspect))))))
9
9
 
10
- (puts (eval-expr [:add [:num 2] [:mul [:num 3] [:num 4]]]))
10
+ (print (eval-expr [:add [:num 2] [:mul [:num 3] [:num 4]]]))
data/examples/counter.kap CHANGED
@@ -16,4 +16,4 @@
16
16
  (let [c (Counter.new 10)]
17
17
  (c.tick)
18
18
  (c.tick)
19
- (puts (c.value)))
19
+ (print (c.value)))
data/examples/doto.kap CHANGED
@@ -1,2 +1,2 @@
1
1
  (let [xs (doto [] (: :push 1) (: :push 2) (: :push 3))]
2
- (puts (xs.join ", ")))
2
+ (print (xs.join ", ")))
@@ -4,4 +4,4 @@
4
4
  (: :select (fn [n] (n.even?)))
5
5
  (: :map (fn [n] (* n n))))
6
6
  ]
7
- (puts (even-squares.join ", ")))
7
+ (print (even-squares.join ", ")))
@@ -8,7 +8,7 @@
8
8
  (catch ArgumentError e
9
9
  (.. "bad: " s))
10
10
  (finally
11
- (puts (.. "seen: " s)))))
11
+ (print (.. "seen: " s)))))
12
12
 
13
13
  (each [_ s (ipairs ["12" "oops"])]
14
- (puts (parse-score s)))
14
+ (print (parse-score s)))
@@ -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
- (puts (.. "Hello, " name "!")))
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
- (puts (format "%<name>s has %<count>d tasks" {:name "Ada" :count 3}))
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
- (puts (describe-user {:name "Ada" :stats {:score 9}}))
8
- (puts (describe-user {:name "Lin"}))
9
- (puts (describe-user {}))
7
+ (print (describe-user {:name "Ada" :stats {:score 9}}))
8
+ (print (describe-user {:name "Lin"}))
9
+ (print (describe-user {}))
@@ -1,6 +1,7 @@
1
+ ; File header forms map directly to Ruby modules.
1
2
  (module HeaderDemo)
2
3
 
3
4
  (fn self.greet [name]
4
5
  (.. "Hello, " name "!"))
5
6
 
6
- (puts (self.greet "Ada"))
7
+ (print (self.greet "Ada"))
@@ -3,6 +3,6 @@
3
3
  normalized (lower.gsub (ruby "/[^a-z]/") "")]
4
4
  (= normalized (normalized.reverse))))
5
5
 
6
- (puts (palindrome? "racecar"))
7
- (puts (palindrome? "A man, a plan, a canal: Panama"))
8
- (puts (palindrome? "kapusta"))
6
+ (print (palindrome? "racecar"))
7
+ (print (palindrome? "A man, a plan, a canal: Panama"))
8
+ (print (palindrome? "kapusta"))
data/examples/pangram.kap CHANGED
@@ -5,5 +5,5 @@
5
5
  uniq (chars.uniq)]
6
6
  (= (uniq.length) 26)))
7
7
 
8
- (puts (pangram? "The quick brown fox jumps over the lazy dog"))
9
- (puts (pangram? "Hello, world"))
8
+ (print (pangram? "The quick brown fox jumps over the lazy dog"))
9
+ (print (pangram? "Hello, world"))
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
- (puts ok)
5
- (puts value)
6
- (puts bad-ok)
7
- (puts (error.class))
8
- (puts handled-ok)
9
- (puts handled))
4
+ (print ok)
5
+ (print value)
6
+ (print bad-ok)
7
+ (print (error.class))
8
+ (print handled-ok)
9
+ (print handled))
@@ -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] (puts w)))))
9
+ (: :each (fn [w] (print w)))))
data/examples/points.kap CHANGED
@@ -6,4 +6,4 @@
6
6
  [_ _] "point"))
7
7
 
8
8
  (each [_ point (ipairs [[0 0] [0 2] [3 0] [3 4]])]
9
- (puts (point-kind point)))
9
+ (print (point-kind point)))
data/examples/record.kap CHANGED
@@ -3,4 +3,4 @@
3
3
  (.. name " / " role " / " (tags.join ", "))))
4
4
 
5
5
  (let [record {:name "Ada" :role "engineer" :tags ["ruby" "lisp"]}]
6
- (puts (format-record record)))
6
+ (print (format-record record)))
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
- (puts (.. s " -> " (parsed.inspect)))))
11
+ (print (.. s " -> " parsed))))
@@ -1 +1 @@
1
- (puts (ruby "[1, 2, 3].map { _1 * 10 }.join('-')"))
1
+ (print (ruby "[1, 2, 3].map { _1 * 10 }.join('-')"))
@@ -2,5 +2,5 @@
2
2
  missing {}
3
3
  name (?. user :profile :name)
4
4
  missing-name (?. missing :profile :name)]
5
- (puts name)
6
- (puts (missing-name.inspect)))
5
+ (print name)
6
+ (print missing-name))
data/examples/scopes.kap CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  (let [a (ScopeCounter.new)
14
14
  b (ScopeCounter.new)]
15
- (puts (a.add! 5))
16
- (puts (b.add! 4))
17
- (puts (ScopeCounter.total))
18
- (puts (gvar last-total)))
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
@@ -1,4 +1,4 @@
1
1
  (let [person {:name "Ada"}]
2
2
  (tset person :city "Amsterdam")
3
- (puts (person.inspect))
4
- (puts (. person :city)))
3
+ (print person)
4
+ (print (. person :city)))
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
- (puts (first-pair.inspect))
16
- (puts (second-pair.inspect))
17
- (puts (missing-pair.inspect)))
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 :pairs
54
+ attr_reader :entries
55
+
56
+ def initialize(entries)
57
+ @entries = entries
58
+ end
47
59
 
48
- def initialize(pairs)
49
- @pairs = pairs
60
+ def pairs
61
+ @entries.grep(Array)
50
62
  end
51
63
 
52
64
  def all_sym_keys?
53
- @pairs.all? { |key, _| key.is_a?(Symbol) }
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