mini_kraken 0.1.08 → 0.1.09

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: 32a6f92773457338343dffa1343906883ffdbafbf7f8fd9ace170062cc34d345
4
- data.tar.gz: cb7d3e6b88f8bea952bea7f214eaf73af97857de6958f65592a15b7ee7ef2510
3
+ metadata.gz: 10a0607b5621effb10eab26d64ff7f52228f93e9c7907b91488613c1d72469aa
4
+ data.tar.gz: 9b43c9b0d729f2c7944be72f6599decae48d977ea0be5257a365111508473ef9
5
5
  SHA512:
6
- metadata.gz: db730590bcc99203f8983fe19b5e0d0b90c77819c07875bc34c100bc843c9b3e5e3e148833149d3264847a190cc9ce5bcf1fa31f74723b8c8e8006a56948d738
7
- data.tar.gz: 4cb5cb8f327e7cb81028621ad67e36afecee8bc90beac1cd6449573b85092191d39d55dfbe2de9f52a5868a66bf4c8288a3223b41815528fb651eead36d3f4fa
6
+ metadata.gz: 32c14d82b93ddf21d2b369745b1e556faf1736b53fd7f1782809370777e059c498e1d8347806dc2135aab3bedf429c8d2b93b5f9be4b9b361f86926edb1be20f
7
+ data.tar.gz: 9b82b3afd53af4da54042f8a8852367250bc8225c1229b31486b4e8223ca533d55f3dcc6250c4de4c03d0d84b77a5e5cf737a6c151f734d7a08334ac442fa330
@@ -1,3 +1,11 @@
1
+ ## [0.1.09] - 2020-06-06
2
+ - Supports frames from "The Reasoned Scheme" book up to frame [1:76]
3
+
4
+ ### CHANGED
5
+ - Method `FreshEnv#initialize`accepts an array of goals as second argument. This array is transformed into a conjunction of goals.
6
+ - Method `RunStarExpression#initialize` accepts multiple multiple variable names and goals.
7
+ - Method `RunStarExpression#run` can handle solutions with multiple variables.
8
+
1
9
  ## [0.1.08] - 2020-05-30
2
10
  - Fix of nasty bug (object aliasing) that caused flaky failures in specs.
3
11
 
@@ -31,12 +31,11 @@ unless MiniKraken::Core.constants(false).include? :Conj2
31
31
  # @param g2 [Goal] Second goal argument
32
32
  # @param voc [Vocabulary] A vocabulary object
33
33
  def conjunction(g1, g2, voc)
34
- # require 'debug'
35
- outcome1 = nil
36
- outcome2 = nil
37
34
  if g1.relation.kind_of?(Fail) || g2.relation.kind_of?(Fail)
38
35
  Fiber.yield Outcome.new(:"#u", voc)
39
36
  else
37
+ outcome1 = nil
38
+ outcome2 = nil
40
39
  f1 = g1.attain(voc)
41
40
  loop do
42
41
  outcome1 = f1.resume
@@ -148,8 +148,8 @@ unless MiniKraken::Core.constants(false).include? :Equals
148
148
  result
149
149
  end
150
150
  end # class
151
-
151
+
152
152
  Equals.instance.freeze
153
153
  end # module
154
154
  end # module
155
- end # unless
155
+ end # unless
@@ -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,38 @@ module MiniKraken
28
32
  self.parent = aParent
29
33
  goal.attain(self)
30
34
  end
35
+
36
+ private
37
+
38
+ def valid_goal(aGoal)
39
+ result = nil
40
+
41
+ case aGoal
42
+ when Core::Goal
43
+ result = aGoal
44
+ when FreshEnv
45
+ result = aGoal
46
+ when Array # an Array of Goal?..
47
+ goal_array = aGoal
48
+ loop do
49
+ conjunctions = []
50
+ goal_array.each_slice(2) do |uno_duo|
51
+ if uno_duo.size == 2
52
+ conjunctions << Core::Goal.new(Core::Conj2.instance, uno_duo)
53
+ else
54
+ conjunctions << uno_duo[0]
55
+ end
56
+ end
57
+ if conjunctions.size == 1
58
+ result = conjunctions[0]
59
+ break
60
+ end
61
+ goal_array = conjunctions
62
+ end
63
+ end
64
+
65
+ result
66
+ end
31
67
  end # class
