data types chapter
This commit is contained in:
+113
-3
@@ -16,12 +16,16 @@ pub trait StatementTrait: NodeTrait {
|
||||
pub enum Expression {
|
||||
Identifier(Identifier),
|
||||
IntegerLiteral(IntegerLiteral),
|
||||
StringLiteral(StringLiteral),
|
||||
Boolean(Boolean),
|
||||
PrefixExpression(PrefixExpression),
|
||||
InfixExpression(InfixExpression),
|
||||
Boolean(Boolean),
|
||||
IfExpression(IfExpression),
|
||||
FunctionLiteral(FunctionLiteral),
|
||||
CallExpression(CallExpression),
|
||||
ArrayLiteral(ArrayLiteral),
|
||||
HashLiteral(HashLiteral),
|
||||
IndexExpression(IndexExpression),
|
||||
}
|
||||
|
||||
impl NodeTrait for Expression {
|
||||
@@ -29,12 +33,16 @@ impl NodeTrait for Expression {
|
||||
match self {
|
||||
Expression::Identifier(i) => i.token_literal(),
|
||||
Expression::IntegerLiteral(i) => i.token_literal(),
|
||||
Expression::StringLiteral(i) => i.token_literal(),
|
||||
Expression::Boolean(i) => i.token_literal(),
|
||||
Expression::PrefixExpression(i) => i.token_literal(),
|
||||
Expression::InfixExpression(i) => i.token_literal(),
|
||||
Expression::Boolean(i) => i.token_literal(),
|
||||
Expression::IfExpression(i) => i.token_literal(),
|
||||
Expression::FunctionLiteral(i) => i.token_literal(),
|
||||
Expression::CallExpression(i) => i.token_literal(),
|
||||
Expression::ArrayLiteral(i) => i.token_literal(),
|
||||
Expression::HashLiteral(i) => i.token_literal(),
|
||||
Expression::IndexExpression(i) => i.token_literal(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,12 +50,16 @@ impl NodeTrait for Expression {
|
||||
match self {
|
||||
Expression::Identifier(i) => i.string(),
|
||||
Expression::IntegerLiteral(i) => i.string(),
|
||||
Expression::StringLiteral(i) => i.string(),
|
||||
Expression::Boolean(i) => i.string(),
|
||||
Expression::PrefixExpression(i) => i.string(),
|
||||
Expression::InfixExpression(i) => i.string(),
|
||||
Expression::Boolean(i) => i.string(),
|
||||
Expression::IfExpression(i) => i.string(),
|
||||
Expression::FunctionLiteral(i) => i.string(),
|
||||
Expression::CallExpression(i) => i.string(),
|
||||
Expression::ArrayLiteral(i) => i.string(),
|
||||
Expression::HashLiteral(i) => i.string(),
|
||||
Expression::IndexExpression(i) => i.string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,3 +392,101 @@ impl NodeTrait for CallExpression {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StringLiteral {
|
||||
pub token: Token,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl NodeTrait for StringLiteral {
|
||||
fn token_literal(&self) -> String {
|
||||
String::from(&self.token.literal)
|
||||
}
|
||||
|
||||
fn string(&self) -> String {
|
||||
self.value.clone()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArrayLiteral {
|
||||
pub token: Token,
|
||||
pub elements: Vec<Expression>,
|
||||
}
|
||||
|
||||
impl NodeTrait for ArrayLiteral {
|
||||
fn token_literal(&self) -> String {
|
||||
String::from(&self.token.literal)
|
||||
}
|
||||
|
||||
fn string(&self) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("[");
|
||||
let elements: Vec<String> = self.elements.iter().map(|e| e.string()).collect();
|
||||
out.push_str(&elements.join(", "));
|
||||
out.push_str("]");
|
||||
out
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HashLiteral {
|
||||
pub token: Token,
|
||||
pub pairs: Vec<(Expression, Expression)>,
|
||||
}
|
||||
|
||||
impl NodeTrait for HashLiteral {
|
||||
fn token_literal(&self) -> String {
|
||||
String::from(&self.token.literal)
|
||||
}
|
||||
|
||||
fn string(&self) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("{");
|
||||
let pairs: Vec<String> = self
|
||||
.pairs
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}: {}", k.string(), v.string()))
|
||||
.collect();
|
||||
out.push_str(&pairs.join(", "));
|
||||
out.push_str("}");
|
||||
out
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IndexExpression {
|
||||
pub token: Token,
|
||||
pub left: Box<Expression>,
|
||||
pub index: Box<Expression>,
|
||||
}
|
||||
|
||||
impl NodeTrait for IndexExpression {
|
||||
fn token_literal(&self) -> String {
|
||||
String::from(&self.token.literal)
|
||||
}
|
||||
|
||||
fn string(&self) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("(");
|
||||
out.push_str(&self.left.string());
|
||||
out.push_str("[");
|
||||
out.push_str(&self.index.string());
|
||||
out.push_str("])");
|
||||
out
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
+185
-1
@@ -1,8 +1,9 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::ast::{self, NodeTrait};
|
||||
use crate::object::{self, Environment, Null, Object, ObjectType};
|
||||
use crate::object::{self, Environment, Hashable, Null, Object, ObjectType};
|
||||
|
||||
const NULL: Null = Null;
|
||||
const BOOL_TRUE: object::Boolean = object::Boolean { value: true };
|
||||
@@ -85,6 +86,9 @@ fn eval_expression(
|
||||
ast::Expression::IntegerLiteral(int) => {
|
||||
Some(Box::new(object::Integer { value: int.value }))
|
||||
}
|
||||
ast::Expression::StringLiteral(s) => Some(Box::new(object::StringObj {
|
||||
value: s.value.clone(),
|
||||
})),
|
||||
ast::Expression::Boolean(b) => Some(Box::new(native_bool_to_bool_object(b.value))),
|
||||
ast::Expression::PrefixExpression(expr) => {
|
||||
let right = eval_expression(expr.right.as_ref(), Rc::clone(&env))?;
|
||||
@@ -121,6 +125,25 @@ fn eval_expression(
|
||||
}
|
||||
apply_function(function, args)
|
||||
}
|
||||
ast::Expression::ArrayLiteral(arr) => {
|
||||
let elements = eval_expressions(&arr.elements, Rc::clone(&env))?;
|
||||
if elements.len() == 1 && is_error(elements[0].as_ref()) {
|
||||
return Some(elements.into_iter().next().unwrap());
|
||||
}
|
||||
Some(Box::new(object::Array { elements }))
|
||||
}
|
||||
ast::Expression::HashLiteral(hash) => eval_hash_literal(hash, env),
|
||||
ast::Expression::IndexExpression(idx) => {
|
||||
let left = eval_expression(idx.left.as_ref(), Rc::clone(&env))?;
|
||||
if is_error(left.as_ref()) {
|
||||
return Some(left);
|
||||
}
|
||||
let index = eval_expression(idx.index.as_ref(), Rc::clone(&env))?;
|
||||
if is_error(index.as_ref()) {
|
||||
return Some(index);
|
||||
}
|
||||
Some(eval_index_expression(left, index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +191,10 @@ fn apply_function(
|
||||
return Some(Box::new(NULL));
|
||||
}
|
||||
|
||||
if let Some(builtin) = function.as_any().downcast_ref::<object::Builtin>() {
|
||||
return Some((builtin.func)(args));
|
||||
}
|
||||
|
||||
Some(new_error(format!(
|
||||
"not a function: {:?}",
|
||||
function.object_type()
|
||||
@@ -237,6 +264,10 @@ fn eval_infix_expression(
|
||||
return eval_boolean_infix_expression(operator, left, right);
|
||||
}
|
||||
|
||||
if left.object_type() == ObjectType::STRING && right.object_type() == ObjectType::STRING {
|
||||
return eval_string_infix_expression(operator, left, right);
|
||||
}
|
||||
|
||||
if left.object_type() != right.object_type() {
|
||||
return Some(new_error(format!(
|
||||
"type mismatch: {:?} {} {:?}",
|
||||
@@ -292,6 +323,37 @@ fn eval_integer_infix_expression(
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_string_infix_expression(
|
||||
operator: &str,
|
||||
left: Box<dyn Object>,
|
||||
right: Box<dyn Object>,
|
||||
) -> Option<Box<dyn Object>> {
|
||||
let left_val = left
|
||||
.as_any()
|
||||
.downcast_ref::<object::StringObj>()
|
||||
.unwrap()
|
||||
.value
|
||||
.clone();
|
||||
let right_val = right
|
||||
.as_any()
|
||||
.downcast_ref::<object::StringObj>()
|
||||
.unwrap()
|
||||
.value
|
||||
.clone();
|
||||
|
||||
match operator {
|
||||
"+" => Some(Box::new(object::StringObj {
|
||||
value: format!("{}{}", left_val, right_val),
|
||||
})),
|
||||
_ => Some(new_error(format!(
|
||||
"unknown operator: {:?} {} {:?}",
|
||||
left.object_type(),
|
||||
operator,
|
||||
right.object_type()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_boolean_infix_expression(
|
||||
operator: &str,
|
||||
left: Box<dyn Object>,
|
||||
@@ -316,6 +378,128 @@ fn eval_boolean_infix_expression(
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_hash_literal(
|
||||
hash: &ast::HashLiteral,
|
||||
env: Rc<RefCell<Environment>>,
|
||||
) -> Option<Box<dyn Object>> {
|
||||
let mut pairs = HashMap::new();
|
||||
|
||||
for (k, v) in &hash.pairs {
|
||||
let key = eval_expression(k, Rc::clone(&env))?;
|
||||
if is_error(key.as_ref()) {
|
||||
return Some(key);
|
||||
}
|
||||
let value = eval_expression(v, Rc::clone(&env))?;
|
||||
if is_error(value.as_ref()) {
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
let hash_key = match key.object_type() {
|
||||
ObjectType::INTEGER => {
|
||||
key.as_any()
|
||||
.downcast_ref::<object::Integer>()
|
||||
.unwrap()
|
||||
.hash_key()
|
||||
}
|
||||
ObjectType::BOOLEAN => {
|
||||
key.as_any()
|
||||
.downcast_ref::<object::Boolean>()
|
||||
.unwrap()
|
||||
.hash_key()
|
||||
}
|
||||
ObjectType::STRING => {
|
||||
key.as_any()
|
||||
.downcast_ref::<object::StringObj>()
|
||||
.unwrap()
|
||||
.hash_key()
|
||||
}
|
||||
_ => return Some(new_error(format!("unusable as hash key: {:?}", key.object_type()))),
|
||||
};
|
||||
|
||||
pairs.insert(
|
||||
hash_key,
|
||||
object::HashPair {
|
||||
key: key.clone_box(),
|
||||
value: value.clone_box(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Some(Box::new(object::Hash { pairs }))
|
||||
}
|
||||
|
||||
fn eval_index_expression(left: Box<dyn Object>, index: Box<dyn Object>) -> Box<dyn Object> {
|
||||
match (left.object_type(), index.object_type()) {
|
||||
(ObjectType::ARRAY, ObjectType::INTEGER) => eval_array_index_expression(left, index),
|
||||
(ObjectType::HASH, _) => eval_hash_index_expression(left, index),
|
||||
(ObjectType::STRING, ObjectType::INTEGER) => eval_string_index_expression(left, index),
|
||||
_ => new_error(format!(
|
||||
"index operator not supported: {:?}[{:?}]",
|
||||
left.object_type(),
|
||||
index.object_type()
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_array_index_expression(
|
||||
array: Box<dyn Object>,
|
||||
index: Box<dyn Object>,
|
||||
) -> Box<dyn Object> {
|
||||
let arr = array.as_any().downcast_ref::<object::Array>().unwrap();
|
||||
let idx = index.as_any().downcast_ref::<object::Integer>().unwrap().value;
|
||||
let max = arr.elements.len() as i64 - 1;
|
||||
if idx < 0 || idx > max {
|
||||
return Box::new(NULL);
|
||||
}
|
||||
arr.elements[idx as usize].clone_box()
|
||||
}
|
||||
|
||||
fn eval_hash_index_expression(
|
||||
hash: Box<dyn Object>,
|
||||
index: Box<dyn Object>,
|
||||
) -> Box<dyn Object> {
|
||||
let hash_obj = hash.as_any().downcast_ref::<object::Hash>().unwrap();
|
||||
|
||||
let key = match index.object_type() {
|
||||
ObjectType::INTEGER => index
|
||||
.as_any()
|
||||
.downcast_ref::<object::Integer>()
|
||||
.unwrap()
|
||||
.hash_key(),
|
||||
ObjectType::BOOLEAN => index
|
||||
.as_any()
|
||||
.downcast_ref::<object::Boolean>()
|
||||
.unwrap()
|
||||
.hash_key(),
|
||||
ObjectType::STRING => index
|
||||
.as_any()
|
||||
.downcast_ref::<object::StringObj>()
|
||||
.unwrap()
|
||||
.hash_key(),
|
||||
_ => return new_error(format!("unusable as hash key: {:?}", index.object_type())),
|
||||
};
|
||||
|
||||
match hash_obj.pairs.get(&key) {
|
||||
Some(pair) => pair.value.clone_box(),
|
||||
None => Box::new(NULL),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_string_index_expression(
|
||||
string: Box<dyn Object>,
|
||||
index: Box<dyn Object>,
|
||||
) -> Box<dyn Object> {
|
||||
let s = string.as_any().downcast_ref::<object::StringObj>().unwrap();
|
||||
let idx = index.as_any().downcast_ref::<object::Integer>().unwrap().value;
|
||||
let max = s.value.len() as i64 - 1;
|
||||
if idx < 0 || idx > max {
|
||||
return Box::new(NULL);
|
||||
}
|
||||
Box::new(object::StringObj {
|
||||
value: s.value.chars().nth(idx as usize).unwrap().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn eval_if_expression(
|
||||
expr: &ast::IfExpression,
|
||||
env: Rc<RefCell<Environment>>,
|
||||
|
||||
@@ -30,6 +30,12 @@ fn test_null_object(obj: &dyn Object) {
|
||||
assert_eq!(obj.object_type(), ObjectType::NULL);
|
||||
}
|
||||
|
||||
fn test_string_object(obj: &dyn Object, expected: &str) {
|
||||
assert_eq!(obj.object_type(), ObjectType::STRING);
|
||||
let s = obj.as_any().downcast_ref::<object::StringObj>().unwrap();
|
||||
assert_eq!(s.value, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_integer_expression() {
|
||||
let tests = vec![("5", 5), ("10", 10), ("-5", -5), ("-10", -10)];
|
||||
@@ -219,6 +225,8 @@ fn test_error_handling() {
|
||||
("5; true + false; 5", "unknown operator: BOOLEAN + BOOLEAN"),
|
||||
("if (10 > 1) { true + false; }", "unknown operator: BOOLEAN + BOOLEAN"),
|
||||
("foobar", "identifier not found: foobar"),
|
||||
("len(1)", "argument to `len` not supported"),
|
||||
("first(1)", "argument to `first` must be ARRAY"),
|
||||
];
|
||||
|
||||
for (input, expected) in tests {
|
||||
@@ -228,3 +236,127 @@ fn test_error_handling() {
|
||||
assert!(err.message.contains(expected), "expected '{}' to contain '{}'", err.message, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_string_expression() {
|
||||
let input = "\"Hello World\"";
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
test_string_object(evaluated.as_ref(), "Hello World");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_concatenation() {
|
||||
let input = "\"Hello\" + \" \" + \"World!\"";
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
test_string_object(evaluated.as_ref(), "Hello World!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_indexing() {
|
||||
let tests = vec![
|
||||
("\"abc\"[0]", "a"),
|
||||
("\"abc\"[1]", "b"),
|
||||
("\"abc\"[2]", "c"),
|
||||
];
|
||||
|
||||
for (input, expected) in tests {
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
test_string_object(evaluated.as_ref(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_array_expression() {
|
||||
let input = "[1, 2 * 2, 3 + 3]";
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
assert_eq!(evaluated.object_type(), ObjectType::ARRAY);
|
||||
let arr = evaluated.as_any().downcast_ref::<object::Array>().unwrap();
|
||||
assert_eq!(arr.elements.len(), 3);
|
||||
test_integer_object(arr.elements[0].as_ref(), 1);
|
||||
test_integer_object(arr.elements[1].as_ref(), 4);
|
||||
test_integer_object(arr.elements[2].as_ref(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_array_index() {
|
||||
let tests = vec![
|
||||
("[1, 2, 3][0]", 1),
|
||||
("[1, 2, 3][1]", 2),
|
||||
("[1, 2, 3][2]", 3),
|
||||
("let i = 0; [1][i];", 1),
|
||||
("[1, 2, 3][1 + 1];", 3),
|
||||
("let myArray = [1, 2, 3]; myArray[2];", 3),
|
||||
("let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", 6),
|
||||
("let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i];", 2),
|
||||
];
|
||||
|
||||
for (input, expected) in tests {
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
test_integer_object(evaluated.as_ref(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_hash_expression() {
|
||||
let input = r#"{"one": 1, "two": 2, "three": 3}"#;
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
assert_eq!(evaluated.object_type(), ObjectType::HASH);
|
||||
let hash = evaluated.as_any().downcast_ref::<object::Hash>().unwrap();
|
||||
assert_eq!(hash.pairs.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_hash_index() {
|
||||
let tests = vec![
|
||||
(r#"{"foo": 5}["foo"]"#, 5),
|
||||
(r#"{"foo": 5}["bar"]"#, 0), // expect null, we'll check separately
|
||||
(r#"let key = "foo"; {"foo": 5}[key]"#, 5),
|
||||
("{true: 1}[true]", 1),
|
||||
("{1: 1, 2: 2}[2]", 2),
|
||||
];
|
||||
|
||||
for (input, expected) in tests {
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
if input.contains("\"bar\"") {
|
||||
test_null_object(evaluated.as_ref());
|
||||
} else {
|
||||
test_integer_object(evaluated.as_ref(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builtin_functions() {
|
||||
let tests = vec![
|
||||
("len(\"\")", 0),
|
||||
("len(\"four\")", 4),
|
||||
("len(\"hello world\")", 11),
|
||||
("len([1, 2, 3])", 3),
|
||||
("len([])", 0),
|
||||
("first([1, 2, 3])", 1),
|
||||
("last([1, 2, 3])", 3),
|
||||
];
|
||||
|
||||
for (input, expected) in tests {
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
test_integer_object(evaluated.as_ref(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builtin_array_functions() {
|
||||
let input = "let a = [1, 2, 3]; push(a, 4);";
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
assert_eq!(evaluated.object_type(), ObjectType::ARRAY);
|
||||
let arr = evaluated.as_any().downcast_ref::<object::Array>().unwrap();
|
||||
assert_eq!(arr.elements.len(), 4);
|
||||
test_integer_object(arr.elements[3].as_ref(), 4);
|
||||
|
||||
let input = "rest([1, 2, 3]);";
|
||||
let evaluated = test_eval(input).unwrap();
|
||||
assert_eq!(evaluated.object_type(), ObjectType::ARRAY);
|
||||
let arr = evaluated.as_any().downcast_ref::<object::Array>().unwrap();
|
||||
assert_eq!(arr.elements.len(), 2);
|
||||
test_integer_object(arr.elements[0].as_ref(), 2);
|
||||
test_integer_object(arr.elements[1].as_ref(), 3);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ pub trait LexerTraits {
|
||||
fn read_char(&mut self);
|
||||
fn next_token(&mut self) -> Token;
|
||||
fn read_identifier(&mut self) -> String;
|
||||
fn read_string(&mut self) -> String;
|
||||
fn skip_whitespace(&mut self);
|
||||
fn read_number(&mut self) -> String;
|
||||
fn peek_char(&self) -> u8;
|
||||
@@ -81,6 +82,14 @@ impl LexerTraits for Lexer {
|
||||
'>' => new_token(TokenType::Gt, lit),
|
||||
'{' => new_token(TokenType::Lbrace, lit),
|
||||
'}' => new_token(TokenType::Rbrace, lit),
|
||||
'[' => new_token(TokenType::Lbracket, lit),
|
||||
']' => new_token(TokenType::Rbracket, lit),
|
||||
':' => new_token(TokenType::Colon, lit),
|
||||
'"' => {
|
||||
let s = self.read_string();
|
||||
self.read_char();
|
||||
return new_token_from_str(TokenType::String, s);
|
||||
}
|
||||
_ => {
|
||||
if c.is_alphabetic() {
|
||||
let lit = self.read_identifier();
|
||||
@@ -110,6 +119,17 @@ impl LexerTraits for Lexer {
|
||||
String::from(res)
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> String {
|
||||
let position = self.position.unwrap() + 1;
|
||||
self.read_char();
|
||||
while self.ch.unwrap() != b'"' && self.ch.unwrap() != 0 {
|
||||
self.read_char();
|
||||
}
|
||||
let read_pos = self.position.unwrap() as usize;
|
||||
let position = position as usize;
|
||||
String::from(&self.input[position..read_pos])
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
while char::from(self.ch.unwrap()) == ' '
|
||||
|| char::from(self.ch.unwrap()) == '\n'
|
||||
|
||||
+279
-1
@@ -13,6 +13,10 @@ pub enum ObjectType {
|
||||
RETURN_VALUE,
|
||||
ERROR,
|
||||
FUNCTION,
|
||||
STRING,
|
||||
ARRAY,
|
||||
HASH,
|
||||
BUILTIN,
|
||||
}
|
||||
|
||||
pub trait Object {
|
||||
@@ -167,6 +171,151 @@ impl Object for Function {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StringObj {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl Object for StringObj {
|
||||
fn object_type(&self) -> ObjectType {
|
||||
ObjectType::STRING
|
||||
}
|
||||
|
||||
fn inspect(&self) -> String {
|
||||
self.value.clone()
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn Object> {
|
||||
Box::new(StringObj {
|
||||
value: self.value.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Array {
|
||||
pub elements: Vec<Box<dyn Object>>,
|
||||
}
|
||||
|
||||
impl Object for Array {
|
||||
fn object_type(&self) -> ObjectType {
|
||||
ObjectType::ARRAY
|
||||
}
|
||||
|
||||
fn inspect(&self) -> String {
|
||||
let elements: Vec<String> = self.elements.iter().map(|e| e.inspect()).collect();
|
||||
format!("[{}]", elements.join(", "))
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn Object> {
|
||||
Box::new(Array {
|
||||
elements: self.elements.iter().map(|e| e.clone_box()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub enum HashKey {
|
||||
Integer(i64),
|
||||
Boolean(bool),
|
||||
String(String),
|
||||
}
|
||||
|
||||
pub trait Hashable {
|
||||
fn hash_key(&self) -> HashKey;
|
||||
}
|
||||
|
||||
impl Hashable for Integer {
|
||||
fn hash_key(&self) -> HashKey {
|
||||
HashKey::Integer(self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hashable for Boolean {
|
||||
fn hash_key(&self) -> HashKey {
|
||||
HashKey::Boolean(self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hashable for StringObj {
|
||||
fn hash_key(&self) -> HashKey {
|
||||
HashKey::String(self.value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HashPair {
|
||||
pub key: Box<dyn Object>,
|
||||
pub value: Box<dyn Object>,
|
||||
}
|
||||
|
||||
pub struct Hash {
|
||||
pub pairs: HashMap<HashKey, HashPair>,
|
||||
}
|
||||
|
||||
impl Object for Hash {
|
||||
fn object_type(&self) -> ObjectType {
|
||||
ObjectType::HASH
|
||||
}
|
||||
|
||||
fn inspect(&self) -> String {
|
||||
let mut pairs: Vec<String> = self
|
||||
.pairs
|
||||
.values()
|
||||
.map(|pair| format!("{}: {}", pair.key.inspect(), pair.value.inspect()))
|
||||
.collect();
|
||||
pairs.sort();
|
||||
format!("{{{}}}", pairs.join(", "))
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn Object> {
|
||||
let mut new_pairs = HashMap::new();
|
||||
for (k, v) in &self.pairs {
|
||||
new_pairs.insert(
|
||||
k.clone(),
|
||||
HashPair {
|
||||
key: v.key.clone_box(),
|
||||
value: v.value.clone_box(),
|
||||
},
|
||||
);
|
||||
}
|
||||
Box::new(Hash { pairs: new_pairs })
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub type BuiltinFunction = fn(Vec<Box<dyn Object>>) -> Box<dyn Object>;
|
||||
|
||||
pub struct Builtin {
|
||||
pub func: BuiltinFunction,
|
||||
}
|
||||
|
||||
impl Object for Builtin {
|
||||
fn object_type(&self) -> ObjectType {
|
||||
ObjectType::BUILTIN
|
||||
}
|
||||
|
||||
fn inspect(&self) -> String {
|
||||
"builtin function".to_string()
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn Object> {
|
||||
Box::new(Builtin { func: self.func })
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Environment {
|
||||
store: HashMap<String, Box<dyn Object>>,
|
||||
@@ -175,10 +324,21 @@ pub struct Environment {
|
||||
|
||||
impl Environment {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
let mut env = Self {
|
||||
store: HashMap::new(),
|
||||
outer: None,
|
||||
};
|
||||
env.register_builtins();
|
||||
env
|
||||
}
|
||||
|
||||
fn register_builtins(&mut self) {
|
||||
self.set("len", Box::new(Builtin { func: builtin_len }));
|
||||
self.set("first", Box::new(Builtin { func: builtin_first }));
|
||||
self.set("last", Box::new(Builtin { func: builtin_last }));
|
||||
self.set("rest", Box::new(Builtin { func: builtin_rest }));
|
||||
self.set("push", Box::new(Builtin { func: builtin_push }));
|
||||
self.set("puts", Box::new(Builtin { func: builtin_puts }));
|
||||
}
|
||||
|
||||
pub fn from_outer(outer: Rc<RefCell<Environment>>) -> Self {
|
||||
@@ -202,3 +362,121 @@ impl Environment {
|
||||
self.store.insert(name.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
fn builtin_len(args: Vec<Box<dyn Object>>) -> Box<dyn Object> {
|
||||
if args.len() != 1 {
|
||||
return Box::new(Error {
|
||||
message: format!("wrong number of arguments. got={}, want=1", args.len()),
|
||||
});
|
||||
}
|
||||
if let Some(s) = args[0].as_any().downcast_ref::<StringObj>() {
|
||||
return Box::new(Integer {
|
||||
value: s.value.len() as i64,
|
||||
});
|
||||
}
|
||||
if let Some(arr) = args[0].as_any().downcast_ref::<Array>() {
|
||||
return Box::new(Integer {
|
||||
value: arr.elements.len() as i64,
|
||||
});
|
||||
}
|
||||
Box::new(Error {
|
||||
message: format!("argument to `len` not supported, got {:?}", args[0].object_type()),
|
||||
})
|
||||
}
|
||||
|
||||
fn builtin_first(args: Vec<Box<dyn Object>>) -> Box<dyn Object> {
|
||||
if args.len() != 1 {
|
||||
return Box::new(Error {
|
||||
message: format!("wrong number of arguments. got={}, want=1", args.len()),
|
||||
});
|
||||
}
|
||||
if let Some(arr) = args[0].as_any().downcast_ref::<Array>() {
|
||||
if !arr.elements.is_empty() {
|
||||
return arr.elements[0].clone_box();
|
||||
}
|
||||
return Box::new(Null);
|
||||
}
|
||||
Box::new(Error {
|
||||
message: format!(
|
||||
"argument to `first` must be ARRAY, got {:?}",
|
||||
args[0].object_type()
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn builtin_last(args: Vec<Box<dyn Object>>) -> Box<dyn Object> {
|
||||
if args.len() != 1 {
|
||||
return Box::new(Error {
|
||||
message: format!("wrong number of arguments. got={}, want=1", args.len()),
|
||||
});
|
||||
}
|
||||
if let Some(arr) = args[0].as_any().downcast_ref::<Array>() {
|
||||
if let Some(last) = arr.elements.last() {
|
||||
return last.clone_box();
|
||||
}
|
||||
return Box::new(Null);
|
||||
}
|
||||
Box::new(Error {
|
||||
message: format!(
|
||||
"argument to `last` must be ARRAY, got {:?}",
|
||||
args[0].object_type()
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn builtin_rest(args: Vec<Box<dyn Object>>) -> Box<dyn Object> {
|
||||
if args.len() != 1 {
|
||||
return Box::new(Error {
|
||||
message: format!("wrong number of arguments. got={}, want=1", args.len()),
|
||||
});
|
||||
}
|
||||
if let Some(arr) = args[0].as_any().downcast_ref::<Array>() {
|
||||
if arr.elements.is_empty() {
|
||||
return Box::new(Null);
|
||||
}
|
||||
let mut new_elements = Vec::new();
|
||||
for (i, el) in arr.elements.iter().enumerate() {
|
||||
if i != 0 {
|
||||
new_elements.push(el.clone_box());
|
||||
}
|
||||
}
|
||||
return Box::new(Array {
|
||||
elements: new_elements,
|
||||
});
|
||||
}
|
||||
Box::new(Error {
|
||||
message: format!(
|
||||
"argument to `rest` must be ARRAY, got {:?}",
|
||||
args[0].object_type()
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn builtin_push(args: Vec<Box<dyn Object>>) -> Box<dyn Object> {
|
||||
if args.len() != 2 {
|
||||
return Box::new(Error {
|
||||
message: format!("wrong number of arguments. got={}, want=2", args.len()),
|
||||
});
|
||||
}
|
||||
if let Some(arr) = args[0].as_any().downcast_ref::<Array>() {
|
||||
let mut new_elements: Vec<Box<dyn Object>> =
|
||||
arr.elements.iter().map(|e| e.clone_box()).collect();
|
||||
new_elements.push(args[1].clone_box());
|
||||
return Box::new(Array {
|
||||
elements: new_elements,
|
||||
});
|
||||
}
|
||||
Box::new(Error {
|
||||
message: format!(
|
||||
"argument to `push` must be ARRAY, got {:?}",
|
||||
args[0].object_type()
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn builtin_puts(args: Vec<Box<dyn Object>>) -> Box<dyn Object> {
|
||||
for arg in args {
|
||||
println!("{}", arg.inspect());
|
||||
}
|
||||
Box::new(Null)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,11 @@ pub fn new_parser(l: lexer::Lexer) -> Parser {
|
||||
p.register_prefix(TokenType::Lparen, Parser::parse_grouped_expression);
|
||||
p.register_prefix(TokenType::If, Parser::parse_if_expression);
|
||||
p.register_prefix(TokenType::Function, Parser::parse_function_literal);
|
||||
p.register_prefix(TokenType::String, Parser::parse_string_literal);
|
||||
p.register_prefix(TokenType::Lbracket, Parser::parse_array_literal);
|
||||
p.register_prefix(TokenType::Lbrace, Parser::parse_hash_literal);
|
||||
p.register_infix(TokenType::Lparen, Parser::parse_call_expression);
|
||||
p.register_infix(TokenType::Lbracket, Parser::parse_index_expression);
|
||||
p.next_token();
|
||||
p.next_token();
|
||||
p
|
||||
@@ -96,6 +100,7 @@ impl Parser {
|
||||
TokenType::Plus | TokenType::Minus => Precedence::Sum,
|
||||
TokenType::Slash | TokenType::Asterisk => Precedence::Product,
|
||||
TokenType::Lparen => Precedence::Call,
|
||||
TokenType::Lbracket => Precedence::Index,
|
||||
_ => Precedence::Lowest,
|
||||
}
|
||||
}
|
||||
@@ -107,6 +112,7 @@ impl Parser {
|
||||
TokenType::Plus | TokenType::Minus => Precedence::Sum,
|
||||
TokenType::Slash | TokenType::Asterisk => Precedence::Product,
|
||||
TokenType::Lparen => Precedence::Call,
|
||||
TokenType::Lbracket => Precedence::Index,
|
||||
_ => Precedence::Lowest,
|
||||
}
|
||||
}
|
||||
@@ -304,6 +310,83 @@ impl Parser {
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_string_literal(&mut self) -> Option<ast::Expression> {
|
||||
Some(ast::Expression::StringLiteral(ast::StringLiteral {
|
||||
token: self.cur_token.clone(),
|
||||
value: self.cur_token.literal.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_array_literal(&mut self) -> Option<ast::Expression> {
|
||||
let token = self.cur_token.clone();
|
||||
let elements = self.parse_expression_list(TokenType::Rbracket)?;
|
||||
Some(ast::Expression::ArrayLiteral(ast::ArrayLiteral {
|
||||
token,
|
||||
elements,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_hash_literal(&mut self) -> Option<ast::Expression> {
|
||||
let token = self.cur_token.clone();
|
||||
let mut pairs = Vec::new();
|
||||
|
||||
while !self.peek_token_is(TokenType::Rbrace) {
|
||||
self.next_token();
|
||||
let key = self.parse_expression(Precedence::Lowest)?;
|
||||
if !self.expect_peek(TokenType::Colon) {
|
||||
return None;
|
||||
}
|
||||
self.next_token();
|
||||
let value = self.parse_expression(Precedence::Lowest)?;
|
||||
pairs.push((key, value));
|
||||
if !self.peek_token_is(TokenType::Rbrace) && !self.expect_peek(TokenType::Comma) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.expect_peek(TokenType::Rbrace) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ast::Expression::HashLiteral(ast::HashLiteral { token, pairs }))
|
||||
}
|
||||
|
||||
fn parse_expression_list(&mut self, end: TokenType) -> Option<Vec<ast::Expression>> {
|
||||
let mut list = Vec::new();
|
||||
if self.peek_token_is(end) {
|
||||
self.next_token();
|
||||
return Some(list);
|
||||
}
|
||||
self.next_token();
|
||||
list.push(self.parse_expression(Precedence::Lowest)?);
|
||||
while self.peek_token_is(TokenType::Comma) {
|
||||
self.next_token();
|
||||
self.next_token();
|
||||
list.push(self.parse_expression(Precedence::Lowest)?);
|
||||
}
|
||||
if !self.expect_peek(end) {
|
||||
return None;
|
||||
}
|
||||
Some(list)
|
||||
}
|
||||
|
||||
fn parse_index_expression(
|
||||
&mut self,
|
||||
left: ast::Expression,
|
||||
) -> Option<ast::Expression> {
|
||||
let token = self.cur_token.clone();
|
||||
self.next_token();
|
||||
let index = self.parse_expression(Precedence::Lowest)?;
|
||||
if !self.expect_peek(TokenType::Rbracket) {
|
||||
return None;
|
||||
}
|
||||
Some(ast::Expression::IndexExpression(ast::IndexExpression {
|
||||
token,
|
||||
left: Box::new(left),
|
||||
index: Box::new(index),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_grouped_expression(&mut self) -> Option<ast::Expression> {
|
||||
self.next_token();
|
||||
let exp = self.parse_expression(Precedence::Lowest)?;
|
||||
@@ -464,4 +547,5 @@ pub enum Precedence {
|
||||
Product,
|
||||
Prefix,
|
||||
Call,
|
||||
Index,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ pub enum TokenType {
|
||||
Eof,
|
||||
Ident,
|
||||
Int,
|
||||
String,
|
||||
Assign,
|
||||
Plus,
|
||||
Minus,
|
||||
@@ -17,10 +18,13 @@ pub enum TokenType {
|
||||
Gt,
|
||||
Comma,
|
||||
Semicolon,
|
||||
Colon,
|
||||
Lparen,
|
||||
Rparen,
|
||||
Lbrace,
|
||||
Rbrace,
|
||||
Lbracket,
|
||||
Rbracket,
|
||||
Function,
|
||||
Let,
|
||||
True,
|
||||
@@ -39,6 +43,7 @@ impl fmt::Display for TokenType {
|
||||
TokenType::Eof => write!(f, "EOF"),
|
||||
TokenType::Ident => write!(f, "IDENT"),
|
||||
TokenType::Int => write!(f, "INT"),
|
||||
TokenType::String => write!(f, "STRING"),
|
||||
TokenType::Assign => write!(f, "="),
|
||||
TokenType::Plus => write!(f, "+"),
|
||||
TokenType::Minus => write!(f, "-"),
|
||||
@@ -49,10 +54,13 @@ impl fmt::Display for TokenType {
|
||||
TokenType::Gt => write!(f, ">"),
|
||||
TokenType::Comma => write!(f, ","),
|
||||
TokenType::Semicolon => write!(f, ";"),
|
||||
TokenType::Colon => write!(f, ":"),
|
||||
TokenType::Lparen => write!(f, "("),
|
||||
TokenType::Rparen => write!(f, ")"),
|
||||
TokenType::Lbrace => write!(f, "{{"),
|
||||
TokenType::Rbrace => write!(f, "}}"),
|
||||
TokenType::Lbracket => write!(f, "["),
|
||||
TokenType::Rbracket => write!(f, "]"),
|
||||
TokenType::Function => write!(f, "FUNCTION"),
|
||||
TokenType::Let => write!(f, "LET"),
|
||||
TokenType::True => write!(f, "TRUE"),
|
||||
|
||||
Reference in New Issue
Block a user