aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-03-02 17:27:19 +0000
committers-ol <s-ol@users.noreply.github.com>2020-03-02 17:43:17 +0000
commitd8e2bad7d08bb96937815de4f3fd1bcbe2c440bd (patch)
tree64463299ae1b363a2addde0559f094193dd94759
parentlift remaining libs to new op interface (diff)
downloadalive-d8e2bad7d08bb96937815de4f3fd1bcbe2c440bd.tar.gz
alive-d8e2bad7d08bb96937815de4f3fd1bcbe2c440bd.zip
dynamic scoping
-rw-r--r--copilot.moon2
-rw-r--r--core/builtin.moon1
-rw-r--r--core/invoke.moon4
-rw-r--r--core/scope.moon11
-rw-r--r--spec/core/scope_spec.moon50
5 files changed, 59 insertions, 9 deletions
diff --git a/copilot.moon b/copilot.moon
index 6d8d977..607edff 100644
--- a/copilot.moon
+++ b/copilot.moon
@@ -27,7 +27,7 @@ class Copilot
L\error "error parsing"
return
- scope = Scope ast, globals
+ scope = Scope globals
root = L\try "error evaluating:", ast\eval, scope, @registry
return unless root
diff --git a/core/builtin.moon b/core/builtin.moon
index 5dd296a..c91e930 100644
--- a/core/builtin.moon
+++ b/core/builtin.moon
@@ -148,6 +148,7 @@ evaluates and continously updates expr1, expr2, ...
the last expression's value is returned."
eval: (scope, tail) =>
+ scope = Scope scope
Result children: [expr\eval scope for expr in *tail]
class if_ extends Action
diff --git a/core/invoke.moon b/core/invoke.moon
index 7b14e97..1fac7b8 100644
--- a/core/invoke.moon
+++ b/core/invoke.moon
@@ -39,12 +39,12 @@ class fn_invoke extends Action
assert #params == #tail, "argument count mismatch in #{@head}"
- fn_scope = Scope @, scope
+ fn_scope = Scope scope, outer_scope
children = for i=1,#params
name = params[i]\unwrap 'sym'
with L\push tail[i]\eval, outer_scope
- fn_scope\set name, .value
+ fn_scope\set name, \make_ref!
body = body\clone @tag
result = body\eval fn_scope
diff --git a/core/scope.moon b/core/scope.moon
index f795f97..5e73f64 100644
--- a/core/scope.moon
+++ b/core/scope.moon
@@ -1,7 +1,7 @@
import Result, Value from require 'core.value'
class Scope
- new: (@node, @parent) =>
+ new: (@parent, @dynamic_parent) =>
@values = {}
set_raw: (key, val) =>
@@ -11,8 +11,14 @@ class Scope
set: (key, val) =>
L\trace "setting #{key} = #{val} in #{@}"
assert val.__class == Result, "expected #{key}=#{val} to be Result"
+ assert not @values[key], "cannot redefine symbol #{key}!"
@values[key] = val
+ recurse: (key) =>
+ parent = if key\match '^%*.*%*$' then @dynamic_parent else @parent
+ parent or= @parent
+ return parent and L\push parent\get, key
+
get: (key, prefix='') =>
L\debug "checking for #{key} in #{@}"
if val = @values[key]
@@ -22,7 +28,7 @@ class Scope
start, rest = key\match '^(.-)/(.+)'
if not start
- return @parent and L\push -> @parent\get key
+ return @recurse key
child = @get start
assert child and child.value.type == 'scope', "#{start} is not a scope (looking for #{key})"
@@ -40,7 +46,6 @@ class Scope
__tostring: =>
buf = "<Scope"
- buf ..= "@#{@node}" if @node
depth = -1
parent = @parent
diff --git a/spec/core/scope_spec.moon b/spec/core/scope_spec.moon
index 471bbce..563f243 100644
--- a/spec/core/scope_spec.moon
+++ b/spec/core/scope_spec.moon
@@ -1,10 +1,12 @@
-import Scope, Value, Op from require 'core'
+import Scope, Value, Result, Op from require 'core'
import Logger from require 'logger'
Logger.init 'silent'
class TestOp extends Op
new: (...) => super ...
+wrap_res = (value) -> Result :value
+
describe 'Scope', ->
describe 'constifies', ->
scope = Scope!
@@ -94,21 +96,63 @@ describe 'Scope', ->
assert.is.equal pi, (root\get 'deep/child/test')\const!
+ describe 'can set symbols', ->
+ one = wrap_res Value.num 1
+ two = wrap_res Value.num 2
+ scope = Scope!
+
+ it 'disallows re-setting symbols', ->
+ scope\set 'test', one
+ assert.is.equal one, scope\get 'test'
+
+ it 'throws if overwriting', ->
+ assert.has.error -> scope\set 'test', two
+ assert.is.equal one, scope\get 'test'
+
describe 'inheritance', ->
root = Scope!
root\set_raw 'hidden', 1234
root\set_raw 'inherited', "inherited string"
- scope = Scope nil, root
+ scope = Scope root
it 'allows access', ->
got = (scope\get 'inherited')\const!
assert.is.equal 'str', got.type
assert.is.equal "inherited string", got.value
- it 'can keep defs', ->
+ it 'can be shadowed', ->
scope\set_raw 'hidden', "overwritten"
got = (scope\get 'hidden')\const!
assert.is.equal 'str', got.type
assert.is.equal "overwritten", got.value
+
+ describe 'dynamic inheritance', ->
+ root = Scope!
+ dyn_root = Scope!
+
+ root\set_raw 'normal', 'normal'
+ root\set_raw '*dynamic*', 'normal'
+ dyn_root\set_raw 'normal', 'dynamic'
+ dyn_root\set_raw '*dynamic*', 'dynamic'
+
+ dyn_root\set_raw '*nested*', { value: 3 }
+
+ it 'follows a different parent', ->
+ merged = Scope root, dyn_root
+ assert.is.equal 'normal', (merged\get 'normal').value!
+ assert.is.equal 'dynamic', (merged\get '*dynamic*').value!
+
+ it 'falls back to the immediate parent', ->
+ merged = Scope root
+ assert.is.equal 'normal', (merged\get '*dynamic*').value!
+
+ it 'looks in self first', ->
+ merged = Scope root
+ merged\set_raw '*dynamic*', 'merged'
+ assert.is.equal 'merged', (merged\get '*dynamic*').value!
+
+ it 'can resolve nested', ->
+ merged = Scope root, dyn_root
+ assert.is.equal 3, (merged\get '*nested*/value').value!