1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! Example implementations about _environment_.

use gcmodule::{Cc, Trace, Tracer};
use std::cell::RefCell;
use std::collections::HashMap;

/// Recursive key-value environment.
///
/// Keys are string names.
///
/// An environment has an optional parent environment, which enables scopes.
/// An environment exclusive to a function is useful for implementing closures.
#[derive(Clone)]
pub struct KvEnv<V: Trace>(Cc<RefCell<Inner<V>>>);

#[derive(Trace)]
struct Inner<V: Trace> {
    parent: Option<KvEnv<V>>,
    names: HashMap<String, V>,
}

impl<V: Trace> Trace for KvEnv<V> {
    fn trace(&self, tracer: &mut Tracer) {
        self.0.trace(tracer)
    }

    fn is_type_tracked() -> bool {
        true
    }

    fn as_any(&self) -> Option<&dyn std::any::Any> {
        Some(self)
    }
}

impl<V: Trace> Default for KvEnv<V> {
    fn default() -> Self {
        let inner = Inner {
            parent: None,
            names: HashMap::new(),
        };
        KvEnv(Cc::new(RefCell::new(inner)))
    }
}

impl<V: Trace + Clone> KvEnv<V> {
    /// Resolve a name. Ignore parent environments.
    pub fn get(&self, name: &str) -> Option<V> {
        self.lookup(name).map(|v| v.1)
    }

    /// Set a name to a value. Ignore parent environments.
    pub fn set_local(&self, name: &str, value: V) {
        let mut inner = self.0.borrow_mut();
        if let Some(existing_value) = inner.names.get_mut(name) {
            *existing_value = value;
        } else {
            inner.names.insert(name.to_string(), value);
        }
    }

    /// Set a name to a value.
    ///
    /// - If the name exists in this environment, it will be updated in-place.
    /// - If the name exists in a parent environment, the closest parent
    ///   environment will be updated.
    /// - If the name does not exist in this or any of the parent environments,
    ///   it will be added in this environment.
    pub fn set(&self, name: &str, value: V) {
        match self.lookup(name) {
            Some((env, _)) => env.set_local(name, value),
            None => self.set_local(name, value),
        }
    }

    /// Create a nested environment. The current environment will be the parent
    /// of the returned environment.
    pub fn nested(&self) -> Self {
        let inner = Inner {
            parent: Some(self.clone()),
            names: HashMap::new(),
        };
        KvEnv(Cc::new(RefCell::new(inner)))
    }

    /// Lookup a name in a closest environment.
    fn lookup(&self, name: &str) -> Option<(Self, V)> {
        let inner = self.0.borrow();
        match inner.names.get(name) {
            Some(v) => Some((self.clone(), v.clone())),
            None => inner.parent.clone().and_then(|p| p.lookup(name)),
        }
    }
}