mini_kraken 0.1.05 → 0.1.06

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4ce5f0f43f5e84f377cf90e2ed49d6ff54de93b66641655fb41dd6b8feef277
4
- data.tar.gz: 2081e486f8beba0b0073461464c632fa63af83cec27a48fb3d84c1a972b821f9
3
+ metadata.gz: 800d58c5efac32c005931c5a019f4440060728d1061c381bd10f95d554a525b1
4
+ data.tar.gz: 217448cd67ca952439e81da5c89393ddd57d061d7ca9d5ed310119d1aab32cbf
5
5
  SHA512:
6
- metadata.gz: ea0580c9219f1a1fd586d024f06e11cf6dd1f979182c6f5a15473c65b78fa7244ba01ca9191f31e73de78b20d7aa88c60d86afa1207dbaab246c64c6650f0d1f
7
- data.tar.gz: 9af43ee21606cac23d2512002491d0264fcbc7a74b9974edbd70c23f4bcbf25c7cc7010845aad88dee2d507a1f6d070dd402cd9a6f2abe0234999617b3ae7b68
6
+ metadata.gz: e52d5bdc7a34e1b303f2423fd8463766852bd61dc14d77e7bce43bcf653fd43d2331eb8f4bbc1beee13958e4621c9729b728d0cf5bbb5336bd5e277bf859a473
7
+ data.tar.gz: 75b7a091579f667b41949905861ec314eb6a269ca5316b67df80cbf9f02d6d86ecd1c0ff74d61de3018c802e7c48ff7a2e4719bcb75f0ef45cca0092ebd3cb25
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [0.1.06] - 2020-05-20
2
+ - Implementation of `conj2` (two arguments conjunction)
3
+
4
+ ### New
5
+ - Class `CompositeGoal`
6
+ - Class `Conj2` as subclass of `GoalRelation` that implements the conjunction of two subgoals
7
+ - Mixin module `Designation` to factor out the common methods in `Variable` and `VariableRef` classes
8
+ - Class `GoalArg` abstract class, that is a generalization for anything that be be argument of a goal.
9
+ - Class `GoalRelation` as subclass of `Relation`. A goal that is linked to a such relation may have goals as its arguments only.
10
+
11
+ ### Changed
12
+ - Class `Goal` is new subclass of class `GoalArg`. Therefore a goal can be an argument to another goal.
13
+ - Class `Term` is new subclass of class `GoalArg`. Therefore a term can be an argument of a goal.
14
+ - Classes `Variable`, `VariableRef` now include mix-in module `Designation`
15
+ - File `cd_implementation.txt` Updated with changes of class relationship
16
+
1
17
  ## [0.1.05] - 2020-05-09
2
18
  - Changed implementation of fused variables
3
19
  - Magic comments for frozen string literal
data/README.md CHANGED
@@ -13,12 +13,14 @@ ISBN: 9780262535519, (2018), MIT Press.
13
13
  ### Features
14
14
  - [X] ==
15
15
  - [X] run\*
16
- - [X] fresh
16
+ - [X] fresh
17
+ - [X] conj2
17
18
 
18
19
  ### TODO
19
20
  - [ ] disj2
20
- - [ ] conj2
21
+ - [ ] defrel
21
22
  - [ ] conde
23
+ - [ ] Occurs check
22
24
 
23
25
  ## Installation
24
26
 
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'environment'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ class CompositeGoal
8
+ # @return [Operator] The operator corresponding to this goal
9
+ attr_reader :operator
10
+
11
+ # @return [Array<Goal>] The child goals (sub-goals)
12
+ attr_reader :children
13
+
14
+ # @param anOperator [Operator] The operator corresponding to this goal
15
+ # @param theChildren [Array<Goal>] The child goals (sub-goals)
16
+ def initialize(anOperator, theChildren)
17
+ @operator = anOperator
18
+ @children = validated_children(theChildren)
19
+ end
20
+
21
+ # Attempt to achieve the goal for a given context (environment)
22
+ # @param anEnv [Environment] The context in which the goal take place.
23
+ # @return [Fiber<Outcome>] A Fiber object that will generate the results.
24
+ def attain(anEnv)
25
+ operator.solver_for(children, anEnv)
26
+ end
27
+
28
+ private
29
+
30
+ def validated_children(theChildren)
31
+ my_arity = operator.arity
32
+ if args.size != my_arity
33
+ err_msg = "Goal has #{theChildren.size} arguments, expected #{my_arity}"
34
+ raise StandardError, err_msg
35
+ end
36
+
37
+ prefix = 'Invalid goal argument '
38
+ theChildren.each do |subg|
39
+ raise StandardError, prefix + subg.to_s unless subg.kind_of?(Goal)
40
+ end
41
+
42
+ theChildren.dup
43
+ end
44
+ end # class
45
+ end # module
46
+ end # module
@@ -8,6 +8,8 @@ module MiniKraken
8
8
  # An composite term is an Minikraken term that can be
