nbcl 0.1.3

Configuration language designed to be easy and understandable.
Documentation
use super::{Evaluator, FlowControl, Scope, ScopeKind};
use crate::{
    ast::Value,
    ast::source::*,
    error::{NbclError, Result},
};

impl Evaluator {
    pub(crate) fn execute_stmt(&mut self, stmt: &Stmt) -> Result<Value> {
        if let FlowControl::Return(val) = &self.flow {
            return Ok(val.clone());
        }

        let result = match stmt {
            Stmt::Expr(expr) => {
                // Standalone expressions are evaluated and discarded
                self.eval_expr(&expr)?
            }
            Stmt::Return(maybe_expr, span) => {
                // Ensure that we cant return at:
                // TopLevel or a Block child of TopLevel
                let is_at_root = match self.scopes.as_slice() {
                    [root] if root.kind == ScopeKind::TopLevel => true,
                    [root, current]
                        if root.kind == ScopeKind::TopLevel && current.kind == ScopeKind::Block =>
                    {
                        true
                    }
                    _ => false,
                };

                if is_at_root {
                    return Err(NbclError::Runtime {
                        message: "cannot return from the top level".to_string(),
                        hint: Some("Move this logic into a function or component if you need early returns.".to_string()),
                        span: Some(span.clone()),
                    });
                }

                let val = match maybe_expr {
                    Some(e) => self.eval_expr(&e)?,
                    None => Value::Null,
                };
                self.flow = FlowControl::Return(val.clone());
                val
            }
            // TODO: Use typehint in global and local
            Stmt::Global(name, _type_hint, expr) => {
                let val = self.eval_expr(&expr)?;

                // A 'global' always goes into the very first scope (index 0),
                // regardless of how many components or blocks deep we are.
                if let Some(global_scope) = self.scopes.first_mut() {
                    global_scope.variables.insert(name.to_string(), val);
                } else {
                    // Fallback: if somehow scopes is empty (shouldn't happen),
                    // create a new one.
                    let mut map = Scope::new(ScopeKind::TopLevel);
                    map.variables.insert(name.to_string(), val);
                    self.scopes.push(map);
                }

                Value::Null
            }
            Stmt::Local(name, _type_hint, expr) => {
                let val = self.eval_expr(&expr)?;
                if let Some(current_scope) = self.scopes.last_mut() {
                    current_scope.variables.insert(name.to_string(), val);
                }

                Value::Null
            }
            Stmt::Assign(lhs, rhs_expr, span) => {
                let new_val = self.eval_expr(&rhs_expr)?;

                match &lhs.kind {
                    ExprKind::Variable(name) => {
                        let mut found = false;
                        for scope in self.scopes.iter_mut().rev() {
                            if scope.variables.contains_key(name) {
                                scope.variables.insert(name.clone(), new_val);
                                found = true;
                                break;
                            }
                        }
                        if !found {
                            let candidates = self.scopes.iter().flat_map(|s| s.variables.keys());
                            let suggestion = crate::utils::find_best_match(&name, candidates);

                            let hint = suggestion.map(|s| format!("Did you mean \"{}\"?", s));

                            return Err(NbclError::Runtime {
                                message: format!("variable '{}' doesn't exist", name),
                                hint,
                                span: Some(span.clone()),

                            });
                        }
                    }

                    ExprKind::Field(base, field_name, _is_safe) => {
                        let mut target_map = self.eval_expr(&base)?;
                        if let Value::Map(ref mut entries) = target_map {
                            if let Some(pos) = entries.iter().position(|(k, _)| k == field_name) {
                                entries[pos].1 = new_val;
                            } else {
                                entries.push((field_name.clone(), new_val));
                            }
                            self.reassign_to_lhs(*base.clone(), target_map)?;
                        } else {
                            return Err(NbclError::Runtime {
                                message: "cannot set field on non-map".into(),
                                span: Some(span.clone()),
                                hint: None,
                            });
                        }
                    }

                    ExprKind::Index(base, index_expr) => {
                        let mut target_coll = self.eval_expr(&base)?;
                        let index_val = self.eval_expr(&index_expr)?;
                        
                        match (&mut target_coll, index_val) {
                            (Value::List(items), Value::Int(i)) => {
                                let idx = i as usize;
                                if idx < items.len() {
                                    items[idx] = new_val;
                                    self.reassign_to_lhs(*base.clone(), target_coll)?;
                                } else {
                                    return Err(NbclError::Runtime {
                                        message: format!("index {} out of bounds", i),
                                        span: Some(span.clone()),
                                        hint: None,
                                    });
                                }
                            }
                            _ => return Err(NbclError::Runtime {
                                message: "invalid index operation".into(),
                                span: Some(span.clone()),
                                hint: None,
                            }),
                        }
                    }
                    _ => return Err(NbclError::Runtime {
                        message: "invalid assignment target".into(),
                        span: Some(span.clone()),
                        hint: None,
                    }),
                }
                Value::Null
            }
            Stmt::For(patterns, iter_expr, body) => {
                let iter_val = self.eval_expr(&iter_expr)?;
                match iter_val {
                    Value::Range(start, end) => {
                        for i in start..end {
                            if let FlowControl::Return(_) = self.flow {
                                break;
                            }

                            let mut loop_scope = Scope::new(ScopeKind::Block);
                            let val_i = Value::Int(i);

                            if patterns.len() == 1 {
                                loop_scope.variables.insert(patterns[0].clone(), val_i);
                            } else if patterns.len() == 2 {
                                loop_scope.variables.insert(patterns[0].clone(), val_i.clone());
                                loop_scope.variables.insert(patterns[1].clone(), val_i);
                            }

                            self.scopes.push(loop_scope);
                            self.execute_block_internal(&body)?;
                            self.scopes.pop();

                            if let FlowControl::Return(_) = self.flow {
                                break;
                            }
                        }
                    }
                    Value::List(items) => {
                        for (i, item) in items.into_iter().enumerate() {
                            if let FlowControl::Return(_) = self.flow {
                                break;
                            }

                            let mut loop_scope = Scope::new(ScopeKind::Block);

                            // Handle pattern matching (len 1 or len 2)
                            if patterns.len() == 1 {
                                loop_scope.variables.insert(patterns[0].clone(), item);
                            } else if patterns.len() == 2 {
                                loop_scope.variables.insert(patterns[0].clone(), Value::Int(i as i64));
                                loop_scope.variables.insert(patterns[1].clone(), item);
                            }

                            self.scopes.push(loop_scope);

                            // Execute the block logic
                            self.execute_block_internal(&body)?;

                            self.scopes.pop();

                            if let FlowControl::Return(_) = self.flow {
                                break;
                            }
                        }
                    }

                    // unreachable
                    _ => {}
                }

                Value::Null
            }

            Stmt::While(condition_expr, body) => {
                // Keep looping as long as the condition evaluates to truthy
                // and we haven't hit a Return statement.
                while self.eval_expr(&condition_expr)?.is_truthy() {
                    if let FlowControl::Return(_) = self.flow {
                        break;
                    }

                    self.scopes.push(Scope::new(ScopeKind::Block));

                    // Execute the block logic
                    self.execute_block_internal(&body)?;

                    self.scopes.pop();

                    if let FlowControl::Return(_) = self.flow {
                        break;
                    }
                }

                Value::Null
            }
        };
        Ok(result)
    }

