// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: internal/program/static_analyzer.go
// Source: interpreter.go
// Copyright 2016 Google Inc. All rights reserved.
// Modifications: Adapted captured-variable analysis to local environments and bounded inspection.
// License and retained attribution: LICENSE and NOTICE at the module root.
///|
priv struct DebugBoundScope {
names : Map[String, Unit]
parent : DebugBoundScope?
}
///|
fn DebugBoundScope::child(
self : DebugBoundScope,
names : Array[String],
tick : () -> Unit raise,
) -> DebugBoundScope raise {
{
names: Map(
names.map(name => {
tick()
(name, ())
}),
),
parent: Some(self),
}
}
///|
fn DebugBoundScope::contains(
self : DebugBoundScope,
name : String,
tick : () -> Unit raise,
) -> Bool raise {
let mut next = Some(self)
while next is Some(scope) {
tick()
if scope.names.contains(name) {
return true
}
next = scope.parent
}
false
}
///|
// The debugger projects captured scopes without changing interpreter lookup or
// thunk lifetime. Compute lazily only when a host actually requests variables.
fn debug_visibility(
roots : Array[Expr],
bound : Array[String],
extra : Array[String],
in_object : Bool,
) -> (() -> Unit raise) -> Array[String] raise {
let saved : Ref[Array[String]?] = Ref(None)
fn(tick) raise {
if saved.val is Some(names) {
return names
}
let initial : DebugBoundScope = {
names: Map(
bound.map(name => {
tick()
(name, ())
}),
),
parent: None,
}
let work = roots.map(root => (root, initial, in_object))
let free : Map[String, Unit] = Map(
extra.map(name => {
tick()
(name, ())
}),
)
let push_clauses = fn(
clauses : Array[Clause],
outer : DebugBoundScope,
in_object : Bool,
) raise {
let mut scope = outer
for clause in clauses {
match clause {
For(name, expr) => {
work.push((expr, scope, in_object))
scope = scope.child([name], tick)
}
When(expr) => work.push((expr, scope, in_object))
}
}
scope
}
while work.pop() is Some((node, scope, in_object)) {
tick()
match node.kind {
Var(name) =>
if name != "self" && name != "super" && !scope.contains(name, tick) {
free[name] = ()
}
Local(bindings, body) => {
let inner = scope.child(bindings.map(b => b.name), tick)
work.push((body, inner, in_object))
for binding in bindings {
work.push((binding.value, inner, in_object))
}
}
Func(params, body) => {
let inner = scope.child(params.map(p => p.name), tick)
work.push((body, inner, in_object))
for parameter in params {
if parameter.default_value is Some(value) {
work.push((value, inner, in_object))
}
}
}
Obj(fields, locals, assertions, _) => {
let bound = locals.map(b => b.name)
if !in_object {
bound.push("$")
}
let inner = scope.child(bound, tick)
for field in fields {
work.push((field.key, scope, in_object))
work.push((field.value, inner, true))
}
for binding in locals {
work.push((binding.value, inner, true))
}
for (condition, message) in assertions {
work.push((condition, inner, true))
if message is Some(value) {
work.push((value, inner, true))
}
}
}
ArrayComp(body, clauses) => {
free["$std"] = ()
work.push((body, push_clauses(clauses, scope, in_object), in_object))
}
ObjectComp(key, body, clauses, locals) => {
free["$std"] = ()
let outer = push_clauses(clauses, scope, in_object)
work.push((key, outer, in_object))
let bound = locals.map(b => b.name)
if !in_object {
bound.push("$")
}
let inner = outer.child(bound, tick)
work.push((body, inner, true))
for binding in locals {
work.push((binding.value, inner, true))
}
}
Slice(_, _, _, _) | Binary("%", _, _) => {
free["$std"] = ()
for child in ast_export_children(node) {
work.push((child, scope, in_object))
}
}
_ =>
for child in ast_export_children(node) {
work.push((child, scope, in_object))
}
}
}
let names = free.keys().collect()
names.sort_by(compare_unicode)
saved.val = Some(names)
names
}
}
///|
fn Env::debug_capture(
self : Env,
roots : Array[Expr],
bound? : Array[String] = [],
extra? : Array[String] = [],
) -> Env {
{
..self,
debug_visible: Some(
debug_visibility(roots, bound, extra, self.lookup("self") is Some(_)),
),
}
}