9
9
  # decomposed into simpler MiniKraken data value(s).
10
10
  class CompositeTerm < Term
11
+ # Abstract method (to override). Return the child terms.
12
+ # @return [Array<Term>]
11
13
  def children
12
14
  raise NotImplementedError, 'This method must re-defined in subclass(es).'
13
15
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require_relative 'duck_fiber'
5
+ require_relative 'goal'
6
+ require_relative 'goal_relation'
7
+ require_relative 'outcome'
8
+
9
+ module MiniKraken
10
+ module Core
11
+ # The conjunction is a relation that accepts only goal(s) as its two
12
+ # arguments. It succeeds if and only both its goal arguments succeeds.
13
+ class Conj2 < GoalRelation
14
+ include Singleton
15
+
16
+ def initialize
17
+ super('conj2', nil)
18
+ end
19
+
20
+ # @param actuals [Array<Term>] A two-elements array
21
+ # @param anEnv [Vocabulary] A vocabulary object
22
+ # @return [Fiber<Outcome>] A Fiber that yields Outcomes objects
23
+ def solver_for(actuals, anEnv)
24
+ g1, g2 = *validated_args(actuals)
25
+ Fiber.new { conjunction(g1, g2, anEnv) }
26
+ end
27
+
28
+ # Yields [Outcome, NilClass] result of the conjunction
29
+ # @param g1 [Goal] First goal argument
30
+ # @param g2 [Goal] Second goal argument
31
+ # @param voc [Vocabulary] A vocabulary object
32
+ def conjunction(g1, g2, voc)
33
+ # require 'debug'
34
+ outcome1 = nil
35
+ outcome2 = nil
36
+ if g1.relation.kind_of?(Fail) || g2.relation.kind_of?(Fail)
37
+ Fiber.yield Outcome.new(:"#u", voc)
38
+ else
39
+ f1 = g1.attain(voc)
40
+ loop do
41
+ outcome1 = f1.resume
42
+ break unless outcome1
43
+
44
+ outcome1.parent = voc unless outcome1.parent
45
+ if outcome1.successful?
46
+ f2 = g2.attain(outcome1)
47
+ loop do
48
+ outcome2 = f2.resume
49
+ break unless outcome2
50
+
51
+ outcome2.parent = voc unless outcome2.parent
52
+ if outcome2.successful?
53
+ res = Outcome.new(:"#s", voc)
54
+ res.merge(outcome1)
55
+ res.merge(outcome2)
56
+ Fiber.yield res
57
+ else
58
+ Fiber.yield outcome2
59
+ end
60
+ outcome2.clear
61
+ end
62
+ else
63
+ Fiber.yield outcome1
64
+ end
65
+ voc.clear if outcome1&.successful? && outcome2&.successful?
66
+ end
67
+ end
68
+
69
+ Fiber.yield nil
70
+ end
71
+
72
+ private
73
+
74
+ def validated_args(actuals)
75
+ actuals.each do |arg|
76
+ unless arg.kind_of?(Goal)
77
+ prefix = 'conj2 expects goal as argument, found a '
78
+ raise StandardError, prefix + "'#{arg.class}'"
79
+ end
80
+ end
81
+
82
+ actuals
83
+ end
84
+ end # class
85
+ end # module
86
+ end # module
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # Wordnet definition: Identifying word or words by which someone or
6
+ # something is called and classified or distinguished from others.
7
+ # Mix-in module that contains factored code for managing named entries
8
+ # in a vocabulary such as variables and variable references.
9
+ module Designation
10
+ # @return [String] User-defined name of the variable
11
+ attr_reader :name
12
+
13
+ def init_designation(aName)
14
+ @name = valid_name(aName)
15
+ end
16
+
17
+ # @param voc [Vocabulary]
18
+ # @return [Freshness]
19
+ def freshness(voc)
20
+ voc.freshness_ref(self)
21
+ end
22
+
23
+ # @param voc [Vocabulary]
24
+ # @return [Boolean]
25
+ def fresh?(voc)
26
+ frsh = freshness(voc)
27
+ frsh.degree == :fresh || frsh.degree == :bound
28
+ end
29
+
30
+ # @param voc [Vocabulary]
31
+ # @return [Boolean]
32
+ def bound?(voc)
33
+ frsh = freshness(voc)
34
+ frsh.degree == :bound
35
+ end
36
+
37
+ # @param voc [Vocabulary]
38
+ # @return [Boolean]
39
+ def ground?(voc)
40
+ frsh = freshness(voc)
41
+ frsh.degree == :bound
42
+ end
43
+
44
+ private
45
+
46
+ def valid_name(aName)
47
+ if aName.empty?
48
+ raise StandardError, 'Variable name may not be empty.'
49
+ end
50
+
51
+ aName
52
+ end
53
+ end # class
54
+ end # module
55
+ end # module
@@ -17,15 +17,9 @@ module MiniKraken
17
17
  super('equals', '==')
