clips 0 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +2 -1
  3. data/README.md +42 -12
  4. data/Rakefile +19 -2
  5. data/clips.gemspec +17 -16
  6. data/lib/clips.rb +119 -3
  7. data/lib/clips/rule.rb +30 -0
  8. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d2d574cceb27423714c8425c620c823b64e9f9ac
4
- data.tar.gz: bdda1bd12c1ba6d79d4df4fe8202590b2737de8e
2
+ SHA256:
3
+ metadata.gz: 3d39db49bfb6f9123fa8c21e90eb2fb6b898e24cffb22e95bbdcb3245314e9f6
4
+ data.tar.gz: e8ecc9cfdc1d829d4c30491c0fcadf79e924d8e03ac07bab9c56bc7fcd72b3a4
5
5
  SHA512:
6
- metadata.gz: 493022213d08b3d21be2e859355e7cd22cc1c8625093187126d092030632fe306939d874e4bc9c75a4e392f32803e7b19bf559210623b36d5e8189400b49973f
7
- data.tar.gz: 97cb6aa55b88e7b4e14a1bd1ae2e246bc509f9934e0b896c1d4078f8eab0f5c8dd08ec5bed67580e298b8def3bd52ac88d5ab8eb6b5a6ee9915f9f8e4bd12f86
6
+ metadata.gz: 2a79b726cf9b0ef5be8dbefa531b61ebebb85427827a6ffa345ff56fcce9fb67197d631d785ae0ad31b616d2650d792a6ce96556e6f57da9df1c6b33bd393bbb
7
+ data.tar.gz: 671637f65a5c8a854f70be32769e8aa5985b60dc6845646a9230d5d47d54d9e99c67a26a691d0fdfbfb9cd8902d315118be682f9a6d88ffda8984c1a29f1a9ea
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --format documentation
2
2
  --color
