mini_kraken 0.1.03 → 0.1.08

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/CHANGELOG.md +54 -3
  4. data/Gemfile +3 -1
  5. data/README.md +22 -1
  6. data/Rakefile +5 -3
  7. data/lib/mini_kraken.rb +3 -1
  8. data/lib/mini_kraken/core/any_value.rb +9 -7
  9. data/lib/mini_kraken/core/association.rb +20 -7
  10. data/lib/mini_kraken/core/association_walker.rb +5 -1
  11. data/lib/mini_kraken/core/atomic_term.rb +5 -3
  12. data/lib/mini_kraken/core/binary_relation.rb +8 -6
  13. data/lib/mini_kraken/core/composite_goal.rb +46 -0
  14. data/lib/mini_kraken/core/composite_term.rb +7 -20
  15. data/lib/mini_kraken/core/conj2.rb +77 -0
  16. data/lib/mini_kraken/core/cons_cell.rb +51 -41
  17. data/lib/mini_kraken/core/designation.rb +55 -0
  18. data/lib/mini_kraken/core/disj2.rb +71 -0
  19. data/lib/mini_kraken/core/duck_fiber.rb +4 -2
  20. data/lib/mini_kraken/core/environment.rb +25 -11
  21. data/lib/mini_kraken/core/equals.rb +128 -189
  22. data/lib/mini_kraken/core/fail.rb +20 -14
  23. data/lib/mini_kraken/core/freshness.rb +11 -8
  24. data/lib/mini_kraken/core/goal.rb +8 -4
  25. data/lib/mini_kraken/core/goal_arg.rb +10 -0
  26. data/lib/mini_kraken/core/goal_relation.rb +28 -0
  27. data/lib/mini_kraken/core/k_integer.rb +4 -3
  28. data/lib/mini_kraken/core/k_symbol.rb +4 -3
  29. data/lib/mini_kraken/core/nullary_relation.rb +3 -1
  30. data/lib/mini_kraken/core/outcome.rb +29 -25
  31. data/lib/mini_kraken/core/relation.rb +4 -18
  32. data/lib/mini_kraken/core/succeed.rb +20 -14
  33. data/lib/mini_kraken/core/term.rb +7 -2
  34. data/lib/mini_kraken/core/variable.rb +11 -25
  35. data/lib/mini_kraken/core/variable_ref.rb +12 -59
  36. data/lib/mini_kraken/core/vocabulary.rb +267 -48
  37. data/lib/mini_kraken/glue/fresh_env.rb +5 -3
  38. data/lib/mini_kraken/glue/run_star_expression.rb +18 -8
  39. data/lib/mini_kraken/version.rb +3 -1
  40. data/mini_kraken.gemspec +15 -13
  41. data/spec/core/association_spec.rb +4 -4
  42. data/spec/core/association_walker_spec.rb +25 -24
  43. data/spec/core/conj2_spec.rb +114 -0
  44. data/spec/core/cons_cell_spec.rb +12 -3
  45. data/spec/core/disj2_spec.rb +99 -0
  46. data/spec/core/duck_fiber_spec.rb +22 -12
  47. data/spec/core/environment_spec.rb +16 -28
  48. data/spec/core/equals_spec.rb +7 -7
  49. data/spec/core/fail_spec.rb +7 -7
  50. data/spec/core/goal_spec.rb +10 -10
  51. data/spec/core/k_symbol_spec.rb +5 -6
  52. data/spec/core/succeed_spec.rb +4 -4
  53. data/spec/core/variable_ref_spec.rb +0 -4
  54. data/spec/core/vocabulary_spec.rb +33 -27
  55. data/spec/glue/fresh_env_spec.rb +1 -1
  56. data/spec/glue/run_star_expression_spec.rb +213 -60
  57. data/spec/mini_kraken_spec.rb +4 -0
  58. data/spec/spec_helper.rb +3 -2
  59. data/spec/support/factory_methods.rb +20 -2
  60. metadata +12 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba23145a960593f3910547ace544235fcf3f9c9f5ee45b593e3aa465231e37fe
