WIP: Hash evaluator

This commit is contained in:
Ishan Jain 2024-05-27 16:26:52 +05:30
parent 37306989fe
commit f51ead1641
Signed by: ishan
GPG Key ID: 0506DB2A1CC75C27
5 changed files with 285 additions and 48 deletions

View File

@ -3,8 +3,9 @@ use {
itertools::Itertools,
std::{
cell::RefCell,
collections::HashMap,
collections::BTreeMap,
fmt::{self, Display, Formatter, Result as FmtResult, Write},
hash::{Hash, Hasher},
rc::Rc,
},
};
@ -15,16 +16,25 @@ pub trait Evaluator {
fn eval(&self, node: Node, env: Rc<RefCell<Environment>>) -> Option<Object>;
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Environment {
store: HashMap<String, Object>,
outer: Option<Rc<RefCell<Environment>>>,
store: BTreeMap<String, Object>,
outer: Option<OuterEnvironment>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct OuterEnvironment(Rc<RefCell<Environment>>);
impl Hash for OuterEnvironment {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.borrow().hash(state);
}
}
impl Environment {
pub fn new() -> Self {
Self {
store: HashMap::new(),
store: BTreeMap::new(),
outer: None,
}
}
@ -33,7 +43,7 @@ impl Environment {
Some(v) => Some(v.clone()),
None => match &self.outer {
Some(outer) => {
let outer = outer.borrow();
let outer = outer.0.borrow();
outer.get(name)
}
None => None,
@ -44,15 +54,15 @@ impl Environment {
self.store.insert(name, val);
}
pub fn new_enclosed(env: Rc<RefCell<Environment>>) -> Self {
pub fn new_enclosed(env: OuterEnvironment) -> Self {
Self {
store: HashMap::new(),
store: BTreeMap::new(),
outer: Some(env),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Hash, Ord, PartialOrd, Eq)]
pub enum Object {
Integer(i64),
String(String),
@ -62,6 +72,7 @@ pub enum Object {
Function(Function),
Builtin(BuiltinFunction),
Array(Array),
Hash(HashObject),
Null,
}
@ -90,6 +101,15 @@ impl Object {
out
}
Object::Hash(h) => {
let mut pairs = vec![];
for (k, v) in h.pairs.iter() {
pairs.push(format!("{}: {}", k.inspect(), v.inspect()));
}
format!("{{ {} }}", pairs.join(", "))
}
}
}
}
@ -106,20 +126,21 @@ impl Display for Object {
Object::Null => "NULL",
Object::Builtin(_) => "BUILTIN",
Object::Array(_) => "ARRAY",
Object::Hash(_) => "HASH",
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct Function {
parameters: Vec<Identifier>,
body: BlockStatement,
env: Rc<RefCell<Environment>>,
env: OuterEnvironment,
}
type Builtin = fn(Vec<Object>) -> Object;
#[derive(Clone)]
#[derive(Clone, Hash, Ord, PartialOrd, PartialEq, Eq)]
pub struct BuiltinFunction {
func: Box<Builtin>,
}
@ -132,17 +153,22 @@ impl fmt::Debug for BuiltinFunction {
}
}
impl PartialEq for BuiltinFunction {
fn eq(&self, _: &Self) -> bool {
false
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub struct Array {
elements: Vec<Object>,
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub struct HashObject {
pairs: BTreeMap<Object, Object>,
}
impl HashObject {
fn new(ip: impl Into<BTreeMap<Object, Object>>) -> Self {
Self { pairs: ip.into() }
}
}
#[cfg(test)]
mod tests {
use std::{assert_matches::assert_matches, cell::RefCell, rc::Rc};
@ -153,7 +179,7 @@ mod tests {
parser::{ast::Node, Parser},
};
use super::Array;
use super::{Array, HashObject};
const TRUE: Object = Object::Boolean(true);
const FALSE: Object = Object::Boolean(false);
const NULL: Object = Object::Null;
@ -340,6 +366,10 @@ mod tests {
"\"Hello\" - \"World\"",
Some(Object::Error("unknown operator: STRING - STRING".into())),
),
(
"{\"name\": \"Monkey\"}[fn(x) {x}];",
Some(Object::Error("unusable as hash key: FUNCTION".into())),
),
];
run_test_cases(&test_cases);
@ -527,4 +557,49 @@ mod tests {
run_test_cases(&test_cases);
}
#[test]
fn hash_literals() {
let test_cases = [(
"let two = \"two\";
{
\"one\": 10 - 9,
two: 1 + 1,
\"thr\" + \"ee\": 6 / 2,
4: 4,
true: 5,
false: 6
}
",
Some(Object::Hash(HashObject::new([
(Object::String("one".to_string()), Object::Integer(1)),
(Object::String("two".to_string()), Object::Integer(2)),
(Object::String("three".to_string()), Object::Integer(3)),
(Object::Integer(4), Object::Integer(4)),
(Object::Boolean(true), Object::Integer(5)),
(Object::Boolean(false), Object::Integer(6)),
]))),
)];
run_test_cases(&test_cases);
}
#[test]
fn hash_index_expressions() {
let test_cases = [
("{\"foo\": 5}[\"foo\"]", Some(Object::Integer(5))),
("{\"foo\": 5}[\"var\"]", Some(Object::Null)),
(
"let key = \"foo\"; {\"foo\": 5}[key]",
Some(Object::Integer(5)),
),
("{}[\"foo\"]", Some(Object::Null)),
("{5: 5}[5]", Some(Object::Integer(5))),
("{true: 5}[true]", Some(Object::Integer(5))),
("{false: 5}[false]", Some(Object::Integer(5))),
("{true: 5}[false]", Some(Object::Null)),
];
run_test_cases(&test_cases);
}
}

View File

@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
// TODO: This is all a mess. Almost certainly because right now, I don't know any better way to do this.
// It's just constantly unwrapping enums from one place and rewrapping it to some other enum(or even the same enum) and returning it
@ -7,12 +7,12 @@ use crate::{
evaluator::{Array, Evaluator, Object},
lexer::TokenType,
parser::ast::{
BlockStatement, Expression, ExpressionStatement, Identifier, LetStatement, Node, Program,
Statement,
BlockStatement, Expression, ExpressionStatement, HashLiteral, Identifier, LetStatement,
Node, Program, Statement,
},
};
use super::{builtins::BUILTINS, Environment, Function};
use super::{builtins::BUILTINS, Environment, Function, HashObject, OuterEnvironment};
pub struct TreeWalker;
@ -47,9 +47,8 @@ impl Evaluator for TreeWalker {
Expression::Identifier(v) => self.eval_identifier(v, env),
Expression::IntegerLiteral(il) => Some(Object::Integer(il.value)),
Expression::StringLiteral(s) => Some(Object::String(s.value)),
Expression::HashLiteral(h) => self.eval_hash_literal(h, env),
Expression::ArrayLiteral(v) => {
println!("{:?}", v);
let args = match self.eval_expression(v.elements, env) {
Ok(v) => v,
Err(e) => return Some(e),
@ -76,7 +75,7 @@ impl Evaluator for TreeWalker {
Expression::FunctionExpression(fnl) => Some(Object::Function(Function {
body: fnl.body,
parameters: fnl.parameters,
env,
env: OuterEnvironment(env),
})),
Expression::CallExpression(v) => {
let function = self.eval(Node::Expression(*v.function), env.clone())?;
@ -305,8 +304,11 @@ impl TreeWalker {
}
fn eval_index_expression(&self, left: Object, index: Object) -> Option<Object> {
match (&left, index) {
(Object::Array(a), Object::Integer(i)) => Some(Self::eval_array_index_expression(a, i)),
match (&left, &index) {
(Object::Array(a), Object::Integer(i)) => {
Some(Self::eval_array_index_expression(a, *i))
}
(Object::Hash(h), _) => self.eval_hash_index_expression(h, index),
_ => Some(Object::Error(format!(
"index operator not supported: {}",
@ -315,6 +317,12 @@ impl TreeWalker {
}
}
fn eval_hash_index_expression(&self, left: &HashObject, index: Object) -> Option<Object> {
Some(Object::Error(format!(
"index operator not supported: {:?}",
left
)))
}
fn eval_array_index_expression(array: &Array, index: i64) -> Object {
let max = array.elements.len() as i64;
if index < 0 || index >= max {
@ -322,4 +330,22 @@ impl TreeWalker {
}
array.elements[index as usize].clone()
}
fn eval_hash_literal(
&self,
node: HashLiteral,
env: Rc<RefCell<Environment>>,
) -> Option<Object> {
let mut out = BTreeMap::new();
for (k, v) in node.pairs.into_iter() {
let k = self.eval(Node::Expression(k), env.clone())?;
let v = self.eval(Node::Expression(v), env.clone())?;
out.insert(k, v);
}
Some(Object::Hash(HashObject { pairs: out }))
}
}

View File

@ -19,7 +19,7 @@ lazy_static! {
};
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Ord, PartialOrd, Hash)]
pub enum TokenType {
Illegal,
#[allow(clippy::upper_case_acronyms)]
@ -47,6 +47,7 @@ pub enum TokenType {
// Delimiter
Comma,
Semicolon,
Colon,
LParen,
RParen,
LBrace,
@ -79,6 +80,7 @@ impl Display for TokenType {
TokenType::NotEquals => "!=",
TokenType::Comma => ",",
TokenType::Semicolon => ";",
TokenType::Colon => ":",
TokenType::LParen => "(",
TokenType::RParen => ")",
TokenType::LBrace => "{",
@ -101,7 +103,7 @@ impl Display for TokenType {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[derive(Debug, PartialEq, Eq, Clone, Ord, PartialOrd, Hash)]
pub struct Token {
pub name: TokenType,
pub literal: Option<String>,
@ -243,6 +245,7 @@ impl<'a> Iterator for Lexer<'a> {
Some('-') => Some(token!(TokenType::Minus)),
Some(',') => Some(token!(TokenType::Comma)),
Some(';') => Some(token!(TokenType::Semicolon)),
Some(':') => Some(token!(TokenType::Colon)),
Some('(') => Some(token!(TokenType::LParen)),
Some(')') => Some(token!(TokenType::RParen)),
Some('[') => Some(token!(TokenType::LBracket)),
@ -387,6 +390,8 @@ mod tests {
\"foo bar\"
[1,2];
{\"foo\": \"bar\"}
"
)
.collect::<Vec<Token>>(),
@ -446,6 +451,11 @@ mod tests {
token!(TokenType::Int, "2"),
token!(TokenType::RBracket),
token!(TokenType::Semicolon),
token!(TokenType::LBrace),
token!(TokenType::String, "foo"),
token!(TokenType::Colon),
token!(TokenType::String, "bar"),
token!(TokenType::RBrace),
token!(TokenType::EOF),
],
);

View File

@ -6,6 +6,7 @@ use {
itertools::Itertools,
std::{
cmp::PartialOrd,
collections::BTreeMap,
convert::From,
fmt::{Display, Formatter, Result as FmtResult, Write},
},
@ -34,7 +35,7 @@ impl Display for Program {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub enum Statement {
Let(LetStatement),
Return(ReturnStatement),
@ -65,7 +66,7 @@ impl Display for Statement {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct LetStatement {
// name field is to store the identifier of the binding
pub name: Identifier,
@ -113,7 +114,7 @@ impl Display for LetStatement {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct ReturnStatement {
pub value: Option<Expression>,
}
@ -145,7 +146,7 @@ impl Display for ReturnStatement {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct ExpressionStatement {
token: Token,
pub expression: Expression,
@ -185,12 +186,13 @@ pub enum ExpressionPriority {
Index = 7,
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Clone)]
pub enum Expression {
Identifier(Identifier),
IntegerLiteral(IntegerLiteral),
StringLiteral(StringLiteral),
ArrayLiteral(ArrayLiteral),
HashLiteral(HashLiteral),
IndexExpression(IndexExpression),
PrefixExpression(PrefixExpression),
InfixExpression(InfixExpression),
@ -261,6 +263,7 @@ impl Display for Expression {
Expression::IndexExpression(v) => {
format!("({}[{}])", v.left, v.index)
}
Expression::HashLiteral(v) => v.to_string(),
};
f.write_str(&value)
@ -276,7 +279,7 @@ impl From<&Expression> for String {
// Identifier will be an expression
// Identifier in a let statement like, let x = 5; where `x` is an identifier doesn't produce a value
// but an identifier *can* produce value when used on rhs, e.g. let x = y; Here `y` is producing a value
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct Identifier {
token: TokenType,
pub value: String,
@ -303,7 +306,7 @@ impl Display for Identifier {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct IntegerLiteral {
pub value: i64,
}
@ -326,7 +329,7 @@ impl IntegerLiteral {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct StringLiteral {
pub value: String,
}
@ -344,7 +347,7 @@ impl StringLiteral {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct ArrayLiteral {
pub elements: Vec<Expression>,
}
@ -371,7 +374,7 @@ impl Display for ArrayLiteral {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct IndexExpression {
pub left: Box<Expression>,
pub index: Box<Expression>,
@ -399,7 +402,62 @@ impl IndexExpression {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct HashLiteral {
pub pairs: BTreeMap<Expression, Expression>,
}
impl HashLiteral {
pub fn new(pairs: impl Into<BTreeMap<Expression, Expression>>) -> Self {
Self {
pairs: pairs.into(),
}
}
pub fn parse(parser: &mut Parser, _token: Token) -> Option<Expression> {
let mut map = BTreeMap::new();
while !parser.peek_token_is(TokenType::RBrace) {
let ctoken = parser.lexer.next()?;
let key = Expression::parse(parser, ctoken, ExpressionPriority::Lowest)?;
parser.expect_peek(TokenType::Colon)?;
let ctoken = parser.lexer.next()?;
let value = Expression::parse(parser, ctoken, ExpressionPriority::Lowest)?;
map.insert(key, value);
if !parser.peek_token_is(TokenType::RBrace)
&& parser.expect_peek(TokenType::Comma).is_none()
{
return None;
}
}
parser.expect_peek(TokenType::RBrace)?;
Some(Expression::HashLiteral(HashLiteral { pairs: map }))
}
}
impl Display for HashLiteral {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_char('{')?;
f.write_str(
&self
.pairs
.iter()
.map(|(k, v)| format!("{}:{}", k, v))
.join(", "),
)?;
f.write_char('}')
}
}
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct PrefixExpression {
pub operator: TokenType,
pub right: Box<Expression>,
@ -429,7 +487,7 @@ impl Display for PrefixExpression {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct InfixExpression {
pub left: Box<Expression>,
pub operator: TokenType,
@ -463,7 +521,7 @@ impl Display for InfixExpression {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct BooleanExpression {
token: TokenType,
pub value: bool,
@ -489,7 +547,7 @@ impl Display for BooleanExpression {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct IfExpression {
pub condition: Box<Expression>,
pub consequence: BlockStatement,
@ -537,7 +595,7 @@ impl Display for IfExpression {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct BlockStatement {
pub statements: Vec<Statement>,
}
@ -577,7 +635,7 @@ impl Display for BlockStatement {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct FunctionLiteral {
token: Token,
pub parameters: Vec<Identifier>,
@ -643,7 +701,7 @@ impl Display for FunctionLiteral {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Clone)]
pub struct CallExpression {
pub function: Box<Expression>,
pub arguments: Vec<Expression>,

View File

@ -60,6 +60,7 @@ impl<'a> Parser<'a> {
parser.register_prefix(TokenType::If, IfExpression::parse);
parser.register_prefix(TokenType::Function, FunctionLiteral::parse);
parser.register_prefix(TokenType::LBracket, ArrayLiteral::parse);
parser.register_prefix(TokenType::LBrace, HashLiteral::parse);
// Neat trick!
// Call expressions looks like <ident>(<args>).
@ -980,4 +981,71 @@ mod tests {
check_test_cases(&test_cases);
}
#[test]
fn hash_literal() {
let test_cases = [
(
"{\"one\": 1, \"two\": 2, \"three\": 3}",
vec![Statement::ExpressionStatement(ExpressionStatement::new(
token!(TokenType::LBrace),
Expression::HashLiteral(HashLiteral::new([
(
Expression::StringLiteral(StringLiteral::new("one")),
Expression::IntegerLiteral(IntegerLiteral::new(1)),
),
(
Expression::StringLiteral(StringLiteral::new("two")),
Expression::IntegerLiteral(IntegerLiteral::new(2)),
),
(
Expression::StringLiteral(StringLiteral::new("three")),
Expression::IntegerLiteral(IntegerLiteral::new(3)),
),
])),
))],
),
(
"{}",
vec![Statement::ExpressionStatement(ExpressionStatement::new(
token!(TokenType::LBrace),
Expression::HashLiteral(HashLiteral::new([])),
))],
),
(
"{\"one\": 0 + 1, \"two\": 10-8, \"three\": 15/5}",
vec![Statement::ExpressionStatement(ExpressionStatement::new(
token!(TokenType::LBrace),
Expression::HashLiteral(HashLiteral::new([
(
Expression::StringLiteral(StringLiteral::new("one")),
Expression::InfixExpression(InfixExpression::new(
Expression::IntegerLiteral(IntegerLiteral::new(0)),
TokenType::Plus,
Expression::IntegerLiteral(IntegerLiteral::new(1)),
)),
),
(
Expression::StringLiteral(StringLiteral::new("two")),
Expression::InfixExpression(InfixExpression::new(
Expression::IntegerLiteral(IntegerLiteral::new(10)),
TokenType::Minus,
Expression::IntegerLiteral(IntegerLiteral::new(8)),
)),
),
(
Expression::StringLiteral(StringLiteral::new("three")),
Expression::InfixExpression(InfixExpression::new(
Expression::IntegerLiteral(IntegerLiteral::new(15)),
TokenType::Slash,
Expression::IntegerLiteral(IntegerLiteral::new(5)),
)),
),
])),
))],
),
];
check_test_cases(&test_cases);
}
}