predicator 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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