///|
pub(all) struct Binding {
name : String
value : Ref[Value]
}
///|
struct Scope {
bindings : Array[Binding]
}
///|
/// Stack of lexical scopes used by the interpreter and embedding API.
pub struct Environment {
scopes : Array[Scope]
}
///|
pub fn Environment::new() -> Environment {
{ scopes: [{ bindings: [], }], }
}
///|
pub fn Environment::depth(self : Environment) -> Int {
self.scopes.length()
}
///|
pub fn Environment::push_scope(self : Environment) -> Unit {
self.scopes.push({ bindings: [], })
}
///|
pub fn Environment::pop_scope(self : Environment) -> Bool {
if self.scopes.length() <= 1 {
false
} else {
ignore(self.scopes.pop())
true
}
}
///|
/// Define a name in the innermost scope. Returns false for a duplicate.
pub fn Environment::bind(
self : Environment,
name : String,
value : Value,
) -> Bool {
let scope = self.scopes[self.scopes.length() - 1]
for binding in scope.bindings {
if binding.name == name {
return false
}
}
scope.bindings.push({ name, value: Ref(value), })
true
}
///|
fn Environment::has_current(self : Environment, name : String) -> Bool {
let scope = self.scopes[self.scopes.length() - 1]
for binding in scope.bindings {
if binding.name == name {
return true
}
}
false
}
///|
fn Environment::has_method_scope(self : Environment) -> Bool {
let mut scope_index = self.scopes.length() - 1
while scope_index >= 0 {
for binding in self.scopes[scope_index].bindings {
if binding.name == "$method" {
return true
}
}
scope_index = scope_index - 1
}
false
}
///|
fn Environment::get_method_local(self : Environment, name : String) -> Value? {
let mut scope_index = self.scopes.length() - 1
while scope_index >= 0 {
let scope = self.scopes[scope_index]
for binding in scope.bindings {
if binding.name == name {
return Some(binding.value.val)
}
}
if string_scope_has_marker(scope.bindings) {
return None
}
scope_index = scope_index - 1
}
None
}
///|
fn string_scope_has_marker(bindings : Array[Binding]) -> Bool {
for binding in bindings {
if binding.name == "$method" {
return true
}
}
false
}
///|
fn Environment::assign_method_local(
self : Environment,
name : String,
value : Value,
) -> Bool {
let mut scope_index = self.scopes.length() - 1
while scope_index >= 0 {
let scope = self.scopes[scope_index]
for binding in scope.bindings {
if binding.name == name {
binding.value.val = value
return true
}
}
if string_scope_has_marker(scope.bindings) {
return false
}
scope_index = scope_index - 1
}
false
}
///|
pub fn Environment::get(self : Environment, name : String) -> Value? {
let mut scope_index = self.scopes.length() - 1
while scope_index >= 0 {
for binding in self.scopes[scope_index].bindings {
if binding.name == name {
return Some(binding.value.val)
}
}
scope_index = scope_index - 1
}
None
}
///|
/// Replace the nearest visible binding. Returns false when the name is absent.
pub fn Environment::assign(
self : Environment,
name : String,
value : Value,
) -> Bool {
let mut scope_index = self.scopes.length() - 1
while scope_index >= 0 {
for binding in self.scopes[scope_index].bindings {
if binding.name == name {
binding.value.val = value
return true
}
}
scope_index = scope_index - 1
}
false
}
///|
/// Snapshot visible scopes while retaining shared variable cells.
pub fn Environment::capture(self : Environment) -> Environment {
let scopes : Array[Scope] = []
for scope in self.scopes {
let bindings : Array[Binding] = []
for binding in scope.bindings {
bindings.push({ name: binding.name, value: binding.value, })
}
scopes.push({ bindings, })
}
{ scopes, }
}
///|
/// Return the values currently retained by this environment. The runtime uses
/// this only while tracing reachability between VM operations; callers cannot
/// mutate the environment through the returned array.
fn Environment::retained_values(self : Environment) -> Array[Value] {
let values : Array[Value] = []
for scope in self.scopes {
for binding in scope.bindings {
values.push(binding.value.val)
}
}
values
}
///|
/// Names visible from a module environment, preserving declaration order and
/// excluding interpreter-only bindings.
fn Environment::binding_names(self : Environment) -> Array[String] {
let names : Array[String] = []
for scope in self.scopes {
for binding in scope.bindings {
if !binding.name.has_prefix("$") {
let mut present = false
for name in names {
if name == binding.name {
present = true
}
}
if !present {
names.push(binding.name)
}
}
}
}
names
}
///|
/// Copy the visible user bindings for a debugger stop. Inner scopes shadow
/// outer scopes and runtime-only names beginning with `$` are omitted.
fn Environment::debug_bindings(self : Environment) -> Array[DebugVariable] {
let result : Array[DebugVariable] = []
let mut scope_index = self.scopes.length() - 1
while scope_index >= 0 {
for binding in self.scopes[scope_index].bindings {
if !binding.name.has_prefix("$") && !debug_core_binding(binding.name) {
let mut present = false
for existing in result {
if existing.name == binding.name {
present = true
}
}
if !present {
result.push({ name: binding.name, value: binding.value.val, })
}
}
}
scope_index = scope_index - 1
}
result
}
///|
fn debug_core_binding(name : String) -> Bool {
name == "System" ||
name == "Object" ||
name == "Bool" ||
name == "Class" ||
name == "Fiber" ||
name == "Fn" ||
name == "List" ||
name == "Map" ||
name == "MapEntry" ||
name == "MapKeySequence" ||
name == "MapValueSequence" ||
name == "Null" ||
name == "Num" ||
name == "Range" ||
name == "Sequence" ||
name == "String" ||
name == "StringByteSequence" ||
name == "StringCodePointSequence"
}