predicator 0.4.0 → 1.0.0

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +3 -2
  4. data/HISTORY.md +9 -0
  5. data/README.md +7 -8
  6. data/Rakefile +14 -5
  7. data/lib/predicator.rb +18 -10
  8. data/lib/predicator/ast.rb +138 -0
  9. data/lib/predicator/context.rb +7 -63
  10. data/lib/predicator/evaluator.rb +108 -0
  11. data/lib/predicator/lexer.rex +51 -0
  12. data/lib/predicator/lexer.rex.rb +160 -0
  13. data/lib/predicator/parser.rb +291 -7
  14. data/lib/predicator/parser.y +66 -40
  15. data/lib/predicator/version.rb +1 -1
  16. data/lib/predicator/visitors.rb +5 -0
  17. data/lib/predicator/visitors/dot.rb +100 -0
  18. data/lib/predicator/visitors/each.rb +16 -0
  19. data/lib/predicator/visitors/instructions.rb +117 -0
  20. data/lib/predicator/visitors/string.rb +60 -0
  21. data/lib/predicator/visitors/visitor.rb +48 -0
  22. data/predicator.gemspec +3 -2
  23. metadata +29 -32
  24. data/lib/predicator/errors.rb +0 -5
  25. data/lib/predicator/generated_parser.rb +0 -335
  26. data/lib/predicator/lexer.rb +0 -125
  27. data/lib/predicator/nodes.rb +0 -6
  28. data/lib/predicator/nodes/base_node.rb +0 -53
  29. data/lib/predicator/nodes/date_node.rb +0 -13
  30. data/lib/predicator/nodes/fixnum_node.rb +0 -9
  31. data/lib/predicator/nodes/float_node.rb +0 -9
  32. data/lib/predicator/nodes/nil_class_node.rb +0 -25
  33. data/lib/predicator/nodes/string_node.rb +0 -13
  34. data/lib/predicator/predicates.rb +0 -14
  35. data/lib/predicator/predicates/and.rb +0 -20
  36. data/lib/predicator/predicates/between.rb +0 -31
  37. data/lib/predicator/predicates/equal.rb +0 -9
  38. data/lib/predicator/predicates/false.rb +0 -13
  39. data/lib/predicator/predicates/greater_than.rb +0 -9
  40. data/lib/predicator/predicates/greater_than_or_equal.rb +0 -9
  41. data/lib/predicator/predicates/less_than.rb +0 -9
  42. data/lib/predicator/predicates/less_than_or_equal.rb +0 -9
  43. data/lib/predicator/predicates/method.rb +0 -17
  44. data/lib/predicator/predicates/not.rb +0 -20
  45. data/lib/predicator/predicates/not_equal.rb +0 -9
  46. data/lib/predicator/predicates/or.rb +0 -20
  47. data/lib/predicator/predicates/relation.rb +0 -31
  48. data/lib/predicator/predicates/true.rb +0 -13
  49. data/lib/predicator/variable.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cccb4a93e1358c39913a03aff288de916bf3fe43
4
- data.tar.gz: e28bf0d18f7b94d27c94baaec203eb99a1457ed7
3
+ metadata.gz: ac9999a26155d5318cda41ac8d119352e63aa0b5
4
+ data.tar.gz: 3c1411fe73bae5d3ca3ee30604856b975d9027c3
5
5
  SHA512:
6
- metadata.gz: a6e74e9fae07264c552612a5fcd6c804b6f0deaf42b6a81012fd1e48900ee923e7b21ebd2e36a6b828bc6942358c1fada37ab25436a5d6dc090db89ee22d3d0b
7
- data.tar.gz: 394b6bd004026f79cd6aed9bada7fefa863df8a44a1f2b64056c53f455b94106da9a5cc4fb54741f9894e848f763f7f4d30b4a7554c68addeb23ecaf3efa34b1
6
+ metadata.gz: 2d5156cbdd62b081816d8d4035a5de20f410bddc9bc57540054de0e433ce61510d80ce694c452e1b4a3f219c7fc9ce8edea38e73db3d763a7f37587fb1b5d7eb
7
+ data.tar.gz: 25e56d9f070f10da1e956922072fe6f3ab086b4f16a99a77c281dd6e1c53028f5230a3247f2d473df54b844d916e0758f9be46ba1e2f3761ceb8f7e9b8646aca
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /test/reports/
9
9
  /tmp/
