Skip to main content

sim_lib_binding/
modes.rs

1use sim_kernel::{Error, Expr, Result, Symbol};
2
3/// How a language profile scopes its bindings.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum BindingScopeMode {
6    /// Lexical scope (the default): bindings follow textual nesting.
7    Lexical,
8    /// Dynamic scope: bindings follow the dynamic call extent.
9    Dynamic,
10    /// Hybrid scope: both lexical and dynamic bindings coexist.
11    Hybrid,
12}
13
14/// How a language profile treats macro hygiene.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum HygieneMode {
17    /// Hygienic (the default): introduced names cannot capture.
18    Hygienic,
19    /// Explicit: hygiene is opt-in per identifier.
20    Explicit,
21    /// Unhygienic: introduced names may capture freely.
22    Unhygienic,
23}
24
25/// The binding and hygiene modes selected for a language profile.
26///
27/// Parsed from profile options and consumed by the `profile-modes` form to
28/// configure how a profile's surface binds names.
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub struct BindingProfileModes {
31    /// The binding scope mode.
32    pub scope: BindingScopeMode,
33    /// The macro hygiene mode.
34    pub hygiene: HygieneMode,
35}
36
37impl Default for BindingProfileModes {
38    fn default() -> Self {
39        Self {
40            scope: BindingScopeMode::Lexical,
41            hygiene: HygieneMode::Hygienic,
42        }
43    }
44}
45
46impl BindingProfileModes {
47    /// Derives modes from profile option pairs, defaulting unset modes.
48    ///
49    /// Recognizes `scope`/`binding` keys for [`BindingScopeMode`] and
50    /// `hygiene` for [`HygieneMode`]; other keys are ignored. Errors on an
51    /// unknown mode symbol.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use sim_kernel::{Expr, Symbol};
57    /// use sim_lib_binding::{BindingProfileModes, BindingScopeMode, HygieneMode};
58    ///
59    /// let modes = BindingProfileModes::from_options(&[
60    ///     (Symbol::new("scope"), Expr::Symbol(Symbol::new("dynamic"))),
61    ///     (Symbol::new("hygiene"), Expr::Symbol(Symbol::new("explicit"))),
62    /// ])
63    /// .unwrap();
64    /// assert_eq!(modes.scope, BindingScopeMode::Dynamic);
65    /// assert_eq!(modes.hygiene, HygieneMode::Explicit);
66    /// ```
67    pub fn from_options(options: &[(Symbol, Expr)]) -> Result<Self> {
68        let mut modes = Self::default();
69        for (key, value) in options {
70            if key_matches(key, "scope") || key_matches(key, "binding") {
71                modes.scope = parse_scope_mode(value)?;
72            } else if key_matches(key, "hygiene") {
73                modes.hygiene = parse_hygiene_mode(value)?;
74            }
75        }
76        Ok(modes)
77    }
78}
79
80fn parse_scope_mode(expr: &Expr) -> Result<BindingScopeMode> {
81    let symbol = symbol_value(expr, "binding scope mode")?;
82    match symbol.name.as_ref() {
83        "lexical" => Ok(BindingScopeMode::Lexical),
84        "dynamic" => Ok(BindingScopeMode::Dynamic),
85        "hybrid" => Ok(BindingScopeMode::Hybrid),
86        _ => Err(Error::Eval(format!(
87            "unsupported binding scope mode {symbol}"
88        ))),
89    }
90}
91
92fn parse_hygiene_mode(expr: &Expr) -> Result<HygieneMode> {
93    let symbol = symbol_value(expr, "binding hygiene mode")?;
94    match symbol.name.as_ref() {
95        "hygienic" => Ok(HygieneMode::Hygienic),
96        "explicit" => Ok(HygieneMode::Explicit),
97        "unhygienic" => Ok(HygieneMode::Unhygienic),
98        _ => Err(Error::Eval(format!(
99            "unsupported binding hygiene mode {symbol}"
100        ))),
101    }
102}
103
104fn symbol_value<'a>(expr: &'a Expr, expected: &'static str) -> Result<&'a Symbol> {
105    match expr {
106        Expr::Symbol(symbol) => Ok(symbol),
107        _ => Err(Error::TypeMismatch {
108            expected,
109            found: "non-symbol",
110        }),
111    }
112}
113
114fn key_matches(symbol: &Symbol, name: &str) -> bool {
115    symbol.name.as_ref() == name
116}