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.
This commit is contained in:
+127
-12
@@ -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<K> {
|
||||
|
||||
/// The base for custom node types (extensions)
|
||||
pub trait CustomNode: Debug + Send + Sync {
|
||||
fn eval(&self, node: &Node<UntypedKind>, ctx: &mut Context) -> Value;
|
||||
fn eval(&self, node: &Node<UntypedKind>, ctx: &mut Context) -> Result<Value, String>;
|
||||
fn display_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
/// Dynamic Scope for the interpreter (Temporary solution)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Scope {
|
||||
values: HashMap<String, Value>,
|
||||
parent: Option<Arc<Mutex<Scope>>>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(parent: Option<Arc<Mutex<Scope>>>) -> 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<Value> {
|
||||
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<Mutex<Scope>>,
|
||||
}
|
||||
|
||||
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<UntypedKind> {
|
||||
pub fn eval(&self, ctx: &mut Context) -> Value {
|
||||
pub fn eval(&self, ctx: &mut Context) -> Result<Value, String> {
|
||||
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<Value> = 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),
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -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)?;
|
||||
|
||||
+13
-7
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user