From 647fc79d28b30e1d082ea2408491693b2e454f5b Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 17 Feb 2026 00:33:58 +0100 Subject: [PATCH] Add scope and basic stdlib for evaluation Introduces a `Scope` struct for dynamic scope management and a `Context` struct to hold the current scope. Implements a basic standard library with arithmetic operations and a greater-than comparison. Modifies `Node::eval` to handle identifiers by resolving them within the current scope and `Call` nodes for function execution. Updates the parser to use `Context::new()` for evaluating vector elements, ensuring a fresh scope. Enhances the main loop to handle potential runtime errors during evaluation. --- src/ast/nodes.rs | 139 ++++++++++++++++++++++++++++++++++++++++++---- src/ast/parser.rs | 6 +- src/main.rs | 20 ++++--- 3 files changed, 145 insertions(+), 20 deletions(-) diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 80aad4a..b2b644c 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -1,4 +1,5 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; +use std::collections::HashMap; use std::fmt::Debug; use crate::ast::types::{Identity, Value}; @@ -11,15 +12,111 @@ pub struct Node { /// The base for custom node types (extensions) pub trait CustomNode: Debug + Send + Sync { - fn eval(&self, node: &Node, ctx: &mut Context) -> Value; + fn eval(&self, node: &Node, ctx: &mut Context) -> Result; fn display_name(&self) -> &'static str; } +/// Dynamic Scope for the interpreter (Temporary solution) +#[derive(Debug, Clone)] +pub struct Scope { + values: HashMap, + parent: Option>>, +} + +impl Scope { + pub fn new(parent: Option>>) -> Self { + Self { + values: HashMap::new(), + parent, + } + } + + pub fn define(&mut self, name: &str, value: Value) { + self.values.insert(name.to_string(), value); + } + + pub fn resolve(&self, name: &str) -> Option { + if let Some(val) = self.values.get(name) { + return Some(val.clone()); + } + if let Some(parent) = &self.parent { + return parent.lock().unwrap().resolve(name); + } + None + } +} + /// Evaluation Context (Scope management) pub struct Context { - // Add variables, scope management here later + pub scope: Arc>, } +impl Context { + pub fn new() -> Self { + let mut root_scope = Scope::new(None); + + // Register standard library (built-ins) + register_stdlib(&mut root_scope); + + Self { + scope: Arc::new(Mutex::new(root_scope)), + } + } +} + +fn register_stdlib(scope: &mut Scope) { + // Helper macro to reduce boilerplate + macro_rules! bin_op { + ($name:expr, $op:tt) => { + scope.define($name, Value::Function(Arc::new(|args| { + if args.len() < 2 { return Value::Void; } // Error handling later + + // Fold allow multi-arg: (+ 1 2 3) -> 6 + let mut acc = match &args[0] { + Value::Int(i) => *i as f64, + Value::Float(f) => *f, + _ => return Value::Void, + }; + + for arg in &args[1..] { + let val = match arg { + Value::Int(i) => *i as f64, + Value::Float(f) => *f, + _ => return Value::Void, + }; + acc = acc $op val; + } + + // Return Int if result is integer-like (simplified) + if acc.fract() == 0.0 { + Value::Int(acc as i64) + } else { + Value::Float(acc) + } + }))); + }; + } + + bin_op!("+", +); + bin_op!("-", -); + bin_op!("*", *); + bin_op!("/", /); + + // Logic + scope.define(">", Value::Function(Arc::new(|args| { + if args.len() != 2 { return Value::Void; } + let (v1, v2) = (&args[0], &args[1]); + match (v1, v2) { + (Value::Int(a), Value::Int(b)) => Value::Bool(a > b), + (Value::Float(a), Value::Float(b)) => Value::Bool(a > b), + (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b), + (Value::Float(a), Value::Int(b)) => Value::Bool(*a > (*b as f64)), + _ => Value::Bool(false), + } + }))); +} + + /// The core AST variant enum for performance and structure #[derive(Debug)] pub enum UntypedKind { @@ -41,25 +138,43 @@ pub enum UntypedKind { } impl Node { - pub fn eval(&self, ctx: &mut Context) -> Value { + pub fn eval(&self, ctx: &mut Context) -> Result { match &self.kind { - UntypedKind::Nop => Value::Void, - UntypedKind::Constant(v) => v.clone(), - UntypedKind::Identifier(_) => todo!("Lookup in Context"), + UntypedKind::Nop => Ok(Value::Void), + UntypedKind::Constant(v) => Ok(v.clone()), + + UntypedKind::Identifier(name) => { + let scope = ctx.scope.lock().unwrap(); + scope.resolve(name) + .ok_or_else(|| format!("Undefined variable: '{}'", name)) + }, + UntypedKind::If { cond, then_br, else_br } => { - if cond.eval(ctx).is_truthy() { + let cond_val = cond.eval(ctx)?; + if cond_val.is_truthy() { then_br.eval(ctx) } else if let Some(eb) = else_br { eb.eval(ctx) } else { - Value::Void + Ok(Value::Void) } } + UntypedKind::Call { callee, args } => { - let _func = callee.eval(ctx); - let _eval_args: Vec = args.iter().map(|a| a.eval(ctx)).collect(); - todo!("Execute func with args") + let func_val = callee.eval(ctx)?; + + // Evaluate all arguments first (strict evaluation) + let mut eval_args = Vec::with_capacity(args.len()); + for arg in args { + eval_args.push(arg.eval(ctx)?); + } + + match func_val { + Value::Function(f) => Ok(f(eval_args)), + _ => Err(format!("Attempt to call a non-function value: {}", func_val)), + } } + UntypedKind::Extension(ext) => ext.eval(self, ctx), } } diff --git a/src/ast/parser.rs b/src/ast/parser.rs index c5a1664..53b1a84 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -122,7 +122,11 @@ impl<'a> Parser<'a> { while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { // Vector elements in Myc are usually just constants in a list let expr = self.parse_expression()?; - elements.push(expr.eval(&mut crate::ast::nodes::Context {})); // Simple direct eval for literals + // Simple direct eval for literals (Context::new creates fresh scope) + match expr.eval(&mut crate::ast::nodes::Context::new()) { + Ok(val) => elements.push(val), + Err(e) => return Err(format!("Error evaluating vector element: {}", e)), + } } self.expect(TokenKind::RightBracket)?; diff --git a/src/main.rs b/src/main.rs index 0fbc661..35925d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,13 +53,19 @@ impl eframe::App for CompilerApp { match parser.parse_expression() { Ok(ast) => { - let mut context = Context {}; - let result = ast.eval(&mut context); - self.output_log = format!( - "AST Parsed Successfully.\nResult: {}\n\nFinished at {:?}", - result, - std::time::SystemTime::now(), - ); + let mut context = Context::new(); // Use new() which registers stdlib + match ast.eval(&mut context) { + Ok(result) => { + self.output_log = format!( + "AST Parsed & Evaluated Successfully.\nResult: {}\n\nFinished at {:?}", + result, + std::time::SystemTime::now(), + ); + } + Err(e) => { + self.output_log = format!("Runtime Error: {}", e); + } + } } Err(e) => { self.output_log = format!("Parser Error: {}", e);