added arrays, index operations
This commit is contained in:
parent
3eeec60d3b
commit
cef5e1676e
|
@ -18,6 +18,7 @@ pub const BUILTINS: LazyCell<HashMap<&'static str, Object>> = LazyCell::new(|| {
|
||||||
|
|
||||||
match &args[0] {
|
match &args[0] {
|
||||||
Object::String(s) => Object::Integer(s.len() as i64),
|
Object::String(s) => Object::Integer(s.len() as i64),
|
||||||
|
Object::Array(s) => Object::Integer(s.elements.len() as i64),
|
||||||
v => Object::Error(format!("argument to `len` not supported, got {}", v)),
|
v => Object::Error(format!("argument to `len` not supported, got {}", v)),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -61,6 +61,7 @@ pub enum Object {
|
||||||
Error(String),
|
Error(String),
|
||||||
Function(Function),
|
Function(Function),
|
||||||
Builtin(BuiltinFunction),
|
Builtin(BuiltinFunction),
|
||||||
|
Array(Array),
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +75,9 @@ impl Object {
|
||||||
Object::Error(s) => s.to_string(),
|
Object::Error(s) => s.to_string(),
|
||||||
Object::Null => "NULL".into(),
|
Object::Null => "NULL".into(),
|
||||||
Object::Builtin(_) => "builtin function".to_string(),
|
Object::Builtin(_) => "builtin function".to_string(),
|
||||||
|
Object::Array(v) => {
|
||||||
|
format!("[{}]", v.elements.iter().map(|x| x.to_string()).join(", "))
|
||||||
|
}
|
||||||
Object::Function(s) => {
|
Object::Function(s) => {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
|
|
||||||
|
@ -101,6 +105,7 @@ impl Display for Object {
|
||||||
Object::Function(_) => "FUNCTION",
|
Object::Function(_) => "FUNCTION",
|
||||||
Object::Null => "NULL",
|
Object::Null => "NULL",
|
||||||
Object::Builtin(_) => "BUILTIN",
|
Object::Builtin(_) => "BUILTIN",
|
||||||
|
Object::Array(_) => "ARRAY",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,6 +138,11 @@ impl PartialEq for BuiltinFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Array {
|
||||||
|
elements: Vec<Object>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{assert_matches::assert_matches, cell::RefCell, rc::Rc};
|
use std::{assert_matches::assert_matches, cell::RefCell, rc::Rc};
|
||||||
|
@ -142,6 +152,8 @@ mod tests {
|
||||||
lexer::Lexer,
|
lexer::Lexer,
|
||||||
parser::{ast::Node, Parser},
|
parser::{ast::Node, Parser},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::Array;
|
||||||
const TRUE: Object = Object::Boolean(true);
|
const TRUE: Object = Object::Boolean(true);
|
||||||
const FALSE: Object = Object::Boolean(false);
|
const FALSE: Object = Object::Boolean(false);
|
||||||
const NULL: Object = Object::Null;
|
const NULL: Object = Object::Null;
|
||||||
|
@ -438,12 +450,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn builtin_function() {
|
fn builtin_function() {
|
||||||
// This test case allocates memory for `foobar=9999` over and over
|
|
||||||
// even though it is never used.
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
("len(\"\")", Some(Object::Integer(0))),
|
("len(\"\")", Some(Object::Integer(0))),
|
||||||
("len(\"four\")", Some(Object::Integer(4))),
|
("len(\"four\")", Some(Object::Integer(4))),
|
||||||
("len(\"hello world\")", Some(Object::Integer(11))),
|
("len(\"hello world\")", Some(Object::Integer(11))),
|
||||||
|
("len([1,2,3,4])", Some(Object::Integer(4))),
|
||||||
(
|
(
|
||||||
"len(1)",
|
"len(1)",
|
||||||
Some(Object::Error(
|
Some(Object::Error(
|
||||||
|
@ -460,4 +471,40 @@ mod tests {
|
||||||
|
|
||||||
run_test_cases(&test_cases);
|
run_test_cases(&test_cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn array_literals() {
|
||||||
|
let test_cases = [(
|
||||||
|
"[1, 2* 2, 3+3]",
|
||||||
|
Some(Object::Array(Array {
|
||||||
|
elements: vec![Object::Integer(1), Object::Integer(4), Object::Integer(6)],
|
||||||
|
})),
|
||||||
|
)];
|
||||||
|
|
||||||
|
run_test_cases(&test_cases);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn array_index_expressions() {
|
||||||
|
let test_cases = [
|
||||||
|
("[1, 2, 3][0]", Some(Object::Integer(1))),
|
||||||
|
("[1, 2, 3][1]", Some(Object::Integer(2))),
|
||||||
|
("[1, 2, 3][2]", Some(Object::Integer(3))),
|
||||||
|
("let i = 0; [1][i];", Some(Object::Integer(1))),
|
||||||
|
("[1,2,3][1+1]", Some(Object::Integer(3))),
|
||||||
|
("let array = [1,2,3]; array[2];", Some(Object::Integer(3))),
|
||||||
|
(
|
||||||
|
"let array = [1,2,3]; array[0]+array[1]+array[2];",
|
||||||
|
Some(Object::Integer(6)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"let array = [1,2,3]; let i = array[0]; array[i];",
|
||||||
|
Some(Object::Integer(2)),
|
||||||
|
),
|
||||||
|
("[1,2,3][3];", Some(Object::Null)),
|
||||||
|
("[1,2,3][-1];", Some(Object::Null)),
|
||||||
|
];
|
||||||
|
|
||||||
|
run_test_cases(&test_cases);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::{cell::RefCell, rc::Rc};
|
||||||
// It's just constantly unwrapping enums from one place and rewrapping it to some other enum(or even the same enum) and returning it
|
// It's just constantly unwrapping enums from one place and rewrapping it to some other enum(or even the same enum) and returning it
|
||||||
// The error handling story is pretty bad too
|
// The error handling story is pretty bad too
|
||||||
use crate::{
|
use crate::{
|
||||||
evaluator::{Evaluator, Object},
|
evaluator::{Array, Evaluator, Object},
|
||||||
lexer::TokenType,
|
lexer::TokenType,
|
||||||
parser::ast::{
|
parser::ast::{
|
||||||
BlockStatement, Expression, ExpressionStatement, Identifier, LetStatement, Node, Program,
|
BlockStatement, Expression, ExpressionStatement, Identifier, LetStatement, Node, Program,
|
||||||
|
@ -47,7 +47,22 @@ impl Evaluator for TreeWalker {
|
||||||
Expression::Identifier(v) => self.eval_identifier(v, env),
|
Expression::Identifier(v) => self.eval_identifier(v, env),
|
||||||
Expression::IntegerLiteral(il) => Some(Object::Integer(il.value)),
|
Expression::IntegerLiteral(il) => Some(Object::Integer(il.value)),
|
||||||
Expression::StringLiteral(s) => Some(Object::String(s.value)),
|
Expression::StringLiteral(s) => Some(Object::String(s.value)),
|
||||||
Expression::ArrayLiteral(v) => unimplemented!(),
|
Expression::ArrayLiteral(v) => {
|
||||||
|
println!("{:?}", v);
|
||||||
|
|
||||||
|
let args = match self.eval_expression(v.elements, env) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return Some(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Object::Array(Array { elements: args }))
|
||||||
|
}
|
||||||
|
Expression::IndexExpression(v) => {
|
||||||
|
let left = self.eval(Node::Expression(*v.left), env.clone())?;
|
||||||
|
let index = self.eval(Node::Expression(*v.index), env)?;
|
||||||
|
|
||||||
|
self.eval_index_expression(left, index)
|
||||||
|
}
|
||||||
Expression::BooleanExpression(b) => Some(Object::Boolean(b.value)),
|
Expression::BooleanExpression(b) => Some(Object::Boolean(b.value)),
|
||||||
Expression::PrefixExpression(p) => {
|
Expression::PrefixExpression(p) => {
|
||||||
let expr = self.eval(Node::Expression(*p.right), env)?;
|
let expr = self.eval(Node::Expression(*p.right), env)?;
|
||||||
|
@ -288,4 +303,23 @@ impl TreeWalker {
|
||||||
|
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)),
|
||||||
|
|
||||||
|
_ => 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 {
|
||||||
|
return Object::Null;
|
||||||
|
}
|
||||||
|
array.elements[index as usize].clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,6 +182,7 @@ pub enum ExpressionPriority {
|
||||||
Product = 4,
|
Product = 4,
|
||||||
Prefix = 5,
|
Prefix = 5,
|
||||||
Call = 6,
|
Call = 6,
|
||||||
|
Index = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
@ -190,6 +191,7 @@ pub enum Expression {
|
||||||
IntegerLiteral(IntegerLiteral),
|
IntegerLiteral(IntegerLiteral),
|
||||||
StringLiteral(StringLiteral),
|
StringLiteral(StringLiteral),
|
||||||
ArrayLiteral(ArrayLiteral),
|
ArrayLiteral(ArrayLiteral),
|
||||||
|
IndexExpression(IndexExpression),
|
||||||
PrefixExpression(PrefixExpression),
|
PrefixExpression(PrefixExpression),
|
||||||
InfixExpression(InfixExpression),
|
InfixExpression(InfixExpression),
|
||||||
BooleanExpression(BooleanExpression),
|
BooleanExpression(BooleanExpression),
|
||||||
|
@ -256,6 +258,9 @@ impl Display for Expression {
|
||||||
Expression::IfExpression(v) => v.to_string(),
|
Expression::IfExpression(v) => v.to_string(),
|
||||||
Expression::FunctionExpression(v) => v.to_string(),
|
Expression::FunctionExpression(v) => v.to_string(),
|
||||||
Expression::CallExpression(v) => v.to_string(),
|
Expression::CallExpression(v) => v.to_string(),
|
||||||
|
Expression::IndexExpression(v) => {
|
||||||
|
format!("({}[{}])", v.left, v.index)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
f.write_str(&value)
|
f.write_str(&value)
|
||||||
|
@ -341,7 +346,7 @@ impl StringLiteral {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct ArrayLiteral {
|
pub struct ArrayLiteral {
|
||||||
elements: Vec<Expression>,
|
pub elements: Vec<Expression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArrayLiteral {
|
impl ArrayLiteral {
|
||||||
|
@ -366,6 +371,34 @@ impl Display for ArrayLiteral {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct IndexExpression {
|
||||||
|
pub left: Box<Expression>,
|
||||||
|
pub index: Box<Expression>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexExpression {
|
||||||
|
pub fn new(left: Expression, index: Expression) -> Self {
|
||||||
|
Self {
|
||||||
|
left: Box::new(left),
|
||||||
|
index: Box::new(index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(parser: &mut Parser, _token: Token, left: Expression) -> Option<Expression> {
|
||||||
|
let next_token = parser.lexer.next()?;
|
||||||
|
|
||||||
|
let index = Expression::parse(parser, next_token, ExpressionPriority::Lowest)?;
|
||||||
|
|
||||||
|
parser.expect_peek(TokenType::RBracket)?;
|
||||||
|
|
||||||
|
Some(Expression::IndexExpression(IndexExpression {
|
||||||
|
left: Box::new(left),
|
||||||
|
index: Box::new(index),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct PrefixExpression {
|
pub struct PrefixExpression {
|
||||||
pub operator: TokenType,
|
pub operator: TokenType,
|
||||||
|
|
|
@ -28,6 +28,7 @@ lazy_static! {
|
||||||
m.insert(TokenType::Slash, ExpressionPriority::Product);
|
m.insert(TokenType::Slash, ExpressionPriority::Product);
|
||||||
m.insert(TokenType::Asterisk, ExpressionPriority::Product);
|
m.insert(TokenType::Asterisk, ExpressionPriority::Product);
|
||||||
m.insert(TokenType::LParen, ExpressionPriority::Call);
|
m.insert(TokenType::LParen, ExpressionPriority::Call);
|
||||||
|
m.insert(TokenType::LBracket, ExpressionPriority::Index);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -72,6 +73,8 @@ impl<'a> Parser<'a> {
|
||||||
parser.register_infix(TokenType::NotEquals, InfixExpression::parse);
|
parser.register_infix(TokenType::NotEquals, InfixExpression::parse);
|
||||||
parser.register_infix(TokenType::LessThan, InfixExpression::parse);
|
parser.register_infix(TokenType::LessThan, InfixExpression::parse);
|
||||||
parser.register_infix(TokenType::GreaterThan, InfixExpression::parse);
|
parser.register_infix(TokenType::GreaterThan, InfixExpression::parse);
|
||||||
|
parser.register_infix(TokenType::LBracket, IndexExpression::parse);
|
||||||
|
|
||||||
parser
|
parser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,6 +551,14 @@ mod tests {
|
||||||
("2 / (5 + 5)", "(2 / (5 + 5))"),
|
("2 / (5 + 5)", "(2 / (5 + 5))"),
|
||||||
("-(5 + 5)", "(-(5 + 5))"),
|
("-(5 + 5)", "(-(5 + 5))"),
|
||||||
("!(true == true)", "(!(true == true))"),
|
("!(true == true)", "(!(true == true))"),
|
||||||
|
(
|
||||||
|
"a * [1, 2, 3, 4][b * c] * d",
|
||||||
|
"((a * ([1, 2, 3, 4][(b * c)])) * d)",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"add(a * b[2], b[1], 2 * [1, 2][1])",
|
||||||
|
"add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))",
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
for test in test_cases.iter() {
|
for test in test_cases.iter() {
|
||||||
|
@ -949,4 +960,24 @@ mod tests {
|
||||||
|
|
||||||
check_test_cases(&test_cases);
|
check_test_cases(&test_cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn index_expression() {
|
||||||
|
let test_cases = [(
|
||||||
|
"myArray[ 1 + 1 ]",
|
||||||
|
vec![Statement::ExpressionStatement(ExpressionStatement::new(
|
||||||
|
token!(TokenType::Ident, "myArray"),
|
||||||
|
Expression::IndexExpression(IndexExpression::new(
|
||||||
|
Expression::Identifier(Identifier::new(TokenType::Ident, "myArray")),
|
||||||
|
Expression::InfixExpression(InfixExpression::new(
|
||||||
|
Expression::IntegerLiteral(IntegerLiteral::new(1)),
|
||||||
|
TokenType::Plus,
|
||||||
|
Expression::IntegerLiteral(IntegerLiteral::new(1)),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
))],
|
||||||
|
)];
|
||||||
|
|
||||||
|
check_test_cases(&test_cases);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user