police-dataflow 0.0.1 → 0.0.2

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.
@@ -3,37 +3,35 @@ module Police
3
3
  module DataFlow
4
4
  # Attaches a label to a piece of data.
5
5
  #
6
- # @param [Object] data the data that will be labeled
7
- # @param [Police::DataFlow::Label] label the label to be applied to the object
8
- # @return [BasicObject] either the given piece of data, or a proxy that should
9
- # be used instead of it
6
+ # @param [BasicObject] data the data that will be labeled
7
+ # @param [Police::DataFlow::Label] label the label to be applied to the
8
+ # object
9
+ # @return [BasicObject] either the given piece of data, or a proxy that
10
+ # should be used instead of it
10
11
  def self.label(data, label)
11
12
  label_set = data.__police_labels__
12
- if label_set.nil?
13
+ if nil == label_set
13
14
  proxied = data
14
15
  label_set = {}
15
- autoflow_set = {}
16
16
  else
17
17
  proxied = data.__police_proxied__
18
- autoflow_set = data.__police_autoflows__
19
18
  end
20
-
21
- if Police::DataFlow::Labeling.add_label_to_set label, label_set,
22
- autoflow_set
23
- data = Police::DataFlow::Proxying.proxy proxied, label_set, autoflow_set
24
- data
19
+
20
+ if Police::DataFlow::Labeling.add_label_to_set label, label_set
21
+ data = Police::DataFlow::Proxying.proxy proxied, label_set
25
22
  end
26
23
  data
27
24
  end
28
-
25
+
29
26
  # All the labels attached to a piece of data.
30
27
  #
31
28
  # @param [Object] data the data whose labels are queried
32
- # @return [Array<Police::DataFlow::Label>] all the labels attached to the data
29
+ # @return [Array<Police::DataFlow::Label>] all the labels attached to the
30
+ # data
33
31
  def self.labels(data)
34
32
  return [] unless label_set = data.__police_labels__
35
33
  return label_set.first.last.keys if label_set.length == 1
36
-
34
+
37
35
  labels = []
38
36
  label_set.each { |label_key, label_hash| labels.concat label_hash.keys }
39
37
  labels
@@ -41,31 +39,114 @@ module DataFlow
41
39
 
42
40
  # Label algebra.
43
41
  module Labeling
42
+ # The actual class of a proxy object.
43
+ #
44
+ # This is necessary because the class method for a proxy object must return
45
+ # the proxied object's class.
46
+ #
47
+ # @private
48
+ # This is intended to help testing the proxying code. It should not be used
49
+ # by client code.
50
+ #
51
+ # @param [BasicObject] data a proxy returned by {Police::DataFlow#label}
52
+ # @return [Class] a subclass of {Police::DataFlow::ProxyBase}, or nil if the
53
+ # given object is not a proxy
54
+ def self.proxy_class(data)
55
+ if data.__police_labels__.nil?
56
+ nil
57
+ else
58
+ data.__police_class__
59
+ end
60
+ end
61
+
44
62
  # Adds a label to the set of labels held by an object's proxy.
45
63
  #
64
+ # @param [Police::DataFlow::Label] label the label to be added to the set
46
65
  # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the
47
66
  # set of all labels that will be held by the object's proxy
48
- # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] autoflow_set
49
- # the set of labels whose autoflow? method returned true
50
67
  # @return [Boolean] false if the set already had a label of the same type, so
51
68
  # the proxy holding the set can still be used; true if the set had to be
52
69
  # expanded, so a new proxy is needed
53
- def self.add_label_to_set(label, label_set, autoflow_set)
70
+ def self.add_label_to_set(label, label_set)
54
71
  label_class = label.class
55
72
  label_key = label_class.__id__
56
73
  if label_set.has_key? label_key
57
74
  label_set[label_key][label] = true
58
- # NOTE: autoflow_set uses use the same hash, so no work is necessary
59
75
  return false
60
76
  end
61
-
77
+
62
78
  label_entry = { label => true }
63
79
  label_set[label_key] = label_entry
64
- autoflow_set[label_key] = label_entry if label_class.autoflow?
65
80
  true
66
81
  end
