abstract_mapper 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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +9 -0
  4. data/.metrics +9 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +2 -0
  7. data/.travis.yml +19 -0
  8. data/.yardopts +3 -0
  9. data/CHANGELOG.md +3 -0
  10. data/Gemfile +9 -0
  11. data/Guardfile +14 -0
  12. data/LICENSE +21 -0
  13. data/README.md +239 -0
  14. data/Rakefile +34 -0
  15. data/abstract_mapper.gemspec +26 -0
  16. data/config/metrics/STYLEGUIDE +230 -0
  17. data/config/metrics/cane.yml +5 -0
  18. data/config/metrics/churn.yml +6 -0
  19. data/config/metrics/flay.yml +2 -0
  20. data/config/metrics/metric_fu.yml +15 -0
  21. data/config/metrics/reek.yml +1 -0
  22. data/config/metrics/roodi.yml +24 -0
  23. data/config/metrics/rubocop.yml +76 -0
  24. data/config/metrics/saikuro.yml +3 -0
  25. data/config/metrics/simplecov.yml +6 -0
  26. data/config/metrics/yardstick.yml +37 -0
  27. data/lib/abstract_mapper/branch.rb +110 -0
  28. data/lib/abstract_mapper/builder.rb +84 -0
  29. data/lib/abstract_mapper/commands.rb +71 -0
  30. data/lib/abstract_mapper/dsl.rb +62 -0
  31. data/lib/abstract_mapper/errors/unknown_command.rb +25 -0
  32. data/lib/abstract_mapper/errors/wrong_node.rb +23 -0
  33. data/lib/abstract_mapper/errors/wrong_rule.rb +23 -0
  34. data/lib/abstract_mapper/functions.rb +76 -0
  35. data/lib/abstract_mapper/node.rb +76 -0
  36. data/lib/abstract_mapper/optimizer.rb +48 -0
  37. data/lib/abstract_mapper/pair_rule.rb +66 -0
  38. data/lib/abstract_mapper/rspec.rb +7 -0
  39. data/lib/abstract_mapper/rule.rb +52 -0
  40. data/lib/abstract_mapper/rules.rb +61 -0
  41. data/lib/abstract_mapper/settings.rb +95 -0
  42. data/lib/abstract_mapper/sole_rule.rb +58 -0
  43. data/lib/abstract_mapper/version.rb +9 -0
  44. data/lib/abstract_mapper.rb +82 -0
  45. data/lib/rspec/functions.rb +25 -0
  46. data/lib/rspec/mapper.rb +40 -0
  47. data/lib/rspec/nodes.rb +58 -0
  48. data/lib/rspec/rules.rb +59 -0
  49. data/spec/integration/faceter.rb +62 -0
  50. data/spec/integration/mapper_definition_spec.rb +33 -0
  51. data/spec/integration/rspec_examples_spec.rb +77 -0
  52. data/spec/spec_helper.rb +20 -0
  53. data/spec/unit/abstract_mapper/branch_spec.rb +126 -0
  54. data/spec/unit/abstract_mapper/builder_spec.rb +78 -0
  55. data/spec/unit/abstract_mapper/commands_spec.rb +105 -0
  56. data/spec/unit/abstract_mapper/dsl_spec.rb +82 -0
  57. data/spec/unit/abstract_mapper/errors/unknown_command_spec.rb +21 -0
  58. data/spec/unit/abstract_mapper/errors/wrong_node_spec.rb +21 -0
  59. data/spec/unit/abstract_mapper/errors/wrong_rule_spec.rb +23 -0
  60. data/spec/unit/abstract_mapper/functions/compact_spec.rb +25 -0
  61. data/spec/unit/abstract_mapper/functions/filter_spec.rb +26 -0
  62. data/spec/unit/abstract_mapper/functions/subclass_spec.rb +66 -0
  63. data/spec/unit/abstract_mapper/node_spec.rb +84 -0
  64. data/spec/unit/abstract_mapper/optimizer_spec.rb +48 -0
  65. data/spec/unit/abstract_mapper/pair_rule_spec.rb +59 -0
  66. data/spec/unit/abstract_mapper/rule_spec.rb +83 -0
  67. data/spec/unit/abstract_mapper/rules_spec.rb +88 -0
  68. data/spec/unit/abstract_mapper/settings_spec.rb +113 -0
  69. data/spec/unit/abstract_mapper/sole_rule_spec.rb +51 -0
  70. data/spec/unit/abstract_mapper_spec.rb +36 -0
  71. metadata +171 -0
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # Optimizes the immutable abstract syntax tree (AST) using comfigurable
6
+ # collection of applicable rules.
7
+ #
8
+ # @api private
9
+ #
10
+ class Optimizer
11
+
12
+ # @!attribute [r] rules
13
+ #
14
+ # @return [AbstractMapper::Rules]
15
+ # The collection of applicable optimization rules
16
+ #
17
+ attr_reader :rules
18
+
19
+ # @!scope class
20
+ # @!method new(rules = Rules.new)
21
+ # Creates the optimizer
22
+ #
23
+ # @param [AbstractMapper::Rules] rules
24
+ # The collection of applicable optimization rules
25
+ #
26
+ # @return [AbstractMapper::Optimizer]
27
+
28
+ # @private
29
+ def initialize(rules)
30
+ @rules = rules
31
+ freeze
32
+ end
33
+
34
+ # Recursively optimizes the AST from root to leafs
35
+ #
36
+ # @param [AbstractMapper::Branch] tree
37
+ #
38
+ # @return [AbstractMapper::Branch]
39
+ #
40
+ def update(tree)
41
+ return tree unless tree.is_a? Branch
42
+ new_tree = tree.rebuild { rules[tree] }
43
+ new_tree.rebuild { new_tree.map(&method(:update)) }
44
+ end
45
+
46
+ end # class Optimizer
47
+
48
+ end # class AbstractMapper
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # The base class for rules to be applied to pairs of nodes.
6
+ #
7
+ # The subclass should implement two instance methods:
8
+ #
9
+ # * `#optimize?` to check if the optimization should be applied to the nodes
10
+ # * `#optimize` that should return the merged node
11
+ #
12
+ # @example
13
+ # class MergeConsecutiveLists < AbstractMapper::PairRule
14
+ # def optimize?
15
+ # left.is_a?(List) & right.is_a?(List)
16
+ # end
17
+ #
18
+ # def optimize
19
+ # List.new { left.entries + right.entries }
20
+ # end
21
+ # end
22
+ #
23
+ # @abstract
24
+ #
25
+ # @api public
26
+ #
27
+ class PairRule < Rule
28
+
29
+ # Returns the name of transproc used to compose rules
30
+ #
31
+ # @return [Symbol]
32
+ #
33
+ def self.composer
34
+ :compact
35
+ end
36
+
37
+ # @!attribute [r] node
38
+ #
39
+ # @return [AbstractMapper::Node] The left node in the pair to be optimized
40
+ #
41
+ attr_reader :left
42
+
43
+ # @!attribute [r] node
44
+ #
45
+ # @return [AbstractMapper::Node] The right node in the pair to be optimized
46
+ #
47
+ attr_reader :right
48
+
49
+ # @!scope class
50
+ # @!method new(node)
51
+ # Creates a rule applied to the sole node
52
+ #
53
+ # @param [AbstractMapper::Node] node
54
+ #
55
+ # @return [AbstractMapper::Rule]
56
+
57
+ # @private
58
+ def initialize(left, right)
59
+ @left = left
60
+ @right = right
61
+ super
62
+ end
63
+
64
+ end # class PairRule
65
+
66
+ end # class AbstractMapper
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ # Collection of shared examples for testing parts of DSL
4
+ require_relative "../rspec/functions"
5
+ require_relative "../rspec/nodes"
6
+ require_relative "../rspec/rules"
7
+ require_relative "../rspec/mapper"
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # Base class for nodes optimization rules
6
+ #
7
+ # @abstract
8
+ #
9
+ # @api private
10
+ #
11
+ class Rule
12
+
13
+ # The transformation function that applies the rule to the array of nodes
14
+ #
15
+ # @return [Transproc::Function]
16
+ #
17
+ def self.transproc
18
+ Functions[composer, proc { |*nodes| new(*nodes).call }]
19
+ end
20
+
21
+ # @!attribute [r] nodes
22
+ #
23
+ # @return [Array<AbstractMapper::Node>]
24
+ # Either one or two nodes to be optimized
25
+ #
26
+ attr_reader :nodes
27
+
28
+ # @!scope class
29
+ # @!method new(*nodes)
30
+ # Creates the rule for a sole node, or a pair of consecutive nodes
31
+ #
32
+ # @param [Array<AbstractMapper::Node>] nodes
33
+ #
34
+ # @return [AbstractMapper::Rule]
35
+
36
+ # @private
37
+ def initialize(*nodes)
38
+ @nodes = nodes.freeze
39
+ freeze
40
+ end
41
+
42
+ # Returns the result of the rule applied to the initialized [#nodes]
43
+ #
44
+ # @return [Array<AbstractMapper::Node>]
45
+ #
46
+ def call
47
+ optimize? ? [optimize].flatten.compact : nodes
48
+ end
49
+
50
+ end # class Rule
51
+
52
+ end # class AbstractMapper
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # Registry of DSL rules applicable to nodes of AST
6
+ #
7
+ # @api private
8
+ #
9
+ class Rules
10
+
11
+ # @!attribute [r] registry
12
+ #
13
+ # @return [Array<AbstractMapper::SoleRule>] list of rules applicable to AST
14
+ #
15
+ attr_reader :registry
16
+
17
+ # @!scope class
18
+ # @!method new(registry)
19
+ # Creates a registry with array of rules
20
+ #
21
+ # @param [Array<AbstractMapper::SoleRule>] registry Array of rules
22
+ #
23
+ # @return [AbstractMapper::Rules]
24
+
25
+ # @private
26
+ def initialize(registry = [])
27
+ @registry = registry.dup.freeze
28
+ @transproc = ordered.map(&:transproc).inject(:>>)
29
+ freeze
30
+ end
31
+
32
+ # Returns the copy of current registry with the new rule added
33
+ #
34
+ # @param [AbstractMapper::SoleRule] other
35
+ #
36
+ # @return (see #new)
37
+ #
38
+ def <<(other)
39
+ self.class.new(registry + [other])
40
+ end
41
+
42
+ # Applies all the registered rules to the array of nodes
43
+ #
44
+ # @param [Array<AbstractMapper::Node>] nodes
45
+ #
46
+ # @return [Array<AbstractMapper::Node>] The optimized array of nodes
47
+ #
48
+ def [](nodes)
49
+ @transproc ? @transproc[nodes] : nodes
50
+ end
51
+
52
+ private
53
+
54
+ # Calls all sole rules first
55
+ def ordered
56
+ registry.sort_by { |rule| Functions[:subclass?, PairRule][rule].to_s }
57
+ end
58
+
59
+ end # class Rules
60
+
61
+ end # class AbstractMapper
@@ -0,0 +1,95 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # The configurable container of domain-specific DSL commands and rules
6
+ # along with corresponding AST builder and optimizer
7
+ #
8
+ # @api private
9
+ #
10
+ class Settings
11
+
12
+ # @!attribute [r] commands
13
+ #
14
+ # @return [AbstractMapper::Commands]
15
+ # The collection of registered DSL commands
16
+ #
17
+ attr_reader :commands
18
+
19
+ # @!attribute [r] rules
20
+ #
21
+ # @return [AbstractMapper::Rules]
22
+ # The collection of registered optimization rules
23
+ #
24
+ attr_reader :rules
25
+
26
+ # @!attribute [r] optimizer
27
+ #
28
+ # @return [AbstractMapper::Optimizer]
29
+ # The optimizer with domain-specific rules
30
+ #
31
+ attr_reader :optimizer
32
+
33
+ # @!attribute [r] builder
34
+ #
35
+ # @return [Class] The builder class with domain-specific commands
36
+ #
37
+ attr_reader :builder
38
+
39
+ # @!scope class
40
+ # @!method new(&block)
41
+ # Creates a domain-specific settings with commands and rules initialized
42
+ # from inside a block
43
+ #
44
+ # @param [Proc] block
45
+ #
46
+ # @return [AbstractMapper::Settings]
47
+
48
+ # @private
49
+ def initialize(&block)
50
+ __set_rules__
51
+ __set_commands__
52
+ __configure__(&block)
53
+ __set_builder__
54
+ __set_optimizer__
55
+ freeze
56
+ end
57
+
58
+ private
59
+
60
+ def rule(value)
61
+ fn = Functions[:subclass?, Rule]
62
+ fail Errors::WrongRule.new(value) unless fn[value]
63
+ @rules = rules << value
64
+ end
65
+
66
+ def command(name, node)
67
+ fn = Functions[:subclass?, Node]
68
+ fail Errors::WrongNode.new(node) unless fn[node]
69
+ @commands = commands << [name, node]
70
+ end
71
+
72
+ def __set_rules__
73
+ @rules = Rules.new
74
+ end
75
+
76
+ def __set_commands__
77
+ @commands = Commands.new
78
+ end
79
+
80
+ def __configure__(&block)
81
+ instance_eval(&block) if block_given?
82
+ end
83
+
84
+ def __set_builder__
85
+ @builder = Class.new(Builder)
86
+ builder.commands = commands
87
+ end
88
+
89
+ def __set_optimizer__
90
+ @optimizer = Optimizer.new(rules)
91
+ end
92
+
93
+ end # class Settings
94
+
95
+ end # class AbstractMapper
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # The abstract class that describes the rule to be applied to sole nodes.
6
+ #
7
+ # The subclass should implement two instance methods:
8
+ #
9
+ # * `#optimize?` to check if the optimization should be applied to the node
10
+ # * `#optimize` that should return the optimized node
11
+ #
12
+ # @example
13
+ # class RemoveEmptyList < AbstractMapper::SoleRule
14
+ # def optimize?
15
+ # node.is_a?(List) && node.empty?
16
+ # end
17
+ #
18
+ # def optimize
19
+ # end
20
+ # end
21
+ #
22
+ # @abstract
23
+ #
24
+ # @api public
25
+ #
26
+ class SoleRule < Rule
27
+
28
+ # Returns the name of transproc used to compose rules
29
+ #
30
+ # @return [Symbol]
31
+ #
32
+ def self.composer
33
+ :filter
34
+ end
35
+
36
+ # @!attribute [r] node
37
+ #
38
+ # @return [AbstractMapper::Node] The node to be optimized
39
+ #
40
+ attr_reader :node
41
+
42
+ # @!scope class
43
+ # @!method new(node)
44
+ # Creates a rule applied to the sole node
45
+ #
46
+ # @param [AbstractMapper::Node] node
47
+ #
48
+ # @return [AbstractMapper::Rule]
49
+
50
+ # @private
51
+ def initialize(node)
52
+ @node = node
53
+ super
54
+ end
55
+
56
+ end # class SoleRule
57
+
58
+ end # class AbstractMapper
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # The semantic version of the module.
6
+ # @see http://semver.org/ Semantic versioning 2.0
7
+ VERSION = "0.0.1".freeze
8
+
9
+ end # class AbstractMapper
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+
3
+ require "transproc"
4
+
5
+ require_relative "abstract_mapper/functions"
6
+
7
+ require_relative "abstract_mapper/errors/unknown_command"
8
+ require_relative "abstract_mapper/errors/wrong_node"
9
+ require_relative "abstract_mapper/errors/wrong_rule"
10
+
11
+ require_relative "abstract_mapper/node"
12
+ require_relative "abstract_mapper/branch"
13
+ require_relative "abstract_mapper/commands"
14
+ require_relative "abstract_mapper/builder"
15
+
16
+ require_relative "abstract_mapper/rule"
17
+ require_relative "abstract_mapper/sole_rule"
18
+ require_relative "abstract_mapper/pair_rule"
19
+ require_relative "abstract_mapper/rules"
20
+ require_relative "abstract_mapper/optimizer"
21
+
22
+ require_relative "abstract_mapper/settings"
23
+ require_relative "abstract_mapper/dsl"
24
+
25
+ # The configurable base class for mappers
26
+ #
27
+ # The mapper DSL is configured by assigning it a specific settings:
28
+ #
29
+ # class BaseMapper < AbstractMapper::Mapper
30
+ # configure do
31
+ # command :list, List # domain-specific command
32
+ # command :rename, Rename # domain-specific command
33
+ #
34
+ # rule MergeLists # domain-specific rule
35
+ # end
36
+ # end
37
+ #
38
+ # Then a configured mapper can use a corresponding DSL commands
39
+ #
40
+ # class ConcreteMapper < BaseMapper
41
+ # list do
42
+ # rename :foo, to: :bar
43
+ # end
44
+ # end
45
+ #
46
+ # @api public
47
+ #
48
+ class AbstractMapper
49
+
50
+ extend DSL # configurable DSL container
51
+
52
+ # @!attribute [r] tree
53
+ #
54
+ # @return [AbstractMapper::Branch] AST
55
+ #
56
+ attr_reader :tree
57
+
58
+ # @!scope class
59
+ # @!method new
60
+ # Creates a mapper instance with the optimized AST
61
+ #
62
+ # @return [AbstractMapper::Mapper]
63
+
64
+ # @private
65
+ def initialize
66
+ @tree = self.class.finalize
67
+ @transproc = @tree.transproc
68
+ freeze
69
+ end
70
+
71
+ # Maps the input data to some output using the transformation,
72
+ # described by the optimized [#tree]
73
+ #
74
+ # @param [Object] input
75
+ #
76
+ # @return [Object]
77
+ #
78
+ def call(input)
79
+ @transproc.call(input)
80
+ end
81
+
82
+ end # class AbstractMapper
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ # ==============================================================================
4
+ # Examples for testing transproc functions
5
+ # ==============================================================================
6
+
7
+ shared_examples :transforming_immutable_data do
8
+
9
+ let(:fn) { described_class[*arguments] }
10
+
11
+ subject { fn[input.dup.freeze] }
12
+
13
+ it do
14
+ is_expected.to eql(output), <<-REPORT.gsub(/.+\|/, "")
15
+ |
16
+ |fn = #{described_class}[{arguments}]
17
+ |
18
+ |fn#{Array[*input]}
19
+ |
20
+ | expected: #{output}
21
+ | got: #{subject}
22
+ REPORT
23
+ end
24
+
25
+ end # shared examples
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ # ==============================================================================
4
+ # Examples for testing DSL registry
5
+ # ==============================================================================
6
+
7
+ shared_examples :defining_rule do
8
+
9
+ subject { described_class.settings.rules.registry }
10
+ it do
11
+ is_expected.to include(rule), <<-REPORT.gsub(/.+\|/, "")
12
+ |
13
+ |#{described_class} optimization rules
14
+ |
15
+ |expected to include #{rule.inspect}
16
+ | got rules:
17
+ |#{subject.map { |rule| "#{" " * 9}- #{rule.inspect}" }.join("\n")}
18
+ REPORT
19
+ end
20
+
21
+ end # shared examples
22
+
23
+ shared_examples :defining_command do
24
+
25
+ subject { described_class.settings.commands.registry }
26
+ it "registers the command" do
27
+ expect(subject[name]).to eql(node), <<-REPORT.gsub(/.+\|/, "")
28
+ |
29
+ |#{described_class} DSL commands
30
+ |
31
+ |expected to include '#{name}' adding #{node.inspect}
32
+ | got commands:
33
+ |#{subject
34
+ .map { |name, node| "#{" " * 9}- #{name}: #{node.inspect}" }
35
+ .sort
36
+ .join("\n")}
37
+ REPORT
38
+ end
39
+
40
+ end # shared examples
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ # ==============================================================================
4
+ # Examples for testing nodes
5
+ # ==============================================================================
6
+
7
+ shared_context :node do
8
+
9
+ def node
10
+ attrs__ = defined?(attributes) ? attributes : []
11
+ block__ = defined?(block) ? block : nil
12
+ described_class.new(*attrs__, &block__)
13
+ end
14
+
15
+ end # shared context
16
+
17
+ shared_examples :creating_immutable_node do
18
+
19
+ include_context :node
20
+
21
+ subject { node }
22
+
23
+ it { is_expected.to be_kind_of AbstractMapper::Node }
24
+ it { is_expected.to be_frozen, "expected #{subject.inspect} to be immutable" }
25
+
26
+ end # shared examples
27
+
28
+ shared_examples :creating_immutable_branch do
29
+
30
+ include_context :node
31
+
32
+ subject { node }
33
+
34
+ it { is_expected.to be_kind_of AbstractMapper::Branch }
35
+ it { is_expected.to be_frozen, "expected #{subject.inspect} to be immutable" }
36
+
37
+ end # shared examples
38
+
39
+ shared_examples :mapping_immutable_input do
40
+
41
+ include_context :node
42
+
43
+ subject { node.transproc.call(input.dup.freeze) }
44
+
45
+ it do
46
+ is_expected.to eql(output), <<-REPORT.gsub(/.+\|/, "")
47
+ |
48
+ |#{node}
49
+ |
50
+ |Input: #{input}
51
+ |
52
+ |Output:
53
+ | expected: #{output}
54
+ | got: #{subject}
55
+ REPORT
56
+ end
57
+
58
+ end # shared examples
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ # ==============================================================================
4
+ # Examples for testing rules
5
+ # ==============================================================================
6
+
7
+ shared_context :rule do
8
+
9
+ def nodes
10
+ defined?(input) ? [input].flatten : []
11
+ end
12
+
13
+ def optimized
14
+ defined?(output) ? [output].flatten : []
15
+ end
16
+
17
+ let(:rule) { described_class }
18
+
19
+ subject { rule.new(*nodes).call.inspect }
20
+
21
+ end # shared context
22
+
23
+ shared_examples :skipping_nodes do
24
+
25
+ include_context :rule
26
+
27
+ it do
28
+ is_expected.to eql(nodes.inspect), <<-REPORT.gsub(/.+\|/, "")
29
+ |
30
+ |#{rule}
31
+ |
32
+ |Input: #{nodes.inspect}
33
+ |
34
+ |Output:
35
+ | expected: #{nodes.inspect}
36
+ | got: #{subject}
37
+ REPORT
38
+ end
39
+
40
+ end # shared examples
41
+
42
+ shared_examples :optimizing_nodes do
43
+
44
+ include_context :rule
45
+
46
+ it do
47
+ is_expected.to eql(optimized.inspect), <<-REPORT.gsub(/.+\|/, "")
48
+ |
49
+ |#{rule}
50
+ |
51
+ |Input: #{nodes.inspect}
52
+ |
53
+ |Output:
54
+ | expected: #{optimized.inspect}
55
+ | got: #{subject}
56
+ REPORT
57
+ end
58
+
59
+ end # shared examples