18
18
  end
19
19
 
20
- =begin
21
- double data flow:
22
- A goal has actual arguments
23
- When its corresponding relation is invoked
24
- this one will return two things: (a success, the bindings/constraints
25
- resulting from the relation)
26
- the bindings/constraints can be undone if enclosing fails, otherwise
27
- the bindings/constraints are rolled up.
28
- =end
20
+ # @param actuals [Array<Term>] A two-elements array
21
+ # @param anEnv [Vocabulary] A vocabulary object
22
+ # @return [Fiber<Outcome>] A Fiber(-like) instance that yields Outcomes
29
23
  def solver_for(actuals, anEnv)
30
24
  arg1, arg2 = *actuals
31
25
  DuckFiber.new(:custom) { unification(arg1, arg2, anEnv) }
@@ -3,8 +3,10 @@
3
3
  require_relative 'environment'
4
4
 
5
5
  module MiniKraken
6
+ require_relative 'goal_arg'
7
+
6
8
  module Core
7
- class Goal
9
+ class Goal < GoalArg
8
10
  # @return [Relation] The relation corresponding to this goal
9
11
  attr_reader :relation
10
12
 
@@ -35,7 +37,7 @@ module MiniKraken
35
37
 
36
38
  prefix = 'Invalid goal argument '
37
39
  args.each do |actl|
38
- raise StandardError, prefix + actl.to_s unless actl.kind_of?(Term)
40
+ raise StandardError, prefix + actl.to_s unless actl.kind_of?(GoalArg)
39
41
  end
40
42
 
41
43
  args.dup
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # The generalization of any iem that can be
6
+ # passed as arugement to a goal.
7
+ class GoalArg
8
+ end # class
9
+ end # module
10
+ end # module
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'relation'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # A specialization of a relation that accepts only goal(s)
8
+ # as its arguments.
9
+ class GoalRelation < Relation
10
+ def arity
11
+ 2
12
+ end
13
+ end # class
14
+ end # module
15
+ end # module
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'goal_arg'
4
+
3
5
  module MiniKraken
4
6
  module Core
5
- # The generalization of data value in MiniKraken
6
- class Term
7
+ # The generalization of any data value that can be
8
+ # passed as arugement to a goal.
9
+ class Term < GoalArg
7
10
  end # class
8
11
  end # module
9
12
  end # module
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'designation'
3
4
  require_relative 'any_value'
4
5
  require_relative 'vocabulary'
5
6
 
@@ -8,32 +9,17 @@ module MiniKraken
8
9
  # Representation of a MiniKraken variable.
9
10
  # It is a named slot that can be associated with one value.
10
11
  class Variable
