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.
- data/LICENSE +20 -0
- data/README.md +39 -0
- data/lib/global/fix.rb +72 -0
- data/lib/global/fix/add_atom.rb +28 -0
- data/lib/global/fix/atom.rb +51 -0
- data/lib/global/fix/init.rb +4 -0
- data/lib/global/fix/remove_atom.rb +16 -0
- data/lib/global/fix/swap_atom.rb +26 -0
- data/lib/global/global.rb +155 -0
- data/lib/global/init.rb +10 -0
- data/lib/global/kernel/argument_error.rb +22 -0
- data/lib/global/kernel/exception.rb +9 -0
- data/lib/global/kernel/init.rb +4 -0
- data/lib/global/kernel/no_method_error.rb +15 -0
- data/lib/global/kernel/zero_division_error.rb +21 -0
- data/lib/global/report.rb +42 -0
- data/lib/global/robust_proc.rb +48 -0
- data/lib/global/strategies/divide_by_zero_error_strategy.rb +277 -0
- data/lib/global/strategies/init.rb +5 -0
- data/lib/global/strategies/no_method_error_strategy.rb +92 -0
- data/lib/global/strategies/wrong_arguments_error_strategy.rb +174 -0
- data/lib/global/strategy.rb +67 -0
- data/lib/local/init.rb +6 -0
- data/lib/local/kernel/init.rb +1 -0
- data/lib/local/kernel/module.rb +43 -0
- data/lib/local/local.rb +43 -0
- data/lib/local/strategies/bignum_division_strategy.rb +11 -0
- data/lib/local/strategies/bignum_modulus_strategy.rb +11 -0
- data/lib/local/strategies/fixnum_coerce_strategy.rb +11 -0
- data/lib/local/strategies/fixnum_division_strategy.rb +12 -0
- data/lib/local/strategies/fixnum_modulus_strategy.rb +12 -0
- data/lib/local/strategies/float_division_strategy.rb +11 -0
- data/lib/local/strategies/float_modulus_strategy.rb +12 -0
- data/lib/local/strategies/init.rb +37 -0
- data/lib/local/strategies/numeric_division_strategy.rb +11 -0
- data/lib/local/strategies/numeric_modulus_strategy.rb +12 -0
- data/lib/local/strategies/soft_binding_strategy.rb +108 -0
- data/lib/local/strategies/swap_method_strategy.rb +32 -0
- data/lib/local/strategy.rb +18 -0
- data/lib/to_robust.rb +4 -0
- data/test/local/float_division.rb +28 -0
- data/test/local/integer_division.rb +28 -0
- data/test/local/method_binding.rb +45 -0
- 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,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
|