lisette-emit 0.2.5

Little language inspired by Rust that compiles to Go
Documentation
use rustc_hash::FxHashMap as HashMap;
use rustc_hash::FxHashSet as HashSet;

use crate::Bindings;
use crate::types::emitter::LoopContext;

pub(crate) struct ScopeState {
    next_var: usize,
    bindings: Bindings,
    declared: Vec<HashSet<String>>,
    scope_depth: usize,
    loop_stack: Vec<LoopContext>,
    assign_targets: HashSet<String>,
    go_const_bindings: Vec<HashSet<String>>,
}

pub(crate) struct BindingSnapshot {
    inner: HashMap<String, String>,
}

pub(crate) struct IsolatedFunctionFrame {
    declared: Vec<HashSet<String>>,
    scope_depth: usize,
}

impl ScopeState {
    pub(crate) fn new() -> Self {
        Self {
            next_var: 0,
            bindings: Bindings::new(),
            declared: vec![HashSet::default()],
            scope_depth: 0,
            loop_stack: Vec::new(),
            assign_targets: HashSet::default(),
            go_const_bindings: vec![HashSet::default()],
        }
    }

    pub(crate) fn reset_for_top_level(&mut self) {
        self.next_var = 0;
        self.bindings.reset();
        self.declared.clear();
        self.declared.push(HashSet::default());
        self.go_const_bindings.truncate(1);
    }

    pub(crate) fn bind(
        &mut self,
        lisette_name: impl Into<String>,
        go_name: impl Into<String>,
    ) -> String {
        self.bindings.add(lisette_name, go_name)
    }

    pub(crate) fn resolve_binding(&self, lisette_name: &str) -> Option<&str> {
        self.bindings.get(lisette_name)
    }

    /// Resolved Go name for `lisette_name`, falling back to the
    /// keyword-escaped form when unbound.
    pub(crate) fn resolve_or_escape(&self, lisette_name: &str) -> String {
        self.bindings
            .get(lisette_name)
            .map(String::from)
            .unwrap_or_else(|| crate::escape_reserved(lisette_name).into_owned())
    }

    pub(crate) fn has_binding_for_go_name(&self, go_name: &str) -> bool {
        self.bindings.has_go_name(go_name)
    }

    pub(crate) fn push_binding_frame(&mut self) {
        self.bindings.save();
    }

    pub(crate) fn pop_binding_frame(&mut self) {
        self.bindings.restore();
    }

    pub(crate) fn binding_snapshot(&self) -> BindingSnapshot {
        BindingSnapshot {
            inner: self.bindings.snapshot(),
        }
    }

    pub(crate) fn restore_binding_snapshot(&mut self, snapshot: BindingSnapshot) {
        self.bindings.restore_snapshot(snapshot.inner);
    }

    pub(crate) fn declare_go_name(&mut self, go_name: &str) {
        if let Some(current) = self.declared.last_mut() {
            current.insert(go_name.to_string());
        }
    }

    pub(crate) fn try_declare_go_name(&mut self, go_name: &str) -> bool {
        let Some(current) = self.declared.last_mut() else {
            return true;
        };
        if current.contains(go_name) {
            false
        } else {
            current.insert(go_name.to_string());
            true
        }
    }

    pub(crate) fn is_go_name_declared(&self, go_name: &str) -> bool {
        self.declared.iter().any(|s| s.contains(go_name))
    }

    pub(crate) fn enter_block(&mut self) {
        self.scope_depth += 1;
        self.bindings.save();
        self.declared.push(HashSet::default());
        self.go_const_bindings.push(HashSet::default());
    }

    pub(crate) fn exit_block(&mut self) {
        self.scope_depth = self.scope_depth.saturating_sub(1);
        self.bindings.restore();
        pop_keep_base(&mut self.declared);
        pop_keep_base(&mut self.go_const_bindings);
    }

    pub(crate) fn enter_isolated_function(&mut self) -> IsolatedFunctionFrame {
        let saved = IsolatedFunctionFrame {
            declared: std::mem::take(&mut self.declared),
            scope_depth: self.scope_depth,
        };
        self.declared = vec![HashSet::default()];
        self.scope_depth = 0;
        self.bindings.save();
        saved
    }

    pub(crate) fn exit_isolated_function(&mut self, frame: IsolatedFunctionFrame) {
        self.bindings.restore();
        self.declared = frame.declared;
        self.scope_depth = frame.scope_depth;
    }

    pub(crate) fn fresh_go_name(&mut self, hint: Option<&str>) -> String {
        loop {
            self.next_var += 1;
            let name = match hint {
                Some(h) => format!("{}_{}", h, self.next_var),
                None => format!("tmp_{}", self.next_var),
            };
            if !self.bindings.has_go_name(&name) && !self.is_go_name_declared(&name) {
                return name;
            }
        }
    }

    pub(crate) fn push_loop(&mut self, ctx: LoopContext) {
        self.loop_stack.push(ctx);
    }

    pub(crate) fn pop_loop(&mut self) {
        self.loop_stack.pop();
    }

    pub(crate) fn current_loop_result_var(&self) -> Option<&str> {
        self.loop_stack.last().map(|c| c.result_var.as_str())
    }

    pub(crate) fn current_loop_label(&self) -> Option<&str> {
        self.loop_stack.last().and_then(|c| c.label.as_deref())
    }

    pub(crate) fn set_current_loop_label(&mut self, label: String) {
        if let Some(ctx) = self.loop_stack.last_mut() {
            ctx.label = Some(label);
        }
    }

    pub(crate) fn try_acquire_assign_target(&mut self, var: &str) -> bool {
        self.assign_targets.insert(var.to_string())
    }

    pub(crate) fn release_assign_target(&mut self, var: &str) {
        self.assign_targets.remove(var);
    }

    pub(crate) fn is_active_assign_target(&self, var: &str) -> bool {
        self.assign_targets.contains(var)
    }

    pub(crate) fn push_const_frame(&mut self) {
        self.go_const_bindings.push(HashSet::default());
    }

    pub(crate) fn pop_const_frame(&mut self) {
        pop_keep_base(&mut self.go_const_bindings);
    }

    pub(crate) fn record_go_const_binding(&mut self, go_identifier: String) {
        if let Some(top) = self.go_const_bindings.last_mut() {
            top.insert(go_identifier);
        }
    }

    pub(crate) fn is_go_const_binding(&self, go_identifier: &str) -> bool {
        self.go_const_bindings
            .iter()
            .any(|frame| frame.contains(go_identifier))
    }
}

fn pop_keep_base<T>(stack: &mut Vec<T>) {
    if stack.len() > 1 {
        stack.pop();
    }
}