diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-04-24 12:14:31 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-04-24 12:27:34 +0000 |
| commit | f1eb271d4701494e4faa2d71e45ad8ec4aa635a4 (patch) | |
| tree | fea24fb48fc58a8cd3c8dd8721b9068bc631fb7f | |
| parent | move tick-counting into COPILOT (diff) | |
| download | alive-f1eb271d4701494e4faa2d71e45ad8ec4aa635a4.tar.gz alive-f1eb271d4701494e4faa2d71e45ad8ec4aa635a4.zip | |
better (require) implementation
Close #16
| -rw-r--r-- | alv/builtin.moon | 29 | ||||
| -rw-r--r-- | alv/copilot.moon | 89 | ||||
| -rw-r--r-- | alv/load.moon | 43 | ||||
| -rw-r--r-- | alv/module.moon | 78 | ||||
| -rw-r--r-- | alv/registry.moon | 32 | ||||
| -rw-r--r-- | dist/rocks/alive-scm-3.rockspec | 2 |
6 files changed, 166 insertions, 107 deletions
diff --git a/alv/builtin.moon b/alv/builtin.moon index 9b88bd4..022c40c 100644 --- a/alv/builtin.moon +++ b/alv/builtin.moon @@ -13,7 +13,6 @@ import Cell from require 'alv.cell' import Scope from require 'alv.scope' import Tag from require 'alv.tag' import op_invoke from require 'alv.invoke' -import load from require 'alv.cycle' lfs = require 'lfs' doc = ValueStream.meta @@ -85,16 +84,6 @@ All arguments have to be evaltime constant." Result! -load_module = (name, tag) -> - Error.wrap "loading module '#{name}'", -> - ok, lua = pcall require, "alv-lib.#{name}" - if ok - ValueStream.wrap lua - else - result,_ = load.loadfile "#{name}.alv" - assert result, "empty return value" - result.value - require_ = ValueStream.meta meta: name: 'require' @@ -111,7 +100,7 @@ require_ = ValueStream.meta name = result\const!\unwrap 'str' L\trace @, "loading module #{name}" - Result value: load_module name, @tag + COPILOT\require name import_ = ValueStream.meta meta: @@ -127,10 +116,11 @@ current scope." L\trace "evaling #{@}" assert #tail > 0, "'import' requires at least one arguments" - for i, child in ipairs tail + children = for i, child in ipairs tail name = child\quote(scope)\unwrap 'sym' - scope\set name, Result value: load_module name, @tag\clone Tag i - Result! + with COPILOT\require name + scope\set name, \make_ref! + Result :children import_star = ValueStream.meta meta: @@ -145,11 +135,10 @@ Requires modules `sym1`, `sym2`, … and merges them into the current scope." L\trace "evaling #{@}" assert #tail > 0, "'import' requires at least one arguments" - for i, child in ipairs tail - value = load_module child\quote(scope)\unwrap('sym'), @tag\clone Tag i - scope\use value\unwrap 'scope' - - Result! + children = for i, child in ipairs tail + with COPILOT\require child\quote(scope)\unwrap 'sym' + scope\use .value\unwrap 'scope' + Result :children export_ = ValueStream.meta meta: diff --git a/alv/copilot.moon b/alv/copilot.moon index d4612c8..5d9536a 100644 --- a/alv/copilot.moon +++ b/alv/copilot.moon @@ -4,14 +4,10 @@ -- @classmod Copilot lfs = require 'lfs' import Scope from require 'alv.scope' -import Registry from require 'alv.registry' +import Module from require 'alv.module' import Error from require 'alv.error' -import loadfile from require 'alv.load' - -spit = (file, str) -> - file = io.open file, 'w' - file\write str - file\close! +import Result from require 'alv.result' +import ValueStream from require 'alv.stream' export COPILOT @@ -24,7 +20,7 @@ class Copilot -- @tparam string file name/path of the alive file to watch and execute new: (file) => @T = 0 - @registry = Registry! + @last_modules = {} @open file if file --- members @@ -36,27 +32,44 @@ class Copilot --- change the running script. -- @tparam string file open: (file) => - mode = lfs.attributes file, 'mode' - if mode != 'file' - error "not a file: #{file}" - - @last_modification = 0 - @file = file + if old = @last_modules.__root + old\destroy! + + @last_modules.__root = Module file + + --- require a module. + -- @tparam string name + -- @treturn Result result + require: (name) => + Error.wrap "loading module '#{name}'", -> + ok, lua = pcall require, "alv-lib.#{name}" + if ok + Result value: ValueStream.wrap lua + else + assert @modules, "no current eval cycle?" + if mod = @modules[name] + mod.root\make_ref! + else + @modules[name] = @last_modules[name] or Module "#{name}.alv" + @modules[name]\eval! + @modules[name].root --- poll for changes and tick. tick: => assert not COPILOT, "another Copilot is already running!" + return unless @last_modules.__root + COPILOT = @ @T += 1 - return unless @file @poll! - if @root + root = @last_modules.__root + if root and root.root L\set_time 'run' ok, error = Error.try "updating", -> - @root\tick_io! - @root\tick! + root.root\tick_io! + root.root\tick! if not ok L\print error @@ -67,28 +80,34 @@ class Copilot -- Call `eval` if there are any, and write changed and newly added modules -- back to disk. poll: => - { :mode, :modification } = (lfs.attributes @file) or {} - if mode != 'file' - return + dirty = {} + for name, mod in pairs @last_modules + if mod\poll! + table.insert dirty, mod - if @last_modification < modification - L\set_time 'eval' - L\log "#{@file} changed at #{modification}" - @eval! - @last_modification = os.time! + return if #dirty == 0 + + L\set_time 'eval' + L\print "changed to files: #{table.concat [m.file for m in *dirty], ', '}" + + @modules = { __root: @last_modules.__root } + ok, err = Error.try "processing changes", @modules.__root\eval - --- perform an eval-cycle. - eval: => - @registry\begin_eval! - ok, root, ast = Error.try "running '#{@file}'", loadfile, @file if not ok - L\print root - @registry\rollback_eval! + for name, mod in pairs @modules + mod\rollback! + @modules = {} + L\error err return - @registry\end_eval! - @root = root - spit @file, ast\stringify! + for name, mod in pairs @last_modules + if not @modules[name] + mod\destroy! + + for name, mod in pairs @modules + mod\finish! + + @last_modules, @modules = @modules, nil { :Copilot diff --git a/alv/load.moon b/alv/load.moon deleted file mode 100644 index 2ae2a9b..0000000 --- a/alv/load.moon +++ /dev/null @@ -1,43 +0,0 @@ ----- --- Functions for loading strings and files of alive code. --- --- @module load -import Result from require 'alv.result' -import Builtin from require 'alv.base' -import Scope from require 'alv.scope' -import Error from require 'alv.error' -import program from require 'alv.parsing' -builtin = require 'alv.builtin' - -slurp = (file) -> - file = assert (io.open file, 'r'), Error 'io', "couldn't open '#{file}'" - with file\read '*all' - file\close! - ---- Attempt to load alive code from string. --- --- @tparam string code the code to load --- @tparam ?string file name of the source file (for error reporting) --- @treturn Result --- @treturn AST the parsed and updated AST -loadstring = (code, file='(unnamed)') -> - Error.wrap "evaluating '#{file}'", -> - ast = program\match code - if not ast - error Error 'syntax', "failed to parse" - - scope = Scope builtin - result = ast\eval scope - result, ast - ---- Attempt to load alive code from a file. --- --- @tparam string file filepath of the source file --- @treturn Result --- @treturn AST the parsed and updated AST -loadfile = (file) -> loadstring (slurp file), file - -{ - :loadstring - :loadfile -} diff --git a/alv/module.moon b/alv/module.moon new file mode 100644 index 0000000..b6244d8 --- /dev/null +++ b/alv/module.moon @@ -0,0 +1,78 @@ +---- +-- Per-file execution context. +-- +-- @classmod Module +import Registry from require 'alv.registry' +import Error from require 'alv.error' +import Scope from require 'alv.scope' +import program from require 'alv.parsing' +builtin = require 'alv.builtin' + +slurp = (file) -> + file = assert (io.open file, 'r'), Error 'io', "couldn't open '#{file}'" + with file\read '*all' + file\close! + +spit = (file, str) -> + file = io.open file, 'w' + file\write str + file\close! + +class Module +--- static functions +-- @section static + + --- create a new Module. + -- @classmethod + new: (@file) => + @registry = Registry! + @last_modification = 0 + +--- members +-- @section members + + --- check whether file was changed. + -- @treturn bool whether the file was changed since the last call + poll: => + { :mode, :modification } = (lfs.attributes @file) or {} + assert mode == 'file', Error 'io', "not a file: '#{file}'" + + if @last_modification < modification + true + + --- start an evaluation cycle. + -- + -- If the module has already been evaluated this tick, this is a noop. + -- Otherwise, register the module with the `Copilot`. Updates `root`. + eval: => + @last_modification = os.time! + @ast = Error.wrap "parsing '#{@file}'", -> program\match slurp @file + assert @ast, Error 'syntax', "failed to parse" + + scope = Scope builtin + @registry\begin_eval! + @root = Error.wrap "evaluating '#{@file}'", @ast\eval, scope, @registry + @registry\release! + + --- rollback the last evaluation cycle. + rollback: => @registry\rollback_eval! + + --- finish the last evaluation cycle. + finish: => + tags_changed = @registry\end_eval! + if tags_changed + spit @file, @ast\stringify! + + --- destroy this module. + destroy: => + @registry\destroy! + + --- the last updated AST tree for this module. + -- @tfield ?AST ast + + --- the root Result of this module. + -- @tfield ?Result root + +{ + :Module +} diff --git a/alv/registry.moon b/alv/registry.moon index 4bd03f6..ba35a18 100644 --- a/alv/registry.moon +++ b/alv/registry.moon @@ -45,12 +45,23 @@ class Registry -- `end_eval` or `rollback_eval`. begin_eval: => @grab! + assert not @map, "unfinished evaluation cycle" @map, @pending = {}, {} + --- abort an evaluation cycle. + -- + -- Unset the active Registry. + rollback_eval: => + for { :tag, :expr } in *@pending + expr\destroy! + + @map, @pending = nil, nil + --- end an evaluation cycle. -- -- Register all pending `Tag`s and destroy all orphaned registrations. -- Unset the active Registry. + -- @treturn bool whether any changes to the AST were made end_eval: => for tag, val in pairs @last_map val\destroy! unless @map[tag] @@ -65,14 +76,10 @@ class Registry tag\set next_tag @map[tag\index!] = expr - @last_map = @map - @release! + dirty = #@pending > 0 + @last_map, @map, @pending = @map, nil, nil - --- abort an evaluation cycle. - -- - -- Unset the active Registry. - rollback_eval: => - @release! + dirty --- set the active Registry. grab: => @@ -84,13 +91,22 @@ class Registry assert @ == Registry.active_registry, "not the active registry!" Registry.active_registry, @prev = @prev, nil + --- destroy this Registry and all associated Registrations. + -- needs to be called *after* `:eval`. + destroy: => + assert not @tag, "unfinished evaluation cycle" + for tag, val in pairs @last_map + val\destroy! + + @last_map = {} + --- static functions -- @section static --- create a new Registry. -- @classmethod new: => - @last_map, @map = {}, {} + @last_map = {} --- get the active Registry. -- diff --git a/dist/rocks/alive-scm-3.rockspec b/dist/rocks/alive-scm-3.rockspec index 7c9cbf0..4623319 100644 --- a/dist/rocks/alive-scm-3.rockspec +++ b/dist/rocks/alive-scm-3.rockspec @@ -47,8 +47,8 @@ build = { ["alv.error"] = "alv/error.moon", ["alv.init"] = "alv/init.moon", ["alv.invoke"] = "alv/invoke.moon", - ["alv.load"] = "alv/load.moon", ["alv.logger"] = "alv/logger.moon", + ["alv.module"] = "alv/module.moon", ["alv.parsing"] = "alv/parsing.moon", ["alv.registry"] = "alv/registry.moon", ["alv.result"] = "alv/result.moon", |