    /// Executes the statements in a block and evaluates the terminator if present.
    fn execute_block_internal(&mut self, block: &Block) -> Result<Value> {
        // Run all statements
        for s in &block.stmts {
            self.execute_stmt(s)?;
            if let FlowControl::Return(_) = self.flow {
                return Ok(Value::Null);
            }
        }

        // Evaluate the implicit return (terminator) if it exists
        if let Some(expr) = &block.terminator {
            let val = self.eval_expr(expr)?;
            return Ok(val);
        }

        Ok(Value::Null)
    }

    fn reassign_to_lhs(&mut self, lhs: Expr, value: Value) -> Result<()> {
        match lhs.kind {
            ExprKind::Variable(name) => {
                for scope in self.scopes.iter_mut().rev() {
                    if scope.variables.contains_key(&name) {
                        scope.variables.insert(name, value);
                        return Ok(());
                    }
                }
                Err(NbclError::Runtime { 
                    message: "Variable lost during assignment".into(), 
                    hint: None,
                    span: None,
                })
            }
            ExprKind::Field(base, field, _) => {
                let mut parent = self.eval_expr(&base)?;
                if let Value::Map(ref mut entries) = parent {
                    if let Some(pos) = entries.iter().position(|(k, _)| k == &field) {
                        entries[pos].1 = value;
                    } else {
                        entries.push((field, value));
                    }
                    self.reassign_to_lhs(*base, parent)
                } else { Ok(()) }
            }
            ExprKind::Index(base, index_expr) => {
                let mut parent = self.eval_expr(&base)?;
                let idx_val = self.eval_expr(&index_expr)?;
                if let (Value::List(items), Value::Int(i)) = (&mut parent, idx_val) {
                    let idx = i as usize;
                    if idx < items.len() {
                        items[idx] = value;
                        self.reassign_to_lhs(*base, parent)?;
                    }
                }
                Ok(())
            }
            _ => Ok(()),
        }
    }
}