32
68
  end # module
33
69
  end # module
@@ -9,19 +9,15 @@ module MiniKraken
9
9
  class RunStarExpression
10
10
  attr_reader :env
11
11
 
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
12
+ # @param var_names [String, Array<String>] One variable name or an array of names
13
+ # @param goal [Core::Goal, Array<Core::Goal>] A single goal or an array of goals to conjunct
14
+ def initialize(var_names, goal)
15
+ vnames = var_names.kind_of?(String) ? [var_names] : var_names
16
+ @env = FreshEnv.new(vnames, goal)
20
17
  end
21
18
 
22
19
  def run
23
- result = nil
24
- next_result = nil
20
+ result = []
25
21
  solver = env.goal.attain(env)
26
22
  # require 'debug'
27
23
  loop do
@@ -30,24 +26,44 @@ module MiniKraken
30
26
  outcome = solver.resume
31
27
  break if outcome.nil?
32
28
 
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
29
+ env.propagate(outcome) if result.empty? && outcome.successful?
30
+ result << build_solution(outcome)
31
+ end
32
+
33
+ format_solutions(result)
34
+ end
35
+
36
+ private
37
+
38
+ # @return [Array] A vector of assignment for each variable
39
+ def build_solution(outcome)
40
+ sol = env.vars.values.map do |var|
41
+ outcome.successful? ? var.quote(outcome) : nil
42
+ end
43
+
44
+ sol
45
+ end
46
+
47
+ # Transform the solutions into sequence of conscells.
48
+ # @param solutions [Array<Array>] An array of solution.
49
+ # A solution is in itself an array of bindings (one per variable)
50
+ def format_solutions(solutions)
51
+ solutions_as_list = solutions.map { |sol| arr2list(sol, true) }
52
+ arr2list(solutions_as_list, false)
53
+ end
54
+
55
+ # Utility method. Transform an array into a ConsCell-based list.
56
+ # @param anArray [Array]
57
+ # @param simplify [Boolean]
58
+ def arr2list(anArray, simplify)
59
+ return anArray[0] if anArray.size == 1 && simplify
60
+
61
+ new_tail = nil
62
+ anArray.reverse_each do |elem|
63
+ new_tail = Core::ConsCell.new(elem, new_tail)
48
64
  end
49
65
 
50
- result
66
+ new_tail
51
67
  end
52
68
  end # class
53
69
  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.09'
5
5
  end
@@ -3,7 +3,9 @@
3
3
  require_relative '../spec_helper' # Use the RSpec framework
4
4
  require_relative '../../lib/mini_kraken/core/goal'
5
5
  require_relative '../../lib/mini_kraken/core/equals'
6
+ require_relative '../../lib/mini_kraken/core/fail'
6
7
  require_relative '../../lib/mini_kraken/core/k_symbol'
8
+ require_relative '../../lib/mini_kraken/core/succeed'
7
9
 
8
10
  # Load the class under test
9
11
  require_relative '../../lib/mini_kraken/glue/fresh_env'
@@ -17,15 +19,39 @@ module MiniKraken
17
19
  let(:sample_goal) do
18
20
  Core::Goal.new(Core::Equals.instance, [pea, pod])
19
21
  end
22
+ let(:pea_goal) do
23
+ Core::Goal.new(Core::Equals.instance, [pea, pea])
24
+ end
25
+ let(:goal_succeeds) { Core::Goal.new(Core::Succeed.instance, []) }
26
+ let(:goal_fails) { Core::Goal.new(Core::Fail.instance, []) }
20
27
  subject { FreshEnv.new(['q'], sample_goal) }
21
28
 
22
29
  context 'Initialization:' do
23
- it 'should be initialized with an array of names' do
30
+ it 'could be initialized with names and a goal' do
24
31
  expect { FreshEnv.new(['q'], sample_goal) }.not_to raise_error
25
32
  end
26
33
 
34
+ it 'could be initialized with names and goals' do
35
+ expect { FreshEnv.new(%w[x y], [pea_goal, goal_succeeds]) }.not_to raise_error
36
+ end
37
+
27
38
  it 'should know its variables' do