11
- # @return [String] User-defined name of the variable
12
- attr_reader :name
12
+ include Designation # Mixin: Acquire name attribute
13
13
 
14
14
  # @return [String] Internal variable name used by MiniKraken
15
15
  attr_accessor :i_name
16
16
 
17
17
  # @param aName [String] The name of the variable
18
18
  def initialize(aName)
19
- @name = valid_name(aName)
19
+ init_designation(aName)
20
20
  @i_name = name.dup
21
21
  end
22
22
 
23
- def fresh?(anEnvironment)
24
- anEnvironment.fresh?(self)
25
- end
26
-
27
- # @param env [Environment]
28
- # @return [Freshness]
29
- def freshness(env)
30
- env.freshness_ref(self)
31
- end
32
-
33
- def ground?(anEnvironment)
34
- !fresh?(anEnvironment)
35
- end
36
-
37
23
  def fused?
38
24
  name != i_name
39
25
  end
@@ -44,16 +30,6 @@ module MiniKraken
44
30
  val = anEnvironment.quote_ref(self)
45
31
  val.nil? ? AnyValue.new(name, anEnvironment) : val
46
32
  end
47
-
48
- private
49
-
50
- def valid_name(aName)
51
- if aName.empty?
52
- raise StandardError, 'Variable name may not be empty.'
53
- end
54
-
55
- aName
56
- end
57
33
  end # class
58
34
  end # module
59
35
  end # module
@@ -8,31 +8,12 @@ module MiniKraken
8
8
  # A variable reference represents the occurrence of a variable (name) in a
9
9
  # MiniKraken term.
10
10
  class VariableRef < Term
11
- # @return [String] Name of the variable
12
- attr_reader :var_name
11
+ include Designation # Mixin: Acquire name attribute
12
+ alias var_name name
13
13
 
14
14
  # @param aName [String] The name of the variable
15
15
  def initialize(aName)
16
- @var_name = valid_name(aName)
17
- end
18
-
19
- # @param env [Environment]
20
- # @return [Boolean]
21
- def fresh?(env)
22
- env.fresh?(self)
23
- end
24
-
25
- # @param env [Environment]
26
- # @return [Boolean]
27
- def bound?(env)
28
- freshness = env.freshness_ref(self)
29
- freshness.degree == :bound
30
- end
31
-
32
- # @param env [Environment]
33
- # @return [Boolean]
34
- def ground?(env)
35
- !fresh?(env)
16
+ init_designation(aName)
36
17
  end
37
18
 
38
19
  # @param aValue [Term]
@@ -54,12 +35,6 @@ module MiniKraken
54
35
  freshness.associated
55
36
  end
56
37
 
57
- # @param env [Environment]
58
- # @return [Freshness]
59
- def freshness(env)
60
- env.freshness_ref(self)
61
- end
62
-
63
38
  # @param env [Environment]
64
39
  def quote(env)
65
40
  val = env.quote_ref(self)
@@ -236,7 +236,7 @@ module MiniKraken
236
236
  walker.find_ground(name, self)
237
237
  end
238
238
 
239
- # @param var [CompositeTerm] the composite term to check.
239
+ # @param val [CompositeTerm] the composite term to check.
240
240
  # @return [Boolean]
241
241
  def fresh_value?(val)
242
242
  walker = AssociationWalker.new
@@ -291,7 +291,7 @@ module MiniKraken
291
291
  end
292
292
 
293
293
  # Return the variable with given internal variable name.
294
- # @param aName [String] internal variable name
294
+ # @param i_name [String] internal variable name
295
295
  # @return [Variable]
296
296
  def i_name2var(i_name)
297
297
  var = nil
@@ -31,10 +31,10 @@ module MiniKraken
31
31
  if result # ... more than one result...
32
32
  elsif outcome.successful?
33
33
  env.propagate(outcome)
34
- # require 'debug'
35
34
  result = Core::ConsCell.new(var.quote(outcome))
36
35
  else
37
36
  result = Core::NullList
37
+ env.associations.freeze
38
38
  end
39
39
  end
40
40
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniKraken
4
- VERSION = '0.1.05'
4
+ VERSION = '0.1.06'
5
5
  end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/k_symbol'
