to_robust 1.0.0.pre

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 (44) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +39 -0
  3. data/lib/global/fix.rb +72 -0
  4. data/lib/global/fix/add_atom.rb +28 -0
  5. data/lib/global/fix/atom.rb +51 -0
  6. data/lib/global/fix/init.rb +4 -0
  7. data/lib/global/fix/remove_atom.rb +16 -0
  8. data/lib/global/fix/swap_atom.rb +26 -0
  9. data/lib/global/global.rb +155 -0
  10. data/lib/global/init.rb +10 -0
  11. data/lib/global/kernel/argument_error.rb +22 -0
  12. data/lib/global/kernel/exception.rb +9 -0
  13. data/lib/global/kernel/init.rb +4 -0
  14. data/lib/global/kernel/no_method_error.rb +15 -0
  15. data/lib/global/kernel/zero_division_error.rb +21 -0
  16. data/lib/global/report.rb +42 -0
  17. data/lib/global/robust_proc.rb +48 -0
  18. data/lib/global/strategies/divide_by_zero_error_strategy.rb +277 -0
  19. data/lib/global/strategies/init.rb +5 -0
  20. data/lib/global/strategies/no_method_error_strategy.rb +92 -0
  21. data/lib/global/strategies/wrong_arguments_error_strategy.rb +174 -0
  22. data/lib/global/strategy.rb +67 -0
  23. data/lib/local/init.rb +6 -0
  24. data/lib/local/kernel/init.rb +1 -0
  25. data/lib/local/kernel/module.rb +43 -0
  26. data/lib/local/local.rb +43 -0
  27. data/lib/local/strategies/bignum_division_strategy.rb +11 -0
  28. data/lib/local/strategies/bignum_modulus_strategy.rb +11 -0
  29. data/lib/local/strategies/fixnum_coerce_strategy.rb +11 -0
  30. data/lib/local/strategies/fixnum_division_strategy.rb +12 -0
  31. data/lib/local/strategies/fixnum_modulus_strategy.rb +12 -0
  32. data/lib/local/strategies/float_division_strategy.rb +11 -0
  33. data/lib/local/strategies/float_modulus_strategy.rb +12 -0
  34. data/lib/local/strategies/init.rb +37 -0
  35. data/lib/local/strategies/numeric_division_strategy.rb +11 -0
  36. data/lib/local/strategies/numeric_modulus_strategy.rb +12 -0
  37. data/lib/local/strategies/soft_binding_strategy.rb +108 -0
  38. data/lib/local/strategies/swap_method_strategy.rb +32 -0
  39. data/lib/local/strategy.rb +18 -0
  40. data/lib/to_robust.rb +4 -0
  41. data/test/local/float_division.rb +28 -0
  42. data/test/local/integer_division.rb +28 -0
  43. data/test/local/method_binding.rb +45 -0
  44. metadata +109 -0