28
39
  expect(subject.vars['q']).not_to be_nil
40
+
41
+ instance = FreshEnv.new(%w[x y], sample_goal)
42
+ expect(instance.vars['x']).not_to be_nil
43
+ expect(instance.vars['y']).not_to be_nil
44
+ end
45
+
46
+ it 'should know its goal' do
47
+ # Single goal at initialization
48
+ expect(subject.goal).to eq(sample_goal)
49
+
50
+ # Multiple goals at initialization
51
+ instance = FreshEnv.new(['q'], [pea_goal, goal_succeeds])
52
+ expect(instance.goal.relation.name).to eq('conj2')
53
+ expect(instance.goal.actuals[0]).to eq(pea_goal)
54
+ expect(instance.goal.actuals[1]).to eq(goal_succeeds)
29
55
  end
30
56
  end # context
31
57
 
@@ -21,16 +21,26 @@ module MiniKraken
21
21
  let(:pea) { k_symbol(:pea) }
22
22
  let(:pod) { k_symbol(:pod) }
23
23
  let(:sample_goal) { equals_goal(pea, pod) }
24
+ let(:fails) { Core::Goal.new(Core::Fail.instance, []) }
25
+ let(:succeeds) { Core::Goal.new(Core::Succeed.instance, []) }
24
26
  subject { RunStarExpression.new('q', sample_goal) }
25
27
 
26
28
  context 'Initialization:' do
27
- it 'should be initialized with a name and a goal' do
29
+ it 'could be initialized with a name and a goal' do
28
30
  expect { RunStarExpression.new('q', sample_goal) }.not_to raise_error
29
31
  end
30
32
 
33
+ it 'could be initialized with multiple names and a goal' do
34
+ expect { RunStarExpression.new(%w[r x y], sample_goal) }.not_to raise_error
35
+ end
36
+
37
+ it 'could be initialized with multiple names and goals' do
38
+ expect { RunStarExpression.new(%w[r x y], [succeeds, succeeds]) }.not_to raise_error
39
+ end
40
+
31
41
  it 'should know its variables' do
32
42
  expect(subject.env.vars['q']).not_to be_nil
33
- expect(subject.var.name).to eq('q')
43
+ expect(subject.env.vars.values[0].name).to eq('q')
34
44
  end
35
45
 
36
46
  it 'should know its goal' do
@@ -39,19 +49,22 @@ module MiniKraken
39
49
  end # context
40
50
 
41
51
  context 'Provided services:' do
52
+ let(:bean) { k_symbol(:bean) }
42
53
  let(:corn) { k_symbol(:corn) }
43
54
  let(:meal) { k_symbol(:meal) }
44
55
  let(:oil) { k_symbol(:oil) }
45
56
  let(:olive) { k_symbol(:olive) }
57
+ let(:red) { k_symbol(:red) }
58
+ let(:soup) { k_symbol(:soup) }
59
+ let(:split) { k_symbol(:split) }
46
60
  let(:virgin) { k_symbol(:virgin) }
47
61
  let(:ref_q) { Core::VariableRef.new('q') }
62
+ let(:ref_r) { Core::VariableRef.new('r') }
48
63
  let(:ref_x) { Core::VariableRef.new('x') }
49
64
  let(:ref_y) { Core::VariableRef.new('y') }
50
65
  let(:ref_s) { Core::VariableRef.new('s') }
51
66
  let(:ref_t) { Core::VariableRef.new('t') }
52
67
  let(:ref_u) { Core::VariableRef.new('u') }
53
- let(:fails) { Core::Goal.new(Core::Fail.instance, []) }
54
- let(:succeeds) { Core::Goal.new(Core::Succeed.instance, []) }
55
68
 
56
69
  it 'should return a null list with the fail goal' do
57
70
  # Reasoned S2, frame 1:7
@@ -386,7 +399,7 @@ module MiniKraken
386
399
  instance = RunStarExpression.new('q', goal)
387
400
 
388
401
  # Reasoned S2, frame 1:56