5
+ require_relative '../../lib/mini_kraken/core/fail'
6
+ require_relative '../../lib/mini_kraken/core/succeed'
7
+ require_relative '../../lib/mini_kraken/core/equals'
8
+ require_relative '../../lib/mini_kraken/core/environment'
9
+ require_relative '../../lib/mini_kraken/core/variable'
10
+ require_relative '../../lib/mini_kraken/core/variable_ref'
11
+
12
+ # Load the class under test
13
+ require_relative '../../lib/mini_kraken/core/conj2'
14
+
15
+
16
+ module MiniKraken
17
+ module Core
18
+ describe Conj2 do
19
+ subject { Conj2.instance }
20
+
21
+ context 'Initialization:' do
22
+ it 'should be initialized without argument' do
23
+ expect { Conj2.instance }.not_to raise_error
24
+ end
25
+
26
+ it 'should know its name' do
27
+ expect(subject.name).to eq('conj2')
28
+ end
29
+ end # context
30
+
31
+ context 'Provided services:' do
32
+ let(:env) { Environment.new }
33
+ let(:pea) { KSymbol.new(:pea) }
34
+ let(:corn) { KSymbol.new(:corn) }
35
+ let(:meal) { KSymbol.new(:meal) }
36
+ let(:fails) { Goal.new(Fail.instance, []) }
37
+ let(:succeeds) { Goal.new(Succeed.instance, []) }
38
+ let(:var_q) { Variable.new('q') }
39
+ let(:ref_q) { VariableRef.new('q') }
40
+
41
+ it 'should complain when one of its argument is not a goal' do
42
+ err = StandardError
43
+ expect { subject.solver_for([succeeds, pea], env) }.to raise_error(err)
44
+ expect { subject.solver_for([pea, succeeds], env) }.to raise_error(err)
45
+ end
46
+
47
+ it 'should yield one failure if one of the goal is fail' do
48
+ # Fail as first argument
49
+ solver = subject.solver_for([fails, succeeds], env)
50
+ expect(solver.resume).not_to be_successful
51
+ expect(solver.resume).to be_nil
52
+
53
+ # Fail as second argument
54
+ solver = subject.solver_for([succeeds, fails], env)
55
+ expect(solver.resume).not_to be_successful
56
+ expect(solver.resume).to be_nil
57
+ end
58
+
59
+ it 'yield success if both arguments are succeed goals' do
60
+ # Covers frame 1-50
61
+ solver = subject.solver_for([succeeds, succeeds], env)
62
+ outcome = solver.resume
63
+ expect(outcome).to be_successful
64
+ expect(outcome.associations).to be_empty
65
+ expect(solver.resume).to be_nil
66
+ end
67
+
68
+ it 'should yield success and set associations' do
69
+ # # Weird: this example succeeds if run alone...
70
+ # # Covers frame 1-51
71
+ # env.add_var(var_q)
72
+ # sub_goal = Goal.new(Equals.instance, [corn, ref_q])
73
+ # solver = subject.solver_for([succeeds, sub_goal], env)
74
+ # outcome = solver.resume
75
+ # expect(outcome).to be_successful
76
+ # expect(outcome.associations).not_to be_empty
77
+ # expect(outcome.associations['q'].first.value).to eq(corn)
78
+ end
79
+
80
+ it 'should yield fails and set no associations' do
81
+ # Covers frame 1-52
82
+ env.add_var(var_q)
83
+ sub_goal = Goal.new(Equals.instance, [corn, ref_q])
84
+ solver = subject.solver_for([fails, sub_goal], env)
85
+ outcome = solver.resume
86
+ expect(outcome).not_to be_successful
87
+ expect(outcome.associations).to be_empty
88
+ end
89
+
90
+ it 'should yield fails when sub-goals are incompatible' do
91
+ # Covers frame 1-53
92
+ env.add_var(var_q)
93
+ sub_goal1 = Goal.new(Equals.instance, [corn, ref_q])
94
+ sub_goal2 = Goal.new(Equals.instance, [meal, ref_q])
95
+ solver = subject.solver_for([sub_goal1, sub_goal2], env)
96
+ outcome = solver.resume
97
+ expect(outcome).not_to be_successful
98
+ expect(outcome.associations).to be_empty
99
+ end
100
+
101
+ it 'should yield success when sub-goals are same and successful' do
102
+ # Covers frame 1-54
103
+ env.add_var(var_q)
104
+ sub_goal1 = Goal.new(Equals.instance, [corn, ref_q])
105
+ sub_goal2 = Goal.new(Equals.instance, [corn, ref_q])
106
+ solver = subject.solver_for([sub_goal1, sub_goal2], env)
107
+ outcome = solver.resume
108
+ expect(outcome).to be_successful
109
+ expect(outcome.associations).not_to be_empty
110
+ expect(outcome.associations['q'].first.value).to eq(corn)
111
+ end
112
+ end # context
113
+ end # describe
114
+ end # module
115
+ end # module
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../spec_helper' # Use the RSpec framework
4
4
  require_relative '../../lib/mini_kraken/core/goal'
