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
|
----
-- File watcher and runtime entrypoint.
--
-- @classmod Copilot
lfs = require 'lfs'
import Scope from require 'alv.scope'
import Module 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={}) ->
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 string file name/path of the alive file to watch and execute
new: (file) =>
@T = 0
@last_modification = 0
@last_modules = {}
@open file if file
--- 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 = Module file
@active_module = @last_modules.__root
COPILOT = nil
--- require a module.
-- @tparam string name
-- @treturn RTNode root
require: (name) =>
Error.wrap "loading module '#{name}'", ->
ok, lua = pcall require, "alv-lib.#{name}"
if ok
RTNode result: Constant.wrap lua
elseif not lua\match "not found"
error lua
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 Module "#{prefix}#{name}.alv"
L\trace "entering module #{mod}"
@modules[name] = mod
@active_module = mod
ok, err = pcall mod\eval
L\trace "returning to module #{mod}"
@active_module = last
if ok
mod.root
else
error err
--- poll for changes and tick.
tick: =>
assert not COPILOT, "another Copilot is already running!"
return unless @last_modules.__root
COPILOT = @
@T += 1
@poll!
root = @last_modules.__root
if root and root.root
L\set_time 'run'
ok, error = Error.try "updating", ->
root.root\poll_io!
root.root\tick!
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.
poll: =>
dirty = {}
for name, mod in pairs @last_modules
if mod\poll! > @last_modification
table.insert dirty, mod
return if #dirty == 0
@eval dirty
--- try to re-evaluate in response to module changes.
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
L\error err
return
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
{
:parse_args
:Copilot
}
|