nodus 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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