3
- --require spec_helper
3
+ --require test_helper
4
+ -I test
data/README.md CHANGED
@@ -1,8 +1,46 @@
1
- # Clips
1
+ # CLIPS for Ruby
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/clips`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ This is a reimplemtation of the [CLIPS](http://www.clipsrules.net/) engine in Ruby, as well as a DSL for working with CLIPS rules.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ The inference engine portion of this implementation has been structured to resemble Ruby's standard Set class. While this doesn't mirror the syntax used by the CLIPS language, it does stay true to the underlying functionality of the engine (ie. no duplicate facts).
6
+
7
+ The C implementation of CLIPS includes a command line shell, however this implementation does not. There are a million ways to make a shell-like interface in Ruby, none of which are appropriate to be included in this gem.
8
+
9
+ ## Usage
10
+
11
+ ```ruby
12
+ require 'clips'
13
+
14
+ clips = CLIPS.new
15
+ clips.add 'rule1', CLIPS::Rule.new(:foo, :actions => :bar)
16
+ clips.add :rule2, CLIPS::Rule.new(:bar) { puts "Rule 2!" }
17
+ clips.add :foo
18
+
19
+ clips.run # => "Rule 2!"
20
+ clips.facts # => #<Set: {[:foo], [:bar]}>
21
+ ```
22
+
23
+ ## Facts and Rules
24
+
25
+ Facts are represented by Arrays, and Rules are instances of the Rule class. The first element of a fact must always be a Symbol. Rule names can be given as Symbols or Strings, and are converted to Symbols internally.
26
+
27
+ ## Default Facts
28
+
29
+ Named sets of default facts can be added to the engine. When `reset` is called, the default facts are asserted as facts.
30
+
31
+ ```ruby
32
+ clips.default_facts['my_defaults'] = Set[[:foo], [:bar]]
33
+ clips.facts # => #<Set: {}>
34
+ ...
35
+ clips.reset
36
+ clips.facts # => #<Set: {[:foo], [:bar]}>
37
+ ```
38
+
39
+ To remove a set of default facts, use the normal Hash `delete` method.
40
+
41
+ ```ruby
42
+ clips.default_facts.delete('my_defaults')
43
+ ```
6
44
 
7
45
  ## Installation
8
46
 
@@ -20,16 +58,8 @@ Or install it yourself as:
20
58
 
21
59
  $ gem install clips
22
60
 
23
- ## Usage
24
-
25
- TODO: Write usage instructions here
26
-
27
61
  ## Development
28
62
 
29
63
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
64
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
-
33
- ## Contributing
34
-
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/bfoz/clips.
65
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `clips.gemspec`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
data/Rakefile CHANGED
@@ -1,6 +1,23 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ RSpec::Core::RakeTask.new(:test).tap do |task|
5
+ # task.pattern = FileList['test/**{,/*/**}/*.rb'].exclude('test/**{,/*/**}/*helper.rb')
6
+ task.pattern = 'test/**{,/*/**}/*.rb'
7
+ end
5
8
 
6
- task :default => :spec
9
+ task :default => :test
10
+
11
+ task :temp do
12
+ require 'clips'
13
+ clips = CLIPS.new
14
+ # clips.add 'rule1', CLIPS::Rule.new(:foo, :actions => :bar)
15
+ clips.add :foo
16
+
17
+ # rule2 = CLIPS::Rule.new(:bar) do
18
+ # rule2 = CLIPS::Rule.new(:foo) { puts "Rule 2!" }
19
+ clips.add :rule2, CLIPS::Rule.new(:foo) { puts "Rule 2!" }
20
+
21
+ puts clips.run # => "Rule 2!"
22
+ p clips.facts # => #<Set: {[:foo], [:bar]}>
23
+ end
@@ -2,23 +2,24 @@ lib = File.expand_path("../lib", __FILE__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
- spec.name = "clips"
6
- spec.version = '0'
7
- spec.authors = ["Brandon Fosdick"]
8
- spec.email = ["bfoz@bfoz.net"]
5
+ spec.name = "clips"
6
+ spec.version = '0.1'
7
+ spec.authors = ["Brandon Fosdick"]
8
+ spec.email = ["bfoz@bfoz.net"]
9
9
 
10
- spec.summary = %q{The CLIPS engine reimplemented in Ruby}
11
- spec.description = %q{The C Language Integrated Production System (CLIPS), but in Ruby}
12
- spec.homepage = "http://github.com/bfoz/clips-ruby"
10
+ spec.summary = %q{The CLIPS engine reimplemented in Ruby}
11
+ spec.description = %q{The C Language Integrated Production System (CLIPS), but in Ruby}
12
+ spec.homepage = "http://github.com/bfoz/clips-ruby"
13
+ spec.license = "BSD"
13
14
 
14
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
15
- f.match(%r{^(test|spec|features)/})
16
- end
17
- spec.bindir = "bin"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "bin"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
20
21
 
21
- spec.add_development_dependency "bundler", "~> 1.16"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "rspec", "~> 3.0"
22
+ spec.add_development_dependency "bundler", "~> 1.16"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
24
25
  end
@@ -1,5 +1,121 @@
1
- require "clips/version"
1
+ require 'set'
2
2
 
3
- module Clips
4
- # Your code goes here...
3
+ require_relative 'clips/rule'
4
+
5
+ class CLIPS
6
+ # @return [Set] The currently activated Facts awaiting processing
7
+ attr_reader :activations
8
+
9
+ # @return [Set] The Rules waiting to be processed
10
+ attr_reader :agenda
11
+
12
+ # @return [Hash] The set of facts that will be instantiated on each reset
13
+ attr_reader :default_facts
14
+
15
+ # @return [Set] Just the Facts
16
+ attr_reader :facts
17
+
18
+ # @return [Hash] The set of Rules and their names
19
+ attr_reader :rules
20
+
21
+ def initialize
22
+ @activations = Set.new
23
+ @agenda = Set.new
24
+ @default_facts = Hash.new
25
+ @facts = Set.new
26
+ @rules = Hash.new
27
+ end
28
+
29
+ # @overload add(name, rule)
30
+ # Add the named {Rule}
31
+ # @param [String] name The name of the {Rule}
32
+ # @param [Rule] rule The {Rule} to add
33
+ # @return [CLIPS]
34
+ # @overload add(*fields)
35
+ # Add a fact using the given field values
36
+ # @return [CLIPS]
37
+ def add(*fields)
38
+ if (2==fields.length) and fields.last&.is_a?(Rule)
39
+ self.rules[fields.first.to_sym] ||= fields.last
40
+ else # Assume the item to be a Fact
41
+ fields[0] = fields.first.to_sym
42
+ # Add the new Fact to the activations list for processing during the next call to run()
43
+ self.activations.add(fields)
44
+ self.facts.add(fields)
45
+ fields
46
+ # else
47
+ # raise ArgumentError.new("Invalid fact")
48
+ end
49
+ self
50
+ end
51
+
52
+ # Remove all Facts and return self
53
+ # @return [CLIPS]
54
+ def clear
55
+ self.activations.clear
56
+ self.agenda.clear
57
+ self.default_facts.clear
58
+ self.facts.clear
59
+ self.rules.clear
60
+ self
61
+ end
62
+
63
+ # Remove the given fact
64
+ # @return [CLIPS]
65
+ def delete(*fact)
66
+ fact[0] = fact.first.to_sym
67
+ if self.facts.delete?(fact)
68
+ self.activations.delete(fact)
69
+ end
70
+ self
71
+ end
72
+
73
+ # Delete everything but the rules, then add the default facts
74
+ # @return [CLIPS] self
75
+ def reset
76
+ self.activations.clear
77
+ self.agenda.clear
78
+ self.facts.clear
79
+
80
+ self.default_facts.each do |name, fact_set|
81
+ fact_set.each {|fact| self.add(*fact)}
82
+ end
83
+
84
+ self
85
+ end
86
+
87
+ # @param [Integer] limit The maximum number of rules that will fire before the function returns. The limit is disabled if nil.
88
+ # @return [Integer] The number of rules that were fired
89
+ def run(limit=nil)
90
+ self.rules.each do |name, rule|
91
+ self.agenda.add(rule) if rule.patterns.empty?
92
+ end
93
+
94
+ rule_counter = 0
95
+ begin
96
+ # If all of a Rule's patterns match, and any of those patterns are on the activation list, then fire the Rule
97
+ self.rules.each do |name, rule|
98
+ activated = false
99
+ _all = rule.patterns.all? do |pattern|
100
+ activated = true if not activated and self.activations.include?(pattern)
101
+ self.facts.include?(pattern)
102
+ end
103
+ self.agenda.add(rule) if activated and _all
104
+ end
105
+
106
+ self.activations.clear # All activations have been processed, so clear the Set
107
+
108
+ # Fire all of the Rules on the agenda in salience-order
109
+ sorted_agenda = self.agenda.sort {|a,b| b.salience <=> a.salience}
110
+ self.agenda.clear
111
+ sorted_agenda.each do |rule|
112
+ rule.run!(self)
113
+ rule_counter += 1
114
+
115
+ break if rule_counter == limit
116
+ end
117
+ end until self.agenda.empty? and self.activations.empty?
118
+
119
+ rule_counter
120
+ end
5
121
  end
@@ -0,0 +1,30 @@
1
+ class CLIPS; end
2
+
3
+ class CLIPS::Rule
4
+ attr_reader :actions
5
+ attr_reader :block
6
+ attr_reader :patterns
7
+ attr_reader :salience
8
+
9
+ def initialize(*args, actions:[], patterns:[], salience:0, &block)
10
+ @actions = [actions].flatten(1)
11
+ @block = block
12
+ @patterns = patterns
13
+ @salience = salience
14
+
15
+ @patterns.push args unless args.empty?
16
+ end
17
+
18
+ # Assumes that the patterns have already been checked
19
+ # @param [CLIPS] environment The thing with all of the facts
20
+ # @param [Array] patterns The matching patterns that triggered this {Rule}
21
+ def run!(environment, *patterns)
22
+ if @block
23
+ @block.call(*patterns)
24
+ else
25
+ self.actions.each do |action|
26
+ environment.add(action)
27
+ end
28
+ end
29
+ end
30
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clips
3
3
  version: !ruby/object:Gem::Version
4
- version: '0'
4
+ version: '0.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Fosdick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-05 00:00:00.000000000 Z
11
+ date: 2018-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -69,8 +69,10 @@ files:
69
69
  - bin/setup
70
70
  - clips.gemspec
71
71
  - lib/clips.rb
72
+ - lib/clips/rule.rb
72
73
  homepage: http://github.com/bfoz/clips-ruby
73
- licenses: []
74
+ licenses:
75
+ - BSD
74
76
  metadata: {}
75
77
  post_install_message:
76
78
  rdoc_options: []
@@ -88,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
90
  version: '0'
89
91
  requirements: []
90
92
  rubyforge_project:
91
- rubygems_version: 2.6.14
93
+ rubygems_version: 2.7.3
92
94
  signing_key:
93
95
  specification_version: 4
94
96
  summary: The CLIPS engine reimplemented in Ruby