rupture 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|