@@ -0,0 +1,11 @@
1
+ # Ensures that integer division returns 0 if the denominator is zero.
2
+ class ToRobust::Local::Strategies::NumericDivisionStrategy < ToRobust::Local::Strategies::SwapMethodStrategy
3
+
4
+ # Constructs a new NumericDivisionStrategy.
5
+ def initialize
6
+ super(Numeric, :div, :__div) do |other|
7
+ other.zero? ? 0 : __div(other)
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,12 @@
1
+ # Ensures that the modulus operator returns 0 if a division by zero
2
+ # error occurs.
3
+ class ToRobust::Local::Strategies::NumericModulusStrategy < ToRobust::Local::Strategies::SwapMethodStrategy
4
+
5
+ # Constructs a new NumericModulusStrategy.
6
+ def initialize
7
+ super(Numeric, :divmod, :__divmod) do |other|
8
+ other.zero? ? 0 : __divmod(other)
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,108 @@
1
+ require 'levenshtein'
2
+
3
+ # Exploits the 'method_missing' hook to implement a custom message passing protocol.
4
+ #
5
+ # All methods for each associated context are hidden by prepending their names with "__" and
6
+ # making them private. These hidden methods can later be accessed using the "hidden_methods"
7
+ # method on an associated context.
8
+ #
9
+ # All method calls will fire the 'method_missing' callback, since the method is no longer
10
+ # attached to the object at the original address. The method missing callback then inspects the
11
+ # method call and attempts to find the nearest method to that which was requested (based
12
+ # on the Levenshtein distance between the requested method and a candidate method).
13
+ #
14
+ # This way method calls like 'ad(3,2)' and 'adda(3,2)' would map to 'add(3,2)', giving methods
15
+ # a soft binding.
16
+ #
17
+ # If the nearest method has an arity which does not match the number of parameters supplied to
18
+ # the method call, then:
19
+ # * If there are too few parameters, pad the list with zeroes (arbitrary choice!).
20
+ # * If there are too many parameters, restrict the length of the list to the arity of the method.
21
+ class ToRobust::Local::Strategies::SoftBindingStrategy < ToRobust::Local::Strategy
22
+
23
+ # Allow the maximum acceptable Levenshtein distance to a candidate method to be
24
+ # adjusted.
25
+ @max_distance = 5
26
+ class << self
27
+ attr_accessor :max_distance
28
+ end
29
+
30
+ # Caches the list of contexts for later use.
31
+ def prepare!(contexts)
32
+ @contexts = contexts
33
+ end
34
+
35
+ # Hides all methods attached to each context (by prepending their names with "__" and making
36
+ # them private) before attaching a method missing handler to each of the contexts.
37
+ def enable!
38
+ @contexts.each do |c|
39
+ c.hide_methods!
40
+ c.singleton_class.send(:define_method, :method_missing) do |method_name, *args, &block|
41
+
42
+ # Convert the method symbol to a string, ready for lookup.
43
+ method_name = method_name.to_s
44
+
45
+ # Maximum levenshtein distance allowed between a candidate and the requested method.
46
+ max_distance = RubyToRobust::Local::Strategies::SoftBindingStrategy.max_distance
47
+
48
+ # Cache the list of candidate methods.
49
+ candidates = hidden_methods
50
+
51
+ # If no method exists with the given name then find the method with the closest
52
+ # name (measured by levenshtein distance). Only consider candidates whose distance
53
+ # to the requested method is less or equal to the maximum distance.
54
+ unless candidates.key? method_name
55
+
56
+ best_candidate = nil
57
+ best_score = nil
58
+ candidates.each_key do |cname|
59
+
60
+ # Only calculate the distance if the difference in length between the requested
61
+ # and candidate methods is less or equal to the maximum distance.
62
+ unless (cname.length - method_name.length).abs > max_distance
63
+ distance = Levenshtein.distance(cname, method_name)
64
+ if distance <= max_distance and (best_score.nil? or distance < best_score)
65
+ best_candidate = cname
66
+ best_score = distance
67
+ end
68
+ end
69
+ end
70
+
71
+ # Attempt to call the best candidate.
72
+ # If no appropriate candidate is found, raise a NoMethodError.
73
+ raise NoMethodError if best_candidate.nil?
74
+ method_name = best_candidate
75
+
76
+ end
77
+
78
+ # Retrieve the method object for the selected method.
79
+ method = candidates[method_name]
80
+
81
+ # If no arguments are provided and the arity of this method is greater than zero,
82
+ # then return zero.
83
+ return 0 if method.arity > 0 and args.length == 0
84
+
85
+ # If fewer arguments than necessary are provided then pad the arguments with zeros.
86
+ args = args.fill(0, args.length...method.arity) if method.arity > args.length
87
+
88
+ # If more arguments than necessary are provided, restrict them to the function's
89
+ # arity.
90
+ args = args.first(method.arity) if method.arity != -1 and args.length > method.arity
91
+
92
+ # Call the method with the filtered arguments.
93
+ return method.call(*args)
94
+
95
+ end
96
+ end
97
+ end
98
+
99
+ # Restores all the hidden methods to their previous state and removes the method missing
100
+ # handler from each associated context.
101
+ def disable!
102
+ @contexts.each do |c|
103
+ c.unhide_methods!
104
+ c.singleton_class.send(:remove_method, :method_missing)
105
+ end
106
+ end
107
+
108
+ end
@@ -0,0 +1,32 @@
1
+ # The swap method strategy is an abstract base strategy used by
2
+ # other strategies which operate by swapping a given method with
3
+ # an alternative which utilises soft semantics.
4
+ class ToRobust::Local::Strategies::SwapMethodStrategy < ToRobust::Local::Strategy
5
+
6
+ # Constructs a new swap method strategy.
7
+ #
8
+ # *Parameters:*
9
+ # * binding, the class or module of the method that should be patched.
10
+ # * name, the name of the method to be patched.
11
+ # * backup, the name to store the old method under.
12
+ # * &replacement, the replacement method itself.
13
+ def initialize(binding, name, backup, &replacement)
14
+ @binding = binding
15
+ @name = name
16
+ @backup = backup
17
+ @replacement = replacement
18
+ end
19
+
20
+ # Swaps the target method with its "softer" counterpart.
21
+ def enable!
22
+ @binding.send(:alias_method, @backup, @name)
23
+ @binding.send(:define_method, @name, @replacement)
24
+ end
25
+
26
+ # Restores the target method to its original "hard" semantics.
27
+ def disable!
28
+ @binding.send(:define_method, @name, @binding.instance_method(@backup))
29
+ @binding.send(:remove_method, @backup)
30
+ end
31
+
32
+ end
@@ -0,0 +1,18 @@
1
+ # Implements a local robustness strategy.
2
+ #
3
+ # These strategies are used to implement "softer" semantics in the program.
4
+ class ToRobust::Local::Strategy
5
+
6
+ # Prepares this strategy.
7
+ #
8
+ # *Parameters:*
9
+ # * contexts, a list of contexts that Local robustness is operating under.
10
+ def prepare!(contexts); end
11
+
12
+ # Enables this patch.
13
+ def enable!; end
14
+
15
+ # Disables this patch.
16
+ def disable!; end
17
+
18
+ end
data/lib/to_robust.rb ADDED
@@ -0,0 +1,4 @@
1
+ module ToRobust; end
2
+
3
+ require_relative 'global/init.rb'
4
+ require_relative 'local/init.rb'
@@ -0,0 +1,28 @@
1
+ require 'to_robust'
2
+ require 'test/unit'
3
+
4
+ class TestFloatDivision < Test::Unit::TestCase
5
+
6
+ def test_standard_behaviour
7
+ program = lambda { |x, y| x / y }
8
+ assert_equal(5.0, ToRobust::Local.protected { program[50.0, 10.0] })
9
+ assert_equal(5.0, program[50.0, 10.0])
10
+ end
11
+
12
+ def test_hard_dbz
13
+ program = lambda { |x, y| x / y }
14
+ assert_equal(Float::INFINITY, program[1.0, 0.0])
15
+ assert_equal(Float::INFINITY, program[2.0, 0.0])
16
+ assert_equal(Float::INFINITY, program[10.0, 0.0])
17
+ assert_equal(Float::INFINITY, program[255.0, 0.0])
18
+ end
19
+
20
+ def test_soft_dbz
21
+ program = lambda { |x, y| x / y }
22
+ assert_equal(0.0, ToRobust::Local.protected { program[1.0, 0.0] })
23
+ assert_equal(0.0, ToRobust::Local.protected { program[2.0, 0.0] })
24
+ assert_equal(0.0, ToRobust::Local.protected { program[10.0, 0.0] })
25
+ assert_equal(0.0, ToRobust::Local.protected { program[255.0, 0.0] })
26
+ end
27
+
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'to_robust'
2
+ require 'test/unit'
3
+
4
+ class TestIntegerDivision < Test::Unit::TestCase
5
+
6
+ def test_standard_behaviour
7
+ program = lambda { |x, y| x / y }
8
+ assert_equal(5, ToRobust::Local.protected { program[50, 10] })
9
+ assert_equal(5, program[50, 10])
10
+ end
11
+
12
+ def test_hard_dbz
13
+ program = lambda { |x, y| x / y }
14
+ assert_raise(ZeroDivisionError) { program[1, 0] }
15
+ assert_raise(ZeroDivisionError) { program[2, 0] }
16
+ assert_raise(ZeroDivisionError) { program[10, 0] }
17
+ assert_raise(ZeroDivisionError) { program[255, 0] }
18
+ end
19
+
20
+ def test_soft_dbz
21
+ program = lambda { |x, y| x / y }
22
+ assert_equal(0, ToRobust::Local.protected { program[1, 0] })
23
+ assert_equal(0, ToRobust::Local.protected { program[2, 0] })
24
+ assert_equal(0, ToRobust::Local.protected { program[10, 0] })
25
+ assert_equal(0, ToRobust::Local.protected { program[255, 0] })
26
+ end
27
+
28
+ end
@@ -0,0 +1,45 @@
1
+ require 'to_robust'
2
+ require 'test/unit'
3
+
4
+ module TestMethods
5
+ def self.add(x,y); x+y; end
6
+ def self.sub(x,y); x-y; end
7
+ def self.mul(x,y); x*y; end
8
+ end
9
+
10
+ class TestMethodBinding < Test::Unit::TestCase
11
+
12
+ def test_standard_bad_method_name
13
+ assert_raise(NoMethodError) { TestMethods.ad(3,2) }
14
+ assert_raise(NoMethodError) { TestMethods.addd(3,2) }
15
+ assert_raise(NoMethodError) { TestMethods.su(3,2) }
16
+ assert_raise(NoMethodError) { TestMethods.sab(3,2) }
17
+ assert_raise(NoMethodError) { TestMethods.mula(3,2) }
18
+ assert_raise(NoMethodError) { TestMethods.mu(3,2) }
19
+ end
20
+
21
+ def test_good_method_call
22
+
23
+ # Without Local robustness.
24
+ assert_equal(6, TestMethods.add(3,3))
25
+ assert_equal(9, TestMethods.mul(3,3))
26
+ assert_equal(0, TestMethods.sub(3,3))
27
+
28
+ # Using Local robustness.
29
+ assert_equal(6, RubyToRobust::Local.protected(TestMethods) { TestMethods.add(3,3) })
30
+ assert_equal(9, RubyToRobust::Local.protected(TestMethods) { TestMethods.mul(3,3) })
31
+ assert_equal(0, RubyToRobust::Local.protected(TestMethods) { TestMethods.sub(3,3) })
32
+
33
+ end
34
+
35
+
36
+ def test_soft_method_call
37
+
38
+ # Enable local robustness.
39
+ assert_equal(6, RubyToRobust::Local.protected(TestMethods) { TestMethods.ad(3,3) })
40
+ assert_equal(9, RubyToRobust::Local.protected(TestMethods) { TestMethods.ml(3,3) })
41
+ assert_equal(0, RubyToRobust::Local.protected(TestMethods) { TestMethods.sab(3,3) })
42
+
43
+ end
44
+
45
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: to_robust
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Chris Timperley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-12-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: levenshtein
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.2.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.2.2
30
+ description: ! " Soft semantics for Ruby to automatically recover from fatal exceptions
31
+ and to repair damaged programs.\n Used to improve robustness, expressiveness
32
+ and performance in Grammatical Evolution.\n"
33
+ email: christimperley@gmail.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - LICENSE
39
+ - README.md
40
+ - lib/to_robust.rb
41
+ - lib/global/fix.rb
42
+ - lib/global/global.rb
43
+ - lib/global/init.rb
44
+ - lib/global/report.rb
45
+ - lib/global/robust_proc.rb
46
+ - lib/global/strategy.rb
47
+ - lib/global/fix/add_atom.rb
48
+ - lib/global/fix/atom.rb
49
+ - lib/global/fix/init.rb
50
+ - lib/global/fix/remove_atom.rb
51
+ - lib/global/fix/swap_atom.rb
52
+ - lib/global/kernel/argument_error.rb
53
+ - lib/global/kernel/exception.rb
54
+ - lib/global/kernel/init.rb
55
+ - lib/global/kernel/no_method_error.rb
56
+ - lib/global/kernel/zero_division_error.rb
57
+ - lib/global/strategies/divide_by_zero_error_strategy.rb
58
+ - lib/global/strategies/init.rb
59
+ - lib/global/strategies/no_method_error_strategy.rb
60
+ - lib/global/strategies/wrong_arguments_error_strategy.rb
61
+ - lib/local/init.rb
62
+ - lib/local/local.rb
63
+ - lib/local/strategy.rb
64
+ - lib/local/kernel/init.rb
65
+ - lib/local/kernel/module.rb
66
+ - lib/local/strategies/bignum_division_strategy.rb
67
+ - lib/local/strategies/bignum_modulus_strategy.rb
68
+ - lib/local/strategies/fixnum_coerce_strategy.rb
69
+ - lib/local/strategies/fixnum_division_strategy.rb
70
+ - lib/local/strategies/fixnum_modulus_strategy.rb
71
+ - lib/local/strategies/float_division_strategy.rb
72
+ - lib/local/strategies/float_modulus_strategy.rb
73
+ - lib/local/strategies/init.rb
74
+ - lib/local/strategies/numeric_division_strategy.rb
75
+ - lib/local/strategies/numeric_modulus_strategy.rb
76
+ - lib/local/strategies/soft_binding_strategy.rb
77
+ - lib/local/strategies/swap_method_strategy.rb
78
+ - test/local/float_division.rb
79
+ - test/local/integer_division.rb
80
+ - test/local/method_binding.rb
81
+ homepage: https://github.com/ChrisTimperley/ruby_to_robust
82
+ licenses:
83
+ - MIT
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 1.8.6
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>'
98
+ - !ruby/object:Gem::Version
99
+ version: 1.3.1
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 1.8.28
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Adds optional soft semantics and self-repair to methods.
106
+ test_files:
107
+ - test/local/float_division.rb
108
+ - test/local/integer_division.rb
109
+ - test/local/method_binding.rb