mini_kraken 0.1.08 → 0.1.13

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.
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_arg'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # A formal reference represents the occurrence of a formal argument name in a
8
+ # goal template argument list.
9
+ class FormalRef < BaseArg
10
+ # @return [String]
11
+ attr_reader :name
12
+
13
+ def initialize(aName)
14
+ @name = validated_name(aName)
15
+ end
16
+
17
+ private
18
+
19
+ def validated_name(aName)
20
+ aName
21
+ end
22
+ end # class
23
+ end # module
24
+ end # module
@@ -30,14 +30,20 @@ module MiniKraken
30
30
  private
31
31
 
32
32
  def validated_actuals(args)
33
- if args.size != relation.arity
33
+ if !relation.polyadic? && (args.size != relation.arity)
34
34
  err_msg = "Goal has #{args.size} arguments, expected #{relation.arity}"
35
35
  raise StandardError, err_msg
36
36
  end
37
37
 
38
38
  prefix = 'Invalid goal argument '
39
- args.each do |actl|
40
- raise StandardError, prefix + actl.to_s unless actl.kind_of?(GoalArg)
39
+ args.each do |actual|
40
+ if actual.kind_of?(GoalArg) || actual.kind_of?(Environment)
41
+ next
42
+ elsif actual.kind_of?(Array)
43
+ validated_actuals(actual)
44
+ else
45
+ raise StandardError, prefix + actual.to_s
46
+ end
41
47
  end
42
48
 
43
49
  args.dup
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base_arg'
4
+
3
5
  module MiniKraken
4
6
  module Core
5
- # The generalization of any iem that can be
6
- # passed as arugement to a goal.
7
- class GoalArg
7
+ # The generalization of any item that can be
8
+ # passed as arugement to a goal object
9
+ class GoalArg < BaseArg
8
10
  end # class
9
11
  end # module
10
12
  end # module
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_arg'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # A meta-goal that is parametrized with generic formal arguments.
8
+ # The individual goals are instantiated when the formal arguments
9
+ # are bound to goal arguments
10
+ class GoalTemplate < BaseArg
11
+ # @return [Array<BaseArg>}] Arguments of goal template.
12
+ attr_reader :args
13
+
14
+ # @return [Relation] Main relation for the goal template
15
+ attr_reader :relation
16
+
17
+ def initialize(aRelation, theArgs)
18
+ @relation = validated_relation(aRelation)
19
+ @args = validated_args(theArgs)
20
+ end
21
+
22
+ # @param formals [Array<FormalArg>] Array of formal arguments
23
+ # @param actuals [Array<GoalArg>] Array of actual arguments
24
+ # @return [Goal] instantiate a goal object given the actuals and environment
25
+ def instantiate(formals, actuals)
26
+ formals2actuals = {}
27
+ formals.each_with_index do |frml, i|
28
+ formals2actuals[frml.name] = actuals[i]
29
+ end
30
+
31
+ do_instantiate(formals2actuals)
32
+ end
33
+
34
+ private
35
+
36
+ def validated_relation(aRelation)
37
+ aRelation
38
+ end
39
+
40
+ def validated_args(theArgs)
41
+ theArgs
42
+ end
43
+
44
+ def do_instantiate(formals2actuals)
45
+ goal_args = []
46
+ args.each do |arg|
47
+ if arg.kind_of?(FormalRef)
48
+ goal_args << formals2actuals[arg.name]
49
+ elsif arg.kind_of?(GoalTemplate)
50
+ goal_args << arg.send(:do_instantiate, formals2actuals)
51
+ else
52
+ goal_args << arg
53
+ end
54
+ end
55
+
56
+ Goal.new(relation, goal_args)
57
+ end
58
+ end # class
59
+ end # module
60
+ end # module
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'atomic_term'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # A specialized atomic term that represents an boolean (true/false) value.
8
+ # in MiniKraken
9
+ class KBoolean < AtomicTerm
10
+ # @param aValue [Boolean, Symbol] Ruby representation of boolean value
11
+ def initialize(aValue)
12
+ super(validated_value(aValue))
13
+ end
14
+
15
+ private
16
+
17
+ def validated_value(aValue)
18
+ case aValue
19
+ when true, false
20
+ aValue
21
+ when :"#t", '#t'
22
+ true
23
+ when :"#f", '#f'
24
+ false
25
+ else
26
+ raise StandardError, "Invalid boolean literal '#{aValue}'"
27
+ end
28
+ end
29
+ end # class
30
+ end # module
31
+ end # module
@@ -16,6 +16,14 @@ unless MiniKraken::Core.constants(false).include? :Outcome
16
16
  @resultant = aResult