5
+ require_relative '../../lib/mini_kraken/core/conj2'
5
6
  require_relative '../../lib/mini_kraken/core/equals'
6
7
  require_relative '../../lib/mini_kraken/core/fail'
7
8
  require_relative '../../lib/mini_kraken/core/succeed'
@@ -37,12 +38,16 @@ module MiniKraken
37
38
  end # context
38
39
 
39
40
  context 'Provided services:' do
41
+ let(:corn) { k_symbol(:corn) }
42
+ let(:meal) { k_symbol(:meal) }
40
43
  let(:ref_q) { Core::VariableRef.new('q') }
41
44
  let(:ref_x) { Core::VariableRef.new('x') }
42
45
  let(:ref_y) { Core::VariableRef.new('y') }
43
46
  let(:ref_s) { Core::VariableRef.new('s') }
44
47
  let(:ref_t) { Core::VariableRef.new('t') }
45
48
  let(:ref_u) { Core::VariableRef.new('u') }
49
+ let(:fails) { Core::Goal.new(Core::Fail.instance, []) }
50
+ let(:succeeds) { Core::Goal.new(Core::Succeed.instance, []) }
46
51
 
47
52
  it 'should return a null list with the fail goal' do
48
53
  # Reasoned S2, frame 1:7
@@ -356,6 +361,66 @@ module MiniKraken
356
361
  expect(ref_y.fresh?(fresh_env_y)).to be_truthy
357
362
  expect(result.car).to eq(cons(any_value(0), cons(any_value(1), cons(any_value(0)))))
358
363
  end
364
+
365
+ it 'should support conjunction of two succeed' do
366
+ goal = conj2_goal(succeeds, succeeds)
367
+ instance = RunStarExpression.new('q', goal)
368
+
369
+ # Reasoned S2, frame 1:50
370
+ # (run* q (conj2 succeed succeed)) ;; => (_0)
371
+ result = instance.run
372
+ expect(ref_q.fresh?(instance.env)).to be_truthy
373
+ expect(result.car).to eq(any_value(0))
374
+ end
375
+
376
+ # TODO: fix erratic RSpec failure
377
+ # it 'should support conjunction of one succeed and a successful goal' do
378
+ # subgoal = equals_goal(corn, ref_q)
379
+ # goal = conj2_goal(succeeds, subgoal)
380
+ # instance = RunStarExpression.new('q', goal)
381
+
382
+ # # Reasoned S2, frame 1:51
383
+ # # (run* q (conj2 succeed (== 'corn q)) ;; => ('corn)
384
+ # result = instance.run
385
+ # expect(ref_q.fresh?(instance.env)).to be_falsy
386
+ # expect(result.car).to eq(corn)
387
+ # end
388
+
389
+ it 'should support conjunction of one fail and a successful goal' do
390
+ subgoal = equals_goal(corn, ref_q)
391
+ goal = conj2_goal(fails, subgoal)
392
+ instance = RunStarExpression.new('q', goal)
393
+
394
+ # Reasoned S2, frame 1:52
395
+ # (run* q (conj2 fail (== 'corn q)) ;; => ()
396
+ expect(instance.run).to be_null
397
+ expect(ref_q.fresh?(instance.env)).to be_truthy
398
+ end
399
+
400
+ it 'should support conjunction of two contradictory goals' do
401
+ subgoal1 = equals_goal(corn, ref_q)
402
+ subgoal2 = equals_goal(meal, ref_q)
403
+ goal = conj2_goal(subgoal1, subgoal2)
404
+ instance = RunStarExpression.new('q', goal)
405
+
406
+ # Reasoned S2, frame 1:53
407
+ # (run* q (conj2 (== 'corn q)(== 'meal q)) ;; => ()
408
+ expect(instance.run).to be_null
409
+ expect(ref_q.fresh?(instance.env)).to be_truthy
410
+ end
411
+
412
+ it 'should succeed the conjunction of two identical goals' do
413
+ subgoal1 = equals_goal(corn, ref_q)
414
+ subgoal2 = equals_goal(corn, ref_q)
415
+ goal = conj2_goal(subgoal1, subgoal2)
416
+ instance = RunStarExpression.new('q', goal)
417
+
418
+ # Reasoned S2, frame 1:54
419
+ # (run* q (conj2 (== 'corn q)(== 'corn q)) ;; => ('corn)
420
+ result = instance.run
421
+ expect(ref_q.fresh?(instance.env)).to be_falsy
422
+ expect(result.car).to eq(corn)
423
+ end
359
424
  end # context