4
- data.tar.gz: f2e1e6371cb9873192fece615acc2a6bd9021c5e7f8b1865b4a108ab414d5e8e
3
+ metadata.gz: 32a6f92773457338343dffa1343906883ffdbafbf7f8fd9ace170062cc34d345
4
+ data.tar.gz: cb7d3e6b88f8bea952bea7f214eaf73af97857de6958f65592a15b7ee7ef2510
5
5
  SHA512:
6
- metadata.gz: 0d8c08206038cb1b1d0fe339ace8f5ee4bcb9a91afb10447b8bc15624253774fcfd8fd1e1c4b07faa687fc2f5071bd4e46db0bcc2323044f2d36222db9906e80
7
- data.tar.gz: 9f55752adc900a4cfa9aa82906aab0c513f3cf35918c7a50e170ce16201fad8dc471e2f364efaaa9f8db935f35b18bbb12e4af1ef4a74d297f03f54dfc92eda3
6
+ metadata.gz: db730590bcc99203f8983fe19b5e0d0b90c77819c07875bc34c100bc843c9b3e5e3e148833149d3264847a190cc9ce5bcf1fa31f74723b8c8e8006a56948d738
7
+ data.tar.gz: 4cb5cb8f327e7cb81028621ad67e36afecee8bc90beac1cd6449573b85092191d39d55dfbe2de9f52a5868a66bf4c8288a3223b41815528fb651eead36d3f4fa
@@ -3,5 +3,9 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.6.3
6
+ - 2.7.1
7
+ - 2.6.6
8
+ - 2.5.8
9
+ - 2.4.10
10
+ - jruby-head
7
11
  before_install: gem install bundler -v 2.0.2
@@ -1,6 +1,60 @@
1
+ ## [0.1.08] - 2020-05-30
2
+ - Fix of nasty bug (object aliasing) that caused flaky failures in specs.
3
+
4
+ ### FIXED
5
+ - `DuckFiber#resume` each call returns a distinct `Outcome` instance when successful.
6
+
7
+ ## [0.1.07] - 2020-05-23
8
+ - Implementation of `disj2` (two arguments disjunction - or -)
9
+
10
+ ### New
11
+ - Class `Disj2` as subclass of `GoalRelation` that implements the disjunction of two subgoals
12
+
13
+ ### CHANGED
14
+ - Class `Disj2`: common code with `Conj2` class factored out to superclass `GoalRelation`
15
+ - File `cons_cell.rb`: prevent multiple inclusions via different requires
16
+ - Method `Vocabulary#ancestor_walker` now returns an `Enumerator` instead of a `Fiber`.
17
+
18
+ ### FIXED
19
+ - Method `RunStarExpression#run` clear associations and rankings for second and consecutive solmutions
20
+
21
+
22
+ ## [0.1.06] - 2020-05-20
23
+ - Implementation of `conj2` (two arguments conjunction - and -)
24
+
25
+ ### New
26
+ - Class `CompositeGoal`
27
+ - Class `Conj2` as subclass of `GoalRelation` that implements the conjunction of two subgoals
28
+ - Mixin module `Designation` to factor out the common methods in `Variable` and `VariableRef` classes
29
+ - Class `GoalArg` abstract class, that is a generalization for anything that be be argument of a goal.
30
+ - Class `GoalRelation` as subclass of `Relation`. A goal that is linked to a such relation may have goals as its arguments only.
31
+
32
+ ### Changed
33
+ - Class `Goal` is new subclass of class `GoalArg`. Therefore a goal can be an argument to another goal.
34
+ - Class `Term` is new subclass of class `GoalArg`. Therefore a term can be an argument of a goal.
35
+ - Classes `Variable`, `VariableRef` now include mix-in module `Designation`
36
+ - File `cd_implementation.txt` Updated with changes of class relationship
37
+
38
+ ## [0.1.05] - 2020-05-09
39
+ - Changed implementation of fused variables
40
+ - Magic comments for frozen string literal
41
+ - Code re-styling to please Rubocop 0.82
42
+
43
+ ### Changed
44
+ - File `README.md` Added "What is mini_kraken" text.
45
+ - File `README.md` Added badges (CI Travis build status, Gem version, license)
46
+
47
+ ## [0.1.04] - 2020-05-02
48
+ ### Changed
49
+ - File `README.md` Added "What is mini_kraken" text.
50
+ - File `README.md` Added badges (CI Travis build status, Gem version, license)
51
+
1
52
  ## [0.1.03] - 2020-05-01
