Mal step 3
This commit is contained in:
parent
dfeb5d1b45
commit
75591cac9a
5 changed files with 232 additions and 156 deletions
123
src/env.rs
123
src/env.rs
|
@ -1,123 +0,0 @@
|
||||||
use std::{borrow::Borrow, collections::HashMap, rc::Rc};
|
|
||||||
|
|
||||||
use crate::eval::{Error, Expression};
|
|
||||||
|
|
||||||
pub type Environment = HashMap<String, Rc<Expression>>;
|
|
||||||
|
|
||||||
pub fn core_environment() -> Environment {
|
|
||||||
[
|
|
||||||
// Arithmetic operations
|
|
||||||
(
|
|
||||||
"+".to_string(),
|
|
||||||
Expression::NativeFunc {
|
|
||||||
func: |args| {
|
|
||||||
let res = args
|
|
||||||
.into_iter()
|
|
||||||
.reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) {
|
|
||||||
(Expression::Int(lhs), Expression::Int(rhs)) => {
|
|
||||||
Expression::Int(lhs + rhs).into()
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
})
|
|
||||||
.unwrap_or(Rc::new(Expression::Int(0)));
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"-".to_string(),
|
|
||||||
Expression::NativeFunc {
|
|
||||||
func: |args| {
|
|
||||||
let res = args
|
|
||||||
.into_iter()
|
|
||||||
.reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) {
|
|
||||||
(Expression::Int(lhs), Expression::Int(rhs)) => {
|
|
||||||
Expression::Int(lhs - rhs).into()
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
})
|
|
||||||
.unwrap_or(Rc::new(Expression::Int(0)));
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"*".to_string(),
|
|
||||||
Expression::NativeFunc {
|
|
||||||
func: |args| {
|
|
||||||
let res = args
|
|
||||||
.into_iter()
|
|
||||||
.reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) {
|
|
||||||
(Expression::Int(lhs), Expression::Int(rhs)) => {
|
|
||||||
Expression::Int(lhs * rhs).into()
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
})
|
|
||||||
.unwrap_or(Rc::new(Expression::Int(0)));
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"/".to_string(),
|
|
||||||
Expression::NativeFunc {
|
|
||||||
func: |args| {
|
|
||||||
let res = args
|
|
||||||
.into_iter()
|
|
||||||
.reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) {
|
|
||||||
(Expression::Int(lhs), Expression::Int(rhs)) => {
|
|
||||||
Expression::Int(lhs / rhs).into()
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
})
|
|
||||||
.unwrap_or(Rc::new(Expression::Int(0)));
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
// Collections
|
|
||||||
(
|
|
||||||
"vector".to_string(),
|
|
||||||
Expression::NativeFunc {
|
|
||||||
func: |args| Ok(Rc::new(Expression::Vector(args))),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"hashmap".to_string(),
|
|
||||||
Expression::NativeFunc {
|
|
||||||
func: |args| {
|
|
||||||
if args.len() % 2 != 0 {
|
|
||||||
Err(Error::MismatchedArgCount)?
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut index = -1;
|
|
||||||
let (keys, values): (Vec<_>, Vec<_>) = args.into_iter().partition(|_| {
|
|
||||||
index += 1;
|
|
||||||
index % 2 == 0
|
|
||||||
});
|
|
||||||
|
|
||||||
let res = keys
|
|
||||||
.into_iter()
|
|
||||||
// We turn the keys into strings because they're hashable
|
|
||||||
// This feels so hacky, but ¯\_(ツ)_/¯
|
|
||||||
.map(|key| key.to_string())
|
|
||||||
.zip(values.into_iter())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(Rc::new(Expression::HashMap(res)))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
.into()
|
|
||||||
}
|
|
186
src/evaluator/env.rs
Normal file
186
src/evaluator/env.rs
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
use std::{borrow::Borrow, cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use super::{eval_ast_node, Error, Expression};
|
||||||
|
use crate::parser::Node;
|
||||||
|
|
||||||
|
pub type RawEnvironment = HashMap<String, Rc<Expression>>;
|
||||||
|
pub struct Environment {
|
||||||
|
current: RefCell<RawEnvironment>,
|
||||||
|
outer: Option<Rc<Environment>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Environment {
|
||||||
|
pub fn get(&self, ident: &str) -> Option<Rc<Expression>> {
|
||||||
|
if let Some(val) = self.current.borrow().get(ident) {
|
||||||
|
return Some(val.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.outer.clone()?.get(ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&self, ident: String, val: Rc<Expression>) {
|
||||||
|
self.current.borrow_mut().insert(ident, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn core_environment() -> Rc<Environment> {
|
||||||
|
let env = [
|
||||||
|
// Arithmetic operations
|
||||||
|
(
|
||||||
|
"+".to_string(),
|
||||||
|
Expression::NativeFunc(|args| {
|
||||||
|
let res = args
|
||||||
|
.into_iter()
|
||||||
|
.reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) {
|
||||||
|
(Expression::Int(lhs), Expression::Int(rhs)) => {
|
||||||
|
Expression::Int(lhs + rhs).into()
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
})
|
||||||
|
.unwrap_or(Rc::new(Expression::Int(0)));
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"-".to_string(),
|
||||||
|
Expression::NativeFunc(|args| {
|
||||||
|
let res = args
|
||||||
|
.into_iter()
|
||||||
|
.reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) {
|
||||||
|
(Expression::Int(lhs), Expression::Int(rhs)) => {
|
||||||
|
Expression::Int(lhs - rhs).into()
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
})
|
||||||
|
.unwrap_or(Rc::new(Expression::Int(0)));
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"*".to_string(),
|
||||||
|
Expression::NativeFunc(|args| {
|
||||||
|
let res = args
|
||||||
|
.into_iter()
|
||||||
|
.reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) {
|
||||||
|
(Expression::Int(lhs), Expression::Int(rhs)) => {
|
||||||
|
Expression::Int(lhs * rhs).into()
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
})
|
||||||
|
.unwrap_or(Rc::new(Expression::Int(0)));
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/".to_string(),
|
||||||
|
Expression::NativeFunc(|args| {
|
||||||
|
let res = args
|
||||||
|
.into_iter()
|
||||||
|
.reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) {
|
||||||
|
(Expression::Int(lhs), Expression::Int(rhs)) => {
|
||||||
|
Expression::Int(lhs / rhs).into()
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
})
|
||||||
|
.unwrap_or(Rc::new(Expression::Int(0)));
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
// Collections
|
||||||
|
(
|
||||||
|
"vector".to_string(),
|
||||||
|
Expression::NativeFunc(|args| Ok(Rc::new(Expression::Vector(args)))).into(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hashmap".to_string(),
|
||||||
|
Expression::NativeFunc(|args| {
|
||||||
|
if args.len() % 2 != 0 {
|
||||||
|
Err(Error::MismatchedArgCount)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut index = -1;
|
||||||
|
let (keys, values): (Vec<_>, Vec<_>) = args.into_iter().partition(|_| {
|
||||||
|
index += 1;
|
||||||
|
index % 2 == 0
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = keys
|
||||||
|
.into_iter()
|
||||||
|
// We turn the keys into strings because they're hashable
|
||||||
|
// This feels so hacky, but ¯\_(ツ)_/¯
|
||||||
|
.map(|key| key.to_string())
|
||||||
|
.zip(values.into_iter())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Rc::new(Expression::HashMap(res)))
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
// Environment Manipulation
|
||||||
|
(
|
||||||
|
"define!".to_string(),
|
||||||
|
Expression::Special(|env, args| {
|
||||||
|
let mut args = args.into_iter();
|
||||||
|
if args.len() != 2 {
|
||||||
|
Err(Error::MismatchedArgCount)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = match args.next().unwrap() {
|
||||||
|
Node::Symbol(name) => name,
|
||||||
|
_ => Err(Error::ExpectedSymbol)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let val = eval_ast_node(env.clone(), args.next().unwrap())?;
|
||||||
|
|
||||||
|
env.set(key, val.clone());
|
||||||
|
|
||||||
|
Ok(val)
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"let*".to_string(),
|
||||||
|
Expression::Special(|env, args| {
|
||||||
|
if args.len() != 2 {
|
||||||
|
Err(Error::MismatchedArgCount)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = args.into_iter();
|
||||||
|
let new_env = match args.next().unwrap() {
|
||||||
|
Node::List(list) if list.len() == 2 => {
|
||||||
|
let mut list = list;
|
||||||
|
let sym = match list.remove(0) {
|
||||||
|
Node::Symbol(name) => name,
|
||||||
|
_ => Err(Error::ExpectedSymbol)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let val = eval_ast_node(env.clone(), list.remove(0))?;
|
||||||
|
Environment {
|
||||||
|
current: RefCell::new([(sym.clone(), val)].into()),
|
||||||
|
outer: Some(env.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Node::List(_) => Err(Error::MismatchedArgCount)?,
|
||||||
|
_ => Err(Error::ExpectedList)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
eval_ast_node(Rc::new(new_env), args.next().unwrap())
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Environment {
|
||||||
|
current: RefCell::new(env.into()),
|
||||||
|
outer: None,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
|
@ -3,7 +3,10 @@ use std::{borrow::Borrow, collections::HashMap, rc::Rc};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{env::Environment, parser::Node};
|
use crate::parser::Node;
|
||||||
|
|
||||||
|
mod env;
|
||||||
|
pub use env::{core_environment, Environment};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static TRUE: Rc<Expression> = Rc::new(Expression::Boolean(true));
|
static TRUE: Rc<Expression> = Rc::new(Expression::Boolean(true));
|
||||||
|
@ -24,9 +27,8 @@ pub enum Expression {
|
||||||
Vector(Vec<Rc<Expression>>),
|
Vector(Vec<Rc<Expression>>),
|
||||||
HashMap(HashMap<String, Rc<Expression>>),
|
HashMap(HashMap<String, Rc<Expression>>),
|
||||||
|
|
||||||
NativeFunc {
|
NativeFunc(fn(args: Vec<Rc<Expression>>) -> Result<Rc<Expression>>),
|
||||||
func: fn(args: Vec<Rc<Expression>>) -> Result<Rc<Expression>>,
|
Special(fn(env: Rc<Environment>, args: Vec<Node>) -> Result<Rc<Expression>>),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Expression {
|
impl std::fmt::Display for Expression {
|
||||||
|
@ -58,7 +60,8 @@ impl std::fmt::Display for Expression {
|
||||||
write!(f, "{{{res}}}")
|
write!(f, "{{{res}}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression::NativeFunc { .. } => write!(f, "function"),
|
Expression::NativeFunc(func) => write!(f, "{func:?}"),
|
||||||
|
Expression::Special(func) => write!(f, "{func:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,38 +72,45 @@ pub enum Error {
|
||||||
NotInEnv,
|
NotInEnv,
|
||||||
#[error("expression does not have a valid operator")]
|
#[error("expression does not have a valid operator")]
|
||||||
InvalidOperator,
|
InvalidOperator,
|
||||||
|
#[error("expected symbol")]
|
||||||
|
ExpectedSymbol,
|
||||||
|
#[error("expected list")]
|
||||||
|
ExpectedList,
|
||||||
#[error("incorrect number of arguments passed to function")]
|
#[error("incorrect number of arguments passed to function")]
|
||||||
MismatchedArgCount,
|
MismatchedArgCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval(env: &Environment, ast: Vec<Node>) -> Result<Vec<Rc<Expression>>> {
|
pub fn eval(env: Rc<Environment>, ast: Vec<Node>) -> Result<Vec<Rc<Expression>>> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
|
|
||||||
for node in ast {
|
for node in ast {
|
||||||
res.push(eval_ast_node(env, node)?);
|
res.push(eval_ast_node(env.clone(), node)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_ast_node(env: &Environment, ast_node: Node) -> Result<Rc<Expression>> {
|
fn eval_ast_node(env: Rc<Environment>, ast_node: Node) -> Result<Rc<Expression>> {
|
||||||
let expr = match ast_node {
|
let expr = match ast_node {
|
||||||
Node::List(list) => {
|
Node::List(list) => {
|
||||||
let mut res = Vec::new();
|
let mut list = list.into_iter();
|
||||||
|
let operator = eval_ast_node(env.clone(), list.next().ok_or(Error::InvalidOperator)?)?;
|
||||||
|
|
||||||
|
match operator.borrow() {
|
||||||
|
Expression::NativeFunc(func) => {
|
||||||
|
let mut args = Vec::new();
|
||||||
for node in list {
|
for node in list {
|
||||||
res.push(eval_ast_node(env, node)?);
|
args.push(eval_ast_node(env.clone(), node)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut list = res.into_iter();
|
func(args)?
|
||||||
let operator = match list.next() {
|
}
|
||||||
Some(rc) => match rc.borrow() {
|
Expression::Special(func) => {
|
||||||
Expression::NativeFunc { func } => *func,
|
let args = list.collect();
|
||||||
|
func(env, args)?
|
||||||
|
}
|
||||||
_ => Err(Error::InvalidOperator)?,
|
_ => Err(Error::InvalidOperator)?,
|
||||||
},
|
}
|
||||||
None => Err(Error::InvalidOperator)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
operator(list.collect())?
|
|
||||||
}
|
}
|
||||||
Node::Symbol(sym) => env.get(&sym).ok_or(Error::NotInEnv)?.to_owned(),
|
Node::Symbol(sym) => env.get(&sym).ok_or(Error::NotInEnv)?.to_owned(),
|
||||||
|
|
||||||
|
@ -118,7 +128,6 @@ fn eval_ast_node(env: &Environment, ast_node: Node) -> Result<Rc<Expression>> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::core_environment;
|
|
||||||
use crate::lexer;
|
use crate::lexer;
|
||||||
use crate::parser;
|
use crate::parser;
|
||||||
|
|
||||||
|
@ -149,8 +158,11 @@ mod test {
|
||||||
#[case("[1 (+ 1 2)]", "[1 3]")]
|
#[case("[1 (+ 1 2)]", "[1 3]")]
|
||||||
#[case("{}", "{}")]
|
#[case("{}", "{}")]
|
||||||
#[case("{:a \"uwu\"}", "{:a: uwu}")]
|
#[case("{:a \"uwu\"}", "{:a: uwu}")]
|
||||||
|
// Environment manipulation
|
||||||
|
#[case("(define! asdf (+ 2 2)) (+ asdf 2)", "4\n6")]
|
||||||
|
#[case("(let* (a 2) (+ a 2))", "4")]
|
||||||
fn test_evaluator(#[case] input: &str, #[case] expected: &str) {
|
fn test_evaluator(#[case] input: &str, #[case] expected: &str) {
|
||||||
let env = &core_environment();
|
let env = core_environment();
|
||||||
let tokens = lexer::read(input).unwrap();
|
let tokens = lexer::read(input).unwrap();
|
||||||
let ast = parser::parse(tokens).unwrap();
|
let ast = parser::parse(tokens).unwrap();
|
||||||
let res = eval(env, ast)
|
let res = eval(env, ast)
|
||||||
|
@ -167,7 +179,7 @@ mod test {
|
||||||
#[case("{:a}")]
|
#[case("{:a}")]
|
||||||
#[case("(not-a-func :uwu)")]
|
#[case("(not-a-func :uwu)")]
|
||||||
fn test_evaluator_fail(#[case] input: &str) {
|
fn test_evaluator_fail(#[case] input: &str) {
|
||||||
let env = &core_environment();
|
let env = core_environment();
|
||||||
let tokens = lexer::read(input).unwrap();
|
let tokens = lexer::read(input).unwrap();
|
||||||
let ast = parser::parse(tokens).unwrap();
|
let ast = parser::parse(tokens).unwrap();
|
||||||
let res = eval(env, ast);
|
let res = eval(env, ast);
|
|
@ -108,7 +108,7 @@ fn next_token(input: &mut Peekable<Chars>) -> Result<Option<Token>> {
|
||||||
Some(tok) => tok,
|
Some(tok) => tok,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
},
|
},
|
||||||
_ => bail!("ilegal token"),
|
_ => bail!("illegal token"),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(tok))
|
Ok(Some(tok))
|
||||||
|
@ -164,10 +164,11 @@ fn read_int(input: &mut Peekable<Chars>, first: char) -> Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_ident(input: &mut Peekable<Chars>, first: char) -> Token {
|
fn read_ident(input: &mut Peekable<Chars>, first: char) -> Token {
|
||||||
|
let special_characters = ['!', '?', '*', '-', '_'];
|
||||||
let mut raw_ident = vec![first];
|
let mut raw_ident = vec![first];
|
||||||
|
|
||||||
while let Some(c) = input.peek() {
|
while let Some(c) = input.peek() {
|
||||||
if !c.is_ascii_alphanumeric() {
|
if !c.is_ascii_alphanumeric() && !special_characters.contains(c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +199,9 @@ mod test {
|
||||||
#[case("(/ 1 2)", vec![Token::LeftParen, Token::Slash, Token::Int(1), Token::Int(2), Token::RightParen])]
|
#[case("(/ 1 2)", vec![Token::LeftParen, Token::Slash, Token::Int(1), Token::Int(2), Token::RightParen])]
|
||||||
#[case("(- -2 1)", vec![Token::LeftParen, Token::Minus, Token::Int(-2), Token::Int(1), Token::RightParen])]
|
#[case("(- -2 1)", vec![Token::LeftParen, Token::Minus, Token::Int(-2), Token::Int(1), Token::RightParen])]
|
||||||
#[case("(\"string and stuff\")", vec![Token::LeftParen, Token::String("string and stuff".into()), Token::RightParen])]
|
#[case("(\"string and stuff\")", vec![Token::LeftParen, Token::String("string and stuff".into()), Token::RightParen])]
|
||||||
|
#[case("define!", vec![Token::Ident("define!".into())])]
|
||||||
|
#[case("let*", vec![Token::Ident("let*".into())])]
|
||||||
|
#[case("is-int?", vec![Token::Ident("is-int?".into())])]
|
||||||
#[case(
|
#[case(
|
||||||
"(func a b)",
|
"(func a b)",
|
||||||
vec![
|
vec![
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use crate::env::core_environment;
|
mod evaluator;
|
||||||
|
|
||||||
mod env;
|
|
||||||
mod eval;
|
|
||||||
mod lexer;
|
mod lexer;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let env = core_environment();
|
let env = evaluator::core_environment();
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
|
||||||
println!("MAL -- REPL");
|
println!("MAL -- REPL");
|
||||||
|
@ -27,7 +24,7 @@ fn main() {
|
||||||
|
|
||||||
let tokens = lexer::read(&input).unwrap();
|
let tokens = lexer::read(&input).unwrap();
|
||||||
let ast = parser::parse(tokens).unwrap();
|
let ast = parser::parse(tokens).unwrap();
|
||||||
let res = eval::eval(&env, ast).unwrap();
|
let res = evaluator::eval(env.clone(), ast).unwrap();
|
||||||
|
|
||||||
for expr in res {
|
for expr in res {
|
||||||
println!("{expr}")
|
println!("{expr}")
|
||||||
|
|
Loading…
Reference in a new issue