mini_kraken 0.1.13 → 0.2.00

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: 79ea33dd475e6dfbed71915177b1581abb5e1ca9a60db04133d11cda7dca9d42
4
- data.tar.gz: 249bea4319183c929e4c291785c6b74e10606889c789c5e0f2b211197cc6f288
3
+ metadata.gz: 4bbfe3d58d4c7271a5c8a54486a743c51d6fddcb3b068095cad44cacad575c83
4
+ data.tar.gz: 012b412a95752592b5be70859e00d94b379e2c4c564d37d932a300a03cbc9716
5
5
  SHA512:
6
- metadata.gz: '086bf0b30750e908dfee1b7e95eb3b0c0e54dff4f7bd700d54bbf28091aaf02080d458c14508ddcaa580248e0234d1da23553869480e3bea930d46708c115d82'
7
- data.tar.gz: d3ada8d056819b2ae1af0d442056008600f82633ee52ca8aa672708943fdf517adb60522bfff6673f9cea86985c1bf5ce2a7a3ea73f07504945e660f31ebee49
6
+ metadata.gz: 952d93b01de392c7168e7cfb4a3cc911e4d1b53be11a4a10d67012bb4ab4a8f69aa42f41bf13a756f89a6d09eb775a5594174d73334c57c2230039b7991e89fd
7
+ data.tar.gz: a60adbd5da627495badab63de355336f131a5e599c40226931366a7c1b4a349b2542ccb55cf30ef8e60990341c1f5d8547804b1552fbbbc504bd1bed40a36d9e
@@ -1,3 +1,15 @@
1
+ ## [0.2.00] - 2020-07-12
2
+ - First release of DSL (Domain Specific Language)
3
+ - Fix defect for fused variables that remain fresh
4
+
5
+ ### NEW
6
+ - Mix-in module `Glue::DSL` hosting methods for implementing the DSL.
7
+ - Method `ConsCell#to_s` uses the Lisp convention for representing lists.
8
+
9
+ ### CHANGED
10
+ - File `README.md` Added a couple of examples of DSL use.
11
+ - Method `AnyValue#==` can compare with symbols with format '_' + integer literal (e.g. :_0).
12
+
1
13
  ## [0.1.13] - 2020-07-01
2
14
  - Cover all frames from Chapter One of "Reasoned Scheme" book.
3
15
  - Fix defect for fused variables that remain fresh
data/README.md CHANGED
@@ -4,22 +4,29 @@
4
4
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://github.com/famished-tiger/mini_kraken/blob/master/LICENSE.txt)
5
5
 
6
6
  ### What is __mini_kraken__ ?