17
17
  end
18
18
 
19
+ def self.failure(aParent = nil)
20
+ new(:"#u", aParent)
21
+ end
22
+
23
+ def self.success(aParent = nil)
24
+ new(:"#s", aParent)
25
+ end
26
+
19
27
  def successful?
20
28
  resultant == :"#s"
21
29
  end
@@ -30,6 +38,12 @@ unless MiniKraken::Core.constants(false).include? :Outcome
30
38
 
31
39
  are_equal
32
40
  end
41
+
42
+ protected
43
+
44
+ def introspect
45
+ ", @resultant=#{resultant}"
46
+ end
33
47
  end # class
34
48
 
35
49
  Failure = Outcome.new(:"#u")
@@ -16,6 +16,13 @@ module MiniKraken
16
16
  @alt_name = alternateName
17
17
  end
18
18
 
19
+ # A relation is polyadic when it accepts an arbitrary number of arguments.
20
+ # Most built-in relation takes a fixed number of arguments (= arity).
21
+ # @return [Boolean]
22
+ def polyadic?
23
+ false
24
+ end
25
+
19
26
  # Number of arguments for the relation.
20
27
  # @return [Integer]
21
28
  def arity
@@ -24,11 +24,17 @@ module MiniKraken
24
24
  name != i_name
25
25
  end
26
26
 
27
- def quote(anEnvironment)
28
- raise StandardError, "class #{anEnvironment}" unless anEnvironment.kind_of?(Vocabulary)
27
+ def quote(env)
28
+ raise StandardError, "class #{env}" unless env.kind_of?(Vocabulary)
29
29
 
30
- val = anEnvironment.quote_ref(self)
31
- val.nil? ? AnyValue.new(name, anEnvironment) : val
30
+ val = env.quote_ref(self)
31
+ unless val
32
+ result = AnyValue.new(name, env, env.names_fused(name))
33
+ else
34
+ result = val
35
+ end
36
+
37
+ result
32
38
  end
33
39
  end # class
34
40
  end # module
@@ -331,8 +331,9 @@ module MiniKraken
331
331
  # variable name.
332
332
  # @param aName [String] User-defined variable name
333
333
  def names_fused(aName)
334
+ # require 'debug'
334
335
  var = name2var(aName)
335
- return [] unless var.fused?
336
+ return [] unless var&.fused?
336
337
 
337
338
  i_name = var.i_name
338
339
  names = []
@@ -394,6 +395,18 @@ module MiniKraken
394
395
  name2var(aVarName) ? true : false
395
396
  end
396
397
 
398
+ def inspect
399
+ result = +"#<#{self.class.name}:#{object_id.to_s(16)} @parent="
400
+ if parent
401
+ result << "#<#{parent.class.name}:#{parent.object_id.to_s(16)}>"
402
+ else
403
+ result << nil
404
+ end
405
+ result << introspect
406
+ result << '>'
407
+ result
408
+ end
409
+
397
410
  protected
398
411
 
399
412
  def validated_parent(aParent)
@@ -420,6 +433,10 @@ module MiniKraken
420
433
  assc
421
434
  end
422
435
  end
436
+
437
+ def introspect
438
+ ''
439
+ end
423
440
  end # class
