andromeda 0.1 → 0.1.2

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