389
- # (run* q (disj2 (equals 'olive q) fail)) ;; => ('olive)
402
+ # (run* q (disj2 (== 'olive q) fail)) ;; => ('olive)
390
403
  result = instance.run
391
404
  expect(result.car).to eq(olive)
392
405
  end
@@ -397,7 +410,7 @@ module MiniKraken
397
410
  instance = RunStarExpression.new('q', goal)
398
411
 
399
412
  # Reasoned S2, frame 1:57
400
- # (run* q (disj2 fail (equals 'oil q)) ;; => (oil)
413
+ # (run* q (disj2 fail (== 'oil q))) ;; => (oil)
401
414
  result = instance.run
402
415
  expect(result.car).to eq(oil)
403
416
  end
@@ -409,7 +422,7 @@ module MiniKraken
409
422
  instance = RunStarExpression.new('q', goal)
410
423
 
411
424
  # Reasoned S2, frame 1:58
412
- # (run* q (disj2 (equals 'olive q) (equals 'oil q)) ;; => (olive oil)
425
+ # (run* q (disj2 (== 'olive q) (== 'oil q))) ;; => (olive oil)
413
426
  result = instance.run
414
427
  expect(result.car).to eq(olive)
415
428
  expect(result.cdr.car).to eq(oil)
@@ -504,6 +517,151 @@ module MiniKraken
504
517
  expect(result.cdr.car).to eq(any_value(0))
505
518
  expect(result.cdr.cdr.car).to eq(oil)
506
519
  end
