yog-script-analyze 0.2.2

Yog Script (.ys, .yns) analyzed: its lexer, parser and syntax tree, names resolved, the class index model — what the build and the editors read a source with.
Documentation
//! Statements: locals declared, values assigned anew, and paths — the
//! branches of an `if` joined, a loop's body read twice (what one turn
//! moves, the next can't use), a `return` ending its path.

use super::Checker;
use super::state::Var;
use crate::ast::{Binding, Block, ExprKind, Stmt};

impl Checker<'_, '_> {
    pub(super) fn block(&mut self, b: &Block) {
        self.scoped(|c| b.stmts.iter().for_each(|s| c.stmt(s)));
    }

    fn scoped(&mut self, f: impl FnOnce(&mut Self)) {
        self.state.push();
        f(self);
        self.state.pop();
    }

    pub(super) fn stmt(&mut self, s: &Stmt) {
        match s {
            Stmt::Local { binding, value, .. } => {
                if let Some(v) = value {
                    self.expr(v);
                }
                self.bind(binding);
            }
            Stmt::Assign { place, op, value } => {
                self.expr(value);
                match (&place.kind, op) {
                    // `x = …`: `x` holds a value again.
                    (ExprKind::Ident(name), None) => self.renew(name),
                    _ => self.expr(place),
                }
            }
            Stmt::If {
                cond,
                then,
                otherwise,
            } => {
                self.expr(cond);
                let before = self.state.clone();
                self.scoped(|c| c.stmt(then));
                let after_then = std::mem::replace(&mut self.state, before);
                if let Some(o) = otherwise {
                    self.scoped(|c| c.stmt(o));
                }
                self.state = after_then.join(self.state.clone());
            }
            Stmt::Foreach {
                binding,
                iter,
                body,
                ..
            } => {
                self.expr(iter);
                self.looping(|c| {
                    c.bind(binding);
                    c.stmt(body);
                });
            }
            Stmt::For {
                init,
                cond,
                step,
                body,
            } => self.scoped(|c| {
                if let Some(i) = init {
                    c.stmt(i);
                }
                c.looping(|c| {
                    c.opt_expr(cond.as_ref());
                    c.stmt(body);
                    if let Some(s) = step {
                        c.stmt(s);
                    }
                });
            }),
            Stmt::While { cond, body } | Stmt::DoWhile { body, cond } => self.looping(|c| {
                c.expr(cond);
                c.stmt(body);
            }),
            _ => self.leaf(s),
        }
    }

    /// The statements with no paths of their own.
    fn leaf(&mut self, s: &Stmt) {
        match s {
            Stmt::Block(b) => self.block(b),
            Stmt::Return(value, _) | Stmt::Skip(value, _) => {
                self.opt_expr(value.as_ref());
                self.state.dead = true;
            }
            Stmt::Expr(e) | Stmt::Value(e) => self.expr(e),
            _ => {}
        }
    }

    /// A loop's body, read twice: the second turn sees what the first moved.
    fn looping(&mut self, mut body: impl FnMut(&mut Self)) {
        let before = self.state.clone();
        for _ in 0..LOOP_READS {
            self.scoped(&mut body);
            self.state = before.clone().join(self.state.clone());
        }
    }

    pub(super) fn bind(&mut self, b: &Binding) {
        match b {
            Binding::One(n) => self.state.declare(&n.text, Var::default()),
            Binding::Tuple(names) => names
                .iter()
                .for_each(|n| self.state.declare(&n.text, Var::default())),
        }
    }

    /// `x = …`: what `x` was moved from holds a value again.
    pub(super) fn renew(&mut self, name: &str) {
        if let Some(v) = self.state.get_mut(name) {
            v.moved = None;
        }
    }
}

/// How often a loop's body is read: once as the first turn, once as any
/// later one.
const LOOP_READS: usize = 2;