1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
----
-- File watcher and runtime entrypoint.
--
-- @classmod Copilot
lfs = require 'lfs'
import Scope from require 'alv.scope'
import FSModule from require 'alv.module'
import Error from require 'alv.error'
import RTNode from require 'alv.rtnode'
import Constant from require 'alv.result'
parse_args = (args, out={ 'udp-server': false }) ->
local key
for a in *args
if key
out[key] = a
key = nil
else if match = a\match '^%-%-(.*)'
if 'boolean' == type out[match]
out[match] = true
else
key = match
else
table.insert out, a
assert not key, "value for option '--#{key}' missing!"
out
export COPILOT
class Copilot
--- static functions
-- @section static
--- create a new Copilot.
-- @classmethod
-- @tparam table args
new: (@args={}) =>
@T = 0
@last_modification = 0
@last_modules = {}
@open @args[1] if @args[1]
if @args['udp-server']
import UDPServer from require 'alv.copilot.udp'
@adapter = UDPServer @
--- members
-- @section members
--- current tick
-- @tfield number T
--- change the running script.
-- @tparam string file
open: (file) =>
assert not COPILOT, "another Copilot is already running!"
COPILOT = @
if old = @last_modules.__root
old\destroy!
@active_modules = nil
@last_modules.__root = nil
@last_modules.__root = FSModule file
@active_module = @last_modules.__root
COPILOT = nil
--- require a module.
-- @tparam string name
-- @tparam Scope scope
-- @treturn RTNode root
require: (name, scope) =>
Error.wrap "loading module '#{name}'", ->
ok, result = pcall require, "alv-lib.#{name}"
if ok
result = RTNode :result unless result.__class == RTNode
result
elseif not result\match "'alv%-lib%.[^']+' not found"
error result
else
assert @modules, "no current eval cycle?"
if mod = @modules[name]
mod.root\make_ref!
else
last = @active_module
prefix = if b = last.file\match'(.*/)[^/]*$' then b else ''
mod = @last_modules[name] or FSModule "#{prefix}#{name}.alv"
L\trace "entering module #{mod}"
@modules[name] = mod
@active_module = mod
ok, err = pcall mod\eval, scope
L\trace "returning to module #{mod}"
@active_module = last
if ok
mod.root
else
error err
--- poll for changes and tick.
tick: =>
@adapter\tick! if @adapter
assert not COPILOT, "another Copilot is already running!"
return unless @last_modules.__root
COPILOT = @
ok, err = @poll!
if not ok
L\error err
root = @last_modules.__root
if root and root.root
L\set_time 'run'
ok, error = Error.try "updating", ->
if root.root\poll_io!
root.root\tick!
@T += 1
if not ok
L\print error
COPILOT = nil
--- poll all loaded modules for changes.
--
-- Call `eval` if there are any, and write changed and newly added modules
-- back to disk.
--
-- @treturn boolean ok
-- @treturn error err
poll: =>
dirty = {}
for name, mod in pairs @last_modules
if mod\poll! > @last_modification
table.insert dirty, mod
return true if #dirty == 0
@eval dirty
--- try to re-evaluate in response to module changes.
-- @treturn boolean ok
-- @treturn error err
eval: (dirty) =>
@last_modification = os.time!
L\set_time 'eval'
L\print "changes to files: #{table.concat [m.file for m in *dirty], ', '}"
@modules = { __root: @last_modules.__root }
ok, err = Error.try "processing changes", @modules.__root\eval
if not ok
for name, mod in pairs @modules
mod\rollback!
@modules = nil
return false, err
for name, mod in pairs @last_modules
if not @modules[name]
mod\destroy!
for name, mod in pairs @modules
mod\finish!
@last_modification = os.time!
@last_modules, @modules = @modules, nil
true
{
:parse_args
:Copilot
}
|