2
53
  Passes all frames 1:1 up to 1:47 of "Reasoned Schemer" book
3
54
 
55
+ ### Fixed
56
+ - Fresh variables are now correctly 'reified' according to the convention in "Reasoned Schemer".
57
+
4
58
  ## [0.1.02] - 2020-04-28
5
59
  Major code refactoring. Passes all frames 1:1 up to 1:36 of "Reasoned Schemer" book
6
60
 
@@ -11,9 +65,6 @@ First code commit
11
65
  - File `CHANGELOG.md`. This file. Adopting `keepachangelog.com` recommended format.
12
66
  - File `min_kraken.gemspec` Updated gem description.
13
67
 
14
- ### Changed
15
- - File `README.md` added badges (Appveyor build status, Gem version, license)
16
-
17
68
  ## [0.1.0] - 2020-02-05
18
69
  ### Added
19
70
  - Initial Github commit as new project
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in mini_kraken.gemspec
4
6
  gemspec
data/README.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # MiniKraken
2
-
2
+ [![Build Status](https://travis-ci.org/famished-tiger/mini_kraken.svg?branch=master)](https://travis-ci.org/famished-tiger/mini_kraken)
3
+ [![Gem Version](https://badge.fury.io/rb/mini_kraken.svg)](https://badge.fury.io/rb/mini_kraken)
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
+
6
+ ### What is __mini_kraken__ ?
7
+ An implemention of the [miniKanren](http://minikanren.org/) relational programming language in Ruby.
8
+ *miniKanren* is a small language for relational (logic) programming.
9
+ Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
10
+ Daniel P. Friedman, William E. Byrd, Oleg Kiselyov, and Jason Hemann: "The Reasoned Schemer", Second Edition,
11
+ ISBN: 9780262535519, (2018), MIT Press.
12
+
13
+ ### Features
14
+ - [X] ==
15
+ - [X] run\*
16
+ - [X] fresh
17
+ - [X] conj2
18
+ - [X] disj2
19
+
20
+ ### TODO
21
+ - [ ] defrel
22
+ - [ ] conde
23
+ - [ ] Occurs check
3
24
 
4
25
  ## Installation
5
26
 
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -1,4 +1,6 @@
1
- require "mini_kraken/version"
1
+ # frozen_string_literal: true
2
+
3
+ require 'mini_kraken/version'
2
4
 
3
5
  module MiniKraken
4
6
  class Error < StandardError; end
@@ -1,31 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniKraken
2
4
  module Core
3
5
  class AnyValue
4
6
  attr_reader :rank
5
-
7
+
6
8
  # @param aName [String]
7
9
  # @param anEnv [Vocabulary]
8
10
  def initialize(aName, anEnv, alternate_names = [])
9
11
  @rank = anEnv.get_rank(aName, alternate_names)
10
12
  end
11
-
13
+
12
14
  def ==(other)
13
15
  rank == other.rank
14
16
  end
15
-
17
+
16
18
  # Use same text representation as in Reasoned Schemer.
17
19
  def to_s
18
20
  "_#{rank}"
19
21
  end
20
-
22
+
21
23
  def ground?(_env)
22
24
  false
23
25
  end
24
-
26
+
25
27
  # @return [AnyValue]
26
28
  def quote(_env)
27
29
  self
28
- end
30
+ end
29
31
  end # class
30
32
  end # module
31
- end # module
33
+ end # module
@@ -1,21 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniKraken
2
4
  module Core
3
5
  # A record that a given vairable is associated with a value.
4
6
  class Association
5
- # @return [String] name of the variable beig association the value.
6
- attr_reader :var_name
7
-
7
+ # @return [String] internal name of variable being associated the value.
8
+ attr_accessor :i_name
9
+
8
10
  # @return [Term] the MiniKraken value associated with the variable
9
11
  attr_reader :value
10
-
11
-
12
+
13
+
12
14
  # @param aVariable [Variable, String] A variable or its name.
13
15
  # @param aValue [Term] value being associated to the variable.
14
16
  def initialize(aVariable, aValue)
15
- @var_name = aVariable.respond_to?(:name) ? aVariable.name : aVariable
17
+ a_name = aVariable.respond_to?(:name) ? aVariable.i_name : aVariable
18
+ @i_name = validated_name(a_name)
16
19
  @value = aValue
17
20
  end
18
-
21
+
22
+ private
23
+
24
+ def validated_name(aName)
25
+ raise StandardError, 'Name cannot be nil' if aName.nil?
26
+
27
+ cleaned = aName.strip
28
+ raise StandardError, 'Name cannot be empty or consists of spaces' if cleaned.empty?
29
+
30
+ cleaned
31
+ end
19
32
  end # class
20
33
  end # module
21
34
  end # module
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
  require_relative 'atomic_term'
3
5
  require_relative 'composite_term'
@@ -23,6 +25,7 @@ module MiniKraken
23
25
  def walk_assocs(assocs, anEnv)
24
26
  # Treat easy cases first...
25
27
  return nil if assocs.empty?
28
+
26
29
  assoc_atomic = assocs.find { |assc| assc.value.kind_of?(AtomicTerm) }
27
30
  return assoc_atomic.value if assoc_atomic
28
31
 
@@ -43,6 +46,7 @@ module MiniKraken
43
46
 
44
47
  def walk_value(aTerm, anEnv)
45
48
  return aTerm if aTerm.kind_of?(AtomicTerm) || aTerm.kind_of?(AnyValue)
49
+
46
50
  result = nil
47
51
 
48
52
  if aTerm.kind_of?(CompositeTerm)
@@ -176,4 +180,4 @@ module MiniKraken
176
180
  end
177
181
  end # class
178
182
  end # module
179
- end # module
183
+ end # module
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'term'
2
4
  require_relative 'freshness'
3
5
 
@@ -22,14 +24,14 @@ module MiniKraken
22
24
  def freshness(_env)
23
25
  Freshness.new(:ground, self)
24
26
  end
25
-
27
+
26
28
  # An atomic term is a ground term: by definition it doesn't contain
27
29
  # any fresh variable.
28
30
  # @param _env [Vocabulary]
29
31
  # @return [FalseClass]
30
32
  def fresh?(_env)
31
33
  false
32
- end
34
+ end
33
35
 
34
36
  # An atomic term is a ground term: by definition it doesn't contain
35
37
  # any fresh variable.
@@ -61,4 +63,4 @@ module MiniKraken
61
63
  end
62
64
  end # class
63
65
  end # module
64
- end # module
66
+ end # module
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'relation'
2
4
  require_relative 'composite_term'
3
5
 
@@ -5,20 +7,20 @@ module MiniKraken
5
7
  module Core
6
8
  class BinaryRelation < Relation
7
9
  # @param aName [String] Name of the relation.
8
- # @param alternateName [String, NilClass] Alternative name (optional).
10
+ # @param alternateName [String, NilClass] Alternative name (optional).
9
11
  def initialize(aName, alternateName = nil)
10
12
  super(aName, alternateName)
11
13
  freeze
12
14
  end
13
-
15
+
14
16
  # Number of arguments for the relation.
15
17
  # @return [Integer]
16
18
  def arity
17
19
  2
18
20
  end
19
-
21
+
20
22
  protected
21
-
23
+
22
24
  # table: Commute
23
25
  # |arg1 | arg2 | arg2.ground? || Commute |
24
26
  # | isa? Atomic | isa? Atomic | dont_care || Yes |
@@ -55,7 +57,7 @@ module MiniKraken
55
57
  else
56
58
  [arg1, arg2]
57
59
  end
58
- end
60
+ end
59
61
  end # class
60
62
  end # module
61
- end # module
63
+ end # module
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'term'
2
4
  require_relative 'freshness'
3
5
 
@@ -6,17 +8,18 @@ module MiniKraken
6
8
  # An composite term is an Minikraken term that can be
7
9
  # decomposed into simpler MiniKraken data value(s).
8
10
  class CompositeTerm < Term
9
-
11
+ # Abstract method (to override). Return the child terms.
12
+ # @return [Array<Term>]
10
13
  def children
11
14
  raise NotImplementedError, 'This method must re-defined in subclass(es).'
12
15
  end
13
16
 
14
17
  # A composite term is fresh when all its members are nil or all non-nil members
15
18
  # are all fresh
16
- # A composite term is bound when it is not fresh and not ground
19
+ # A composite term is bound when it is not fresh and not ground
17
20
  # A composite term is a ground term when all its non-nil members are ground.
18
21
  # @param _env [Vocabulary]
19
- # @return [Freshness]
22
+ # @return [Freshness]
20
23
  def freshness(_env)
21
24
  env.freshness_composite(self)
22
25
  end
@@ -33,22 +36,6 @@ module MiniKraken
33
36
  child.nil? || child.ground?(anEnv)
34
37
  end
35
38
  end
36
-
37
- # # Data equality testing
38
- # # @return [Boolean]
39
- # def ==(other)
40
- # if other.respond_to?(:value)
41
- # value == other.value
42
- # else
43
- # value == other
44
- # end
45
- # end
46
-
47
- # # Type and data equality testing
48
- # # @return [Boolean]
49
- # def eql?(other)
50
- # (self.class == other.class) && value.eql?(other.value)
51
- # end
52
39
  end # class
53
40
  end # module
54
- end # module
41
+ end # module
@@ -0,0 +1,77 @@
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
+ unless MiniKraken::Core.constants(false).include? :Conj2
10
+ module MiniKraken
11
+ module Core
12
+ # The conjunction is a relation that accepts only goal(s) as its two
13
+ # arguments. It succeeds if and only both its goal arguments succeeds.
14
+ class Conj2 < GoalRelation
15
+ include Singleton
16
+
17
+ def initialize
18
+ super('conj2', nil)
19
+ end
20
+
21
+ # @param actuals [Array<Term>] A two-elements array
22
+ # @param anEnv [Vocabulary] A vocabulary object
23
+ # @return [Fiber<Outcome>] A Fiber that yields Outcomes objects
24
+ def solver_for(actuals, anEnv)
25
+ g1, g2 = *validated_args(actuals)
26
+ Fiber.new { conjunction(g1, g2, anEnv) }
27
+ end
28
+
29
+ # Yields [Outcome, NilClass] result of the conjunction
30
+ # @param g1 [Goal] First goal argument
31
+ # @param g2 [Goal] Second goal argument
32
+ # @param voc [Vocabulary] A vocabulary object
33
+ def conjunction(g1, g2, voc)
34
+ # require 'debug'
35
+ outcome1 = nil
36
+ outcome2 = nil
37
+ if g1.relation.kind_of?(Fail) || g2.relation.kind_of?(Fail)
38
+ Fiber.yield Outcome.new(:"#u", voc)
39
+ else
40
+ f1 = g1.attain(voc)
41
+ loop do
42
+ outcome1 = f1.resume
43
+ break unless outcome1
44
+
45
+ outcome1.parent = voc unless outcome1.parent
46
+ if outcome1.successful?
47
+ f2 = g2.attain(outcome1)
48
+ loop do
49
+ outcome2 = f2.resume
50
+ break unless outcome2
51
+
52
+ outcome2.parent = voc unless outcome2.parent
53
+ if outcome2.successful?
54
+ res = Outcome.new(:"#s", voc)
55
+ res.merge(outcome1)
56
+ res.merge(outcome2)
57
+ Fiber.yield res
58
+ else
59
+ Fiber.yield outcome2
60
+ end
61
+ outcome2.clear
62
+ end
63
+ else
64
+ Fiber.yield outcome1
65
+ end
66
+ voc.clear if outcome1&.successful? && outcome2&.successful?
67
+ end
68
+ end
69
+
70
+ Fiber.yield nil
71
+ end
72
+ end # class
73
+
74
+ Conj2.instance.freeze
75
+ end # module
76
+ end # module
77
+ end # unless