424
441
  end # module
425
442
  end # module
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../core/environment'
4
+ require_relative '../core/conj2'
4
5
  require_relative '../core/variable'
5
6
 
6
7
  module MiniKraken
7
8
  module Glue
9
+ # A combination of an Environment (= a scope for one or more variables)
10
+ # and a goal. It quacks like a Goal object: when receiving the attain message,
11
+ # it attempt to achieve its given goal.
8
12
  # (fresh (x) (== 'pea q))
9
13
  # Introduces the new variable 'x'
10
14
  # Takes a list of names and a goal-like object
@@ -13,11 +17,11 @@ module MiniKraken
13
17
  # @return [Goal]
14
18
  attr_reader :goal
15
19
 
16
- # @param theNames [Array<String>]
17
- # @param aGoal [Goal]
20
+ # @param theNames [Array<String>] The variable names
21
+ # @param aGoal [Goal, Array<Goal>] The goal to achieve or the conjunction of them.
18
22
  def initialize(theNames, aGoal)
19
23
  super()
20
- @goal = aGoal
24
+ @goal = valid_goal(aGoal)
21
25
  theNames.each { |nm| add_var(Core::Variable.new(nm)) }
22
26
  end
23
27
 
@@ -28,6 +32,44 @@ module MiniKraken
28
32
  self.parent = aParent
29
33
  goal.attain(self)
30
34
  end
35
+
36
+ protected
37
+
38
+ def introspect
39
+ +", @vars=[#{vars.keys.join(', ')}]"
40
+ end
41
+
42
+ private
43
+
44
+ def valid_goal(aGoal)
45
+ result = nil
46
+
47
+ case aGoal
48
+ when Core::Goal
49
+ result = aGoal
50
+ when FreshEnv
51
+ result = aGoal
52
+ when Array # an Array of Goal?..
53
+ goal_array = aGoal
54
+ loop do
55
+ conjunctions = []
56
+ goal_array.each_slice(2) do |uno_duo|
57
+ if uno_duo.size == 2
58
+ conjunctions << Core::Goal.new(Core::Conj2.instance, uno_duo)
59
+ else
60
+ conjunctions << uno_duo[0]
61
+ end
62
+ end
63
+ if conjunctions.size == 1
64
+ result = conjunctions[0]
65
+ break
66
+ end
67
+ goal_array = conjunctions
68
+ end
69
+ end
70
+
71
+ result
72
+ end
31
73
  end # class
32
74
  end # module
33
75
  end # module
@@ -7,21 +7,18 @@ require_relative 'fresh_env'
7
7
  module MiniKraken
8
8
  module Glue
9
9
  class RunStarExpression
10
+ # @return [FreshEnv] The environment in which run* variables will reside.
10
11
  attr_reader :env
11
12
 
12
- # @param var_name [String]
13
- # @param goal [Core::Goal]
14
- def initialize(var_name, goal)
15
- @env = FreshEnv.new([var_name], goal)
16
- end
17
-
18
- def var
19
- env.vars.values.first
13
+ # @param var_names [String, Array<String>] One variable name or an array of names
14
+ # @param goal [Core::Goal, Array<Core::Goal>] A single goal or an array of goals to conjunct
15
+ def initialize(var_names, goal)
16
+ vnames = var_names.kind_of?(String) ? [var_names] : var_names
17
+ @env = FreshEnv.new(vnames, goal)
20
18
  end
21
19
 
22
20
  def run
23
- result = nil
24
- next_result = nil
21
+ result = []
25
22
  solver = env.goal.attain(env)
26
23
  # require 'debug'
27
24
  loop do
@@ -30,24 +27,44 @@ module MiniKraken
30
27
  outcome = solver.resume
31
28
  break if outcome.nil?
32
29
 