82
+
83
+ # Merges a set of labels into another set.
84
+ #
85
+ # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] target_set one
86
+ # of the label sets to be merged; this set will be modified by the method
87
+ # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] source_set the
88
+ # other label set that will be merged; this set will not be modified
89
+ # @return [Boolean] false if the target set already had labels of all the
90
+ # types in the source set, so the proxy holding the target object can
91
+ # still be used; true if the target set had to be expanded, so a new
92
+ # proxy is needed
93
+ def self.merge_sets!(target_set, source_set)
94
+ expanded = false
95
+ source_set.each do |class_id, label_classes|
96
+ target_classes = target_set[class_id]
97
+ if nil == target_classes
98
+ target_set[class_id] = label_classes.dup
99
+ expanded = true
100
+ else
101
+ target_classes.merge! label_classes
102
+ end
103
+ end
104
+ expanded
105
+ end
106
+
107
+ # Applies sticky labels to a piece of data.
108
+ #
109
+ # @private
110
+ # This is an version of {Police::DataFlow.label} optimized for sticky label
111
+ # propagation. User code should not depend on it.
112
+ #
113
+ # @param [BasicObject] data the data that will be labeled
114
+ # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] sticky_set a
115
+ # set of sticky labels that
116
+ # @param [Police::DataFlow::Label] label the label to be applied to the
117
+ # object
118
+ # @return [BasicObject] either the given piece of data, or a proxy that
119
+ # should be used instead of it
120
+ def self.bulk_sticky_label(data, sticky_set)
121
+ label_set = data.__police_labels__
122
+ if nil == label_set
123
+ # Unlabeled data.
124
+ return Police::DataFlow::Proxying.proxy(data, dup_set(sticky_set))
125
+ end
126
+
127
+ # TODO(pwnall): implement copy-on-write to waste less memory on gated ops
128
+ if merge_sets! label_set, sticky_set
129
+ Police::DataFlow::Proxying.proxy data.__police_proxied__, label_set
130
+ else
131
+ data
132
+ end
133
+ end
134
+
135
+ # Creates a shallow copy of a label set.
136
+ #
137
+ # The resulting copy is slightly deeper than what {Hash#clone} would produce,
138
+ # because the groups are copied as well.
139
+ #
140
+ # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the
141
+ # set of labels that will be copied
142
+ # @return [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set a
143
+ # copy of the given label set that can be used with another object's
144
+ # proxy
145
+ def self.dup_set(label_set)
146
+ Hash[label_set.map { |k, v| [k, v.dup] } ]
147
+ end
67
148
  end # namespace Police::DataFlow::Labeling
68
-
149
+
69
150
  end # namespace Police::DataFlow
70
151
 
71
152
  end # namespace Police
@@ -9,7 +9,7 @@ module Proxies
9
9
  # @param [Class] proxied_class the class whose instances will be proxied by
10
10
  # instances of the returned class
11
11
  # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the
12
- # set of all labels that will be carried by the proxied object
12
+ # set of all labels that will be held by the proxy
13
13
  # @return [Class] a Police::DataFlow::ProxyBase subclass that can proxy
14
14
  # instances of the given class
15
15
  def self.for(proxied_class, label_set)
@@ -17,20 +17,48 @@ module Proxies
17
17
  class_cache = {}
18
18
  @classes[proxied_class] = class_cache
19
19
  end
20
-
20
+
21
21
  cache_key = label_set.keys.sort!.freeze
22
22
  return class_cache[cache_key] if class_cache.has_key? cache_key
23
-
23
+
24
24
  label_classes = label_set.map { |label_key, label_hash|
25
25
  label_hash.first.first.class
26
26
  }.sort_by!(&:__id__).freeze
27
27
 
28
- proxy_class = Class.new Police::DataFlow::ProxyBase
29
- proxy_class.__police_classes__ = label_classes
28
+ proxy_class = for! proxied_class, label_classes
30
29
  class_cache[cache_key] = proxy_class
31
30
  proxy_class
32
31
  end
33
32
 
33
+ # A class whose instances proxy instances of a Ruby class.
34
+ #
35
+ # @private
36
+ # Use for instead of calling this directly.
37
+ #
38
+ # @param [Class] proxied_class the class whose instances will be proxied by
39
+ # instances of the returned class
40
+ # @param [Array<Class>] label_classes classes for all the labels that will be
41
+ # held by proxies instantiated from this class; the array should not have
42
+ # duplicates
43
+ # @return [Class] a Police::DataFlow::ProxyBase subclass that can proxy
44
+ # instances of the given class
45
+ def self.for!(proxied_class, label_classes)
46
+ proxy_class = nil
47
+ klass = proxied_class
48
+ until klass == nil
49
+ if klass == String
50
+ # TODO(pwnall): String-specific proxying
51
+ break
52
+ elsif klass == Numeric
53
+ proxy_class = Class.new Police::DataFlow::ProxyNumeric
54
+ end
55
+ klass = klass.superclass
56
+ end
57
+ proxy_class ||= Class.new Police::DataFlow::ProxyBase
58
+ proxy_class.__police_classes__ = label_classes
59
+ proxy_class
60
+ end
61
+
34
62
  # Clears the cache of proxy classes associated with Ruby classes.
