added a couple macros, removed null type, added environment
This commit is contained in:
@@ -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 {}
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
+24
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Rc<RefCell<Environment>>>,
|
||||
variables: HashMap<String, ValueType>,
|
||||
}
|
||||
|
||||
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<RefCell<...>>
|
||||
/// so it can be passed down safely to deeper scopes.
|
||||
pub fn new_child(parent: Rc<RefCell<Environment>>) -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self {
|
||||
parent: Some(parent),
|
||||
variables: HashMap::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn declare_var(&mut self, varname: String, value: ValueType) -> Result<ValueType, String> {
|
||||
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<RefCell<Self>>,
|
||||
varname: String,
|
||||
value: ValueType,
|
||||
) -> Result<ValueType, String> {
|
||||
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<RefCell<Self>>, varname: &str) -> Result<ValueType, String> {
|
||||
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<RefCell<Self>>,
|
||||
varname: &str,
|
||||
) -> Result<Rc<RefCell<Self>>, 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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-13
@@ -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<RefCell<Environment>>) -> 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<RefCell<Environment>>) -> 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<RefCell<Environment>>` or `env: &Environment`
|
||||
// to `env: &Rc<RefCell<Environment>>`
|
||||
fn eval_ident(ident: String, env: &Rc<RefCell<Environment>>) -> ValueType {
|
||||
// Now &env perfectly matches the expected `&Rc<RefCell<Environment>>`
|
||||
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<RefCell<Environment>>) -> 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.",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod environment;
|
||||
pub mod interpreter;
|
||||
pub mod values;
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user