33
- if result # ... more than one result...
34
- if outcome.successful?
35
- next_result.append(Core::ConsCell.new(var.quote(outcome)))
36
- else
37
- next_result.append(Core::NullList)
38
- end
39
- next_result = next_result.cdr
40
- elsif outcome.successful?
41
- env.propagate(outcome)
42
- result = Core::ConsCell.new(var.quote(outcome))
43
- next_result = result
44
- else
45
- result = Core::NullList
46
- next_result = result
47
- end
30
+ env.propagate(outcome) if result.empty? && outcome.successful?
31
+ result << build_solution(outcome)
32
+ end
33
+
34
+ format_solutions(result)
35
+ end
36
+
37
+ private
38
+
39
+ # @return [Array] A vector of assignment for each variable
40
+ def build_solution(outcome)
41
+ sol = env.vars.values.map do |var|
42
+ outcome.successful? ? var.quote(outcome) : nil
43
+ end
44
+
45
+ sol
46
+ end
47
+
48
+ # Transform the solutions into sequence of conscells.
49
+ # @param solutions [Array<Array>] An array of solution.
50
+ # A solution is in itself an array of bindings (one per variable)
51
+ def format_solutions(solutions)
52
+ solutions_as_list = solutions.map { |sol| arr2list(sol, true) }
53
+ arr2list(solutions_as_list, false)
54
+ end
55
+
56
+ # Utility method. Transform an array into a ConsCell-based list.
57
+ # @param anArray [Array]
58
+ # @param simplify [Boolean]
59
+ def arr2list(anArray, simplify)
60
+ return anArray[0] if anArray.size == 1 && simplify
61
+
62
+ new_tail = nil
63
+ anArray.reverse_each do |elem|
64
+ new_tail = Core::ConsCell.new(elem, new_tail)
48
65
  end
49
66
 
50
- result
67
+ new_tail
51
68
  end
52
69
  end # class
53
70
  end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniKraken
4
- VERSION = '0.1.08'
4
+ VERSION = '0.1.13'
5
5
  end