360
425
  end # describe
361
426
  end # module
@@ -34,6 +34,14 @@ module MiniKraken
34
34
  Core::Goal.new(Core::Equals.instance, [arg1, arg2])
35
35
  end
36
36
 
37
+ # Factory method for constructing a goal using the Equals relation.
38
+ # @param g1 [Core::Goal]
39
+ # @param g2 [Core::Goal]
40
+ # @return [Core::Goal]
41
+ def conj2_goal(g1, g2)
42
+ Core::Goal.new(Core::Conj2.instance, [g1, g2])
43
+ end
44
+
37
45
  # Factory method for constructing a KSymbol instance
38
46
  # @param aSymbol [Symbol]
39
47
  # @return [Core::KSymbol]
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.05
4
+ version: 0.1.06
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-05-09 00:00:00.000000000 Z
11
+ date: 2020-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -73,14 +73,19 @@ files:
73
73
  - lib/mini_kraken/core/association_walker.rb
74
74
  - lib/mini_kraken/core/atomic_term.rb
75
75
  - lib/mini_kraken/core/binary_relation.rb
76
+ - lib/mini_kraken/core/composite_goal.rb
76
77
  - lib/mini_kraken/core/composite_term.rb
78
+ - lib/mini_kraken/core/conj2.rb
77
79
  - lib/mini_kraken/core/cons_cell.rb
80
+ - lib/mini_kraken/core/designation.rb
78
81
  - lib/mini_kraken/core/duck_fiber.rb
79
82
  - lib/mini_kraken/core/environment.rb
80
83
  - lib/mini_kraken/core/equals.rb
81
84
  - lib/mini_kraken/core/fail.rb
82
85
  - lib/mini_kraken/core/freshness.rb
83
86
  - lib/mini_kraken/core/goal.rb
87
+ - lib/mini_kraken/core/goal_arg.rb
88
+ - lib/mini_kraken/core/goal_relation.rb
84
89
  - lib/mini_kraken/core/k_integer.rb
85
90
  - lib/mini_kraken/core/k_symbol.rb
86
91
  - lib/mini_kraken/core/nullary_relation.rb
@@ -97,6 +102,7 @@ files:
97
102
  - mini_kraken.gemspec
98
103
  - spec/core/association_spec.rb
99
104
  - spec/core/association_walker_spec.rb
105
+ - spec/core/conj2_spec.rb
100
106
  - spec/core/cons_cell_spec.rb
101
107
  - spec/core/duck_fiber_spec.rb
102
108
  - spec/core/environment_spec.rb
@@ -140,6 +146,7 @@ summary: Implementation of Minikanren language in Ruby. WIP
140
146
  test_files:
141
147
  - spec/core/association_spec.rb
142
148
  - spec/core/association_walker_spec.rb
149
+ - spec/core/conj2_spec.rb
143
150
  - spec/core/cons_cell_spec.rb
144
151
  - spec/core/duck_fiber_spec.rb
145
152
  - spec/core/environment_spec.rb