git.s-ol.nu alive / f1eb271
better (require) implementation Close #16 s-ol 1 year, 1 month ago
6 changed file(s) with 166 addition(s) and 107 deletion(s). Raw diff Collapse all Expand all
1212 import Scope from require 'alv.scope'
1313 import Tag from require 'alv.tag'
1414 import op_invoke from require 'alv.invoke'
15 import load from require 'alv.cycle'
1615 lfs = require 'lfs'
1716
1817 doc = ValueStream.meta
8483
8584 Result!
8685
87 load_module = (name, tag) ->
88 Error.wrap "loading module '#{name}'", ->
89 ok, lua = pcall require, "alv-lib.#{name}"
90 if ok
91 ValueStream.wrap lua
92 else
93 result,_ = load.loadfile "#{name}.alv"
94 assert result, "empty return value"
95 result.value
96
9786 require_ = ValueStream.meta
9887 meta:
9988 name: 'require'
11099 name = result\const!\unwrap 'str'
111100
112101 L\trace @, "loading module #{name}"
113 Result value: load_module name, @tag
102 COPILOT\require name
114103
115104 import_ = ValueStream.meta
116105 meta:
126115 L\trace "evaling #{@}"
127116 assert #tail > 0, "'import' requires at least one arguments"
128117
129 for i, child in ipairs tail
118 children = for i, child in ipairs tail
130119 name = child\quote(scope)\unwrap 'sym'
131 scope\set name, Result value: load_module name, @tag\clone Tag i
132 Result!
120 with COPILOT\require name
121 scope\set name, \make_ref!
122 Result :children
133123
134124 import_star = ValueStream.meta
135125 meta:
144134 L\trace "evaling #{@}"
145135 assert #tail > 0, "'import' requires at least one arguments"
146136
147 for i, child in ipairs tail
148 value = load_module child\quote(scope)\unwrap('sym'), @tag\clone Tag i
149 scope\use value\unwrap 'scope'
150
151 Result!
137 children = for i, child in ipairs tail
138 with COPILOT\require child\quote(scope)\unwrap 'sym'
139 scope\use .value\unwrap 'scope'
140 Result :children
152141
153142 export_ = ValueStream.meta
154143 meta:
33 -- @classmod Copilot
44 lfs = require 'lfs'
55 import Scope from require 'alv.scope'
6 import Registry from require 'alv.registry'
6 import Module from require 'alv.module'
77 import Error from require 'alv.error'
8 import loadfile from require 'alv.load'
9
10 spit = (file, str) ->
11 file = io.open file, 'w'
12 file\write str
13 file\close!
8 import Result from require 'alv.result'
9 import ValueStream from require 'alv.stream'
1410
1511 export COPILOT
1612
2319 -- @tparam string file name/path of the alive file to watch and execute
2420 new: (file) =>
2521 @T = 0
26 @registry = Registry!
22 @last_modules = {}
2723 @open file if file
2824
2925 --- members
3531 --- change the running script.
3632 -- @tparam string file
3733 open: (file) =>
38 mode = lfs.attributes file, 'mode'
39 if mode != 'file'
40 error "not a file: #{file}"
34 if old = @last_modules.__root
35 old\destroy!
4136
42 @last_modification = 0
43 @file = file
37 @last_modules.__root = Module file
38
39 --- require a module.
40 -- @tparam string name
41 -- @treturn Result result
42 require: (name) =>
43 Error.wrap "loading module '#{name}'", ->
44 ok, lua = pcall require, "alv-lib.#{name}"
45 if ok
46 Result value: ValueStream.wrap lua
47 else
48 assert @modules, "no current eval cycle?"
49 if mod = @modules[name]
50 mod.root\make_ref!
51 else
52 @modules[name] = @last_modules[name] or Module "#{name}.alv"
53 @modules[name]\eval!
54 @modules[name].root
4455
4556 --- poll for changes and tick.
4657 tick: =>
4758 assert not COPILOT, "another Copilot is already running!"
59 return unless @last_modules.__root
60
4861 COPILOT = @
4962 @T += 1
5063
51 return unless @file
5264 @poll!
5365
54 if @root
66 root = @last_modules.__root
67 if root and root.root
5568 L\set_time 'run'
5669 ok, error = Error.try "updating", ->
57 @root\tick_io!
58 @root\tick!
70 root.root\tick_io!
71 root.root\tick!
5972 if not ok
6073 L\print error
6174
6679 -- Call `eval` if there are any, and write changed and newly added modules
6780 -- back to disk.
6881 poll: =>
69 { :mode, :modification } = (lfs.attributes @file) or {}
70 if mode != 'file'
82 dirty = {}
83 for name, mod in pairs @last_modules
84 if mod\poll!
85 table.insert dirty, mod
86
87 return if #dirty == 0
88
89 L\set_time 'eval'
90 L\print "changed to files: #{table.concat [m.file for m in *dirty], ', '}"
91
92 @modules = { __root: @last_modules.__root }
93 ok, err = Error.try "processing changes", @modules.__root\eval
94
95 if not ok
96 for name, mod in pairs @modules
97 mod\rollback!
98 @modules = {}
99 L\error err
71100 return
72101
73 if @last_modification < modification
74 L\set_time 'eval'
75 L\log "#{@file} changed at #{modification}"
76 @eval!
77 @last_modification = os.time!
102 for name, mod in pairs @last_modules
103 if not @modules[name]
104 mod\destroy!
78105
79 --- perform an eval-cycle.
80 eval: =>
81 @registry\begin_eval!
82 ok, root, ast = Error.try "running '#{@file}'", loadfile, @file
83 if not ok
84 L\print root
85 @registry\rollback_eval!
86 return
106 for name, mod in pairs @modules
107 mod\finish!
87108
88 @registry\end_eval!
89 @root = root
90 spit @file, ast\stringify!
109 @last_modules, @modules = @modules, nil
91110
92111 {
93112 :Copilot
+0
-43
alv/load.moon less more
0 ----
1 -- Functions for loading strings and files of alive code.
2 --
3 -- @module load
4 import Result from require 'alv.result'
5 import Builtin from require 'alv.base'
6 import Scope from require 'alv.scope'
7 import Error from require 'alv.error'
8 import program from require 'alv.parsing'
9 builtin = require 'alv.builtin'
10
11 slurp = (file) ->
12 file = assert (io.open file, 'r'), Error 'io', "couldn't open '#{file}'"
13 with file\read '*all'
14 file\close!
15
16 --- Attempt to load alive code from string.
17 --
18 -- @tparam string code the code to load
19 -- @tparam ?string file name of the source file (for error reporting)
20 -- @treturn Result
21 -- @treturn AST the parsed and updated AST
22 loadstring = (code, file='(unnamed)') ->
23 Error.wrap "evaluating '#{file}'", ->
24 ast = program\match code
25 if not ast
26 error Error 'syntax', "failed to parse"
27
28 scope = Scope builtin
29 result = ast\eval scope
30 result, ast
31
32 --- Attempt to load alive code from a file.
33 --
34 -- @tparam string file filepath of the source file
35 -- @treturn Result
36 -- @treturn AST the parsed and updated AST
37 loadfile = (file) -> loadstring (slurp file), file
38
39 {
40 :loadstring
41 :loadfile
42 }
0 ----
1 -- Per-file execution context.
2 --
3 -- @classmod Module
4 import Registry from require 'alv.registry'
5 import Error from require 'alv.error'
6 import Scope from require 'alv.scope'
7 import program from require 'alv.parsing'
8 builtin = require 'alv.builtin'
9
10 slurp = (file) ->
11 file = assert (io.open file, 'r'), Error 'io', "couldn't open '#{file}'"
12 with file\read '*all'
13 file\close!
14
15 spit = (file, str) ->
16 file = io.open file, 'w'
17 file\write str
18 file\close!
19
20 class Module
21 --- static functions
22 -- @section static
23
24 --- create a new Module.
25 -- @classmethod
26 new: (@file) =>
27 @registry = Registry!
28 @last_modification = 0
29
30 --- members
31 -- @section members
32
33 --- check whether file was changed.
34 -- @treturn bool whether the file was changed since the last call
35 poll: =>
36 { :mode, :modification } = (lfs.attributes @file) or {}
37 assert mode == 'file', Error 'io', "not a file: '#{file}'"
38
39 if @last_modification < modification
40 true
41
42 --- start an evaluation cycle.
43 --
44 -- If the module has already been evaluated this tick, this is a noop.
45 -- Otherwise, register the module with the `Copilot`. Updates `root`.
46 eval: =>
47 @last_modification = os.time!
48 @ast = Error.wrap "parsing '#{@file}'", -> program\match slurp @file
49 assert @ast, Error 'syntax', "failed to parse"
50
51 scope = Scope builtin
52 @registry\begin_eval!
53 @root = Error.wrap "evaluating '#{@file}'", @ast\eval, scope, @registry
54 @registry\release!
55
56 --- rollback the last evaluation cycle.
57 rollback: => @registry\rollback_eval!
58
59 --- finish the last evaluation cycle.
60 finish: =>
61 tags_changed = @registry\end_eval!
62 if tags_changed
63 spit @file, @ast\stringify!
64
65 --- destroy this module.
66 destroy: =>
67 @registry\destroy!
68
69 --- the last updated AST tree for this module.
70 -- @tfield ?AST ast
71
72 --- the root Result of this module.
73 -- @tfield ?Result root
74
75 {
76 :Module
77 }
4444 -- `end_eval` or `rollback_eval`.
4545 begin_eval: =>
4646 @grab!
47 assert not @map, "unfinished evaluation cycle"
4748 @map, @pending = {}, {}
49
50 --- abort an evaluation cycle.
51 --
52 -- Unset the active Registry.
53 rollback_eval: =>
54 for { :tag, :expr } in *@pending
55 expr\destroy!
56
57 @map, @pending = nil, nil
4858
4959 --- end an evaluation cycle.
5060 --
5161 -- Register all pending `Tag`s and destroy all orphaned registrations.
5262 -- Unset the active Registry.
63 -- @treturn bool whether any changes to the AST were made
5364 end_eval: =>
5465 for tag, val in pairs @last_map
5566 val\destroy! unless @map[tag]
6475 tag\set next_tag
6576 @map[tag\index!] = expr
6677
67 @last_map = @map
68 @release!
78 dirty = #@pending > 0
79 @last_map, @map, @pending = @map, nil, nil
6980
70 --- abort an evaluation cycle.
71 --
72 -- Unset the active Registry.
73 rollback_eval: =>
74 @release!
81 dirty
7582
7683 --- set the active Registry.
7784 grab: =>
8390 assert @ == Registry.active_registry, "not the active registry!"
8491 Registry.active_registry, @prev = @prev, nil
8592
93 --- destroy this Registry and all associated Registrations.
94 -- needs to be called *after* `:eval`.
95 destroy: =>
96 assert not @tag, "unfinished evaluation cycle"
97 for tag, val in pairs @last_map
98 val\destroy!
99
100 @last_map = {}
101
86102 --- static functions
87103 -- @section static
88104
89105 --- create a new Registry.
90106 -- @classmethod
91107 new: =>
92 @last_map, @map = {}, {}
108 @last_map = {}
93109
94110 --- get the active Registry.
95111 --
4646 ["alv.error"] = "alv/error.moon",
4747 ["alv.init"] = "alv/init.moon",
4848 ["alv.invoke"] = "alv/invoke.moon",
49 ["alv.load"] = "alv/load.moon",
5049 ["alv.logger"] = "alv/logger.moon",
50 ["alv.module"] = "alv/module.moon",
5151 ["alv.parsing"] = "alv/parsing.moon",
5252 ["alv.registry"] = "alv/registry.moon",
5353 ["alv.result"] = "alv/result.moon",