@@ -0,0 +1,147 @@
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/conde'
14
+
15
+ module MiniKraken
16
+ module Core
17
+ describe Conde do
18
+ subject { Conde.instance }
19
+
20
+ context 'Initialization:' do
21
+ it 'should be initialized without argument' do
22
+ expect { Conde.instance }.not_to raise_error
23
+ end
24
+
25
+ it 'should know its name' do
26
+ expect(subject.name).to eq('conde')
27
+ end
28
+ end # context
29
+
30
+ context 'Provided services:' do
31
+ let(:bean) { KSymbol.new(:bean) }
32
+ let(:corn) { KSymbol.new(:corn) }
33
+ let(:meal) { KSymbol.new(:meal) }
34
+ let(:oil) { KSymbol.new(:oil) }
35
+ let(:olive) { KSymbol.new(:olive) }
36
+ let(:pea) { KSymbol.new(:pea) }
37
+ let(:red) { KSymbol.new(:red) }
38
+ let(:split) { KSymbol.new(:split) }
39
+ let(:fails) { Goal.new(Fail.instance, []) }
40
+ let(:succeeds) { Goal.new(Succeed.instance, []) }
41
+ let(:var_q) { Variable.new('q') }
42
+ let(:var_x) { Variable.new('x') }
43
+ let(:var_y) { Variable.new('y') }
44
+ let(:ref_q) { VariableRef.new('q') }
45
+ let(:ref_x) { VariableRef.new('x') }
46
+ let(:ref_y) { VariableRef.new('y') }
47
+ let(:env) do
48
+ e = Environment.new
49
+ e.add_var(var_q)
50
+ e.add_var(var_x)
51
+ e.add_var(var_y)
52
+ e
53
+ end
54
+
55
+ it 'should complain when one of its argument is not a goal' do
56
+ err = StandardError
57
+ expect { subject.solver_for([succeeds, pea], env) }.to raise_error(err)
58
+ expect { subject.solver_for([pea, succeeds], env) }.to raise_error(err)
59
+ end
60
+
61
+ it 'should fails if when all goals fail' do
62
+ solver = subject.solver_for([fails, fails, fails], env)
63
+ expect(solver.resume).not_to be_successful
64
+ expect(solver.resume).to be_nil
65
+ end
66
+
67
+ it 'yield success if first argument succeeds' do
68
+ subgoal = Goal.new(Equals.instance, [olive, ref_q])
69
+ solver = subject.solver_for([subgoal, fails, fails], env)
70
+ outcome = solver.resume
71
+ expect(outcome).to be_successful
72
+ expect(outcome.associations['q'].first.value).to eq(olive)
73
+ expect(solver.resume).to be_nil
74
+ end
75
+
76
+ it 'yield success if second argument succeeds' do
77
+ subgoal = Goal.new(Equals.instance, [oil, ref_q])
78
+ solver = subject.solver_for([fails, subgoal, fails], env)
79
+ outcome = solver.resume
80
+ expect(outcome).to be_successful
81
+ expect(outcome.associations['q'].first.value).to eq(oil)
82
+ expect(solver.resume).to be_nil
83
+ end
84
+
85
+ it 'yield success if third argument succeeds' do
86
+ subgoal = Goal.new(Equals.instance, [oil, ref_q])
87
+ solver = subject.solver_for([fails, fails, subgoal], env)
88
+ outcome = solver.resume
89
+ expect(outcome).to be_successful
90
+ expect(outcome.associations['q'].first.value).to eq(oil)
91
+ expect(solver.resume).to be_nil
92
+ end
93
+
94
+ it 'yields three solutions if three goals succeed' do
95
+ # Covers frame 1:58
96
+ subgoal1 = Goal.new(Equals.instance, [olive, ref_q])
97
+ subgoal2 = Goal.new(Equals.instance, [oil, ref_q])
98
+ subgoal3 = Goal.new(Equals.instance, [pea, ref_q])
99
+ solver = subject.solver_for([subgoal1, subgoal2, subgoal3, fails], env)
100
+
101
+ # First solution
102
+ outcome1 = solver.resume
103
+ expect(outcome1).to be_successful
104
+ expect(outcome1.associations['q'].first.value).to eq(olive)
105
+
106
+ # Second solution
107
+ outcome2 = solver.resume
108
+ expect(outcome2).to be_successful
109
+ expect(outcome2.associations['q'].first.value).to eq(oil)
110
+
111
+ # Third solution
112
+ outcome3 = solver.resume
113
+ expect(outcome3).to be_successful
114
+ expect(outcome3.associations['q'].first.value).to eq(pea)
115
+
116
+ expect(solver.resume).to be_nil
117
+ end
118
+
119
+ it 'also use conjunctions for nested goals' do
120
+ # Covers frame 1:88
121
+ subgoal1 = Goal.new(Equals.instance, [split, ref_x])
122
+ subgoal2 = Goal.new(Equals.instance, [pea, ref_y])
123
+ combo1 = [subgoal1, subgoal2]
124
+
125
+ subgoal3 = Goal.new(Equals.instance, [red, ref_x])
126
+ subgoal4 = Goal.new(Equals.instance, [bean, ref_y])
127
+ combo2 = [subgoal3, subgoal4]
128
+ solver = subject.solver_for([combo1, combo2], env)
129
+
130
+ # First solution
131
+ outcome1 = solver.resume
132
+ expect(outcome1).to be_successful
133
+ expect(outcome1.associations['x'].first.value).to eq(split)
134
+ expect(outcome1.associations['y'].first.value).to eq(pea)
135
+
136
+ # Second solution
137
+ outcome2 = solver.resume
138
+ expect(outcome2).to be_successful
139
+ expect(outcome2.associations['x'].first.value).to eq(red)
140
+ expect(outcome2.associations['y'].first.value).to eq(bean)
141
+
142
+ expect(solver.resume).to be_nil
143
+ end
144
+ end # context
145
+ end # describe
146
+ end # module
147
+ end # module