rupture 0.0.0 → 0.1.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.
- data/README.md +17 -0
- data/Rakefile +2 -10
- data/VERSION +1 -1
- data/init.rb +1 -0
- data/lib/rupture/array_seq.rb +65 -0
- data/lib/rupture/cons.rb +18 -0
- data/lib/rupture/core_ext.rb +21 -0
- data/lib/rupture/fn.rb +88 -0
- data/lib/rupture/function.rb +114 -0
- data/lib/rupture/lazy_seq.rb +35 -0
- data/lib/rupture/list.rb +31 -0
- data/lib/rupture/lookup.rb +24 -0
- data/lib/rupture/meta.rb +22 -0
- data/lib/rupture/rails_ext.rb +38 -0
- data/lib/rupture/reader.rb +110 -0
- data/lib/rupture/seq.rb +62 -0
- data/lib/rupture/sequence.rb +274 -0
- data/lib/rupture/symbol.rb +32 -0
- data/lib/rupture/utils.rb +7 -0
- data/lib/rupture.rb +20 -0
- data/rupture.gemspec +28 -6
- data/test/fn_test.rb +41 -0
- data/test/list_test.rb +20 -0
- data/test/meta_test.rb +43 -0
- data/test/seq_test.rb +255 -0
- data/test/test_helper.rb +11 -0
- metadata +29 -8
- data/README.rdoc +0 -0
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
It's hard to go back to Ruby once you've gotten used to programming in Clojure. Rupture makes the
|
2
|
+
transition easier by giving you the power of Clojure's sequence library and higher order functions
|
3
|
+
in Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
gem install rupture
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
require 'rupture'
|
12
|
+
# see the tests for examples
|
13
|
+
|
14
|
+
## Coming soon
|
15
|
+
|
16
|
+
* improved README
|
17
|
+
* hamster-based persistent data structures
|
data/Rakefile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rake/testtask'
|
3
|
-
require '
|
3
|
+
require 'rubygems'
|
4
4
|
|
5
5
|
begin
|
6
6
|
require 'jeweler'
|
@@ -14,7 +14,7 @@ begin
|
|
14
14
|
end
|
15
15
|
Jeweler::GemcutterTasks.new
|
16
16
|
rescue LoadError
|
17
|
-
puts "Jeweler not available. Install it with: sudo gem install
|
17
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
18
18
|
end
|
19
19
|
|
20
20
|
Rake::TestTask.new do |t|
|
@@ -23,14 +23,6 @@ Rake::TestTask.new do |t|
|
|
23
23
|
t.verbose = false
|
24
24
|
end
|
25
25
|
|
26
|
-
Rake::RDocTask.new do |rdoc|
|
27
|
-
rdoc.rdoc_dir = 'rdoc'
|
28
|
-
rdoc.title = 'model_set'
|
29
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
30
|
-
rdoc.rdoc_files.include('README*')
|
31
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
32
|
-
end
|
33
|
-
|
34
26
|
begin
|
35
27
|
require 'rcov/rcovtask'
|
36
28
|
Rcov::RcovTask.new do |t|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.1.0
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rupture'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Rupture
|
2
|
+
class ArraySeq < Seq
|
3
|
+
def initialize(array, index = 0)
|
4
|
+
@array = array
|
5
|
+
@index = index
|
6
|
+
super()
|
7
|
+
end
|
8
|
+
|
9
|
+
def first
|
10
|
+
@array[@index]
|
11
|
+
end
|
12
|
+
|
13
|
+
def rest
|
14
|
+
ArraySeq.new(@array, @index.inc)
|
15
|
+
end
|
16
|
+
|
17
|
+
def seq
|
18
|
+
self if @index < @array.size
|
19
|
+
end
|
20
|
+
|
21
|
+
def size
|
22
|
+
@array.size - @index
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class RArraySeq < ArraySeq
|
27
|
+
def initialize(array, index = array.size - 1)
|
28
|
+
super(array, index)
|
29
|
+
end
|
30
|
+
|
31
|
+
def rest
|
32
|
+
RArraySeq.new(@array, @index.dec)
|
33
|
+
end
|
34
|
+
|
35
|
+
def seq
|
36
|
+
self if @index >= 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def size
|
40
|
+
@index.inc
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ArraySeqable
|
45
|
+
def seq
|
46
|
+
Rupture::ArraySeq.new(self).seq
|
47
|
+
end
|
48
|
+
|
49
|
+
def rseq
|
50
|
+
Rupture::RArraySeq.new(self).seq
|
51
|
+
end
|
52
|
+
|
53
|
+
def not_empty
|
54
|
+
self if seq
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Array
|
60
|
+
include Rupture::ArraySeqable
|
61
|
+
end
|
62
|
+
|
63
|
+
class String
|
64
|
+
include Rupture::ArraySeqable
|
65
|
+
end
|
data/lib/rupture/cons.rb
ADDED
data/lib/rupture/fn.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Rupture
|
4
|
+
module Fn
|
5
|
+
def complement
|
6
|
+
lambda do |*args|
|
7
|
+
not call(*args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
alias -@ complement
|
11
|
+
|
12
|
+
def comp(fn)
|
13
|
+
lambda do |*args|
|
14
|
+
call(fn[*args])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias * comp
|
18
|
+
|
19
|
+
def partial(*partials)
|
20
|
+
lambda do |*args|
|
21
|
+
call(*(partials + args))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# def apply(*args)
|
26
|
+
# last = args.pop
|
27
|
+
# call(*F.concat(args, last))
|
28
|
+
# end
|
29
|
+
|
30
|
+
def to_proc
|
31
|
+
lambda do |key|
|
32
|
+
self[key]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def arity
|
37
|
+
-1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Proc
|
43
|
+
include Rupture::Fn
|
44
|
+
end
|
45
|
+
|
46
|
+
class Method
|
47
|
+
include Rupture::Fn
|
48
|
+
end
|
49
|
+
|
50
|
+
class Symbol
|
51
|
+
include Rupture::Fn
|
52
|
+
|
53
|
+
def call(object = nil, *args)
|
54
|
+
object.method(self)[*args]
|
55
|
+
end
|
56
|
+
alias [] call
|
57
|
+
|
58
|
+
def arity
|
59
|
+
-2
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Hash
|
64
|
+
include Rupture::Fn
|
65
|
+
alias call []
|
66
|
+
end
|
67
|
+
|
68
|
+
class Array
|
69
|
+
include Rupture::Fn
|
70
|
+
alias call []
|
71
|
+
end
|
72
|
+
|
73
|
+
class Set
|
74
|
+
include Rupture::Fn
|
75
|
+
|
76
|
+
def [](key)
|
77
|
+
key if include?(key)
|
78
|
+
end
|
79
|
+
alias call []
|
80
|
+
end
|
81
|
+
|
82
|
+
class Module
|
83
|
+
def [](method_name, *partials)
|
84
|
+
lambda do |*args|
|
85
|
+
self.send(method_name, *(args + partials))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Rupture
|
2
|
+
module Function
|
3
|
+
# FIXME isn't totally lazy when working with > 1 collection
|
4
|
+
# If the first is empty, the second is still seq'd
|
5
|
+
def map(*colls, &f)
|
6
|
+
f ||= colls.shift
|
7
|
+
lazy_seq do
|
8
|
+
seqs = colls.collect(&:seq)
|
9
|
+
if seqs.all?
|
10
|
+
firsts = seqs.collect(&:first)
|
11
|
+
rests = seqs.collect(&:rest)
|
12
|
+
cons(f[*firsts], map(*rests, &f))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def concat(*colls)
|
18
|
+
lazy_seq do
|
19
|
+
head, *tail = colls.collect(&:seq)
|
20
|
+
if head
|
21
|
+
cons(head.first, concat(head.rest, *tail))
|
22
|
+
elsif tail.any?
|
23
|
+
concat(*tail)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def mapcat(*colls, &f)
|
29
|
+
f ||= colls.shift
|
30
|
+
concat(*map(*colls, &f))
|
31
|
+
end
|
32
|
+
|
33
|
+
def zip(*colls)
|
34
|
+
lazy_seq do
|
35
|
+
seqs = colls.collect(&:seq)
|
36
|
+
if seqs.any?
|
37
|
+
firsts = seqs.collect(&:first)
|
38
|
+
rests = seqs.collect(&:rest)
|
39
|
+
cons(firsts, zip(*rests))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def loop(*vals)
|
45
|
+
more = true
|
46
|
+
recur = lambda {|*vals| more = true}
|
47
|
+
|
48
|
+
while more
|
49
|
+
more = nil
|
50
|
+
result = yield(recur, *vals)
|
51
|
+
end
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def iterate(*args, &f)
|
56
|
+
f ||= args.shift
|
57
|
+
Utils.verify_args(args, 1)
|
58
|
+
x = args.first
|
59
|
+
lazy_seq do
|
60
|
+
cons(x, iterate(f[x], &f))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def repeatedly(*args, &fn)
|
65
|
+
fn ||= args.pop
|
66
|
+
Utils.verify_args(args, 0, 1)
|
67
|
+
n = args.first
|
68
|
+
|
69
|
+
lazy_seq do
|
70
|
+
if n.nil?
|
71
|
+
cons(fn[], repeatedly(n, fn))
|
72
|
+
elsif n > 0
|
73
|
+
cons(fn[], repeatedly(n.dec, fn))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def repeat(*args)
|
79
|
+
Utils.verify_args(args, 1, 2)
|
80
|
+
x, n = args.reverse
|
81
|
+
repeatedly(n) {x}
|
82
|
+
end
|
83
|
+
|
84
|
+
def lazy_seq(f = nil, &fn)
|
85
|
+
fn ||= f
|
86
|
+
LazySeq.new(&fn)
|
87
|
+
end
|
88
|
+
|
89
|
+
def cons(head, tail)
|
90
|
+
Cons.new(head, tail)
|
91
|
+
end
|
92
|
+
|
93
|
+
def list(*xs)
|
94
|
+
List.new(*xs)
|
95
|
+
end
|
96
|
+
|
97
|
+
def identity(x)
|
98
|
+
x
|
99
|
+
end
|
100
|
+
|
101
|
+
def juxt(*fs)
|
102
|
+
lambda do |*args|
|
103
|
+
fs.collect {|f| f[*args]}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def decorate(*args)
|
108
|
+
juxt(identity, *args)
|
109
|
+
end
|
110
|
+
|
111
|
+
extend Function
|
112
|
+
F = Function
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Rupture
|
2
|
+
class LazySeq < Seq
|
3
|
+
def initialize(b = nil, &block)
|
4
|
+
@block = block || b
|
5
|
+
super()
|
6
|
+
end
|
7
|
+
|
8
|
+
def seq
|
9
|
+
return @seq unless @block
|
10
|
+
@seq = @block.call.seq
|
11
|
+
@block = nil
|
12
|
+
@seq
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Enumerable
|
18
|
+
def seq
|
19
|
+
F.lazy_seq do
|
20
|
+
callcc do |external|
|
21
|
+
each do |item|
|
22
|
+
external = callcc do |internal|
|
23
|
+
rest = F.lazy_seq do
|
24
|
+
callcc do |external|
|
25
|
+
internal.call(external)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
external.call(F.cons(item, rest))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
external.call(nil)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/rupture/list.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rupture
|
2
|
+
class List < Seq
|
3
|
+
class << self
|
4
|
+
alias create new
|
5
|
+
end
|
6
|
+
private_class_method :create
|
7
|
+
attr_reader :seq, :size
|
8
|
+
|
9
|
+
def self.empty
|
10
|
+
@empty ||= create(nil, 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(seq, size)
|
14
|
+
@seq = seq.seq
|
15
|
+
@size = size
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.new(*args)
|
19
|
+
list = self.empty
|
20
|
+
args.reverse_each do |x|
|
21
|
+
list = list.conj(x)
|
22
|
+
end
|
23
|
+
|
24
|
+
list
|
25
|
+
end
|
26
|
+
|
27
|
+
def conj(x)
|
28
|
+
self.class.send(:create, Cons.new(x, @seq), @size.inc)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rupture
|
2
|
+
module Lookup
|
3
|
+
def lookup
|
4
|
+
lambda do |a|
|
5
|
+
a[self]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Symbol
|
12
|
+
include Rupture::Lookup
|
13
|
+
|
14
|
+
alias ~ lookup
|
15
|
+
end
|
16
|
+
|
17
|
+
class String
|
18
|
+
include Rupture::Lookup
|
19
|
+
end
|
20
|
+
|
21
|
+
class Numeric
|
22
|
+
include Rupture::Lookup
|
23
|
+
end
|
24
|
+
|
data/lib/rupture/meta.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rupture::Meta
|
2
|
+
def meta
|
3
|
+
@_meta ||= {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def clone(meta = nil)
|
7
|
+
meta ||= @_meta.clone if @_meta
|
8
|
+
raise "meta must be of type Hash, it is #{meta.class}" unless meta.nil? or meta.kind_of?(Hash)
|
9
|
+
copy = super()
|
10
|
+
copy.instance_variable_set(:@_meta, meta)
|
11
|
+
copy
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_meta(meta)
|
15
|
+
clone(meta)
|
16
|
+
end
|
17
|
+
|
18
|
+
def vary_meta(*args, &fn)
|
19
|
+
fn ||= args.shift
|
20
|
+
with_meta(fn[meta, *args])
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Hash
|
2
|
+
def symbolize_keys!
|
3
|
+
keys.each do |key|
|
4
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
5
|
+
end
|
6
|
+
self
|
7
|
+
end
|
8
|
+
|
9
|
+
def symbolize_keys
|
10
|
+
dup.symbolize_keys!
|
11
|
+
end
|
12
|
+
|
13
|
+
def deep_symbolize_keys!
|
14
|
+
values.each do |val|
|
15
|
+
val.deep_symbolize_keys! if val.is_a?(Hash)
|
16
|
+
end
|
17
|
+
symbolize_keys!
|
18
|
+
end
|
19
|
+
|
20
|
+
def deep_symbolize_keys
|
21
|
+
copy = symbolize_keys
|
22
|
+
copy.each do |key, val|
|
23
|
+
copy[key] = val.deep_symbolize_keys if val.is_a?(Hash)
|
24
|
+
end
|
25
|
+
copy
|
26
|
+
end
|
27
|
+
|
28
|
+
def stringify_keys!
|
29
|
+
keys.each do |key|
|
30
|
+
self[key.to_s] = delete(key)
|
31
|
+
end
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def stringify_keys
|
36
|
+
dup.stringify_keys!
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Rupture
|
4
|
+
class Reader
|
5
|
+
def initialize(input)
|
6
|
+
@input = input
|
7
|
+
@buffer = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def ungetc(*chars)
|
11
|
+
@buffer.concat(chars)
|
12
|
+
@space = false
|
13
|
+
chars.last
|
14
|
+
end
|
15
|
+
|
16
|
+
def getc
|
17
|
+
while c = (@buffer.shift || @input.getc.chr)
|
18
|
+
if c =~ /[\s,;]/
|
19
|
+
next if @space
|
20
|
+
@space = true
|
21
|
+
@input.gets if c == ';'
|
22
|
+
return ' '
|
23
|
+
else
|
24
|
+
@space = false
|
25
|
+
return c
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def peekc
|
31
|
+
unget(getc)
|
32
|
+
end
|
33
|
+
|
34
|
+
def read
|
35
|
+
case c = getc
|
36
|
+
when '(' : read_list
|
37
|
+
when '[' : read_list(Array, ']')
|
38
|
+
when '{' : read_map
|
39
|
+
when '"' : read_string
|
40
|
+
when ':' : read_keyword
|
41
|
+
when ' ' : read
|
42
|
+
when /\d/ : ungetc(c); read_number
|
43
|
+
when /\w/ : ungetc(c); read_symbol
|
44
|
+
when '-'
|
45
|
+
case c = getc
|
46
|
+
when /\d/ : ungetc(c); -read_number
|
47
|
+
else ungetc('-', c); read_symbol
|
48
|
+
end
|
49
|
+
when '#'
|
50
|
+
case c = getc
|
51
|
+
when '{' : read_list(Set, '}')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def read_list(klass = List, terminator = ')')
|
57
|
+
list = klass.new
|
58
|
+
while c = getc
|
59
|
+
return list if c == terminator
|
60
|
+
ungetc(c)
|
61
|
+
list << read
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def read_map
|
66
|
+
map = {}
|
67
|
+
while c = getc
|
68
|
+
return map if c == '}'
|
69
|
+
ungetc(c)
|
70
|
+
list[read] = read
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def read_string
|
75
|
+
string = ''
|
76
|
+
while c = getc
|
77
|
+
return string if c == '"'
|
78
|
+
string << c
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def read_while(pattern, token = '')
|
83
|
+
while c = getc
|
84
|
+
if c !~ pattern
|
85
|
+
ungetc(c)
|
86
|
+
return token
|
87
|
+
end
|
88
|
+
token << c
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_symbol(prefix = '@')
|
93
|
+
read_while(/[^\s{}\[\]()]/, prefix).to_sym
|
94
|
+
end
|
95
|
+
|
96
|
+
def read_keyword
|
97
|
+
read_symbol('')
|
98
|
+
end
|
99
|
+
|
100
|
+
def read_number
|
101
|
+
number = read_while(/[-\d]/)
|
102
|
+
if (c = getc) == '.'
|
103
|
+
read_while(/\d/, number << '.').to_f
|
104
|
+
else
|
105
|
+
ungetc(c)
|
106
|
+
number.to_i
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/rupture/seq.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Rupture
|
2
|
+
class Seq < Enumerable::Enumerator
|
3
|
+
include Sequence
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
super(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
def each
|
10
|
+
s = self
|
11
|
+
while s = s.seq
|
12
|
+
yield s.first
|
13
|
+
s = s.rest
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](index)
|
18
|
+
nth(index)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_ary
|
22
|
+
to_a
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
s = self.seq
|
27
|
+
o = other.seq
|
28
|
+
while s && o
|
29
|
+
return false if s.first != o.first
|
30
|
+
s = s.next
|
31
|
+
o = o.next
|
32
|
+
end
|
33
|
+
s.nil? and o.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.empty
|
37
|
+
@empty ||= EmptySeq.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class EmptySeq < Seq
|
42
|
+
def seq
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class NilClass
|
49
|
+
include Rupture::Sequence
|
50
|
+
|
51
|
+
def seq
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def first
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def rest
|
60
|
+
Rupture::Seq.empty
|
61
|
+
end
|
62
|
+
end
|