andromeda 0.1 → 0.1.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.
@@ -0,0 +1,105 @@
1
+ module Andromeda
2
+
3
+ module Impl
4
+
5
+ class Atom < Atomic
6
+ include To_S
7
+
8
+ def initialize(init_val = nil)
9
+ super init_val
10
+ end
11
+
12
+ def empty? ; value.nil? end
13
+ def full? ; ! value.nil? end
14
+
15
+ def to_short_s ; "(#{To_S.short_s(value)})" end
16
+ alias_method :inspect, :to_s
17
+
18
+ def wait_while(&test)
19
+ while test.call(value) ; Thread::pass end
20
+ end
21
+
22
+ def wait_until_eq(val = nil)
23
+ raise ArgumentError unless val.kind_of?(Fixnum)
24
+ wait_while { |v| v != val }
25
+ end
26
+
27
+ def wait_until_ne(val = nil)
28
+ raise ArgumentError unless val.kind_of?(Fixnum)
29
+ wait_while { |v| v == val }
30
+ end
31
+
32
+ def wait_until_empty?
33
+ wait_until_eq nil
34
+ end
35
+
36
+ def wait_until_full?
37
+ wait_until_ne nil
38
+ end
39
+
40
+ def with_value
41
+ update { |v| yield v ; v }
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ module Atom
48
+
49
+ class Region
50
+ include Impl::To_S
51
+
52
+ def to_short_s ; "(#{Impl::To_S.short_s(value)})" end
53
+ alias_method :inspect, :to_s
54
+
55
+ def initialize(init_value = 0)
56
+ init_value = init_value[:init_value].to_i if init_value.kind_of?(Hash)
57
+ raise ArgumentError unless init_value.kind_of?(Fixnum)
58
+ @count = Impl::Atom.new init_value
59
+ end
60
+
61
+ alias_method :inspect, :to_s
62
+
63
+ def value ; @count.value end
64
+
65
+ def enter(amount = 1)
66
+ raise ArgumentError unless amount.kind_of?(Fixnum)
67
+ raise ArgumentError unless amount >= 0
68
+ @count.update { |v| v + amount }
69
+ end
70
+
71
+ def leave(amount = 1)
72
+ raise ArgumentError unless amount >= 0
73
+ raise ArgumentError unless amount.kind_of?(Fixnum)
74
+ @count.update { |v| v - amount }
75
+ end
76
+
77
+ def wait_until_eq(val) ; @count.wait_until_eq(val) end
78
+ end
79
+
80
+ class Var < Impl::Atom
81
+ end
82
+
83
+ class FillOnce < Var
84
+ def empty! ; super.update nil end
85
+
86
+ def update(v)
87
+ super.update do |o|
88
+ raise ArgumentError, 'Attempt to refill FillOnce' if o
89
+ v
90
+ end
91
+ end
92
+ end
93
+
94
+ class Combiner < Var
95
+ attr_reader :combiner
96
+
97
+ def update(v)
98
+ super.update do |o|
99
+ if combiner then combiner.call o, v else v end
100
+ end
101
+ end
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,242 @@
1
+ module Andromeda
2
+
3
+ module Cmd
4
+
5
+ class Cmd
6
+ attr_reader :cmd
7
+ attr_reader :data
8
+ attr_reader :time
9
+
10
+ def initialize(cmd, data = {}, cmd_time = nil)
11
+ raise ArgumentError unless cmd.kind_of?(Symbol)
12
+ @cmd = cmd
13
+ @data = data
14
+ @time = if cmd_time then cmd_time else Time.now.to_i end
15
+ @comment = nil
16
+ end
17
+
18
+ def comment ; @comment || '' end
19
+ def comment=(str = nil) ; @comment = str end
20
+
21
+ def if_cmd(sym) ; if sym == cmd then yield data else seld end end
22
+
23
+ def as_json
24
+ h = { cmd: cmd, data: (data.as_json rescue data), time: time }
25
+ c = @comment
26
+ h[:comment] = c if c
27
+ h
28
+ end
29
+
30
+ def to_s ; as_json.to_json end
31
+
32
+ def with_comment(str = nil)
33
+ @comment = str
34
+ self
35
+ end
36
+
37
+ def self.new_input(cmd, data = {}, cmd_time = nil)
38
+ inner = Cmd.new cmd, data, cmd_time
39
+ Cmd.new :input, inner, cmd_time
40
+ end
41
+
42
+ def self.from_json(json)
43
+ Cmd.new json['cmd'].to_sym, json['data'], (json['time'] rescue nil)
44
+ end
45
+ end
46
+
47
+ class FileCmdPlan < Kit::InlineKeyRouter
48
+ attr_reader :path
49
+ attr_reader :mode
50
+ attr_reader :file
51
+
52
+ meth_spot :open
53
+ meth_spot :sync
54
+ meth_spot :close
55
+ meth_spot :input
56
+
57
+ signal_spot :open
58
+ signal_spot :close
59
+ signal_spot :sync
60
+
61
+ def data_map(name, data)
62
+ if data.is_a?(Cmd) then data else Cmd.new(data) end
63
+ end
64
+
65
+ def data_key(name, data) ; data.cmd end
66
+ def data_val(name, data) ; data.data end
67
+
68
+ def data_tag(name, key, val, tags_in)
69
+ tags_out = super
70
+ if name == :input
71
+ tags_out[:time] = val.time
72
+ tags_out[:comment] = val.comment
73
+ end
74
+ tags_out
75
+ end
76
+
77
+ def on_open(key, val)
78
+ if @file
79
+ signal_error ArgumentError.new("associated file already open")
80
+ else
81
+ @path = val[:path] if val[:path]
82
+ @mode = val[:mode] if val[:mode]
83
+ @mode ||= init_mode
84
+ @file = File.open @path, @mode
85
+ end
86
+ end
87
+
88
+ def on_input(key, val)
89
+ exit << val if exit
90
+ end
91
+
92
+ def on_sync(key, val)
93
+ f = @file ; sync_file f if f
94
+ end
95
+
96
+ def on_close(key, val)
97
+ if @file
98
+ begin
99
+ close_file(@file)
100
+ ensure
101
+ @file = nil
102
+ end
103
+ else
104
+ signal_error ArgumentError.new("associated file not open")
105
+ end
106
+ end
107
+
108
+ protected
109
+
110
+ def close_file(f)
111
+ begin
112
+ sync_file f
113
+ ensure
114
+ f.close
115
+ end
116
+ end
117
+
118
+ def sync_file(f) ; end
119
+ end
120
+
121
+ class Writer < FileCmdPlan
122
+
123
+ def init_mode ; 'w' end
124
+
125
+ def on_input(key, val)
126
+ signal_error ArgumentError.new("associated filed not open") unless file
127
+ cmd = val.cmd
128
+ raise ArgumentError, "invalid cmd" unless cmd.kind_of?(Symbol)
129
+ data = val.data
130
+ str = if data then data.to_json else '' end
131
+ len = str.length + 1
132
+ tim = val.time if val.time
133
+ tim = Time.now unless tim
134
+ tim = tim.to_i unless tim.kind_of?(Fixnum)
135
+ new_str = ''
136
+ str.each_line { |line| new_str << "... #{line}\n" }
137
+ str = nil
138
+ len = new_str.length
139
+ val = tags[:comment]
140
+ val.each_line { |line| file.write "\# #{line}\n" } if val
141
+ file.write "<<< ANDROMEDA START :#{cmd} TIME #{tim} LEN #{len} >>>\n"
142
+ file.write new_str
143
+ file.write "<<< ANDROMEDA END :#{cmd} >>>\n"
144
+ super key, val
145
+ end
146
+
147
+ protected
148
+
149
+ def sync_file(f)
150
+ f.sync
151
+ f.fsync rescue nil
152
+ end
153
+ end
154
+
155
+ class Reader < Kit::FileReader
156
+ attr_reader :start_matcher, :end_matcher, :comment_matcher, :line_matcher
157
+
158
+ def initialize(config = {})
159
+ super config
160
+ @start_matcher = /<<< ANDROMEDA START :(\w+) TIME (\d+) LEN (\d+) >>>/
161
+ @end_matcher = /<<< ANDROMEDA END :(\w+) >>>/
162
+ @comment_matcher = /#(.*)/
163
+ # remember to change the offset for :line, too whenever you change this
164
+ @line_matcher = /... (.*)/
165
+ end
166
+
167
+ def match_line(state, line)
168
+ m = @start_matcher.match line
169
+ return yield :start, state, ({ cmd: m[1].to_sym, tim: m[2].to_i, len: m[3].to_i }) if m
170
+ m = @end_matcher.match line
171
+ return yield :end, state, m[1].to_sym if m
172
+ m = @comment_matcher.match line
173
+ return yield :comment, state, m[1] if m
174
+ m = @line_matcher.match line
175
+ return yield :line, state, m[1] if m
176
+ yield :garbage, state, line
177
+ end
178
+
179
+ def on_enter(key, val)
180
+ super key, val do |file|
181
+ fst = tags[:first]
182
+ lst = tags[:last]
183
+
184
+ state = { :comment => true, :start => true, :cont => true }
185
+ while (line = file.gets) && state[:cont]
186
+ line = line.chomp
187
+ match_line(state, line) do |token, state, parts|
188
+ signal_error ArgumentError.new("Skipping unexpected token #{token} in line '#{line}' (state: #{state})") unless state[token]
189
+ case token
190
+ when :comment
191
+ if state[:comment_str]
192
+ then state[:comment_str] << parts
193
+ else state[:comment_str] = parts end
194
+ when :start
195
+ state.delete :comment
196
+ state.delete :start
197
+ state[:line] = true
198
+ state[:end] = true
199
+ parts[:data] = ''
200
+ state[:len] = 0
201
+ state[:cur] = parts
202
+ parts[:comment] = state[:comment_str]
203
+ state.delete :comment_str
204
+ when :line
205
+ state[:len] += parts.length + 5
206
+ state[:cur][:data] << "#{parts}\n"
207
+ when :end
208
+ state.delete :line
209
+ state.delete :end
210
+ state[:start] = true
211
+ state[:comment] = true
212
+ cur = state[:cur]
213
+ data = cur[:data]
214
+ signal_error ArgumentError.new("Start (#{cur[:cmd]}) and end (#{parts})cmd mismatch") unless cur[:cmd] == parts
215
+ signal_error ArgumentError.new("Length mismatch (expected: #{cur[:len]}, found: #{state[:len]})") unless cur[:len] == state[:len]
216
+ exit << cur if exit
217
+ state[:cont] = false unless file.pos <= lst
218
+ else
219
+ signal_error ArgumentError.new("Garbage encountered (line: '#{line}')")
220
+ return
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ end
228
+
229
+ class Parser < Plan
230
+
231
+ def on_enter(key, val)
232
+ data = val[:data].chomp
233
+ data = JSON::parse(data)
234
+ cmd = Cmd.new val[:cmd], data, val[:time]
235
+ rem = val[:comment] rescue nil
236
+ cmd.with_comment rem if rem
237
+ exit << cmd if cmd
238
+ end
239
+ end
240
+
241
+ end
242
+ end
File without changes
@@ -0,0 +1,44 @@
1
+ module Andromeda
2
+
3
+ class ::Object
4
+
5
+ def identical_copy
6
+ case clone_to_copy?
7
+ when false then self
8
+ when true then clone
9
+ else clone rescue self end
10
+ end
11
+
12
+ def clone_to_copy? ; cloneable? end
13
+ def cloneable? ; nil end
14
+ end
15
+
16
+ class ::NilClass
17
+ def cloneable? ; false end
18
+ end
19
+
20
+ class ::FalseClass
21
+ def cloneable? ; false end
22
+ end
23
+
24
+ class ::FalseClass
25
+ def cloneable? ; false end
26
+ end
27
+
28
+ class ::Numeric
29
+ def cloneable? ; false end
30
+ end
31
+
32
+ class ::Symbol
33
+ def cloneable? ; false end
34
+ end
35
+
36
+ class ::Thread
37
+ def cloneable? ; false end
38
+ end
39
+
40
+ class ::Regexp
41
+ def cloneable? ; false end
42
+ end
43
+
44
+ end
@@ -0,0 +1,42 @@
1
+ module Andromeda
2
+
3
+ class InfoMsg
4
+
5
+ def self.str(msg = '', details = {}, cause_ = nil)
6
+ details[:cause] = cause_ if cause_
7
+ (InfoMsg.new msg, details).to_s
8
+ end
9
+
10
+ attr_reader :details
11
+ attr_reader :cause
12
+ attr_reader :msg
13
+
14
+ def initialize(msg = '', details = {})
15
+ @msg = msg
16
+ @details = details
17
+ @cause = details[:cause]
18
+ details.delete :cause if @cause
19
+ end
20
+
21
+ def to_s
22
+ out = msg.dup
23
+ if details && details.length > 0
24
+ if cause
25
+ out << " (cause = #{cause}; details = \{"
26
+ else
27
+ out << ' (details = {'
28
+ end
29
+ details.each_pair { |k,v| out << " #{k}: #{v}" }
30
+ out << ' })'
31
+ else
32
+ out << " (cause = #{cause})" if cause
33
+ end
34
+ out << '.'
35
+ out
36
+ end
37
+ end
38
+
39
+ class SendError < RuntimeError ; end
40
+ class ExecError < RuntimeError ; end
41
+
42
+ end
@@ -0,0 +1,50 @@
1
+ module Andromeda
2
+
3
+ module Guides
4
+
5
+ class Guide
6
+ def track(spot, key, suggested_track = nil)
7
+ raise NoMethodError
8
+ end
9
+
10
+ def pack(track, was_suggested = false)
11
+ return plan.copy if was_suggested
12
+ if plan.frozen? then plan else plan.copy end
13
+ end
14
+ end
15
+
16
+ class Track
17
+ def follow(*args, &thunk) ; thunk.call *args end
18
+ end
19
+
20
+ class LocalGuide < Guide
21
+ include Singleton
22
+
23
+ def track(spot, key, suggested_track = nil)
24
+ return suggested_track if suggested_track
25
+ LocalTrack.instance
26
+ end
27
+ end
28
+
29
+ class LocalTrack < Track
30
+ include Singleton
31
+ end
32
+
33
+ class SpawnGuide < Guide
34
+ include Singleton
35
+
36
+ def track(spot, key, suggested_track = nil)
37
+ SpawnGuide.instance
38
+ end
39
+ end
40
+
41
+ class SpawnTrack < Track
42
+ include Singleton
43
+
44
+ def follow(*args, &thunk)
45
+ Thread.new { || thunk.call *args }
46
+ end
47
+ end
48
+
49
+ end
50
+ end