520
+
521
+ it 'should accept nesting fresh, disj2 and conj2 expressions (I)' do
522
+ subgoal1 = equals_goal(split, ref_x)
523
+ expr1 = equals_goal(pea, ref_y)
524
+ expr2 = equals_goal(cons(ref_x, cons(ref_y)), ref_r)
525
+ subgoal2 = conj2_goal(expr1, expr2)
526
+ goal = conj2_goal(subgoal1, subgoal2)
527
+ fresh_env_y = FreshEnv.new(['y'], goal)
528
+ fresh_env_x = FreshEnv.new(['x'], fresh_env_y)
529
+ instance = RunStarExpression.new('r', fresh_env_x)
530
+
531
+ # Reasoned S2, frame 1:67
532
+ # (run* r
533
+ # (fresh x
534
+ # (fresh y
535
+ # (conj2
536
+ # (== 'split x)
537
+ # (conj2
538
+ # (== 'pea y)
539
+ # (== '(,x ,y) r)))))) ;; => ((split pea))
540
+ result = instance.run
541
+ expect(result.car.car).to eq(split)
542
+ expect(result.car.cdr.car).to eq(pea)
543
+ end
544
+
545
+ it 'should accept nesting fresh, disj2 and conj2 expressions (II)' do
546
+ expr1 = equals_goal(split, ref_x)
547
+ expr2 = equals_goal(pea, ref_y)
548
+ subgoal1 = conj2_goal(expr1, expr2)
549
+ subgoal2 = equals_goal(cons(ref_x, cons(ref_y)), ref_r)
550
+ goal = conj2_goal(subgoal1, subgoal2)
551
+ fresh_env_y = FreshEnv.new(['y'], goal)
552
+ fresh_env_x = FreshEnv.new(['x'], fresh_env_y)
553
+ instance = RunStarExpression.new('r', fresh_env_x)
554
+
555
+ # Reasoned S2, frame 1:68
556
+ # (run* r
557
+ # (fresh x
558
+ # (fresh y
559
+ # (conj2
560
+ # (conj2
561
+ # (== 'split x)
562
+ # (== 'pea y)
563
+ # (== '(,x ,y) r)))))) ;; => ((split pea))
564
+ result = instance.run
565
+ expect(result.car.car).to eq(split)
566
+ expect(result.car.cdr.car).to eq(pea)
567
+ end
568
+
569
+ it 'should accept fresh with multiple variables' do
570
+ expr1 = equals_goal(split, ref_x)
571
+ expr2 = equals_goal(pea, ref_y)
572
+ subgoal1 = conj2_goal(expr1, expr2)
573
+ subgoal2 = equals_goal(cons(ref_x, cons(ref_y)), ref_r)
574
+ goal = conj2_goal(subgoal1, subgoal2)
575
+ fresh_env = FreshEnv.new(%w[x y], goal)
576
+ instance = RunStarExpression.new('r', fresh_env)
577
+
578
+ # Reasoned S2, frame 1:70
579
+ # (run* r
580
+ # (fresh (x y)
581
+ # (conj2
582
+ # (conj2
583
+ # (== 'split x)
584
+ # (== 'pea y)
585
+ # (== '(,x ,y) r))))) ;; => ((split pea))
586
+ result = instance.run
587
+ expect(result.car.car).to eq(split)
588
+ expect(result.car.cdr.car).to eq(pea)
589
+ end
590
+
591
+ it 'should accept multiple variables' do
592
+ expr1 = equals_goal(split, ref_x)
593
+ expr2 = equals_goal(pea, ref_y)
594
+ subgoal1 = conj2_goal(expr1, expr2)
595
+ subgoal2 = equals_goal(cons(ref_x, cons(ref_y)), ref_r)
596
+ goal = conj2_goal(subgoal1, subgoal2)
597
+ instance = RunStarExpression.new(%w[r x y], goal)
598
+
599
+ # Reasoned S2, frame 1:72
600
+ # (run* (r x y)
601
+ # (conj2
602
+ # (conj2
603
+ # (== 'split x)
604
+ # (== 'pea y))
605
+ # (== '(,x ,y) r))) ;; => (((split pea) split pea))
606
+ # o
607
+ # / \
608
+ # o nil
609
+ # / \
610
+ # / \
611
+ # / \
612
+ # / \
613
+ # / \
614
+ # o o
615
+ # / \ / \
616
+ # split o split o
617
+ # / \ / \
618
+ # pea nil pea nil
619
+ result = instance.run
620
+ expect(result.car.car.car).to eq(split)
621
+ expect(result.car.car.cdr.car).to eq(pea)
622
+ expect(result.car.car.cdr.cdr).to be_nil
623
+ expect(result.car.cdr.car).to eq(split)
624
+ expect(result.car.cdr.cdr.car).to eq(pea)
625
+ expect(result.car.cdr.cdr.cdr).to be_nil
626
+ end
627
+
628
+ it 'should allow simplication of expressions' do
629
+ expr1 = equals_goal(split, ref_x)
630
+ expr2 = equals_goal(pea, ref_y)
631
+ goal = conj2_goal(expr1, expr2)
632
+ instance = RunStarExpression.new(%w[x y], goal)
633
+
634
+ # Reasoned S2, frame 1:75
635
+ # (run* (x y)
636
+ # (conj2
637
+ # (== 'split x)
638
+ # (== 'pea y))) ;; => ((split pea))
639
+ result = instance.run
640
+ expect(result.car.car).to eq(split)
641
+ expect(result.car.cdr.car).to eq(pea)
642
+ end
643
+
644
+ it 'should allow simplication of expressions' do
645
+ expr1 = equals_goal(split, ref_x)
646
+ expr2 = equals_goal(pea, ref_y)
647
+ subgoal1 = conj2_goal(expr1, expr2)
648
+ expr3 = equals_goal(red, ref_x)
649
+ expr4 = equals_goal(bean, ref_y)
650
+ subgoal2 = conj2_goal(expr3, expr4)
651
+ goal = disj2_goal(subgoal1, subgoal2)
652
+ instance = RunStarExpression.new(%w[x y], goal)
653
+
654
+ # Reasoned S2, frame 1:76
655
+ # (run* (x y)
656
+ # (disj2
657
+ # (conj2 (== 'split x) (== 'pea y))
658
+ # (conj2 (== 'red x) (== 'bean y)))) ;; => ((split pea)(red bean))
659
+ result = instance.run
660
+ expect(result.car.car).to eq(split)
661
+ expect(result.car.cdr.car).to eq(pea)
662
+ expect(result.cdr.car.car).to eq(red)
663
+ expect(result.cdr.car.cdr.car).to eq(bean)
664
+ end
507
665
  end # context
508
666
  end # describe
509
667
  end # module
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.08
4
+ version: 0.1.09
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-30 00:00:00.000000000 Z
11
+ date: 2020-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler