diff --git a/src/frontend/ast.rs b/src/frontend/ast.rs index 27e843f..6ab95fc 100644 --- a/src/frontend/ast.rs +++ b/src/frontend/ast.rs @@ -5,7 +5,6 @@ pub trait Expr: Stmt {} pub enum NodeType { Program(Program), NumericLiteral(NumericLiteral), - NullLiteral(NullLiteral), Identifier(Identifier), BinaryExpr(BinaryExpr), EOF, @@ -33,9 +32,6 @@ pub struct NumericLiteral { pub value: f64, } -#[derive(Debug)] -pub struct NullLiteral {} - impl Stmt for Program {} impl Stmt for NumericLiteral {} diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs index 505ec39..f3f4f73 100644 --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -27,14 +27,12 @@ pub mod interpreter { CloseParen, OpenParen, Equals, - Null, Let, EOF, } static KEYWORDS: phf::Map<&'static str, Token> = phf::phf_map! { "let" => Token::Let, - "null" => Token::Null, }; impl Token { @@ -81,7 +79,6 @@ pub mod interpreter { Token::OpenParen => write!(f, "Token::OpenParen"), Token::CloseParen => write!(f, "Token::CloseParen"), Token::EOF => write!(f, "Token::EOF"), - Token::Null => write!(f, "Token::Null"), } } } diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index 04f9ca0..c420047 100644 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -1,6 +1,6 @@ use std::process; -use crate::ast::{BinaryExpr, Identifier, NodeType, NullLiteral, NumericLiteral, Program}; +use crate::ast::{BinaryExpr, Identifier, NodeType, NumericLiteral, Program}; use crate::lexer::interpreter; pub struct Parser { @@ -50,7 +50,6 @@ impl Parser { Token::Number(n) => NodeType::NumericLiteral(NumericLiteral { value: n.parse().expect(&format!("Failed to parse number: {}", n)), }), - Token::Null => NodeType::NullLiteral(NullLiteral {}), Token::EOF => NodeType::EOF, Token::OpenParen => { diff --git a/src/main.rs b/src/main.rs index 61cf11f..acad012 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use clap::Parser; +use std::io::Write; use std::process; mod frontend; @@ -7,11 +8,32 @@ use frontend::lexer; use frontend::parser; mod runtime; +use runtime::environment::Environment; use runtime::interpreter::evaluate; +macro_rules! declare_var { + ($env:ident, $name:expr, $value:expr) => { + $env.declare_var(String::from($name), $value) + .expect(&format!( + "Failed to declare variable '{}' in the REPL environment.", + $name + )); + }; +} + pub fn repl() { // Read-Eval-Print Loop - use std::io::{self, Write}; + use std::{cell::RefCell, io, rc::Rc}; + + let mut env = Environment::new(); + + declare_var!(env, "x", make_num!(10.0)); + + declare_var!(env, "true", make_bool!(true)); + declare_var!(env, "false", make_bool!(false)); + declare_var!(env, "null", make_null!()); + + let env = Rc::new(RefCell::new(env)); println!("Welcome to the Riv Lang REPL! Type 'exit' to quit."); @@ -30,7 +52,7 @@ pub fn repl() { let program = parser::Parser::new(input).produce_ast(); - let result = evaluate(frontend::ast::NodeType::Program(program)); + let result = evaluate(frontend::ast::NodeType::Program(program), &env); println!("{:#?}", result); } } diff --git a/src/runtime/environment.rs b/src/runtime/environment.rs new file mode 100644 index 0000000..389607b --- /dev/null +++ b/src/runtime/environment.rs @@ -0,0 +1,87 @@ +use crate::runtime::values::ValueType; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug, Clone)] +pub struct Environment { + parent: Option>>, + variables: HashMap, +} + +impl Environment { + /// Creates a new root environment with no parent. + pub fn new() -> Self { + Self { + parent: None, + variables: HashMap::new(), + } + } + + /// Creates a new child environment wrapped in Rc> + /// so it can be passed down safely to deeper scopes. + pub fn new_child(parent: Rc>) -> Rc> { + Rc::new(RefCell::new(Self { + parent: Some(parent), + variables: HashMap::new(), + })) + } + + pub fn declare_var(&mut self, varname: String, value: ValueType) -> Result { + if self.variables.contains_key(&varname) { + return Err(format!( + "Cannot declare variable '{}'. As it already is defined.", + varname + )); + } + + self.variables.insert(varname, value.clone()); + Ok(value) + } + + pub fn assign_var( + env_ref: &Rc>, + varname: String, + value: ValueType, + ) -> Result { + let resolved_env = Self::resolve(env_ref, &varname)?; + resolved_env + .borrow_mut() + .variables + .insert(varname, value.clone()); + Ok(value) + } + + pub fn lookup_var(env_ref: &Rc>, varname: &str) -> Result { + let resolved_env = Self::resolve(env_ref, varname)?; + // We clone the value out to avoid holding a reference borrow + let val = resolved_env + .borrow() + .variables + .get(varname) + .cloned() + .unwrap(); + Ok(val) + } + + pub fn resolve( + env_ref: &Rc>, + varname: &str, + ) -> Result>, String> { + if env_ref.borrow().variables.contains_key(varname) { + return Ok(Rc::clone(env_ref)); + } + + // Access the parent if it exists + let parent_opt = env_ref.borrow().parent.as_ref().map(Rc::clone); + + if let Some(parent_env) = parent_opt { + Self::resolve(&parent_env, varname) + } else { + Err(format!( + "Cannot resolve '{}' as it does not exist.", + varname + )) + } + } +} diff --git a/src/runtime/interpreter.rs b/src/runtime/interpreter.rs index f07cd73..6b99165 100644 --- a/src/runtime/interpreter.rs +++ b/src/runtime/interpreter.rs @@ -1,27 +1,31 @@ +use std::cell::RefCell; use std::process; +use std::rc::Rc; use crate::frontend::ast::{BinaryExpr, NodeType, Program}; +use crate::runtime::environment::Environment; use crate::runtime::values::ValueType; +use crate::{make_null, make_num}; -fn eval_program(program: Program) -> ValueType { +fn eval_program(program: Program, env: &Rc>) -> ValueType { // Initialize with your equivalent of NullVal - let mut last_evaluated = ValueType::Null; + let mut last_evaluated = make_null!(); // Iterate through each statement in the program body for statement in program.body { - last_evaluated = evaluate(statement); + last_evaluated = evaluate(statement, env); } last_evaluated } -fn eval_binop(binop: BinaryExpr) -> ValueType { - let lhs = evaluate(*binop.left); - let rhs = evaluate(*binop.right); +fn eval_binop(binop: BinaryExpr, env: &Rc>) -> ValueType { + let lhs = evaluate(*binop.left, env); + let rhs = evaluate(*binop.right, env); match (&lhs, &rhs) { (ValueType::Number(l), ValueType::Number(r)) => eval_num_binop(*l, *r, binop.operator), - _ => ValueType::Null, + _ => make_null!(), } } @@ -41,15 +45,28 @@ fn eval_num_binop(lhs: f64, rhs: f64, operator: String) -> ValueType { } } - ValueType::Number(result) + make_num!(result) } -pub fn evaluate(node: NodeType) -> ValueType { +// Change `env: &Rc>` or `env: &Environment` +// to `env: &Rc>` +fn eval_ident(ident: String, env: &Rc>) -> ValueType { + // Now &env perfectly matches the expected `&Rc>` + match Environment::lookup_var(env, &ident) { + Ok(value) => value, + Err(err) => { + eprintln!("Error looking up identifier '{}': {}", ident, err); + std::process::exit(1); + } + } +} + +pub fn evaluate(node: NodeType, env: &Rc>) -> ValueType { match node { - NodeType::NumericLiteral(n) => ValueType::Number(n.value), - NodeType::NullLiteral(_) => ValueType::Null, - NodeType::BinaryExpr(e) => eval_binop(e), - NodeType::Program(p) => eval_program(p), + NodeType::NumericLiteral(n) => make_num!(n.value), + NodeType::BinaryExpr(e) => eval_binop(e, env), + NodeType::Program(p) => eval_program(p, env), + NodeType::Identifier(i) => eval_ident(i.symbol, env), _ => { eprintln!( "Evaluation for node type {:#?} is not implemented yet.", diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 27bd8a9..1ae9de1 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -1,2 +1,3 @@ +pub mod environment; pub mod interpreter; pub mod values; diff --git a/src/runtime/values.rs b/src/runtime/values.rs index b4f5be7..c11b342 100644 --- a/src/runtime/values.rs +++ b/src/runtime/values.rs @@ -2,4 +2,26 @@ pub enum ValueType { Null, Number(f64), + Boolean(bool), +} + +#[macro_export] +macro_rules! make_num { + ($n: expr) => { + crate::runtime::values::ValueType::Number($n) + }; +} + +#[macro_export] +macro_rules! make_null { + () => { + crate::runtime::values::ValueType::Null + }; +} + +#[macro_export] +macro_rules! make_bool { + ($b: expr) => { + crate::runtime::values::ValueType::Boolean($b) + }; }