logicality 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0356f85f51e792d67aaadf12bb510c5c9e798870
4
+ data.tar.gz: 17d3463798a0c252bb4161853254280bbb2f9ccd
5
+ SHA512:
6
+ metadata.gz: '044761792866c934fb3f2827b36a7f1c1d94351ea82dfc85cb79611e7f2f13fa25a906428c1d006ca96c7333bc02455e0e55573503fc8ccb28c0315c2eefd17d'
7
+ data.tar.gz: ab723be91f1b30bf0b1fe5c14e46dec6bdf907729fe8ae9dfbe55d75a24b733c0d2ea98a8aadb21c0ea9d0f4631217a79d20bb71f88ec5b7f4c0bfe42b7f9eed
data/.editorconfig ADDED
@@ -0,0 +1,8 @@
1
+ # See http://editorconfig.org/
2
+
3
+ [*]
4
+ trim_trailing_whitespace = true
5
+ indent_style = space
6
+ indent_size = 2
7
+ insert_final_newline = true
8
+ end_of_line = lf
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .DS_Store
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.7
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.7
4
+ cache: bundler
5
+ script:
6
+ - bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ logicality (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rspec (3.8.0)
11
+ rspec-core (~> 3.8.0)
12
+ rspec-expectations (~> 3.8.0)
13
+ rspec-mocks (~> 3.8.0)
14
+ rspec-core (3.8.0)
15
+ rspec-support (~> 3.8.0)
16
+ rspec-expectations (3.8.1)
17
+ diff-lcs (>= 1.2.0, < 2.0)
18
+ rspec-support (~> 3.8.0)
19
+ rspec-mocks (3.8.0)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.8.0)
22
+ rspec-support (3.8.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ logicality!
29
+ rspec
30
+
31
+ BUNDLED WITH
32
+ 1.16.3
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2018 Blue Marble Payroll, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ *Note: This is a ruby implementation of [Logicality](https://github.com/bluemarblepayroll/logicality).*
2
+
3
+ # Logicality
4
+
5
+ [![Build Status](https://travis-ci.org/bluemarblepayroll/logicality-rb.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/logicality-rb)
6
+
7
+ A common problem that many frameworks have is the ability to give developers an expressive
8
+ intermediary scripting language or DSL. Logicality helps solve this problem by providing a simple
9
+ boolean expression evaluator. That way, your developers can create simple scripts for dynamically
10
+ resolving boolean values such as:
11
+
12
+ * a
13
+ * b
14
+ * a && b
15
+ * a || b
16
+ * a && b || c
17
+ * (a && b) || (c && d)
18
+ * (a && b) || (c && (d || e && f))
19
+
20
+ ## Credit
21
+
22
+ Deep inspiration was taken from [this set of articles](https://ruslanspivak.com/lsbasi-part7/).
23
+ Here, the author gives details around the theory and practical implementation of creating a basic
24
+ language processor and compiler.
25
+
26
+ ## Installation
27
+
28
+ To install through Rubygems:
29
+
30
+ ````
31
+ gem install install logicality
32
+ ````
33
+
34
+ You can also add this to your Gemfile:
35
+
36
+ ````
37
+ bundle add logicality
38
+ ````
39
+
40
+ ## Examples
41
+
42
+ ### A simple object-based example.
43
+
44
+ Consider a case where some content should be displayed if it is marked as visible or if the user is an administrator. You can express this as:
45
+
46
+ ````
47
+ visible || admin
48
+ ````
49
+
50
+ Now you can bind and evaluate this expression against passed in objects:
51
+
52
+ ````
53
+ record = { 'visible' => false, 'admin' => true }
54
+ visible = Logicality::Logic.evaluate('visible || admin', record) # resolves to true.
55
+ ````
56
+
57
+ ### Plugging in a Custom Resolver
58
+
59
+ Notice that the above example uses the default value resolver, which either wants an object that has values as attributes, or an object with a brackets method (i.e. Hash). In the brackets method approach, keys will be accessed using strings (as opposed to symbols.). If we wanted to use a custom resolver, for example to use symbols as keys, we could do this:
60
+
61
+ ````
62
+ resolver = lambda do |value, input|
63
+ symbolized_hash = (input || {}).map { |k,v| [ k.to_s.to_sym, v] }.to_h
64
+ !!symbolized_hash[value]
65
+ end
66
+
67
+ record = { visible: false, admin: true }
68
+ visible = Logicality::Logic.evaluate('visible || admin', record) # resolves to true.
69
+ ````
70
+
71
+ ## Contributing
72
+
73
+ ### Development Environment Configuration
74
+
75
+ Basic steps to take to get this repository compiling:
76
+
77
+ 1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check logicality.gemspec for versions supported)
78
+ 2. Install bundler (gem install bundler)
79
+ 3. Clone the repository (git clone git@github.com:bluemarblepayroll/logicality-rb.git)
80
+ 4. Navigate to the root folder (cd logicality)
81
+ 5. Install dependencies (bundle)
82
+
83
+ ### Running Tests
84
+
85
+ To execute the test suite run:
86
+
87
+ ````
88
+ rspec
89
+ ````
90
+
91
+ ## License
92
+
93
+ This project is MIT Licensed.
@@ -0,0 +1,9 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ require './lib/logicality/interpreter/node_visitor'
9
+ require './lib/logicality/interpreter/simple_interpreter'
@@ -0,0 +1,36 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Interpreter
10
+ class NodeVisitor
11
+
12
+ def visit(node)
13
+ return nil unless node
14
+
15
+ visitor_name = method_name(node)
16
+
17
+ if respond_to?(visitor_name)
18
+ send(visitor_name, node)
19
+ else
20
+ generic_visit(node)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def generic_visit(node)
27
+ raise ArgumentError, "No visitor method: #{method_name(node)}"
28
+ end
29
+
30
+ def method_name(node)
31
+ "visit_#{node.name}"
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,64 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Interpreter
10
+ class SimpleInterpreter < NodeVisitor
11
+
12
+ attr_reader :resolver
13
+
14
+ def initialize(resolver)
15
+ raise ArgumentError, "Resolver is required" unless resolver
16
+
17
+ @resolver = resolver
18
+ end
19
+
20
+ def error(node)
21
+ raise ArgumentError, "Visitor cant process node token type: #{node.token.type}"
22
+ end
23
+
24
+ def visit_binary_operator_node(node)
25
+ if node.token.type == Lexer::Token::Type::AND_OP
26
+ visit(node.left) && visit(node.right)
27
+ elsif node.token.type == Lexer::Token::Type::OR_OP
28
+ visit(node.left) || visit(node.right)
29
+ else
30
+ error(node)
31
+ end
32
+ end
33
+
34
+ def visit_unary_operator_node(node)
35
+ if node.token.type == Lexer::Token::Type::NOT_OP
36
+ !visit(node.child)
37
+ else
38
+ error(node)
39
+ end
40
+ end
41
+
42
+ def visit_value_operand_node(node)
43
+ if node.value == 'true'
44
+ true
45
+ elsif node.value == 'false' || node.value == 'null'
46
+ false
47
+ else
48
+ resolve_value(node.value)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def resolve_value(value)
55
+ if resolver.nil?
56
+ raise ArgumentError, "No resolver function but trying to resolve: #{value}"
57
+ end
58
+
59
+ !!resolver.call(value)
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Lexer
10
+ module Grammar
11
+ VALUE = /([a-zA-Z0-9_$@?\.]+)/
12
+ AND_OP = /(&&)/
13
+ OR_OP = /(\|\|)/
14
+ NOT_OP = /(\!)/
15
+ LEFT_PAREN = /(\()/
16
+ RIGHT_PAREN = /(\))/
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ require './lib/logicality/lexer/grammar'
9
+ require './lib/logicality/lexer/token'
10
+ require './lib/logicality/lexer/regexp_lexer'
@@ -0,0 +1,97 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Lexer
10
+ class RegexpLexer
11
+ include Grammar
12
+
13
+ class << self
14
+
15
+ def invalid_pattern
16
+ "#{pattern}|(\\s*)"
17
+ end
18
+
19
+ def invalid_regexp
20
+ Regexp.new(invalid_pattern)
21
+ end
22
+
23
+ def pattern
24
+ Grammar.constants.map { |c| Grammar.const_get(c).source }
25
+ .join('|')
26
+ end
27
+
28
+ def regexp
29
+ Regexp.new(pattern)
30
+ end
31
+
32
+ end
33
+
34
+ attr_reader :expression
35
+
36
+ def initialize(expression)
37
+ raise ArgumentError, 'Expression is required' unless expression && expression.to_s.length > 0
38
+
39
+ @expression = expression.to_s
40
+
41
+ if invalid_matches.length > 0
42
+ raise ArgumentError, "Invalid syntax: #{invalid_matches}"
43
+ end
44
+
45
+ reset
46
+ end
47
+
48
+ def next_token
49
+ return nil if index > matches.length - 1
50
+
51
+ increment
52
+
53
+ scan_array = matches[index]
54
+
55
+ return nil unless scan_array
56
+
57
+ tokens = scan_array.map.with_index do |value, index|
58
+ const = Grammar.constants[index]
59
+ value ? Token.new(const, value) : nil
60
+ end.compact
61
+
62
+ if tokens.length > 1
63
+ raise ArgumentError, "Too many tokens found for: #{scan_array}"
64
+ elsif tokens.length == 0
65
+ raise ArgumentError, "Cannot tokenize: #{scan_array}"
66
+ end
67
+
68
+ tokens.first
69
+ end
70
+
71
+ def reset
72
+ @index = -1
73
+
74
+ self
75
+ end
76
+
77
+ private
78
+
79
+ attr_reader :index
80
+
81
+ def increment
82
+ @index += 1
83
+
84
+ nil
85
+ end
86
+
87
+ def invalid_matches
88
+ @invalid_matches ||= expression.gsub(self.class.invalid_regexp, '')
89
+ end
90
+
91
+ def matches
92
+ @matches ||= expression.scan(self.class.regexp)
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Lexer
10
+ class Token
11
+
12
+ module Type
13
+ VALUE = :value
14
+ AND_OP = :and_op
15
+ OR_OP = :or_op
16
+ NOT_OP = :not_op
17
+ LEFT_PAREN = :left_paren
18
+ RIGHT_PAREN = :right_paren
19
+ end
20
+
21
+ attr_reader :type, :value
22
+
23
+ def initialize(type, value)
24
+ raise ArgumentError, 'type is required' unless type && type.to_s.length > 0
25
+ raise ArgumentError, 'value is required' unless value && value.to_s.length > 0
26
+
27
+ @type = Type.const_get(type.to_s.upcase.to_sym)
28
+ @value = value.to_s
29
+ end
30
+
31
+ def to_s
32
+ "#{type}::#{value}"
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,65 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ class Logic
10
+ class << self
11
+
12
+ def evaluate(expression, input, resolver = nil)
13
+ node = get(expression)
14
+ wrapper = resolver_wrapper(input, resolver)
15
+ interpreter = Interpreter::SimpleInterpreter.new(wrapper)
16
+
17
+ interpreter.visit(node)
18
+ end
19
+
20
+ private
21
+
22
+ def resolver_wrapper(input, resolver)
23
+ if resolver
24
+ lambda { |value| resolver.call(value, input) }
25
+ else
26
+ lambda { |value| object_resolver.call(value, input) }
27
+ end
28
+ end
29
+
30
+ def object_resolver
31
+ lambda do |value, input|
32
+ return false unless input
33
+
34
+ if input.respond_to?(value)
35
+ !!input.send(value)
36
+ elsif input.respond_to?(:[])
37
+ !!input[value]
38
+ else
39
+ false
40
+ end
41
+ end
42
+ end
43
+
44
+ def cache
45
+ @cache || {}
46
+ end
47
+
48
+ def set(expression, node)
49
+ @cache = {} unless @cache
50
+
51
+ @cache[expression] = node
52
+ end
53
+
54
+ def get(expression)
55
+ return cache[expression] if cache[expression]
56
+
57
+ lexer = Lexer::RegexpLexer.new(expression)
58
+ parser = Parser::SimpleParser.new(lexer)
59
+
60
+ set(expression, parser.parse)
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ require './lib/logicality/lexer/lexer'
9
+ require './lib/logicality/parser/parser'
10
+ require './lib/logicality/interpreter/interpreter'
11
+ require './lib/logicality/logic'
@@ -0,0 +1,11 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ require './lib/logicality/parser/ast/node'
9
+ require './lib/logicality/parser/ast/binary_operator_node'
10
+ require './lib/logicality/parser/ast/unary_operator_node'
11
+ require './lib/logicality/parser/ast/value_operand_node'
@@ -0,0 +1,26 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Parser
10
+ module Ast
11
+ class BinaryOperatorNode < Node
12
+
13
+ attr_reader :left, :right
14
+
15
+ def initialize(left, token, right)
16
+ super(token)
17
+
18
+ @name = 'binary_operator_node'
19
+ @left = left
20
+ @right = right
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Parser
10
+ module Ast
11
+ class Node
12
+
13
+ attr_reader :token, :name
14
+
15
+ def initialize(token)
16
+ @token = token
17
+ @name = ''
18
+ end
19
+
20
+ def to_s
21
+ "AstNode: #{self.class.name}::#{token}"
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Parser
10
+ module Ast
11
+ class UnaryOperatorNode < Node
12
+
13
+ attr_reader :child
14
+
15
+ def initialize(child, token)
16
+ super(token)
17
+
18
+ @name = 'unary_operator_node'
19
+ @child = child
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Parser
10
+ module Ast
11
+ class ValueOperandNode < Node
12
+
13
+ attr_reader :value
14
+
15
+ def initialize(token)
16
+ super(token)
17
+
18
+ @name = 'value_operand_node'
19
+ @value = token.value
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ require './lib/logicality/parser/ast/ast'
9
+ require './lib/logicality/parser/simple_parser'
@@ -0,0 +1,97 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ module Parser
10
+ class SimpleParser
11
+
12
+ attr_reader :lexer
13
+
14
+ def initialize(lexer)
15
+ @lexer = lexer
16
+
17
+ @current_token = lexer.next_token
18
+
19
+ if @current_token.nil?
20
+ raise ArgumentError, 'Lexer must contain at least one token'
21
+ end
22
+
23
+ end
24
+
25
+ def parse
26
+ expr
27
+ end
28
+
29
+ private
30
+
31
+ BINARY_TYPES = [
32
+ Lexer::Token::Type::AND_OP,
33
+ Lexer::Token::Type::OR_OP,
34
+ ].freeze
35
+
36
+ attr_reader :current_token
37
+
38
+ def error
39
+ raise ArgumentError, 'Invalid parser syntax'
40
+ end
41
+
42
+ def eat(token_type)
43
+ if current_token.type == token_type
44
+ @current_token = lexer.next_token
45
+ else
46
+ error
47
+ end
48
+
49
+ nil
50
+ end
51
+
52
+ def factor
53
+ token = current_token
54
+
55
+ if current_token.type == Lexer::Token::Type::VALUE
56
+ eat(Lexer::Token::Type::VALUE)
57
+
58
+ Ast::ValueOperandNode.new(token)
59
+ elsif current_token.type == Lexer::Token::Type::LEFT_PAREN
60
+ eat(Lexer::Token::Type::LEFT_PAREN)
61
+ node = expr
62
+ eat(Lexer::Token::Type::RIGHT_PAREN)
63
+
64
+ node
65
+ elsif current_token.type == Lexer::Token::Type::NOT_OP
66
+ eat(Lexer::Token::Type::NOT_OP)
67
+ node = factor
68
+
69
+ Ast::UnaryOperatorNode.new(node, token)
70
+ else
71
+ raise ArgumentError, "Factor cannot determine what to do with: #{token}"
72
+ end
73
+ end
74
+
75
+ def expr
76
+ node = factor
77
+
78
+ loop do
79
+ break unless current_token && BINARY_TYPES.include?(current_token.type)
80
+
81
+ token = current_token
82
+
83
+ if token.type == Lexer::Token::Type::AND_OP
84
+ eat(Lexer::Token::Type::AND_OP)
85
+ elsif token.type == Lexer::Token::Type::OR_OP
86
+ eat(Lexer::Token::Type::OR_OP)
87
+ end
88
+
89
+ node = Ast::BinaryOperatorNode.new(node, token, factor)
90
+ end
91
+
92
+ node
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,10 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ module Logicality
9
+ VERSION = "0.0.1"
10
+ end
@@ -0,0 +1,28 @@
1
+ require "./lib/logicality/version"
2
+
3
+ Gem::Specification.new do |s|
4
+
5
+ s.name = 'logicality'
6
+ s.version = Logicality::VERSION
7
+ s.summary = "String-based boolean expression evaluator"
8
+
9
+ s.description = <<-EOS
10
+ A common problem that many frameworks have is the ability to give developers
11
+ an expressive intermediary scripting language or DSL.
12
+ Logicality helps solve this problem by providing a simple boolean
13
+ expression evaluator.
14
+ EOS
15
+
16
+ s.authors = [ 'Matthew Ruggio' ]
17
+ s.email = [ 'mruggio@bluemarblepayroll.com' ]
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.homepage = 'https://github.com/bluemarblepayroll/logicality-rb'
22
+ s.license = 'MIT'
23
+
24
+ s.required_ruby_version = '>= 2.3.7'
25
+
26
+ s.add_development_dependency('rspec')
27
+
28
+ end
@@ -0,0 +1,87 @@
1
+ #
2
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
3
+ #
4
+ # This source code is licensed under the MIT license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+
8
+ require './lib/logicality/logicality'
9
+
10
+ def run(tests)
11
+ tests.each do |x|
12
+ input = x[1] ? x[1].map { |k,v| [ k.to_s, v] }.to_h : nil
13
+
14
+ result = Logicality::Logic.evaluate(x[0], input)
15
+
16
+ expect(result).to eq(x[2]), "Failed on #{x[0]} (input: #{input}): expected #{x[2]} but got: #{result}"
17
+ end
18
+
19
+ nil
20
+ end
21
+
22
+ describe Logicality::Logic do
23
+
24
+ context 'when evaluating' do
25
+
26
+ it 'should evaluate boolean-only expressions' do
27
+ tests = [
28
+ [ 'true', nil, true ],
29
+ [ 'false', nil, false ],
30
+ [ 'true && false', nil, false ],
31
+ [ 'true && true', nil, true ]
32
+ ]
33
+
34
+ run(tests)
35
+ end
36
+
37
+ it 'should evaluate and expressions' do
38
+ tests = [
39
+ [ 'a && b', nil, false ],
40
+ [ 'a && b', {}, false ],
41
+ [ 'a && b', { a: true }, false ],
42
+ [ 'a && b', { a: true, b: false }, false ],
43
+ [ 'a && b', { a: false, b: false }, false ],
44
+ [ 'a && b', { a: true, b: true }, true ]
45
+ ]
46
+
47
+ run(tests)
48
+ end
49
+
50
+ it 'should evaluate and-or expressions' do
51
+ tests = [
52
+ [ 'a && b || c', { a: false, b: false, c: true }, true ],
53
+ [ '(a && b) || c', { a: false, b: false, c: true }, true ],
54
+ [ 'a || b && c', { a: false, b: false, c: true }, false ],
55
+ [ 'a || (b && c)', { a: false, b: false, c: true }, false ],
56
+ [ '(a || b) && c', { a: false, b: false, c: true }, false ]
57
+ ]
58
+
59
+ run(tests)
60
+ end
61
+
62
+ it 'should evaluate not expressions' do
63
+ tests = [
64
+ [ '!a', { a: false }, true ],
65
+ [ '!a && !b', { a: false, b: false }, true ],
66
+ [ '!a && b', { a: false, b: false }, false ],
67
+ [ 'a && !b', { a: false, b: false }, false ],
68
+ [ '!(a && b)', { a: false, b: false }, true ]
69
+ ]
70
+
71
+ run(tests)
72
+ end
73
+
74
+ it 'should treat question marks as a valid part of a value token' do
75
+ tests = [
76
+ [ 'a?', { 'a?': true }, true ],
77
+ [ '!a?', { 'a?': true }, false ],
78
+ [ 'a? && b?', { 'a?': true, 'b?': true }, true ],
79
+ [ 'a && b?', { a: true, 'b?': true }, true ]
80
+ ]
81
+
82
+ run(tests)
83
+ end
84
+
85
+ end
86
+
87
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logicality
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Ruggio
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-08-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: |2
28
+ A common problem that many frameworks have is the ability to give developers
29
+ an expressive intermediary scripting language or DSL.
30
+ Logicality helps solve this problem by providing a simple boolean
31
+ expression evaluator.
32
+ email:
33
+ - mruggio@bluemarblepayroll.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - ".editorconfig"
39
+ - ".gitignore"
40
+ - ".ruby-version"
41
+ - ".travis.yml"
42
+ - Gemfile
43
+ - Gemfile.lock
44
+ - LICENSE
45
+ - README.md
46
+ - lib/logicality/interpreter/interpreter.rb
47
+ - lib/logicality/interpreter/node_visitor.rb
48
+ - lib/logicality/interpreter/simple_interpreter.rb
49
+ - lib/logicality/lexer/grammar.rb
50
+ - lib/logicality/lexer/lexer.rb
51
+ - lib/logicality/lexer/regexp_lexer.rb
52
+ - lib/logicality/lexer/token.rb
53
+ - lib/logicality/logic.rb
54
+ - lib/logicality/logicality.rb
55
+ - lib/logicality/parser/ast/ast.rb
56
+ - lib/logicality/parser/ast/binary_operator_node.rb
57
+ - lib/logicality/parser/ast/node.rb
58
+ - lib/logicality/parser/ast/unary_operator_node.rb
59
+ - lib/logicality/parser/ast/value_operand_node.rb
60
+ - lib/logicality/parser/parser.rb
61
+ - lib/logicality/parser/simple_parser.rb
62
+ - lib/logicality/version.rb
63
+ - logicality.gemspec
64
+ - spec/logic_spec.rb
65
+ homepage: https://github.com/bluemarblepayroll/logicality-rb
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 2.3.7
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.5.2.3
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: String-based boolean expression evaluator
89
+ test_files:
90
+ - spec/logic_spec.rb