10
+ .ruby-gemset
11
+ .ruby-version
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- before_install: gem install bundler
3
+ - 2.0
4
+ - 2.1
5
+ - 2.2
data/HISTORY.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### 1.0.0 / 2017-09-22
2
+
3
+ * Adds new lexer
4
+ * Introduces the stack machine and instructions
5
+ * Adds LessThan predicate
6
+ * Adds Between predicate
7
+ * Adds In and Not In predicates (with arrays)
8
+ * Adds BooleanVariable predicate
9
+
1
10
  ### 0.4.0 / 2016-06-09
2
11
 
3
12
  * Adds double dispatch for relation predicates
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/predicator.svg)](http://badge.fury.io/rb/predicator)
2
- [![Build Status](https://travis-ci.org/johnnyt/predicator.svg?branch=master)](https://travis-ci.org/johnnyt/predicator)
2
+ [![Build Status](https://travis-ci.org/predicator/predicator.svg?branch=master)](https://travis-ci.org/predicator/predicator)
3
3
  [![Dependency Status](https://img.shields.io/gemnasium/johnnyt/predicator.svg)](https://gemnasium.com/johnnyt/predicator)
4
- [![Coverage Status](https://coveralls.io/repos/github/johnnyt/predicator/badge.svg?branch=master)](https://coveralls.io/github/johnnyt/predicator?branch=master)
4
+ [![Coverage Status](https://coveralls.io/repos/github/predicator/predicator/badge.svg?branch=master)](https://coveralls.io/github/predicator/predicator?branch=master)
5
5
 
6
6
  # Predicator
7
7
 
8
- Predicator is a predicate engine
8
+ Predicator is a predicate engine.
9
9
 
10
10
  ## Usage
11
11
 
@@ -14,12 +14,11 @@ Example usage:
14
14
  ```ruby
15
15
  require "predicator"
16
16
 
17
- pred = Predicator.parse "a.b = 5"
17
+ Predicator.evaluate "age > 21" # false
18
18
 
19
- context = Predicator::Context.new
20
- context[:a] = {b:5}
19
+ Predicator.evaluate "age > 21", age: 10 # false
21
20
 
22
- pred.satisfied? context
21
+ Predicator.evaluate "age > 21", age: 50 # true
23
22
  ```
24
23
 
25
24
  ## Installation
@@ -48,7 +47,7 @@ To release a new version, update the version number in `version.rb`, and then ru
48
47
 
49
48
  ## Contributing
50
49
 
51
- Bug reports and pull requests are welcome on GitHub at https://github.com/johnnyt/predicator.
50
+ Bug reports and pull requests are welcome on GitHub at https://github.com/predicator/predicator.
52
51
  This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
53
52
 
54
53
 
data/Rakefile CHANGED
@@ -1,15 +1,24 @@
1
+ require "rubygems"
1
2
  require "bundler/gem_tasks"
2
3
  require "rake/testtask"
4
+ require "oedipus_lex"
5
+
6
+ Rake.application.rake_require "oedipus_lex"
3
7
 
4
8
  Rake::TestTask.new :test do |t|
5
9
  t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
10
+ t.test_files = FileList["test/**/test_*.rb"]
11
+ t.warning = false
8
12
  end
9
13
 
10
- task :default => :test
14
+ desc "Generate the lexer"
15
+ task lexer: "lib/predicator/lexer.rex.rb"
11
16
 
12
17
  desc "Compile and generate the parser"
13
- task :compile do
14
- sh "racc lib/predicator/parser.y -o lib/predicator/generated_parser.rb"
18
+ task parser: :lexer do
19
+ sh "racc -l -o lib/predicator/parser.rb lib/predicator/parser.y"
15
20
  end
21
+
22
+ task test: :parser
23
+
24
+ task default: :test
data/lib/predicator.rb CHANGED
@@ -1,16 +1,24 @@
1
- require "date"
2
-
3
1
  require "predicator/context"
4
- require "predicator/errors"
5
- require "predicator/lexer"
6
- require "predicator/nodes"
2
+ require "predicator/evaluator"
7
3
  require "predicator/parser"
8
- require "predicator/predicates"
9
- require "predicator/variable"
10
- require "predicator/version"
11
4
 
12
5
  module Predicator
13
- def self.parse string
14
- Predicator::Parser.new.parse string
6
+ def self.parse source
7
+ Predicator::Parser.new.parse source
8
+ end
9
+
10
+ def self.compile source
11
+ ast = parse source
12
+ ast.to_instructions
13
+ end
14
+
15
+ def self.evaluate source, context={}
16
+ instructions = compile source
17
+ evaluate_instructions instructions, context
18
+ end
19
+
20
+ def self.evaluate_instructions instructions, context={}
21
+ evaluator = Evaluator.new instructions, context
22
+ evaluator.result
15
23
  end
16
24
  end
@@ -0,0 +1,138 @@
1
+ module Predicator
2
+ module AST
3
+ class Node
4
+ include Enumerable
5
+
6
+ attr_accessor :left
7
+
8
+ def initialize left
9
+ @left = left
10
+ end
11
+
12
+ def each &block
13
+ Visitors::Each.new(block).accept self
14
+ end
15
+
16
+ def to_dot
17
+ Visitors::Dot.new.accept self
18
+ end
19
+
20
+ def to_instructions
21
+ Visitors::Instructions.new.accept self
22
+ end
23
+
24
+ def to_predicate
25
+ Visitors::Predicate.new.accept self
26
+ end
27
+
28
+ def to_s
29
+ Visitors::String.new.accept self
30
+ end
31
+
32
+ def type
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def variable?; false; end
37
+ def literal?; false; end
38
+ end
39
+
40
+ class Terminal < Node
41
+ alias :symbol :left
42
+ end
43
+
44
+ class Literal < Terminal
45
+ def type; :LITERAL; end
46
+ def literal?; true; end
47
+ end
48
+
49
+ class Variable < Terminal
50
+ def type; :VARIABLE; end
51
+ def variable?; true; end
52
+ end
53
+
54
+ %w[ True False Integer String ].each do |t|
55
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
56
+ class #{t} < Literal;
57
+ def type; :#{t.upcase}; end
58
+ end
59
+ eoruby
60
+ end
61
+
62
+ class Unary < Node
63
+ def children; [left] end
64
+ end
65
+
66
+ class Array < Unary
67
+ def type; :ARRAY; end
68
+ end
69
+
70
+ class Not < Unary
71
+ def type; :NOT; end
72
+ end
73
+
74
+ class Group < Unary
75
+ def type; :GROUP; end
76
+ end
77
+
78
+ class Binary < Node
79
+ attr_accessor :right
80
+
81
+ def initialize left, right
82
+ super left
83
+ @right = right
84
+ end
85
+
86
+ def children; [left, right] end
87
+ end
88
+
89
+ class Equal < Binary
90
+ def type; :EQ; end
91
+ end
92
+
93
+ class GreaterThan < Binary
94
+ def type; :GT; end
95
+ end
96
+
97
+ class LessThan < Binary
98
+ def type; :LT; end
99
+ end
100
+
101
+ class In < Binary
102
+ def type; :IN; end
103
+ end
104
+
105
+ class NotIn < Binary
106
+ def type; :NOTIN; end
107
+ end
108
+
109
+ class And < Binary
110
+ def type; :AND; end
111
+ end
112
+
113
+ class Or < Binary
114
+ def type; :OR; end
115
+ end
116
+
117
+ class Ternary < Node
118
+ attr_accessor :middle, :right
119
+
120
+ def initialize left, middle, right
121
+ super left
122
+ @middle = middle
123
+ @right = right
124
+ end
125
+
126
+ def children; [left, middle, right] end
127
+ end
128
+
129
+ class Between < Ternary
130
+ def type; :BETWEEN; end
131
+ end
132
+
133
+ class BooleanVariable < Unary
134
+ def type; :BOOL; end
135
+ end
136
+
137
+ end
138
+ end
@@ -1,74 +1,18 @@
1
- require "ostruct"
2
-
3
1
  module Predicator
4
2
  class Context
5
- attr_reader :bindings
6
-
7
- def initialize attrs={}
3
+ def initialize params={}
8
4
  @bindings = {}
9
- attrs.each{ |key, val| bind key, val }
5
+ params.each{ |key,value| bind key, value }
10
6
  end
11
7
 
12
- def bind name, obj
13
- if obj.kind_of? Hash
14
- obj = OpenStruct.new obj
15
- end
16
- bindings[name.to_s] = obj
8
+ def bind name, value
9
+ @bindings[name.to_s] = value
17
10
  end
18
11
  alias :[]= :bind
19
12
 
20
- def node_for input
21
- value = value_for input
22
- node_class = Predicator::Nodes::BaseNode.class_for value
23
- node_class.new value
24
- end
25
-
26
- def value_for input
27
- if input.kind_of? Predicator::Variable
28
- input.value_in self
29
- else
30
- input
31
- end
32
- end
33
- alias :[] :value_for
34
-
35
- def respond_to? method
36
- bindings.key?(method.to_s) || super
37
- end
38
-
39
- def method_missing method, *args, &block
40
- found_binding = bindings.fetch method.to_s, nil
41
- if found_binding.nil?
42
- super
43
- else
44
- found_binding
45
- end
46
- end
47
-
48
- def eval string
49
- string.gsub(/{{([^}]+)}}/) do |match|
50
- name, attribute = $1.strip.split "."
51
- variable = Predicator::Variable.new name, attribute
52
- value_for variable
53
- end
54
- end
55
-
56
- def to_hash
57
- hsh = {}
58
- bindings.each do |key, value|
59
- hsh[key] = hash_value_for value
60
- end
61
- hsh
62
- end
63
-
64
- private
65
-
66
- def hash_value_for value
67
- if value.kind_of? OpenStruct
68
- value.to_h
69
- else
70
- value.to_hash
71
- end
13
+ def binding_for name
14
+ @bindings[name.to_s]
72
15
  end
16
+ alias :[] :binding_for
73
17
  end
74
18
  end
@@ -0,0 +1,108 @@
1
+ module Predicator
2
+ class Evaluator
3
+ attr_reader :instructions, :stack, :context
4
+
5
+ def initialize instructions, context_data={}
6
+ @instructions = instructions
7
+ @context = context_for context_data
8
+ @stack = []
9
+ @ip = 0
10
+ end
11
+
12
+ def context_for context_data
13
+ return context_data unless context_data.kind_of? Hash
14
+ Context.new context_data
15
+ end
16
+
17
+ def result
18
+ while @ip < instructions.length
19
+ process @instructions[@ip]
20
+ @ip += 1
21
+ end
22
+ stack.pop
23
+ end
24
+
25
+ def process instruction
26
+ case instruction.first
27
+ when "not"
28
+ stack.push !stack.pop
29
+ when "jfalse"
30
+ jump_if_false instruction.last
31
+ when "jtrue"
32
+ jump_if_true instruction.last
33
+ when "lit", "array"
34
+ stack.push instruction.last
35
+ when "load"
36
+ stack.push context[instruction.last]
37
+ when "to_bool"
38
+ stack.push !!stack.pop
39
+ when "compare"
40
+ if instruction.last == "BETWEEN"
41
+ compare_BETWEEN
42
+ else
43
+ compare instruction.last
44
+ end
45
+ end
46
+ end
47
+
48
+ def jump_if_false offset
49
+ if stack[-1] == false
50
+ adjusted_offset = offset - 1
51
+ @ip += adjusted_offset
52
+ else
53
+ stack.pop
54
+ end
55
+ end
56
+
57
+ def jump_if_true offset
58
+ if stack[-1] == true
59
+ adjusted_offset = offset - 1
60
+ @ip += adjusted_offset
61
+ else
62
+ stack.pop
63
+ end
64
+ end
65
+
66
+ def compare comparison
67
+ right = stack.pop
68
+ left = stack.pop
69
+ if left.nil? || right.nil?
70
+ stack.push false
71
+ else
72
+ stack.push send("compare_#{comparison}", left, right)
73
+ end
74
+ end
75
+
76
+ def compare_EQ left, right
77
+ left == right
78
+ end
79
+
80
+ def compare_GT left, right
81
+ left > right
82
+ end
83
+
84
+ def compare_LT left, right
85
+ left < right
86
+ end
87
+
88
+ def compare_IN left, right
89
+ right.include? left
90
+ end
91
+
92
+ def compare_NOTIN left, right
93
+ !right.include? left
94
+ end
95
+
96
+ def compare_BETWEEN
97
+ max = stack.pop
98
+ min = stack.pop
99
+ val = stack.pop
100
+ if max.nil? || min.nil? || val.nil?
101
+ stack.push false
102
+ else
103
+ result = val.between? min, max
104
+ stack.push result
105
+ end
106
+ end
107
+ end
108
+ end