35
63
  #
36
64
  # This method has a terrible impact on VM performance, and is only intended
@@ -7,43 +7,62 @@ class ProxyBase < BasicObject
7
7
  # The object being proxied by this object.
8
8
  #
9
9
  # @private
10
- # Use the Police::DataFlow API instead of reading this attribute directly.
10
+ # Use the {Police::DataFlow} API instead of reading this attribute directly.
11
11
  attr_reader :__police_proxied__
12
-
13
- # The Label instances attached to the proxied object.
12
+
13
+ # The {Police::DataFlow::Label} instances attached to the proxied object.
14
+ #
15
+ # This is a Hash whose keys are the __id__s of the Class objects for the
16
+ # labels' classes. The values are sets of labels that are instances of the
17
+ # corresponding label classes. Sets are implemented as Hashes mapping the
18
+ # label instances to boolean true values.
14
19
  #
15
20
  # @private
16
- # Use the Police::DataFlow API instead of reading this attribute directly.
21
+ # Use the {Police::DataFlow} API instead of reading this attribute directly.
17
22
  attr_reader :__police_labels__
18
-
19
- # The subset of this object's labels whose autoflow? method returns true.
23
+
24
+ # Attached {Police::DataFlow::Label} instances whose classes are sticky.
25
+ #
26
+ # @private
27
+ # This is a proxying implementation detail and should not be relied on.
28
+ attr_reader :__police_stickies__
29
+
30
+ # The actual class of a proxy object.
31
+ #
32
+ # This is necessary because the class method for a proxy object must return
33
+ # the proxied object's class.
20
34
  #
21
35
  # @private
22
- # This is an optimization used by the Police::DataFlow implementation. Do not
23
- # read it directly.
24
- attr_reader :__police_autoflows__
25
-
36
+ # Use the {Police::DataFlow} API instead of reading this attribute directly.
37
+ attr_reader :__police_class__
38
+
26
39
  # Creates a proxied object.
27
40
  #
28
41
  # @param [Object] proxied the object that will receive messages sent to the
29
42
  # newly created proxy
30
43
  # @param [Class<Police::DataFlow::ProxyBase>] proxy_class the
31
- # Police::DataFlow::ProxyBase subclass being instantiated; Object
44
+ # {Police::DataFlow::ProxyBase} subclass being instantiated; Object
32
45
  # instances can call Object#class to get to their class, but BasicObject
33
46
  # instances don't have this luxury
34
47
  # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the
35
48
  # set of all labels that will be held by the object's proxy
36
- # @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] autoflow_set
37
- # the set of labels whose autoflow? method returned true
38
- def initialize(proxied, proxy_class, label_set, autoflow_set)
49
+ def initialize(proxied, proxy_class, label_set)
39
50
  @__police_proxied__ = proxied
40
51
  @__police_labels__ = label_set
41
52
 
53
+ # Cache the sticky labels to speed up certain computations.
54
+ @__police_stickies__ = {}
55
+ @__police_labels__.each do |class_id, instance_set|
56
+ label_class = instance_set.first.first.class
57
+ next unless label_class.sticky?
58
+ # NOTE: the set of instances is intentionally shared with
59
+ # @__police_labels__ so updates to the former are automatically
60
+ # reflected in the stickies cache
61
+ @__police_stickies__[class_id] = instance_set
62
+ end
63
+
42
64
  # Holds the object's class, because Object#class is not available.
43
65
  @__police_class__ = proxy_class
44
-
45
- # Labels that flow automatically across method calls.
46
- @__police_autoflows__ = autoflow_set
47
66
  end
48
67
 
49
68
  # Handles method calls to the proxied object.
@@ -54,77 +73,98 @@ class ProxyBase < BasicObject
54
73
  # Build a fast path for future method calls, if possible.
55
74
  respond_to_missing? name, true
56
75
 
76
+ # NOTE: going out of our way to use the fast path methods, because they
77
+ # handle native method argument unwrapping correctly
78
+ if @__police_class__.method_defined?(name) ||
79
+ @__police_class__.private_method_defined?(name)
80
+ return __send__(name, *args, &block)
81
+ end
82
+
57
83
  if block
58
84
  return_value = @__police_proxied__.__send__ name, *args do |*yield_args|
59
85
  # Yielded values filtering.
60
86
  @__police_labels__.each do |_, label_hash|
61
- next unless hook = label_hash.first.first.class.yield_args_hook(name)
62
- label_hash.each do |label, _|
63
- block_args = label.__send__ hook, self, yield_args, *args
87
+ label_class = label_hash.first.first.class
88
+ if hook = label_class.yield_args_hook(name)
89
+ label_hash.each do |label, _|
90
+ yield_args = label.__send__ hook, self, yield_args, *args
91
+ end
92
+ elsif label_class.sticky?
93
+ label_hash.each do |label, _|
94
+ yield_args.map! { |arg| ::Police::DataFlow.label arg, label }
95
+ end
64
96
  end
