master_forest 0.0.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.
- data/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +28 -0
- data/Rakefile +1 -0
- data/benchmark/run.rb +27 -0
- data/lib/master_forest/memcache_term.rb +41 -0
- data/lib/master_forest/term.rb +98 -0
- data/lib/master_forest/version.rb +3 -0
- data/lib/master_forest.rb +3 -0
- data/master_forest.gemspec +22 -0
- data/spec/term_spec.rb +170 -0
- metadata +91 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2@master_forest --create
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Joe Nelson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Master Forest
|
2
|
+
|
3
|
+
This gem parses and reduces combinatory logic written in Lazy K syntax.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
<table>
|
8
|
+
<caption>MasterForest::Term methods</caption>
|
9
|
+
<tbody>
|
10
|
+
<tr><td>initialize(string)</td> <td>argument is a Lazy K string</td></tr>
|
11
|
+
<tr><td>leaf?</td> <td>Is it a single letter like `s`, `k`, or `i`?</td></tr>
|
12
|
+
<tr><td>valid?</td> <td>Is it syntactically valid?</td></tr>
|
13
|
+
<tr><td>normal?</td> <td>Is it in normal form? (i.e. not further reducible)</td></tr>
|
14
|
+
<tr><td>to_s</td> <td>Serialize back to Lazy K</td></tr>
|
15
|
+
<tr><td>l</td> <td>Left applicand; nil if leaf</td></tr>
|
16
|
+
<tr><td>r</td> <td>Right applicand; nil if leaf</td></tr>
|
17
|
+
<tr><td>reduce</td> <td>Return single β reduction of term or term itself</td></tr>
|
18
|
+
<tr><td>fully_reduce(depth)</td> <td>Reduce depth times or until normal, default depth is ∞</td></tr>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
21
|
+
|
22
|
+
## Performance
|
23
|
+
|
24
|
+
This gem includes two implementations of the same functionality,
|
25
|
+
`MasterForest::Term` and `MasterForest::MemcacheTerm`. If you
|
26
|
+
run memcache then the latter will use it to memoize previous
|
27
|
+
reductions. This goes much faster. You can test the speed by running
|
28
|
+
`benchmark/run.rb`.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/benchmark/run.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../lib/master_forest'
|
2
|
+
require 'benchmark'
|
3
|
+
include MasterForest
|
4
|
+
|
5
|
+
def show_performance &blk
|
6
|
+
print Benchmark.measure &blk
|
7
|
+
end
|
8
|
+
|
9
|
+
[Term, MemcacheTerm].each do |impl|
|
10
|
+
puts "*"*80
|
11
|
+
puts "Benchmarking #{impl} implementation"
|
12
|
+
[
|
13
|
+
"```````sss`ssisss", "`````s``ssii`ssss", "`````sii``ss`ssss",
|
14
|
+
"`````sii```ssssss", "```````s`ssssisss", "`````s`ss`ss`ssss",
|
15
|
+
"``````ssi``ssssss", "``````ssi`ss`ssss", "`````s`s`ssi`ssss"
|
16
|
+
].each do |term|
|
17
|
+
puts "Reduce #{term}"
|
18
|
+
2.times do |i|
|
19
|
+
print "#{i+1})"
|
20
|
+
show_performance do
|
21
|
+
t = impl.new term
|
22
|
+
t.fully_reduce
|
23
|
+
end
|
24
|
+
end
|
25
|
+
puts
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'dalli'
|
2
|
+
|
3
|
+
module MasterForest
|
4
|
+
class MemcacheTerm < Term
|
5
|
+
def initialize raw, left = nil, right = nil
|
6
|
+
@cache = Dalli::Client.new 'localhost:11211'
|
7
|
+
super raw, left, right
|
8
|
+
end
|
9
|
+
|
10
|
+
def fully_reduce depth = Float::INFINITY
|
11
|
+
cur = self
|
12
|
+
redices = [self]
|
13
|
+
1.upto(depth) do
|
14
|
+
reduced = @cache.get cur.to_s
|
15
|
+
if reduced
|
16
|
+
cache! redices, reduced
|
17
|
+
return reduced
|
18
|
+
end
|
19
|
+
|
20
|
+
reduced = cur.reduce
|
21
|
+
if reduced.normal?
|
22
|
+
cache! redices, reduced
|
23
|
+
return reduced
|
24
|
+
else
|
25
|
+
redices << reduced
|
26
|
+
end
|
27
|
+
cur = reduced
|
28
|
+
end
|
29
|
+
cur
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def cache! redices, redux
|
35
|
+
redices.each { |redex|
|
36
|
+
@cache.set redex.to_s, redux.to_s
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module MasterForest
|
2
|
+
class Term
|
3
|
+
def initialize raw, left = nil, right = nil
|
4
|
+
@raw = raw
|
5
|
+
if raw.nil?
|
6
|
+
@l, @r = left, right
|
7
|
+
@parsed = @l and @r
|
8
|
+
raise "Empty node" unless @parsed
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
@raw ||= ['`', l.to_s, r.to_s].join
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
to_s == other.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
/[^`ski]/.match(to_s).nil? and (subterm_length(0)+1 == to_s.length)
|
22
|
+
end
|
23
|
+
|
24
|
+
def normal?
|
25
|
+
return @normal ||= if leaf?
|
26
|
+
true
|
27
|
+
elsif /^`(i|`k|``s)/.match(to_s)
|
28
|
+
false
|
29
|
+
else
|
30
|
+
l.normal? && r.normal?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def leaf?
|
35
|
+
to_s[0] != '`'
|
36
|
+
end
|
37
|
+
|
38
|
+
def l
|
39
|
+
shallow_parse unless @parsed
|
40
|
+
@l
|
41
|
+
end
|
42
|
+
|
43
|
+
def r
|
44
|
+
shallow_parse unless @parsed
|
45
|
+
@r
|
46
|
+
end
|
47
|
+
|
48
|
+
def reduce
|
49
|
+
return self if leaf?
|
50
|
+
|
51
|
+
return r if to_s.start_with? '`i'
|
52
|
+
return l.r if to_s.start_with? '``k'
|
53
|
+
if to_s.start_with? '```s'
|
54
|
+
return join(join(l.l.r, r), join(l.r, r))
|
55
|
+
end
|
56
|
+
|
57
|
+
reduced = l.reduce
|
58
|
+
return join(reduced, r) if reduced != l
|
59
|
+
reduced = r.reduce
|
60
|
+
return join(l, reduced) if reduced != r
|
61
|
+
|
62
|
+
return self
|
63
|
+
end
|
64
|
+
|
65
|
+
def fully_reduce depth = Float::INFINITY
|
66
|
+
cur = self
|
67
|
+
1.upto(depth) do
|
68
|
+
reduced = cur.reduce
|
69
|
+
return reduced if reduced.normal?
|
70
|
+
cur = reduced
|
71
|
+
end
|
72
|
+
cur
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def subterm_length subterm_start
|
78
|
+
balance = 1
|
79
|
+
@raw[subterm_start..-1].chars.each_with_index do |symbol, offset|
|
80
|
+
balance += (symbol == '`') ? 1 : -1
|
81
|
+
return offset if balance == 0
|
82
|
+
end
|
83
|
+
raise "Unclosed application #{@raw}:#{subterm_start}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def shallow_parse
|
87
|
+
@parsed = true
|
88
|
+
return if leaf?
|
89
|
+
len = subterm_length 1
|
90
|
+
@l = Term.new @raw[1 .. 1+len]
|
91
|
+
@r = Term.new @raw[2+len .. -1 ]
|
92
|
+
end
|
93
|
+
|
94
|
+
def join left, right
|
95
|
+
Term.new nil, left, right
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'master_forest/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "master_forest"
|
8
|
+
gem.version = MasterForest::VERSION
|
9
|
+
gem.authors = ["Joe Nelson"]
|
10
|
+
gem.email = ["cred+github@begriffs.com"]
|
11
|
+
gem.description = %q{THE MASTER FOREST: ONLY THE ELITE ARE ALLOWED TO ENTER!}
|
12
|
+
gem.summary = %q{Combinatory logic parsing and reduction}
|
13
|
+
gem.homepage = "https://github.com/begriffs/master_forest"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'dalli'
|
21
|
+
gem.add_development_dependency 'rspec'
|
22
|
+
end
|
data/spec/term_spec.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'master_forest'
|
2
|
+
include MasterForest
|
3
|
+
|
4
|
+
require 'ruby-prof'
|
5
|
+
|
6
|
+
describe MasterForest do
|
7
|
+
context 'parsing' do
|
8
|
+
let(:s) { Term.new 's' }
|
9
|
+
let(:k) { Term.new 'k' }
|
10
|
+
let(:sk) { Term.new '`sk' }
|
11
|
+
let(:skk) { Term.new '``skk' }
|
12
|
+
let(:s_kk) { Term.new '`s`kk' }
|
13
|
+
let(:bad) { Term.new '``s' }
|
14
|
+
|
15
|
+
context 'leaf node' do
|
16
|
+
it 'parses' do
|
17
|
+
s.to_s.should == 's'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'is a leaf' do
|
21
|
+
s.leaf?.should be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'has no children' do
|
25
|
+
s.l.should be_nil
|
26
|
+
s.r.should be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'application' do
|
31
|
+
it 'parses' do
|
32
|
+
sk.to_s.should == '`sk'
|
33
|
+
sk.l.to_s.should == 's'
|
34
|
+
sk.r.to_s.should == 'k'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'parses deeper as needed on left' do
|
38
|
+
skk.l.to_s.should == '`sk'
|
39
|
+
skk.r.to_s.should == 'k'
|
40
|
+
skk.l.l.to_s.should == 's'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'parses deeper as needed on right' do
|
44
|
+
s_kk.l.to_s.should == 's'
|
45
|
+
s_kk.r.to_s.should == '`kk'
|
46
|
+
s_kk.r.l.to_s.should == 'k'
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'is not a leaf' do
|
50
|
+
sk.leaf?.should be_false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'invalid' do
|
55
|
+
it 'fails to parse' do
|
56
|
+
expect { bad.l }.to raise_error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#==' do
|
61
|
+
it 'is reflexive' do
|
62
|
+
(Term.new '``skk').should == (Term.new '``skk')
|
63
|
+
end
|
64
|
+
it 'distinct normal terms are not equal' do
|
65
|
+
skk.should_not == s_kk
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'explicit tree building' do
|
70
|
+
it 'generates to_s as needed' do
|
71
|
+
(Term.new nil, s, k).to_s.should == '`sk'
|
72
|
+
end
|
73
|
+
it 'string overrides explicit children' do
|
74
|
+
(Term.new 'i', s, k).to_s.should == 'i'
|
75
|
+
end
|
76
|
+
it 'refuses to create an entirely blank node' do
|
77
|
+
expect { Term.new nil }.to raise_error
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#valid?' do
|
83
|
+
let (:leaf) { Term.new 'k' }
|
84
|
+
let (:good) { Term.new '``ii`ii' }
|
85
|
+
let (:bad) { Term.new '`iX' }
|
86
|
+
let (:long) { Term.new '`iii' }
|
87
|
+
|
88
|
+
it 'says valid terms are valid' do
|
89
|
+
leaf.should be_valid
|
90
|
+
good.should be_valid
|
91
|
+
end
|
92
|
+
it 'does not allow symbols other than `,s,k,i' do
|
93
|
+
bad.should_not be_valid
|
94
|
+
end
|
95
|
+
it 'does not allow extraneous characters at end' do
|
96
|
+
long.should_not be_valid
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#reduce' do
|
101
|
+
let(:s) { Term.new 's' }
|
102
|
+
let(:ii) { Term.new '`ii' }
|
103
|
+
let(:ki) { Term.new '`ki' }
|
104
|
+
let(:si) { Term.new '`si' }
|
105
|
+
let(:kis) { Term.new '``kis' }
|
106
|
+
let(:siks) { Term.new '```siks' }
|
107
|
+
|
108
|
+
it 'does not alter a leaf' do
|
109
|
+
s.reduce.to_s.should == 's'
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'does not alter k or s with too little depth' do
|
113
|
+
ki.reduce.should == ki
|
114
|
+
si.reduce.should == si
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'drops i combinator' do
|
118
|
+
ii.reduce.to_s.should == 'i'
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'applies k combinator' do
|
122
|
+
kis.reduce.to_s.should == 'i'
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'applies s combinator' do
|
126
|
+
reduced = siks.reduce
|
127
|
+
reduced.to_s.should == '``is`ks'
|
128
|
+
reduced.l.to_s.should == '`is'
|
129
|
+
reduced.r.to_s.should == '`ks'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#fully_reduce' do
|
134
|
+
let (:biggie) { Term.new '`````s`ss`ss`ssss' }
|
135
|
+
|
136
|
+
it 'runs through many reductions and succeeds' do
|
137
|
+
biggie.fully_reduce.to_s.should == '``s``s``s``ss``ss``ssss``s``s``ss``ss``ssss``s``s``ss``ss``ssss``ss``s``ss``ss``ssss``s``s``s``ss``ss``ssss``s``s``ss``ss``ssss``s``s``ss``ss``ssss``ss``s``ss``ss``ssss``s``s``s``ss``ss``ssss``s``s``ss``ss``ssss``s``s``ss``ss``ssss``ss``s``ss``ss``ssss``ss``s``s``ss``ss``ssss``s``s``ss``ss``ssss``s``s``ss``ss``ssss``ss``s``ss``ss``ssss'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#normal' do
|
142
|
+
let (:leaf) { Term.new 'i' }
|
143
|
+
let (:app) { Term.new '`ki' }
|
144
|
+
let (:ii) { Term.new '`ii' }
|
145
|
+
let (:active_l) { Term.new '``iik'}
|
146
|
+
let (:active_r) { Term.new '`k`ii'}
|
147
|
+
let (:big) { Term.new '``````ssi``ssssss'}
|
148
|
+
|
149
|
+
context 'given a leaf' do
|
150
|
+
it 'always says normal' do
|
151
|
+
leaf.should be_normal
|
152
|
+
end
|
153
|
+
end
|
154
|
+
context 'given an application' do
|
155
|
+
it 'is sometimes normal' do
|
156
|
+
app.should be_normal
|
157
|
+
end
|
158
|
+
it 'i application is never normal' do
|
159
|
+
ii.should_not be_normal
|
160
|
+
end
|
161
|
+
it 'detects active subterms' do
|
162
|
+
active_l.should_not be_normal
|
163
|
+
active_r.should_not be_normal
|
164
|
+
end
|
165
|
+
it 'detects deeply nested active terms' do
|
166
|
+
big.should_not be_normal
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: master_forest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joe Nelson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: dalli
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: ! 'THE MASTER FOREST: ONLY THE ELITE ARE ALLOWED TO ENTER!'
|
47
|
+
email:
|
48
|
+
- cred+github@begriffs.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- .rvmrc
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- benchmark/run.rb
|
60
|
+
- lib/master_forest.rb
|
61
|
+
- lib/master_forest/memcache_term.rb
|
62
|
+
- lib/master_forest/term.rb
|
63
|
+
- lib/master_forest/version.rb
|
64
|
+
- master_forest.gemspec
|
65
|
+
- spec/term_spec.rb
|
66
|
+
homepage: https://github.com/begriffs/master_forest
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.24
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Combinatory logic parsing and reduction
|
90
|
+
test_files:
|
91
|
+
- spec/term_spec.rb
|