master_forest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@master_forest --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in master_forest.gemspec
4
+ gemspec
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,3 @@
1
+ module MasterForest
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ require "master_forest/version"
2
+ require "master_forest/term"
3
+ require "master_forest/memcache_term"
@@ -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