simple_logic 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f6509f93fc93262c3a09463d3f0174a5608c1016
4
+ data.tar.gz: 5e5cd5ded451b4a9d5bb0416ab9925747fd1a3cb
5
+ SHA512:
6
+ metadata.gz: 3d71ad530e7da3f544fdc4643f2ef0d95ec319431976bebbf00ddd0883b411c4b06d0fa21fdf77cea5cc346a94c22cff7b26f731a280919b373b0056af3e1728
7
+ data.tar.gz: 7c5739d0001d4c00c604621d7579bd13bca266723545efb3a2b04bd4268ec252c5a0d9e0b732d4786ce656d49c8e643651f7190165e00fe1b51b4e7f6758cd5c
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
5
+ - jruby
6
+ - 2.0.0
7
+
8
+ script: 'bundle exec rake'
9
+
10
+ notifications:
11
+ email:
12
+ recipients:
13
+ - pete@schwamb.net
14
+ on_failure: change
15
+ on_success: never
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in simple_logic.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ guard 'rspec' do
2
+ # watch /lib/ files
3
+ watch(%r{^lib/(.+).rb$}) do |m|
4
+ "spec/#{m[1]}_spec.rb"
5
+ end
6
+
7
+ # watch /spec/ files
8
+ watch(%r{^spec/(.+).rb$}) do |m|
9
+ "spec/#{m[1]}.rb"
10
+ end
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Pete Schwamb
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # SimpleLogic
2
+
3
+ A ruby gem that parses and evaluates simple boolean statements specified as a string. Useful for evaluating user supplied input in your application without using ruby's eval().
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'simple_logic'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install simple_logic
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ context = { hungry: true, fridge_empty: true, restaurant_nearby: true}
23
+ in_luck = SimpleLogic.eval("hungry && !fridge_empty || restaurant_nearby", context)
24
+ # => true
25
+ ```
26
+
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ # Default directory to look in is `/specs`
5
+ # Run with `rake spec`
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = ['--color', '--format', 'nested']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,16 @@
1
+ module SimpleLogic
2
+ class ParseError < StandardError
3
+ attr_reader :offset
4
+ def initialize(offset)
5
+ @offset = offset
6
+ end
7
+ end
8
+
9
+ class UndefinedVariableError < StandardError
10
+ attr_reader :offset
11
+ def initialize(offset)
12
+ @offset = offset
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'treetop'
2
+
3
+ module SimpleLogic
4
+
5
+ class Parser
6
+ base_path = File.expand_path(File.dirname(__FILE__))
7
+ Treetop.load(File.join(base_path, 'simple_logic'))
8
+ @@parser = SimpleLogicParser.new
9
+
10
+ def self.parse(data)
11
+ # Pass the data over to the parser instance
12
+ tree = @@parser.parse(data)
13
+
14
+ # If the AST is nil then there was an error during parsing
15
+ # we need to report a simple error message to help the user
16
+ if(tree.nil?)
17
+ raise ParseError.new(@@parser.index), "Parse error at offset: #{@@parser.index}"
18
+ end
19
+
20
+ return tree
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,50 @@
1
+ module SimpleLogic
2
+ module PrecedenceTable
3
+ class Operator
4
+ attr_accessor :precedence, :associativity, :symbol
5
+ def initialize(precedence, associativity, symbol)
6
+ @precedence = precedence
7
+ @associativity = associativity
8
+ @symbol = symbol
9
+ end
10
+
11
+ def left_associative?
12
+ @associativity == :left
13
+ end
14
+
15
+ def apply(l, r)
16
+ if @symbol == "&&"
17
+ l && r
18
+ elsif @symbol == "||"
19
+ l || r
20
+ else
21
+ raise "Invalid operator: #{@symbol}"
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ def self.lookup(operator)
28
+ @operators[operator]
29
+ end
30
+
31
+ def self.op(associativity, *operators)
32
+ @precedence ||= 0
33
+ @operators ||= {}
34
+ operators.each do |operator|
35
+ @operators[operator] = Operator.new(@precedence, associativity, operator)
36
+ end
37
+ @precedence += 1
38
+ end
39
+
40
+ # operator precedence, low to high
41
+ op :left, '||'
42
+ op :left, '&&'
43
+ op :none, '==', '!='
44
+ op :left, '<', '<=', '>', '>='
45
+ op :left, '+', '-'
46
+ op :left, '*', '/'
47
+ op :right, '^'
48
+ end
49
+ end
50
+
@@ -0,0 +1,30 @@
1
+ grammar SimpleLogic
2
+ rule expression
3
+ infix_operation / primary
4
+ end
5
+
6
+ rule infix_operation
7
+ lhs:infix_operation_chain rhs:primary <InfixOperation>
8
+ end
9
+
10
+ rule infix_operation_chain
11
+ (primary space operator space)+ <InfixOperationChain>
12
+ end
13
+
14
+ rule space
15
+ " "*
16
+ end
17
+
18
+ rule operator
19
+ "&&" / "||"
20
+ end
21
+
22
+ rule primary
23
+ variable / "!" primary <NegationOperator> / '(' expression ')'
24
+ end
25
+
26
+ rule variable
27
+ name:[\w]+ <Variable>
28
+ end
29
+
30
+ end
@@ -0,0 +1,77 @@
1
+ module SimpleLogic
2
+ class Expression < Treetop::Runtime::SyntaxNode
3
+ end
4
+
5
+ class InfixOperation < Treetop::Runtime::SyntaxNode
6
+ def eval(context)
7
+ rpn(shunting_yard(values_and_operators(context)))
8
+ end
9
+
10
+ def list
11
+ lhs.list + [rhs]
12
+ end
13
+
14
+ def values_and_operators(context)
15
+ list.map do |node|
16
+ if node.instance_of?(Treetop::Runtime::SyntaxNode)
17
+ PrecedenceTable.lookup(node.text_value)
18
+ else
19
+ node.eval(context)
20
+ end
21
+ end
22
+ end
23
+
24
+ def shunting_yard(input)
25
+ [].tap do |rpn|
26
+ operator_stack = []
27
+ input.each do |object|
28
+ if object.instance_of?(PrecedenceTable::Operator)
29
+ op1 = object
30
+ rpn << operator_stack.pop while (op2 = operator_stack.last) && (op1.left_associative? ? op1.precedence <= op2.precedence : op1.precedence < op2.precedence)
31
+ operator_stack << op1
32
+ else
33
+ rpn << object
34
+ end
35
+ end
36
+ rpn << operator_stack.pop until operator_stack.empty?
37
+ end
38
+ end
39
+
40
+ def rpn(input)
41
+ results = []
42
+ input.each do |object|
43
+ if object.instance_of?(PrecedenceTable::Operator)
44
+ r, l = results.pop, results.pop
45
+ results << object.apply(l, r)
46
+ else
47
+ results << object
48
+ end
49
+ end
50
+ results.first
51
+ end
52
+ end
53
+
54
+ class InfixOperationChain < Treetop::Runtime::SyntaxNode
55
+ def list
56
+ elements.map {|e| [e.primary, e.operator] }.flatten
57
+ end
58
+ end
59
+
60
+ class Variable < Treetop::Runtime::SyntaxNode
61
+ def eval(context)
62
+ key = text_value.to_sym
63
+ if context.include?(key)
64
+ context[key]
65
+ else
66
+ raise UndefinedVariableError, "Undefined variable: \"#{text_value}\""
67
+ end
68
+ end
69
+ end
70
+
71
+ class NegationOperator < Treetop::Runtime::SyntaxNode
72
+ def eval(context)
73
+ ! elements[1].eval(context)
74
+ end
75
+ end
76
+ end
77
+
@@ -0,0 +1,3 @@
1
+ module SimpleLogic
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,14 @@
1
+ require "simple_logic/errors"
2
+ require "simple_logic/parser"
3
+ require "simple_logic/precedence_table"
4
+ require "simple_logic/version"
5
+ require "simple_logic/syntax_nodes"
6
+ require 'treetop'
7
+
8
+ module SimpleLogic
9
+
10
+ def self.eval(data, context)
11
+ Parser.parse(data).eval(context)
12
+ end
13
+
14
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'simple_logic/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "simple_logic"
8
+ spec.version = SimpleLogic::VERSION
9
+ spec.authors = ["Pete Schwamb"]
10
+ spec.email = ["pete@schwamb.net"]
11
+ spec.description = %q{Boolean logic parsing and evaluation engine}
12
+ spec.summary = %q{A ruby gem that parses and evaluates simple boolean statements specified as a string.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "rspec-nc"
25
+ spec.add_development_dependency "guard"
26
+ spec.add_development_dependency "guard-rspec"
27
+
28
+ spec.add_dependency("treetop")
29
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe SimpleLogic do
4
+ it "should evaluate single variable" do
5
+ context = { hungry: true }
6
+ expression = "hungry"
7
+ in_luck = SimpleLogic.eval(expression, context)
8
+ expect(in_luck).to eq true
9
+ end
10
+
11
+ it "should evaluate negated variable" do
12
+ context = { hungry: true }
13
+ expression = "!hungry"
14
+ in_luck = SimpleLogic.eval(expression, context)
15
+ expect(in_luck).to eq false
16
+ end
17
+
18
+ it "should evaluate logical and" do
19
+ context = { hungry: true, fridge_empty: false}
20
+ expression = "hungry && fridge_empty"
21
+ in_luck = SimpleLogic.eval(expression, context)
22
+ expect(in_luck).to eq false
23
+ end
24
+
25
+ it "should evaluate logical or" do
26
+ context = { hungry: true, fridge_empty: false}
27
+ expression = "hungry || fridge_empty"
28
+ in_luck = SimpleLogic.eval(expression, context)
29
+ expect(in_luck).to eq true
30
+ end
31
+
32
+ it "should evaluate chained logic statements" do
33
+ context = { hungry: true, fridge_empty: true, restaurant_nearby: true}
34
+ expression = "hungry && !fridge_empty || restaurant_nearby"
35
+ in_luck = SimpleLogic.eval(expression, context)
36
+ expect(in_luck).to eq true
37
+ end
38
+
39
+ it "should raise an exception on a syntax error" do
40
+ expect { SimpleLogic.eval("> hungry", {}) }.to raise_error {|e|
41
+ expect(e.offset).to eq 0
42
+ }
43
+ end
44
+
45
+ it "should raise an exception on a syntax error and indicate location of parse error" do
46
+ expect {
47
+ SimpleLogic.eval("testing > hungry", {testing: true})
48
+ }.to raise_error(SimpleLogic::ParseError) {|e|
49
+ expect(e.offset).to eq 7
50
+ }
51
+ end
52
+
53
+ it "should raise an exception on undefined variable" do
54
+ expect {
55
+ SimpleLogic.eval("testing && hungry", {testing: true})
56
+ }.to raise_error(SimpleLogic::UndefinedVariableError)
57
+ end
58
+
59
+ end
@@ -0,0 +1 @@
1
+ require 'simple_logic'
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_logic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pete Schwamb
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-nc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: treetop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Boolean logic parsing and evaluation engine
112
+ email:
113
+ - pete@schwamb.net
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .travis.yml
120
+ - Gemfile
121
+ - Guardfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - lib/simple_logic.rb
126
+ - lib/simple_logic/errors.rb
127
+ - lib/simple_logic/parser.rb
128
+ - lib/simple_logic/precedence_table.rb
129
+ - lib/simple_logic/simple_logic.treetop
130
+ - lib/simple_logic/syntax_nodes.rb
131
+ - lib/simple_logic/version.rb
132
+ - simple_logic.gemspec
133
+ - spec/simple_logic_spec.rb
134
+ - spec/spec_helper.rb
135
+ homepage: ''
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.1.11
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: A ruby gem that parses and evaluates simple boolean statements specified
159
+ as a string.
160
+ test_files:
161
+ - spec/simple_logic_spec.rb
162
+ - spec/spec_helper.rb