7
- An implemention of the [miniKanren](http://minikanren.org/) relational programming language in Ruby.
7
+ A library containing an implementation of the [miniKanren](http://minikanren.org/)
8
+ relational programming language in Ruby.
8
9
  *miniKanren* is a small language for relational (logic) programming.
9
10
  Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
10
11
  Daniel P. Friedman, William E. Byrd, Oleg Kiselyov, and Jason Hemann: "The Reasoned Schemer", Second Edition,
11
12
  ISBN: 9780262535519, (2018), MIT Press.
12
13
 
13
14
  ### Features
15
+ - Pure Ruby implementation, not a port from another language
16
+ - Object-Oriented design
17
+ - No runtime dependencies
18
+
19
+ ### miniKanren Features
14
20
  - [X] ==
15
21
  - [X] run\*
16
- - [X] fresh
22
+ - [X] fresh
23
+ - [X] conde
17
24
  - [X] conj2
18
25
  - [X] disj2
19
26
  - [X] defrel
20
27
 
21
28
  ### TODO
22
- - [ ] conde
29
+
23
30
  - [ ] Occurs check
24
31
 
25
32
  List-centric relations from Chapter 2
@@ -46,9 +53,82 @@ Or install it yourself as:
46
53
 
47
54
  $ gem install mini_kraken
48
55
 
49
- ## Usage
56
+ ## Examples
57
+
58
+ The following __MiniKraken__ examples use its DSL (Domain Specific Language).
59
+
60
+ ### Example 1
61
+ Let's first begin with a rather simplistic example.
62
+
63
+ ```ruby
64
+ require 'mini_kraken' # Load MiniKraken library
65
+
66
+ extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
67
+
68
+ result = run_star('q', equals(q, :pea))
69
+ puts result # => (:pea)
70
+ ```
71
+
72
+ The two first lines in the above code snippet are pretty standard:
73
+ - The first line loads the `mini_kraken` library.
74
+ - The second line add the DSL methods to the current object.
75
+
76
+ The next line constitutes a trivial `miniKanren` program.
77
+ The aim of a `miniKanren` program is to find one or more solutions involving provided variable(s)
78
+ and satisfying one or more goals.
79
+ In our example, the `run_star` method instructs `MiniKraken` to find all solutions,
80
+ knowing that each successful solution:
81
+ - binds a value to the provided variable `q` and
82
+ - meets the goal `equals(q, :pea)`.
83
+
84
+ The goal `equals(q, :pea)` succeeds because the logical variable `q` is _fresh_ (that is,
85
+ not yet bound to a value) and will be bound to the symbol `:pea` as a side effect
86
+ of the goal `equals`.
87
+
88
+ So the above program succeeds and the only found solution is obtained by binding
89
+ the variable `q` to the value :pea. Hence the result of the `puts` method.
90
+
91
+ ### Example 2
92
+ The next example illustrates the behavior of a failing `miniKanren` program.
93
+
94
+ ```ruby
95
+ require 'mini_kraken' # Load MiniKraken library
96
+
97
+ extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
98
+
99
+ # Following miniKanren program fails
100
+ result = run_star('q', [equals(q, :pea), equals(q, :pod)])
101
+ puts result # => ()
102
+ ```
103
+ In this example, we learn that `run_star` can take multiple goals placed in an array.
104
+ The program fails to find a solution since it is not possible to satisfy the two `equals` goals simultaneously. In that case, the `run_star` return an empty list represented as `()` in the output.
105
+
106
+
107
+ ### Example 3
108
+ The next example shows the use two logical variables.
109
+
110
+ ```ruby
111
+ # In this example and following, one assumes that DSL is loaded as shown in Example 1
112
+
113
+ result = run_star(['x', 'y'], [equals(:hello, x), equals(y, :world)])
114
+ puts result # => ((:hello :world))
115
+ ```
50
116
 
51
- TODO: Write usage instructions here
117
+ This time, `run_star` takes two logical variables -`x` and `y`- and successfully finds the solution `x = :hello, y = :world`.
118
+
119
+ ### Example 4
120
+ The next example shows the use of `disj2` goals.
121
+ ```ruby
122
+ result = run_star(['x', 'y'],
123
+ [
124
+ disj2(equals(x, :blue), equals(x, :red)),
125
+ disj2(equals(y, :sea), equals(:mountain, y))
126
+ ])
127
+ puts result # => ((:blue :sea) (:blue :mountain) (:red :sea) (:red :mountain))
128
+ ```
129
+
130
+ Here, `run_star` takes two logical variables and two `disj2` goals. A `disj2` succeeds if any of its arguments succeeds.
131
+ This program finds four distinct solutions for x, y pairs.
52
132
 
53
133
  ## Development
54
134
 
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'mini_kraken/version'
3
+ # This file acts as a jumping-off point for loading dependencies expected
4
+ # for a MiniKraken client.
5
+
6
+ require_relative './mini_kraken/version'
7
+ require_relative './mini_kraken/glue/dsl'
8
+
9
+
10
+ # End of file
4
11
 
5
12
  module MiniKraken
6
13
  class Error < StandardError; end
@@ -12,7 +12,11 @@ module MiniKraken
12
12
  end
13
13
 
14
14
  def ==(other)
15
- rank == other.rank
15
+ if other.is_a?(AnyValue)
16
+ rank == other.rank
17
+ elsif other.id2name =~ /_\d+/
18
+ rank == other.id2name.sub(/_/, '').to_i
19
+ end
16
20
  end
17
21
 
18
22
  # Use same text representation as in Reasoned Schemer.
@@ -11,7 +11,11 @@ unless MiniKraken::Core.constants(false).include? :ConsCell
11
11
 
12
12
  def initialize(obj1, obj2 = nil)
13
13
  @car = obj1
14
- @cdr = obj2
14
+ if obj2.kind_of?(ConsCell) && obj2.null?
15
+ @cdr = nil
16
+ else
17
+ @cdr = obj2
18
+ end
15
19
  end
16
20
 
17
21
  def children
@@ -42,9 +46,32 @@ unless MiniKraken::Core.constants(false).include? :ConsCell
42
46
  ConsCell.new(new_car, new_cdr)
43
47
  end
44
48
 
49
+ # Use the list notation from Lisp as a text representation.
50
+ def to_s
51
+ return '()' if null?
52
+
53
+ "(#{pair_to_s})"
54
+ end
55
+
45
56
  def append(another)
46
57
  @cdr = another
47
58
  end
59
+
60
+ protected
61
+
62
+ def pair_to_s
63
+ result = +car.to_s
64
+ if cdr
65
+ result << ' '
66
+ if cdr.kind_of?(ConsCell)
67
+ result << cdr.pair_to_s
68
+ else
69
+ result << ". #{cdr}"
70
+ end
71
+ end
72
+
73
+ result
74
+ end
48
75
  end # class
49
76
 
50
77
  # Constant representing the null (empty) list.
@@ -35,14 +35,15 @@ module MiniKraken
35
35
  raise StandardError, err_msg
36
36
  end
37
37
 
38
- prefix = 'Invalid goal argument '
38
+ prefix = "Invalid goal argument '"
39
39
  args.each do |actual|
40
40
  if actual.kind_of?(GoalArg) || actual.kind_of?(Environment)
41
41
  next
42
42
  elsif actual.kind_of?(Array)
43
43
  validated_actuals(actual)
44
44
  else
45
- raise StandardError, prefix + actual.to_s
45
+ actual_display = actual.nil? ? 'nil' : actual.to_s
46
+ raise StandardError, prefix + actual_display + "'"
46
47
  end
47
48
  end
48
49
 
@@ -11,6 +11,17 @@ module MiniKraken
11
11
  def initialize(aValue)
12
12
  super(aValue)
13
13
  end
14
+
15
+ # Returns the name or string corresponding to value.
16
+ # @return [String]
17
+ def id2name
18
+ value.id2name
19
+ end
20
+
21
+ # Returns a string representing the MiniKraken symbol.
22
+ def to_s
23
+ ':' + id2name
24
+ end
14
25
  end # class
15
26
  end # module
16
27
  end # module
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/any_value'
4
+ require_relative '../core/conj2'
5
+ require_relative '../core/cons_cell'
6
+ require_relative '../core/disj2'
7
+ require_relative '../core/equals'
8
+ require_relative '../core/fail'
9
+ require_relative '../core/k_symbol'
10
+ require_relative '../core/succeed'
11
+ require_relative '../core/variable_ref'
12
+ require_relative 'fresh_env'
13
+ require_relative 'run_star_expression'
14
+
15
+
16
+ module MiniKraken
17
+ module Glue
18
+ module DSL
19
+ # @return [Core::ConsCell] A list of solutions
20
+ def run_star(var_names, goal)
21
+ program = RunStarExpression.new(var_names, goal)
22
+ program.run
23
+ end
24
+
25
+ def conj2(arg1, arg2)
26
+ Core::Goal.new(Core::Conj2.instance, [convert(arg1), convert(arg2)])
27
+ end
28
+
29
+ def cons(car_item, cdr_item = nil)
30
+ Core::ConsCell.new(convert(car_item), convert(cdr_item))
31
+ end
32
+
33
+ def disj2(arg1, arg2)
34
+ Core::Goal.new(Core::Disj2.instance, [convert(arg1), convert(arg2)])
35
+ end
36
+
37
+ def _fail
38
+ Core::Goal.new(Core::Fail.instance, [])
39
+ end
40
+
41
+ def equals(arg1, arg2)
42
+ Core::Goal.new(Core::Equals.instance, [convert(arg1), convert(arg2)])
43
+ end
44
+
45
+ def fresh(var_names, goal)
46
+ vars = nil
47
+
48
+ if var_names.kind_of?(String) || var_names.kind_of?(Core::VariableRef)
49
+ vars = [var_names]
50
+ elsif
51
+
52
+ vars = var_names
53
+ end
54
+ FreshEnv.new(vars, goal)
55
+ end
56
+
57
+ def null
58
+ Core::ConsCell.new(nil, nil)
59
+ end
60
+
61
+ def succeed
62
+ Core::Goal.new(Core::Succeed.instance, [])
63
+ end
64
+
65
+ private
66
+
67
+ def convert(anArgument)
68
+ converted = nil
69
+
70
+ case anArgument
71
+ when Symbol
72
+ if anArgument.id2name =~ /_\d+/
73
+ rank = anArgument.id2name.slice(1..-1).to_i
74
+ any_val = Core::AnyValue.allocate
75
+ any_val.instance_variable_set(:@rank, rank)
76
+ converted = any_val
77
+ else
78
+ converted = Core::KSymbol.new(anArgument)
79
+ end
80
+ when Core::Goal
81
+ converted = anArgument
82
+ when Core::VariableRef
83
+ converted = anArgument
84
+ when Core::ConsCell
85
+ converted = anArgument
86
+ end
87
+
88
+ converted
89
+ end
90
+
91
+ def method_missing(mth, *args)
92
+ result = nil
93
+
94
+ begin
95
+ result = super(mth, *args)
96
+ rescue NameError
97
+ result = Core::VariableRef.new(mth.id2name)
98
+ end
99
+
100
+ result
101
+ end
102
+ end # module
103
+ end # module
104
+ end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniKraken
4
- VERSION = '0.1.13'
4
+ VERSION = '0.2.00'
5
5
  end
@@ -11,6 +11,7 @@ module MiniKraken
11
11
  describe ConsCell do
12
12
  let(:pea) { KSymbol.new(:pea) }
13
13
  let(:pod) { KSymbol.new(:pod) }
14
+ let(:corn) { KSymbol.new(:corn) }
14
15
  subject { ConsCell.new(pea, pod) }
15
16
 
16
17
  context 'Initialization:' do
@@ -39,6 +40,12 @@ module MiniKraken
39
40
  expect(ConsCell.new(nil, nil)).to be_null
40
41
  expect(NullList).to be_null
41
42
  end
43
+
44
+ it 'simplifies cdr if its referencing a null list' do
45
+ instance = ConsCell.new(pea, NullList)
46
+ expect(instance.car).to eq(pea)
47
+ expect(instance.cdr).to be_nil
48
+ end
42
49
  end # context
43
50
 
44
51
  context 'Provided services:' do
@@ -66,6 +73,34 @@ module MiniKraken
66
73
  expect(instance.car).to eq(pea)
67
74
  expect(instance.cdr).to eq(trail)
68
75
  end
76
+
77
+ it 'should provide a list representation of itself' do
78
+ # Case of null list
79
+ expect(NullList.to_s).to eq '()'
80
+
81
+ # Case of one element proper list
82
+ cell = ConsCell.new(pea)
83
+ expect(cell.to_s).to eq '(:pea)'
84
+
85
+ # Case of two elements proper list
86
+ cell = ConsCell.new(pea, ConsCell.new(pod))
87
+ expect(cell.to_s).to eq '(:pea :pod)'
88
+
89
+ # Case of two elements improper list
90
+ expect(subject.to_s).to eq '(:pea . :pod)'
91
+
92
+ # Case of three elements proper list
93
+ cell = ConsCell.new(pea, ConsCell.new(pod, ConsCell.new(corn)))
94
+ expect(cell.to_s).to eq '(:pea :pod :corn)'
95
+
96
+ # Case of three elements improper list
97
+ cell = ConsCell.new(pea, ConsCell.new(pod, corn))
98
+ expect(cell.to_s).to eq '(:pea :pod . :corn)'
99
+
100
+ # Case of a nested list
101
+ cell = ConsCell.new(ConsCell.new(pea), ConsCell.new(pod))
102
+ expect(cell.to_s).to eq '((:pea) :pod)'
103
+ end
69
104
  end # context
70
105
  end # describe
71
106
  end # module
@@ -65,6 +65,10 @@ module MiniKraken
65
65
  # Default Ruby representation, different value
66
66
  expect(subject == :pod).to be_falsy
67
67
  end
68
+
69
+ it 'should provide a string representation of itself' do
70
+ expect(subject.to_s).to eq(':pea')
71
+ end
68
72
  end # context
69
73
  end # describe
70
74
  end # module
@@ -0,0 +1,498 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/mini_kraken/glue/dsl'
7
+
8
+
9
+ module MiniKraken
10
+ module Glue
11
+ describe 'DSL (Chap 1)' do
12
+ include DSL
13
+
14
+ context 'Chapter 1 examples:' do
15
+ it 'passes frame 1:7' do
16
+ # Reasoned S2, frame 1:7
17
+ # (run* q #u) ;; => ()
18
+
19
+ result = run_star('q', _fail)
20
+ expect(result).to be_null
21
+ end
22
+
23
+ it 'passes frame 1:10' do
24
+ # Reasoned S2, frame 1:10
25
+ # (run* q (== 'pea 'pod) ;; => ()
26
+
27
+ result = run_star('q', equals(:pea, :pod))
28
+ expect(result).to be_null
29
+ end
30
+
31
+ it 'passes frame 1:11' do
32
+ # Reasoned S2, frame 1:11
33
+ # (run* q (== q 'pea) ;; => (pea)
34
+
35
+ result = run_star('q', equals(q, :pea))
36
+ expect(result.car).to eq(:pea)
37
+ end
38
+
39
+ it 'passes frame 1:12' do
40
+ # Reasoned S2, frame 1:12
41
+ # (run* q (== 'pea q) ;; => (pea)
42
+
43
+ result = run_star('q', equals(:pea, q))
44
+ expect(result.car).to eq(:pea)
45
+ end
46
+
47
+ it 'passes frame 1:17' do
48
+ # Reasoned S2, frame 1:17
49
+ # (run* q succeed) ;; => (_0)
50
+
51
+ result = run_star('q', succeed)
52
+ expect(result.car).to eq(:_0)
53
+ end
54
+
55
+ it 'passes frame 1:19' do
56
+ # Reasoned S2, frame 1:19
57
+ # (run* q (== 'pea 'pea)) ;; => (_0)
58
+
59
+ result = run_star('q', equals(:pea, :pea))
60
+ expect(result.car).to eq(:_0)
61
+ end
62
+
63
+ it 'passes frame 1:20' do
64
+ # Reasoned S2, frame 1:20
65
+ # (run* q (== q q)) ;; => (_0)
66
+
67
+ result = run_star('q', equals(q, q))
68
+ expect(result.car).to eq(:_0)
69
+ end
70
+
71
+ it 'passes frame 1:21' do
72
+ # Reasoned S2, frame 1:21
73
+ # (run* q (fresh (x) (== 'pea q))) ;; => (pea)
74
+
75
+ result = run_star('q', fresh('x', equals(:pea, q)))
76
+ expect(result.car).to eq(:pea)
77
+ end
78
+
79
+ it 'passes frame 1:25' do
80
+ # Reasoned S2, frame 1:25
81
+ # (run* q (fresh (x) (== (cons x '()) q))) ;; => ((_0))
82
+ # require 'debug' Invalid goal argument
83
+ result = run_star('q', fresh('x', equals(cons(x, null), q)))
84
+ expect(result.car).to eq(cons(:_0))
85
+ end
86
+
87
+ it 'passes frame 1:31' do
88
+ # Reasoned S2, frame 1:31
89
+ # (run* q (fresh (x) (== x q))) ;; => (_0)
90
+
91
+ result = run_star('q', fresh('x', equals(x, q)))
92
+ expect(result.car).to eq(:_0)
93
+ end
94
+
95
+ it 'passes frame 1:32' do
96
+ # Reasoned S2, frame 1:32
97
+ # (run* q (== '(((pea)) pod) '(((pea)) pod))) ;; => (_0)
98
+
99
+ result = run_star('q', equals(cons(cons(:pea), :pod), cons(cons(:pea), :pod)))
100
+ expect(result.car).to eq(:_0)
101
+ end
102
+
103
+ it 'passes frame 1:33' do
104
+ # Beware: quasiquoting
105
+ # Reasoned S2, frame 1:33
106
+ # (run* q (== '(((pea)) pod) '(((pea)) ,q))) ;; => ('pod)
107
+
108
+ result = run_star('q', equals(cons(cons(:pea), :pod), cons(cons(:pea), q)))
109
+ expect(result.car).to eq(:pod)
110
+ end
111
+
112
+ it 'passes frame 1:34' do
113
+ # Reasoned S2, frame 1:34
114
+ # (run* q (== '(((,q)) pod) `(((pea)) pod))) ;; => ('pea)
115
+
116
+ result = run_star('q', equals(cons(cons(q), :pod), cons(cons(:pea), q)))
117
+ expect(result.car).to eq(:pea)
118
+ end
119
+
120
+ it 'passes frame 1:35' do
121
+ # Reasoned S2, frame 1:35
122
+ # (run* q (fresh (x) (== '(((,q)) pod) `(((,x)) pod)))) ;; => (_0)
123
+
124
+ result = run_star('q', fresh('x', equals(cons(cons(q), :pod), cons(cons(x), :pod))))
125
+ expect(result.car).to eq(:_0)
126
+ end
127
+
128
+ it 'passes frame 1:36' do
129
+ # Reasoned S2, frame 1:36
130
+ # (run* q (fresh (x) (== '(((,q)) ,x) `(((,x)) pod)))) ;; => ('pod)
131
+
132
+ result = run_star('q', fresh('x', equals(cons(cons(cons(q)), x), cons(cons(cons(x)), :pod))))
133
+ expect(result.car).to eq(:pod)
134
+ end
135
+
136
+ it 'passes frame 1:37' do
137
+ # Reasoned S2, frame 1:37
138
+ # (run* q (fresh (x) (== '( ,x ,x) q))) ;; => (_0 _0)
139
+
140
+ result = run_star('q', fresh('x', equals(cons(x, cons(x)), q)))
141
+ expect(result.car).to eq(cons(:_0, cons(:_0)))
142
+ end
143
+
144
+ it 'passes frame 1:38' do
145
+ # Reasoned S2, frame 1:38
146
+ # (run* q (fresh (x) (fresh (y) (== '( ,q ,y) '((,x ,y) ,x))))) ;; => (_0 _0)
147
+
148
+ result = run_star('q', fresh('x', fresh('y', equals(cons(q, cons(y)), cons(cons(x, cons(y)), cons(x))))))
149
+ expect(result.car).to eq(cons(:_0, cons(:_0)))
150
+ end
151
+
152
+ it 'passes frame 1:41' do
153
+ # Reasoned S2, frame 1:41
154
+ # (run* q (fresh (x) (fresh (y) (== '( ,x ,y) q)))) ;; => (_0 _1)
155
+
156
+ result = run_star('q', fresh('x', fresh('y', equals(cons(x, cons(y)), q))))
157
+ # q should be bound to '(,x ,y)
158
+ expect(result.car).to eq(cons(:_0, cons(:_1)))
159
+ end
160
+
161
+ it 'passes frame 1:42' do
162
+ # Reasoned S2, frame 1:42
163
+ # (run* s (fresh (t) (fresh (u) (== '( ,t ,u) s)))) ;; => (_0 _1)
164
+
165
+ result = run_star('s', fresh('t', fresh('u', equals(cons(t, cons(u)), s))))
166
+ # s should be bound to '(,t ,u)
167
+ expect(result.car).to eq(cons(:_0, cons(:_1)))
168
+ end
169
+
170
+ it 'passes frame 1:43' do
171
+ # Reasoned S2, frame 1:43
172
+ # (run* q (fresh (x) (fresh (y) (== '( ,x ,y ,x) q)))) ;; => (_0 _1 _0)
173
+
174
+ result = run_star('q', fresh('x', fresh('y', equals(cons(x, cons(y, cons(x))), q))))
175
+ # q should be bound to '(,x ,y, ,x)
176
+ expect(result.car).to eq(cons(:_0, cons(:_1, cons(:_0))))
177
+ end
178
+
179
+ it 'passes frame 1:50' do
180
+ # Reasoned S2, frame 1:50
181
+ # (run* q (conj2 succeed succeed)) ;; => (_0)
182
+
183
+ result = run_star('q', conj2(succeed, succeed))
184
+ expect(result.car).to eq(:_0)
185
+ end
186
+
187
+ it 'passes frame 1:51' do
188
+ # Reasoned S2, frame 1:51
189
+ # (run* q (conj2 succeed (== 'corn q)) ;; => ('corn)
190
+
191
+ result = run_star('q', conj2(succeed, equals(:corn, q)))
192
+ expect(result.car).to eq(:corn)
193
+ end
194
+
195
+ it 'passes frame 1:52' do
196
+ # Reasoned S2, frame 1:52
197
+ # (run* q (conj2 fail (== 'corn q)) ;; => ()
198
+
199
+ result = run_star('q', conj2(_fail, equals(:corn, q)))
200
+ expect(result).to be_null
201
+ end
202
+
203
+ it 'passes frame 1:53' do
204
+ # Reasoned S2, frame 1:53
205
+ # (run* q (conj2 (== 'corn q)(== 'meal q)) ;; => ()
206
+
207
+ result = run_star('q', conj2(equals(:corn, q), equals(:meal, q)))
208
+ expect(result).to be_null
209
+ end
210
+
211
+ it 'passes frame 1:54' do
212
+ # Reasoned S2, frame 1:54
213
+ # (run* q (conj2 (== 'corn q)(== 'corn q)) ;; => ('corn)
214
+
215
+ result = run_star('q', conj2(equals(:corn, q), equals(:corn, q)))
216
+ expect(result.car).to eq(:corn)
217
+ end
218
+
219
+ it 'passes frame 1:55' do
220
+ # Reasoned S2, frame 1:55
221
+ # (run* q (disj2 fail fail)) ;; => ()
222
+
223
+ result = run_star('q', disj2(_fail, _fail))
224
+ expect(result).to be_null
225
+ end
226
+
227
+ it 'passes frame 1:56' do
228
+ # Reasoned S2, frame 1:56
229
+ # (run* q (disj2 (== 'olive q) fail)) ;; => ('olive)
230
+
231
+ result = run_star('q', disj2(equals(:olive, q), _fail))
232
+ expect(result.car).to eq(:olive)
233
+ end
234
+
235
+ it 'passes frame 1:57' do
236
+ # Reasoned S2, frame 1:57
237
+ # (run* q (disj2 fail (== 'oil q))) ;; => (oil)
238
+
239
+ result = run_star('q', disj2(_fail, equals(:oil, q)))
240
+ expect(result.car).to eq(:oil)
241
+ end
242
+
243
+ it 'passes frame 1:58' do
244
+ # Reasoned S2, frame 1:58
245
+ # (run* q (disj2 (== 'olive q) (== 'oil q))) ;; => (olive oil)
246
+
247
+ result = run_star('q', disj2(equals(:olive, q), equals(:oil, q)))
248
+ expect(result.car).to eq(:olive)
249
+ expect(result.cdr.car).to eq(:oil)
250
+ end
251
+
252
+ it 'passes frame 1:59' do
253
+ # Reasoned S2, frame 1:59
254
+ # (run* q (fresh (x) (fresh (y) (disj2 (== '( ,x ,y ) q) (== '( ,x ,y ) q)))))
255
+ # ;; => ((_0 _1) (_0 _1))
256
+
257
+ result = run_star('q', fresh('x', (fresh 'y', disj2(equals(cons(x, cons(y)), q), equals(cons(x, cons(y)), q)))))
258
+ # q should be bound to '(,x ,y), then to '(,y ,x)
259
+ expect(result.car).to eq(cons(:_0, cons(:_1)))
260
+ expect(result.cdr.car).to eq(cons(:_0, cons(:_1)))
261
+ end
262
+
263
+ it 'passes frame 1:62' do
264
+ # Reasoned S2, frame 1:62
265
+ # (run* x (disj2
266
+ # (conj2 (== 'olive x) fail)
267
+ # (== 'oil x))) ;; => (oil)
268
+
269
+ result = run_star('x', disj2(conj2(equals(:olive, x), _fail), equals(:oil, x)))
270
+ expect(result.car).to eq(:oil)
271
+ end
272
+
273
+ it 'passes frame 1:63' do
274
+ # Reasoned S2, frame 1:63
275
+ # (run* x (disj2
276
+ # (conj2 (== 'olive x) succeed)
277
+ # ('oil x))) ;; => (olive oil)
278
+
279
+ result = run_star('x', disj2(conj2(equals(:olive, x), succeed), equals(:oil, x)))
280
+ expect(result).to eq(cons(:olive, cons(:oil)))
281
+ end
282
+
283
+ it 'passes frame 1:64' do
284
+ # Reasoned S2, frame 1:64
285
+ # (run* x (disj2
286
+ # (== 'oil x)
287
+ # (conj2 (== 'olive x) succeed))) ;; => (oil olive)
288
+
289
+ result = run_star('x', disj2(equals(:oil, x), conj2(equals(:olive, x), succeed)))
290
+ expect(result).to eq(cons(:oil, cons(:olive)))
291
+ end
292
+
293
+ it 'passes frame 1:65' do
294
+ # Reasoned S2, frame 1:65
295
+ # (run* x (disj2
296
+ # (conj2(== 'virgin x) fail)
297
+ # (disj2
298
+ # (== 'olive x)
299
+ # (dis2
300
+ # succeed
301
+ # (== 'oil x))))) ;; => (olive _0 oil)
302
+
303
+ result = run_star('x', disj2(conj2(equals(:virgin, x), _fail),
304
+ disj2(equals(:olive, x), disj2(succeed, equals(:oil, x)))))
305
+ expect(result).to eq(cons(:olive, cons(:_0, cons(:oil))))
306
+ end
307
+
308
+ it 'passes frame 1:67' do
309
+ # Reasoned S2, frame 1:67
310
+ # (run* r
311
+ # (fresh x
312
+ # (fresh y
313
+ # (conj2
314
+ # (== 'split x)
315
+ # (conj2
316
+ # (== 'pea y)
317
+ # (== '(,x ,y) r)))))) ;; => ((split pea))
318
+
319
+ result = run_star('r', fresh('x', fresh('y',
320
+ conj2(equals(:split, x), conj2(
321
+ equals(:pea, y), equals(cons(x, cons(y)), r))))))
322
+ expect(result).to eq(cons(cons(:split, cons(:pea))))
323
+ end
324
+
325
+ it 'passes frame 1:68' do
326
+ # Reasoned S2, frame 1:68
327
+ # (run* r
328
+ # (fresh x
329
+ # (fresh y
330
+ # (conj2
331
+ # (conj2
332
+ # (== 'split x)
333
+ # (== 'pea y)
334
+ # (== '(,x ,y) r)))))) ;; => ((split pea))
335
+
336
+ result = run_star('r', fresh('x', fresh('y',
337
+ conj2(conj2(equals(:split, x), equals(:pea, y)),
338
+ equals(cons(x, cons(y)), r)))))
339
+ expect(result).to eq(cons(cons(:split, cons(:pea))))
340
+ end
341
+
342
+ it 'passes frame 1:70' do
343
+ # Reasoned S2, frame 1:70
344
+ # (run* r
345
+ # (fresh (x y)
346
+ # (conj2
347
+ # (conj2
348
+ # (== 'split x)
349
+ # (== 'pea y)
350
+ # (== '(,x ,y) r))))) ;; => ((split pea))
351
+
352
+ result = run_star('r', fresh(%w[x y], conj2(
353
+ conj2(equals(:split, x), equals(:pea, y)),
354
+ equals(cons(x, cons(y)), r))))
355
+ expect(result).to eq(cons(cons(:split, cons(:pea))))
356
+ end
357
+
358
+ it 'passes frame 1:72' do
359
+ # Reasoned S2, frame 1:72
360
+ # (run* (r x y)
361
+ # (conj2
362
+ # (conj2
363
+ # (== 'split x)
364
+ # (== 'pea y))
365
+ # (== '(,x ,y) r))) ;; => (((split pea) split pea))
366
+ # o
367
+ # / \
368
+ # o nil
369
+ # / \
370
+ # / \
371
+ # / \
372
+ # / \
373
+ # / \
374
+ # o o
375
+ # / \ / \
376
+ # split o split o
377
+ # / \ / \
378
+ # pea nil pea nil
379
+
380
+ result = run_star(%w[r x y], conj2(
381
+ conj2(equals(:split, x), equals(:pea, y)),
382
+ equals(cons(x, cons(y)), r)))
383
+ expect(result.car.car.car).to eq(:split)
384
+ expect(result.car.car.cdr.car).to eq(:pea)
385
+ expect(result.car.car.cdr.cdr).to be_nil
386
+ expect(result.car.cdr.car).to eq(:split)
387
+ expect(result.car.cdr.cdr.car).to eq(:pea)
388
+ expect(result.car.cdr.cdr.cdr).to be_nil
389
+ end
390
+
391
+ it 'passes frame 1:75' do
392
+ # Reasoned S2, frame 1:75
393
+ # (run* (x y)
394
+ # (conj2
395
+ # (== 'split x)
396
+ # (== 'pea y))) ;; => ((split pea))
397
+
398
+ result = run_star(%w[x y], conj2(equals(:split, x), equals(:pea, y)))
399
+ expect(result.car.car).to eq(:split)
400
+ expect(result.car.cdr.car).to eq(:pea)
401
+ end
402
+
403
+ it 'passes frame 1:76' do
404
+ # Reasoned S2, frame 1:76
405
+ # (run* (x y)
406
+ # (disj2
407
+ # (conj2 (== 'split x) (== 'pea y))
408
+ # (conj2 (== 'red x) (== 'bean y)))) ;; => ((split pea)(red bean))
409
+
410
+ result = run_star(%w[x y], disj2(
411
+ conj2(equals(:split, x), equals(:pea, y)),
412
+ conj2(equals(:red, x), equals(:bean, y))))
413
+ expect(result.car.car).to eq(:split)
414
+ expect(result.car.cdr.car).to eq(:pea)
415
+ expect(result.cdr.car.car).to eq(:red)
416
+ expect(result.cdr.car.cdr.car).to eq(:bean)
417
+ end
418
+
419
+ it 'passes frame 1:77' do
420
+ # Reasoned S2, frame 1:77
421
+ # (run* r
422
+ # (fresh (x y)
423
+ # (conj2
424
+ # (disj2
425
+ # (conj2 (== 'split x) (== 'pea y))
426
+ # (conj2 (== 'red x) (== 'bean y)))
427
+ # (== '(,x ,y soup) r)))) ;; => ((split pea soup) (red bean soup))
428
+
429
+ result = run_star('r',
430
+ fresh(%w[x y], conj2(
431
+ disj2(
432
+ conj2(equals(:split, x), equals(:pea, y)),
433
+ conj2(equals(:red, x), equals(:bean, y))),
434
+ equals(cons(x, cons(y, cons(:soup))), r))))
435
+ expect(result.car.car).to eq(:split)
436
+ expect(result.car.cdr.car).to eq(:pea)
437
+ expect(result.car.cdr.cdr.car).to eq(:soup)
438
+ expect(result.cdr.car.car).to eq(:red)
439
+ expect(result.cdr.car.cdr.car).to eq(:bean)
440
+ expect(result.cdr.car.cdr.cdr.car).to eq(:soup)
441
+ end
442
+
443
+ it 'passes frame 1:78' do
444
+ # Reasoned S2, frame 1:78
445
+ # (run* r
446
+ # (fresh (x y)
447
+ # (disj2
448
+ # (conj2 (== 'split x) (== 'pea y))
449
+ # (conj2 (== 'red x) (== 'bean y)))
450
+ # (== '(,x ,y soup) r))) ;; => ((split pea soup) (red bean soup))
451
+
452
+ result = run_star('r',
453
+ fresh(%w[x y], [disj2(
454
+ conj2(equals(:split, x), equals(:pea, y)),
455
+ conj2(equals(:red, x), equals(:bean, y))),
456
+ equals(cons(x, cons(y, cons(:soup))), r)]))
457
+ expect(result.car.car).to eq(:split)
458
+ expect(result.car.cdr.car).to eq(:pea)
459
+ expect(result.car.cdr.cdr.car).to eq(:soup)
460
+ expect(result.cdr.car.car).to eq(:red)
461
+ expect(result.cdr.car.cdr.car).to eq(:bean)
462
+ expect(result.cdr.car.cdr.cdr.car).to eq(:soup)
463
+ end
464
+
465
+ it 'passes frame 1:80' do
466
+ # Reasoned S2, frame 1:80
467
+ # (run* (x y z)
468
+ # (disj2
469
+ # (conj2 (== 'split x) (== 'pea y))
470
+ # (conj2 (== 'red x) (== 'bean y)))
471
+ # (== 'soup z)) ;; => ((split pea soup) (red bean soup))
472
+
473
+ result = run_star(%w[x y z], [disj2(
474
+ conj2(equals(:split, x), equals(:pea, y)),
475
+ conj2(equals(:red, x), equals(:bean, y))),
476
+ equals(:soup, z)])
477
+ expect(result.car.car).to eq(:split)
478
+ expect(result.car.cdr.car).to eq(:pea)
479
+ expect(result.car.cdr.cdr.car).to eq(:soup)
480
+ expect(result.cdr.car.car).to eq(:red)
481
+ expect(result.cdr.car.cdr.car).to eq(:bean)
482
+ expect(result.cdr.car.cdr.cdr.car).to eq(:soup)
483
+ end
484
+
485
+ it 'passes frame 1:81' do
486
+ # Reasoned S2, frame 1:81
487
+ # (run* (x y)
488
+ # (== 'split x)
489
+ # (== 'pea y)) ;; => ((split pea))
490
+
491
+ result = run_star(%w[x y], [equals(:split, x), equals(:pea, y)])
492
+ expect(result.car.car).to eq(:split)
493
+ expect(result.car.cdr.car).to eq(:pea)
494
+ end
495
+ end # context
496
+ end # describe
497
+ end # module
498
+ end # module
@@ -244,7 +244,7 @@ module MiniKraken
244
244
  instance = RunStarExpression.new('q', goal)
245
245
 
246
246
  # Reasoned S2, frame 1:34
247
- # (run* q (== '(((,q)) pod) `(((pea)) pod))) ;; => ('pod)
247
+ # (run* q (== '(((,q)) pod) `(((pea)) pod))) ;; => ('pea)
248
248
  result = instance.run
249
249
  expect(result.car).to eq(pea)
250
250
  end
@@ -363,7 +363,6 @@ module MiniKraken
363
363
  expect(result.car).to eq(any_value(0))
364
364
  end
365
365
 
366
- # TODO: fix erratic RSpec failure
367
366
  it 'should support conjunction of one succeed and a successful goal' do
368
367
  subgoal = equals_goal(corn, ref_q)
369
368
  goal = conj2_goal(succeeds, subgoal)
@@ -481,7 +480,7 @@ module MiniKraken
481
480
  # Reasoned S2, frame 1:62
482
481
  # (run* x (disj2
483
482
  # (conj2 (== 'olive x) fail)
484
- # ('oil x))) ;; => (oil)
483
+ # (== 'oil x))) ;; => (oil)
485
484
  result = instance.run
486
485
  expect(result.car).to eq(oil)
487
486
  end
@@ -511,7 +510,7 @@ module MiniKraken
511
510
 
512
511
  # Reasoned S2, frame 1:64
513
512
  # (run* x (disj2
514
- # ('oil x)
513
+ # (== 'oil x)
515
514
  # (conj2 (== 'olive x) succeed))) ;; => (oil olive)
516
515
  result = instance.run
517
516
  expect(result.car).to eq(oil)
@@ -530,11 +529,11 @@ module MiniKraken
530
529
 
531
530
  # Reasoned S2, frame 1:65
532
531
  # (run* x (disj2
533
- # (conj2(== 'virgin x) fails)
532
+ # (conj2(== 'virgin x) fail)
534
533
  # (disj2
535
534
  # (== 'olive x)
536
535
  # (dis2
537
- # succeeds
536
+ # succeed
538
537
  # (== 'oil x))))) ;; => (olive _0 oil)
539
538
  result = instance.run
540
539
  expect(result.car).to eq(olive)
@@ -649,7 +648,7 @@ module MiniKraken
649
648
  expect(result.car.cdr.cdr.cdr).to be_nil
650
649
  end
651
650
 
652
- it 'should allow simplication of expressions' do
651
+ it 'should allow simplification of expressions' do
653
652
  expr1 = equals_goal(split, ref_x)
654
653
  expr2 = equals_goal(pea, ref_y)
655
654
  goal = conj2_goal(expr1, expr2)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_kraken
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.2.00
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-01 00:00:00.000000000 Z
11
+ date: 2020-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -104,6 +104,7 @@ files:
104
104
  - lib/mini_kraken/core/variable.rb
105
105
  - lib/mini_kraken/core/variable_ref.rb
106
106
  - lib/mini_kraken/core/vocabulary.rb
107
+ - lib/mini_kraken/glue/dsl.rb
107
108
  - lib/mini_kraken/glue/fresh_env.rb
108
109
  - lib/mini_kraken/glue/run_star_expression.rb
109
110
  - lib/mini_kraken/version.rb
@@ -128,6 +129,7 @@ files:
128
129
  - spec/core/variable_ref_spec.rb
129
130
  - spec/core/variable_spec.rb
130
131
  - spec/core/vocabulary_spec.rb
132
+ - spec/glue/dsl_chap1_spec.rb
131
133
  - spec/glue/fresh_env_spec.rb
132
134
  - spec/glue/run_star_expression_spec.rb
133
135
  - spec/mini_kraken_spec.rb
@@ -178,6 +180,7 @@ test_files:
178
180
  - spec/core/variable_ref_spec.rb
179
181
  - spec/core/variable_spec.rb
180
182
  - spec/core/vocabulary_spec.rb
183
+ - spec/glue/dsl_chap1_spec.rb
181
184
  - spec/glue/fresh_env_spec.rb
182
185
  - spec/glue/run_star_expression_spec.rb
183
186
  - spec/mini_kraken_spec.rb