65
97
  end
66
-
98
+
67
99
  yield_return = yield(*yield_args)
68
100
  # TODO(pwnall): consider adding a yield value filter
69
101
  next yield_return
70
102
  end
71
103
  else
72
- return_value = @__police_proxied__.__send__ name, *args, &block
104
+ return_value = @__police_proxied__.__send__ name, *args
73
105
  end
74
-
106
+
75
107
  # Return value filtering.
76
108
  @__police_labels__.each do |_, label_hash|
77
- next unless hook = label_hash.first.first.class.return_hook(name)
78
- label_hash.each do |label, _|
79
- return_value = label.__send__ hook, return_value, self, *args
109
+ label_class = label_hash.first.first.class
110
+ if hook = label_hash.first.first.class.return_hook(name)
111
+ label_hash.each do |label, _|
112
+ return_value = label.__send__ hook, return_value, self, *args
113
+ end
114
+ elsif label_class.sticky?
115
+ label_hash.each do |label, _|
116
+ return_value = ::Police::DataFlow.label return_value, label
117
+ end
80
118
  end
81
119
  end
82
120
  return return_value
83
121
  end
84
-
122
+
85
123
  # Called when Object#respond_to? returns false.
86
124
  def respond_to_missing?(name, include_private)
87
125
  return false unless @__police_proxied__.respond_to? name, include_private
88
-
126
+
89
127
  # A method on the proxied object doesn't have a corresponding proxy.
90
128
  # Fix this by creating all possible proxies.
91
-
129
+
92
130
  # NOTE: this approach is cheaper than creating proxies one by one, because
93
131
  # it plays nice with method caches
94
-
95
- ::Police::DataFlow::Proxying.add_class_methods @__police_class__,
96
- @__police_proxied__.class
97
-
132
+
133
+ ::Police::DataFlow::Proxying.add_instance_methods @__police_class__,
134
+ @__police_proxied__.class
135
+
98
136
  # NOTE: we don't want to create unnecessary singleton classes
99
137
  # target_methods = @__police_proxied__.singleton_methods true
100
138
  # unless target_methods.empty?
101
139
  # ::Police::DataFlow::Proxying.add_singleton_methods self,
102
140
  # @__police_proxied__, target_methods
103
141
  # end
104
-
142
+
105
143
  true
106
144
  end
107
-
145
+
108
146
  # Ruby 1.9 throws scary warnings if proxies define object_id.
109
147
  # In either case, it's probably best to have it match __id__.
110
148
  alias object_id __id__
111
-
149
+
112
150
  # Remove the == and != implementations from BasicObject, so that we can proxy
113
151
  # them. This is particularly important for String.
114
152
  #
115
153
  # NOTE: We don't remove equal?, because Object's documentation says that
116
154
  # BasicObject subclasses should really not override it. We also don't
117
- # remove instance_eval and instance_exec, so the code that gets executed
118
- # using them will still have its method calls proxied correctly.
155
+ # remove instance_eval and instance_exec, so the code that gets
156
+ # executed using them will still have its method calls proxied
157
+ # correctly.
119
158
  undef ==, !=
120
159
 
121
160
  class <<self
122
161
  # The classes of the labels supported by the proxy class.
123
162
  #
124
163
  # @private
125
- # This is a Police::DataFlow implementation detail. Do not read it directly.
164
+ # This is a Police::DataFlow implementation detail. Do not read it
165
+ # directly.
126
166
  attr_accessor :__police_classes__
127
- end
167
+ end
128
168
  end # class Police::DataFlow::ProxyBase
129
169
 
130
170
  end # namespace Police::DataFlow
@@ -0,0 +1,22 @@
1
+ module Police
2
+
3
+ module DataFlow
4
+
5
+ # Base class for labeled Numeric replacements.
6
+ class ProxyNumeric < ProxyBase
7
+ # Called when a regular Numeric is added, multiplied, etc to a proxied one.
8
+ #
9
+ # Wraps the regular Numeric instance with a proxy, so that call dispatch can
10
+ # take place.
11
+ def coerce(numeric)
12
+ if numeric.__police_labels__
13
+ return [numeric, self]
14
+ end
15
+ proxied_numeric = ::Police::DataFlow::Proxying.proxy numeric, {}
16
+ [proxied_numeric, self]
17
+ end
18
+ end
19
+
20
+ end # namespace DataFlow
21
+
22
+ end # namespace Police