nodus 0.3.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.
@@ -0,0 +1,175 @@
1
+ # Like a combination of HashWithIndifferentAccess, OpenStruct, plus it takes an integer and tries looking it up
2
+ # positionally. (bleh, like php's but a little more buggy. I know I know- but it's for a very specific purpose).
3
+ #
4
+ # Mostly cribbed from rubinius's ostruct: https://github.com/rubysl/rubysl-ostruct/blob/2.0/lib/rubysl/ostruct/ostruct.rb
5
+ # with just the [] access function changed and method_missing changed so it delegates unfound things to the underlying
6
+ # table.
7
+
8
+ class FlexHash
9
+
10
+ def self.[](*arr_const)
11
+ self.new(arr_const.flatten)
12
+ end
13
+
14
+ def initialize(constructor=nil)
15
+ @table = {}
16
+ if constructor.respond_to?(:each_pair)
17
+ constructor.each_pair{|k,v| self[k.to_sym] = v }
18
+ elsif constructor.respond_to?(:each_slice)
19
+ constructor.each_slice(2){|k,v| self[k.to_sym] = v }
20
+ elsif constructor != nil
21
+ raise ArgumentError, "cannot initialize flexhash with #{constructor}", caller(3)
22
+ end
23
+ end
24
+
25
+ def initialize_copy(orig)
26
+ super
27
+ @table = @table.dup
28
+ @table.each_key { |key| new_flexhash_member(key) }
29
+ end
30
+
31
+ def to_h
32
+ @table.dup
33
+ end
34
+
35
+ def each_pair
36
+ return to_enum __method__ unless block_given?
37
+ @table.each_pair { |p| yield p }
38
+ end
39
+
40
+ def marshal_dump
41
+ @table
42
+ end
43
+
44
+ def marshal_load(x)
45
+ @table = x
46
+ @table.each_key{|key| new_flexhash_member(key)}
47
+ end
48
+
49
+ def modifiable
50
+ begin
51
+ @modifiable = true
52
+ rescue
53
+ raise TypeError, "can't modify frozen #{self.class}", caller(3)
54
+ end
55
+ @table
56
+ end
57
+ protected :modifiable
58
+
59
+ def new_flexhash_member(name)
60
+ name = name.to_sym
61
+ unless respond_to?(name)
62
+ define_singleton_method(name) { @table[name] }
63
+ define_singleton_method("#{name}=") { |x| modifiable[name] = x }
64
+ end
65
+ name
66
+ end
67
+ protected :new_flexhash_member
68
+
69
+ def method_missing(mid, *args, &block)
70
+ mname = mid.id2name
71
+ len = args.length
72
+ if mname.chomp!('=')
73
+ if len != 1
74
+ raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
75
+ end
76
+ modifiable[new_flexhash_member(mname)] = args[0]
77
+ elsif len == 0
78
+ res = @table[mid] || @table.send(mid, *args, &block)
79
+ res = FlexHash.new(res) if res.instance_of? Hash
80
+ res
81
+ else
82
+ res = @table.send(mid, *args, &block)
83
+ res = FlexHash.new(res) if res.instance_of? Hash
84
+ res
85
+ end
86
+ end
87
+
88
+ def [](name)
89
+ if Integer === name
90
+ @table[name] || @table[@table.keys[name]]
91
+ else
92
+ res = @table[name] || @table[name.try(:to_sym)]
93
+ return res if res
94
+ res = @table.select{|k,v| name === k}
95
+ return res.values[0] if res.size == 1
96
+ res
97
+ end
98
+ end
99
+
100
+ def []=(name, value)
101
+ modifiable[new_flexhash_member(name)] = value
102
+ end
103
+
104
+ def <<(kv)
105
+ name, value = kv
106
+ self[name] = value
107
+ end
108
+
109
+ def delete_field(name)
110
+ sym = name.to_sym
111
+ singleton_class.__send__(:remove_method, sym, "#{name}=")
112
+ @table.delete sym
113
+ end
114
+
115
+ InspectKey = :__inspect_key__ # :nodoc:
116
+
117
+ def inspect
118
+ str = "#<#{self.class}"
119
+
120
+ ids = (Thread.current[InspectKey] ||= [])
121
+ if ids.include?(object_id)
122
+ return str << ' ...>'
123
+ end
124
+
125
+ ids << object_id
126
+ begin
127
+ first = true
128
+ for k,v in @table
129
+ str << "," unless first
130
+ first = false
131
+ str << " #{k}=#{v.inspect}"
132
+ end
133
+ return str << '>'
134
+ ensure
135
+ ids.pop
136
+ end
137
+ end
138
+ alias :to_s :inspect
139
+
140
+ attr_reader :table # :nodoc:
141
+ protected :table
142
+
143
+ def ==(other)
144
+ return false unless other.kind_of?(OpenStruct)
145
+ @table == other.table
146
+ end
147
+
148
+ def eql?(other)
149
+ return false unless other.kind_of?(OpenStruct)
150
+ @table.eql?(other.table)
151
+ end
152
+
153
+ def hash
154
+ @table.hash
155
+ end
156
+ end
157
+
158
+ # Kind of like FlexHash, but assumes the elements of the array have a :name method, and allows duplicates
159
+ class FlexArray < Array
160
+ def [](k)
161
+ return super if Fixnum === k
162
+ res = self.find_all{|element| k === element.name || k.to_s === element.name.to_s}
163
+ return nil if res.blank?
164
+ res = res[0] if res.size == 1
165
+ res
166
+ end
167
+
168
+ def method_missing(mid, *args, &block)
169
+ mname = mid.id2name
170
+ len = args.length
171
+ res = self[mid]
172
+ return res unless res.blank?
173
+ super
174
+ end
175
+ end
@@ -0,0 +1,77 @@
1
+ require 'active_support/all'
2
+ require 'extensions'
3
+ require 'flexhash'
4
+ require 'mathn'
5
+
6
+ module Nodus
7
+ SRCDIR = File.dirname(__FILE__)
8
+ @_error_msg_map = {}
9
+
10
+ def self.def_exception(sym, msg, superclass=RuntimeError)
11
+ klass = Class.new(superclass)
12
+ Nodus.const_set(sym, klass)
13
+ @_error_msg_map[klass] = msg
14
+ end
15
+
16
+ def self._error_msg(klass) @_error_msg_map[klass] end
17
+
18
+ def self.const_missing(cname)
19
+ m = "nodus/#{cname.to_s.underscore}"
20
+ require m
21
+ klass = const_get(cname)
22
+ return klass if klass
23
+ super
24
+ end
25
+ end
26
+
27
+ def error(klass, *args)
28
+ msg = Nodus._error_msg(klass)
29
+ msg ||= args.shift
30
+ raise klass, sprintf(*([msg] + args))
31
+ end
32
+
33
+ class Object
34
+ def try_dup()
35
+ self.dup
36
+ rescue
37
+ self
38
+ end
39
+ end
40
+
41
+ class Class
42
+ def save_as(klass_name)
43
+ Object.const_set(klass_name, self)
44
+ end
45
+
46
+ # Set class instance variable attributes, and see that the values get inherited by subclasses. Attribute readers are
47
+ # also set up for object instances that copy it from the class.
48
+ #
49
+ # The values are `dup`ed if possible and simply handed over otherwise (when inheriting and instantiating).
50
+ #
51
+ # Note that this seems to behave very differently than active-support's `class_attribute` method- which seems to use
52
+ # actual class-variables with all the problems they end up having. (of course I could have just been using
53
+ # active-support's wrong).
54
+ #
55
+ # The only safety that keeps these from affecting class instance variables where they shouldn't is the naming
56
+ # convention. Anything more clever than that and my ruby metaprogramming skills weren't up to snuff.
57
+ #
58
+ # Also, as you can surmise, it won't work if a class uses the `inherited` hook and fails to call super.
59
+ #
60
+ def class_attr_inheritable(attr_name, init_as=nil)
61
+ self.class_eval("def self.#{attr_name};@__cai__#{attr_name} end")
62
+ self.class_eval("def self.#{attr_name}=(v);@__cai__#{attr_name}=v end")
63
+ self.send("#{attr_name}=", init_as) unless init_as.nil?
64
+ self.class_eval("def #{attr_name};@#{attr_name} ||= self.class.#{attr_name}.try_dup end")
65
+ end
66
+
67
+ def inherited(subclass)
68
+ instance_variables.each do |v|
69
+ next unless v.to_s.starts_with?('@__cai__')
70
+ new_val = self.instance_variable_get(v).try_dup
71
+ subclass.instance_variable_set(v, new_val)
72
+ end
73
+ end
74
+ end
75
+
76
+ require 'proplist'
77
+ require 'nodus/nodes'
@@ -0,0 +1,160 @@
1
+ class Object
2
+ def kind_of_node?() false end
3
+ end
4
+
5
+ module Nodus
6
+ module Nodes
7
+ class Param < PropSet
8
+ default required: false, hidden: false
9
+ inverse visible: :hidden, required: :optional
10
+ def realize(val) self.default = val; self.hidden = true end
11
+ def realized?() self.hidden? || self.optional? || self.has_default? end
12
+ def realized()
13
+ return self.default if self.has_default?
14
+ error RuntimeError, "Parameter #{self.name} is required but hasn't had any values set." if self.required?
15
+ nil
16
+ end
17
+ end
18
+
19
+ class StreamPort < PropSet
20
+ # `| input: (operational<output-port[s]> | consumed [control]) x (optional | required)`
21
+ # `| output: ( operational<input-port[s]> | generated [control]) x (tap | primary)`
22
+
23
+ inverse input: :output
24
+ inverse optional: :required
25
+ inverse tap: :primary
26
+ inverse control: :stream
27
+
28
+ default optional: true
29
+ default primary: true
30
+ end
31
+
32
+ # I think we'll want the following to be completely disjoint:
33
+ # (common methods) ⊔ (node methods) ⊔ (parameter names) ⊔ (node names) ⊔ (stream-port names)
34
+ # This way we can do method_missing safely to specify params, nodes, ports, or anything else depending on the context.
35
+ #
36
+ # TODO: make nodes aware of their container? in order, perhaps, for them to ask the container who they should be
37
+ # connected to instead of the other way around?
38
+ #
39
+ class Node
40
+ class_attr_inheritable :title, nil
41
+ class_attr_inheritable :parameters, PropList.new(Param)
42
+ class_attr_inheritable :symmetric_ports, PropList.new(StreamPort)
43
+ class_attr_inheritable :consumed_ports, PropList.new(StreamPort)
44
+ class_attr_inheritable :generated_ports, PropList.new(StreamPort)
45
+
46
+ class_attr_inheritable :sub, FlexArray.new # Sub-nodes- mostly for later subclasses
47
+ class_attr_inheritable :channels, FlexArray.new # Binding pairs of inner nodes
48
+
49
+ class << self
50
+ def kind_of_node?() true end
51
+ def param(param_name, *args)
52
+ arg_hash = (Hash === args.last ? args.pop : {})
53
+ arg_hash.merge!({name: param_name, node: self.title, node_type: self, node_name: self.name})
54
+ parameters << [param_name, args << arg_hash]
55
+ end
56
+
57
+ def compose(*args, &block)
58
+ Class.new(self) do |new_klass|
59
+ new_klass.on_compose(*args)
60
+ new_klass.instance_exec(new_klass, &block) if block_given?
61
+ end
62
+ end
63
+ alias_method :[], :compose
64
+
65
+ # Override this at will with whatever parameters you want- just remember to call super, and with the right
66
+ # parameters
67
+ def on_compose(title)
68
+ error ArgumentError, "First argument to compose needs to be the symbolic title, not `#{title.inspect}`" unless title.kind_of? Symbol
69
+ self.title = title
70
+ end
71
+
72
+ # TODO:
73
+ # undefined_parameters() #=> tell what required parameters don't have defaults set
74
+ # complete?() #=> all required parameters & connections defined (well enough)
75
+ end
76
+
77
+
78
+ # --------------------------------- Instance --------------------------------------------------------
79
+
80
+ # Initialize will usually allow any parameters (/parameter overrides), and any object-level connection information
81
+ # required.
82
+ def initialize(*params)
83
+ # fill params with non-hash heads of args and then use any remaining hash to fill in more params
84
+ # runtime error if some required parameters are not set
85
+ end
86
+ end
87
+
88
+
89
+ class ConcurrentNode < Node
90
+ class << self
91
+ # Defined by aggregate of all parameters, input ports, and output ports (of all kinds). Probably automatically
92
+ # need their own naming conventions...
93
+
94
+ def on_compose(title, *sub_nodes)
95
+ super(title)
96
+ sub_nodes.each{|node| sub << node}
97
+ end
98
+
99
+ #def parameters
100
+ # sub.map{|s| s.parameters}
101
+ #end
102
+ end
103
+ end
104
+
105
+
106
+ class Pipe < Node
107
+
108
+ end
109
+
110
+ # Given: enumerator or something that can be to_enum'ed
111
+ # Given: lambda or proc or block - params are gathered by reflection
112
+ # Given: class - assumed to be enumerable (or perhaps have a wrappable loop function?) params gathered via
113
+ # reflection on initialize.
114
+ # see http://stackoverflow.com/questions/4982630/trouble-yielding-inside-a-block-lambda when we get to lambdas etc.
115
+ #
116
+ class Generator < Node
117
+ class << self
118
+ attr_reader :kernel
119
+
120
+ def on_compose(title, kernel=nil, &block)
121
+ super(title)
122
+ @kernel = kernel || block
123
+ case @kernel
124
+ when Enumerator then @kernel = @kernel.lazy # No parameters
125
+ when Class
126
+ init_params = @kernel.instance_method(:initialize).parameters
127
+ init_params.each{|kind,pname| param(pname, (kind == :req ? :required : :optional))}
128
+ when Node
129
+ # TODO: Simply verify that the kernel has no input ports and create this thin wrapper around it...
130
+ # Although it still might make sense to warn that this is a senseless act? (unless it becomes
131
+ # necessary for some sorts of renaming etc.?)
132
+ error NotImplementedError
133
+ else
134
+ error ArgumentError, "Generator Nodes don't support #{kernel.inspect} as a kernel"
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ # given:
143
+ # parent_1 : p1, p2, p3
144
+ # parent_2 : p2, p3, p4
145
+ # parent_3 : p5
146
+ #
147
+ #
148
+ # parent_1__p1 -> valid
149
+ # parent_1__p2 -> valid
150
+ # ...
151
+ #
152
+ # p1, p4, p5 -> valid (maps to parent_1__p1 etc.)
153
+ # p2, p3 -> exception asks parent_1__... or parent_2__...?
154
+ # parent_1, parent_2 -> exception asks which property (p1, p2, or p3), or (p2, p3, p4)
155
+ # parent_3 -> valid (maps to parent_3__p5)
156
+ #
157
+ # doesn't solve the problem of nodes having the same name running in a concurrent composition (for example)!
158
+ #
159
+ # ability to rename params & ports as they get composed... (or as something else done while currying etc...)
160
+ #
@@ -0,0 +1,12 @@
1
+ module Nodus
2
+ class Stream
3
+ def initialize(originator)
4
+ @origin = originator
5
+ @seq_id = 0
6
+ end
7
+
8
+ def make_new_token
9
+ Nodus::Token.new(self, @seq_id += 1)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+
2
+
3
+
4
+ # - [ ] modification graph
5
+ # - [ ] timings in graph
6
+ # - [ ] current-data per thread/context
7
+ # - [ ] data appending
8
+ # - [ ] data freezing/locking per thread/context
9
+ #
10
+
11
+ module Nodus
12
+ def self.timestamp
13
+ # Use a better Process.clock_gettime time instead (not supported by rubinius yet)
14
+ Time.now
15
+ end
16
+
17
+ # TODO: probably a token wrapper which designates the active data of the underlying token. This way the token can be
18
+ # the same token across all parallel streams, while each parallel stream will have it's own temporary view of it at
19
+ # each step...
20
+
21
+ class Token
22
+ attr_reader :seq_id, :stream, :timings
23
+ def initialize(stream, seq_id)
24
+ @stream = stream
25
+ @seq_id = seq_id
26
+ @timings = {generated: Nodus.timestamp}
27
+ @data = {}
28
+
29
+ end
30
+ end
31
+ end