aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-04-24 12:14:31 +0000
committers-ol <s-ol@users.noreply.github.com>2020-04-24 12:27:34 +0000
commitf1eb271d4701494e4faa2d71e45ad8ec4aa635a4 (patch)
treefea24fb48fc58a8cd3c8dd8721b9068bc631fb7f
parentmove tick-counting into COPILOT (diff)
downloadalive-f1eb271d4701494e4faa2d71e45ad8ec4aa635a4.tar.gz
alive-f1eb271d4701494e4faa2d71e45ad8ec4aa635a4.zip
better (require) implementation
Close #16
-rw-r--r--alv/builtin.moon29
-rw-r--r--alv/copilot.moon89
-rw-r--r--alv/load.moon43
-rw-r--r--alv/module.moon78
-rw-r--r--alv/registry.moon32
-rw-r--r--dist/rocks/alive-scm-3.rockspec2
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",