mini_kraken 0.1.05 → 0.1.06
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 +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +4 -2
- data/lib/mini_kraken/core/composite_goal.rb +46 -0
- data/lib/mini_kraken/core/composite_term.rb +2 -0
- data/lib/mini_kraken/core/conj2.rb +86 -0
- data/lib/mini_kraken/core/designation.rb +55 -0
- data/lib/mini_kraken/core/equals.rb +3 -9
- data/lib/mini_kraken/core/goal.rb +4 -2
- data/lib/mini_kraken/core/goal_arg.rb +10 -0
- data/lib/mini_kraken/core/goal_relation.rb +15 -0
- data/lib/mini_kraken/core/term.rb +5 -2
- data/lib/mini_kraken/core/variable.rb +3 -27
- data/lib/mini_kraken/core/variable_ref.rb +3 -28
- data/lib/mini_kraken/core/vocabulary.rb +2 -2
- data/lib/mini_kraken/glue/run_star_expression.rb +1 -1
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/conj2_spec.rb +115 -0
- data/spec/glue/run_star_expression_spec.rb +65 -0
- data/spec/support/factory_methods.rb +8 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 800d58c5efac32c005931c5a019f4440060728d1061c381bd10f95d554a525b1
|
4
|
+
data.tar.gz: 217448cd67ca952439e81da5c89393ddd57d061d7ca9d5ed310119d1aab32cbf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
- [ ]
|
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
|
-
|
21
|
-
|
22
|
-
A
|
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?(
|
40
|
+
raise StandardError, prefix + actl.to_s unless actl.kind_of?(GoalArg)
|
39
41
|
end
|
40
42
|
|
41
43
|
args.dup
|
@@ -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
|
6
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
12
|
-
|
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
|
-
|
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
|
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
|
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
|
|
data/lib/mini_kraken/version.rb
CHANGED
@@ -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.
|
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-
|
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
|