diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-02 17:27:19 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-02 17:43:17 +0000 |
| commit | d8e2bad7d08bb96937815de4f3fd1bcbe2c440bd (patch) | |
| tree | 64463299ae1b363a2addde0559f094193dd94759 | |
| parent | lift remaining libs to new op interface (diff) | |
| download | alive-d8e2bad7d08bb96937815de4f3fd1bcbe2c440bd.tar.gz alive-d8e2bad7d08bb96937815de4f3fd1bcbe2c440bd.zip | |
dynamic scoping
| -rw-r--r-- | copilot.moon | 2 | ||||
| -rw-r--r-- | core/builtin.moon | 1 | ||||
| -rw-r--r-- | core/invoke.moon | 4 | ||||
| -rw-r--r-- | core/scope.moon | 11 | ||||
| -rw-r--r-- | spec/core/scope_spec.moon | 50 |
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! |
