added strings! applied clippy recs
This commit is contained in:
parent
ffe56c86ae
commit
6607d1db02
|
@ -54,6 +54,7 @@ impl Environment {
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Object {
|
pub enum Object {
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
|
String(String),
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
ReturnValue(Box<Object>),
|
ReturnValue(Box<Object>),
|
||||||
Error(String),
|
Error(String),
|
||||||
|
@ -69,6 +70,7 @@ impl Object {
|
||||||
pub fn inspect(&self) -> String {
|
pub fn inspect(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Object::Integer(v) => v.to_string(),
|
Object::Integer(v) => v.to_string(),
|
||||||
|
Object::String(s) => s.to_string(),
|
||||||
Object::Boolean(v) => v.to_string(),
|
Object::Boolean(v) => v.to_string(),
|
||||||
Object::ReturnValue(ret) => ret.inspect(),
|
Object::ReturnValue(ret) => ret.inspect(),
|
||||||
Object::Error(s) => s.to_string(),
|
Object::Error(s) => s.to_string(),
|
||||||
|
@ -79,7 +81,7 @@ impl Object {
|
||||||
out.write_fmt(format_args!(
|
out.write_fmt(format_args!(
|
||||||
"fn({}) {{ {} }}",
|
"fn({}) {{ {} }}",
|
||||||
s.parameters.iter().map(|x| x.to_string()).join(", "),
|
s.parameters.iter().map(|x| x.to_string()).join(", "),
|
||||||
s.body.to_string()
|
s.body
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -93,6 +95,7 @@ impl Display for Object {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
Object::Integer(_) => "INTEGER",
|
Object::Integer(_) => "INTEGER",
|
||||||
|
Object::String(_) => "STRING",
|
||||||
Object::Boolean(_) => "BOOLEAN",
|
Object::Boolean(_) => "BOOLEAN",
|
||||||
Object::ReturnValue(_) => "RETURN_VALUE",
|
Object::ReturnValue(_) => "RETURN_VALUE",
|
||||||
Object::Error(_) => "ERROR",
|
Object::Error(_) => "ERROR",
|
||||||
|
@ -195,6 +198,23 @@ mod tests {
|
||||||
run_test_cases(&test_cases);
|
run_test_cases(&test_cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_string_literal_expression() {
|
||||||
|
let test_cases = [("\"Hello \"", Some(Object::String("Hello ".to_owned())))];
|
||||||
|
|
||||||
|
run_test_cases(&test_cases);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_string_concatenation() {
|
||||||
|
let test_cases = [(
|
||||||
|
"\"Hello \" + \"World\"",
|
||||||
|
Some(Object::String("Hello World".to_owned())),
|
||||||
|
)];
|
||||||
|
|
||||||
|
run_test_cases(&test_cases);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn eval_if_else_expression() {
|
fn eval_if_else_expression() {
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -280,6 +300,10 @@ mod tests {
|
||||||
"foobar",
|
"foobar",
|
||||||
Some(Object::Error("identifier not found: foobar".into())),
|
Some(Object::Error("identifier not found: foobar".into())),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"\"Hello\" - \"World\"",
|
||||||
|
Some(Object::Error("unknown operator: STRING - STRING".into())),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
run_test_cases(&test_cases);
|
run_test_cases(&test_cases);
|
||||||
|
@ -304,7 +328,7 @@ mod tests {
|
||||||
fn test_function_object() {
|
fn test_function_object() {
|
||||||
let test_case = "fn(x) { x + 2;};";
|
let test_case = "fn(x) { x + 2;};";
|
||||||
|
|
||||||
let lexer = Lexer::new(&test_case);
|
let lexer = Lexer::new(test_case);
|
||||||
let mut parser = Parser::new(lexer);
|
let mut parser = Parser::new(lexer);
|
||||||
let program = parser.parse_program();
|
let program = parser.parse_program();
|
||||||
assert!(program.is_some());
|
assert!(program.is_some());
|
||||||
|
@ -373,7 +397,7 @@ mod tests {
|
||||||
// even though it is never used.
|
// even though it is never used.
|
||||||
let test_cases = [(
|
let test_cases = [(
|
||||||
"let counter = fn(x) {
|
"let counter = fn(x) {
|
||||||
if (x > 100 ) {
|
if (x > 30 ) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
let foobar = 9999;
|
let foobar = 9999;
|
||||||
|
|
|
@ -46,6 +46,7 @@ impl Evaluator for TreeWalker {
|
||||||
Node::Expression(expr) => match expr {
|
Node::Expression(expr) => match expr {
|
||||||
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::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)?;
|
||||||
|
@ -56,13 +57,11 @@ impl Evaluator for TreeWalker {
|
||||||
let right = self.eval(Node::Expression(*ie.right), env)?;
|
let right = self.eval(Node::Expression(*ie.right), env)?;
|
||||||
self.eval_infix_expression(left, ie.operator, right)
|
self.eval_infix_expression(left, ie.operator, right)
|
||||||
}
|
}
|
||||||
Expression::FunctionExpression(fnl) => {
|
Expression::FunctionExpression(fnl) => Some(Object::Function(Function {
|
||||||
return Some(Object::Function(Function {
|
|
||||||
body: fnl.body,
|
body: fnl.body,
|
||||||
parameters: fnl.parameters,
|
parameters: fnl.parameters,
|
||||||
env: env,
|
env,
|
||||||
}));
|
})),
|
||||||
}
|
|
||||||
Expression::CallExpression(v) => {
|
Expression::CallExpression(v) => {
|
||||||
let function = self.eval(Node::Expression(*v.function), env.clone())?;
|
let function = self.eval(Node::Expression(*v.function), env.clone())?;
|
||||||
// Resolve function arguments and update the environment
|
// Resolve function arguments and update the environment
|
||||||
|
@ -179,6 +178,9 @@ impl TreeWalker {
|
||||||
TokenType::LessThan => Object::Boolean(l < r),
|
TokenType::LessThan => Object::Boolean(l < r),
|
||||||
_ => Object::Error(format!("unknown operator: {} {} {}", l, operator, r)),
|
_ => Object::Error(format!("unknown operator: {} {} {}", l, operator, r)),
|
||||||
},
|
},
|
||||||
|
(Object::String(l), Object::String(r)) if operator == TokenType::Plus => {
|
||||||
|
Object::String(l.to_owned() + &r)
|
||||||
|
}
|
||||||
(o1, o2) if o1.to_string() != o2.to_string() => {
|
(o1, o2) if o1.to_string() != o2.to_string() => {
|
||||||
Object::Error(format!("type mismatch: {} {} {}", o1, operator, o2))
|
Object::Error(format!("type mismatch: {} {} {}", o1, operator, o2))
|
||||||
}
|
}
|
||||||
|
|
34
src/lexer.rs
34
src/lexer.rs
|
@ -31,6 +31,7 @@ pub enum TokenType {
|
||||||
// by other variants of this enum.
|
// by other variants of this enum.
|
||||||
Ident,
|
Ident,
|
||||||
Int,
|
Int,
|
||||||
|
String,
|
||||||
// Operators
|
// Operators
|
||||||
Assign,
|
Assign,
|
||||||
Plus,
|
Plus,
|
||||||
|
@ -91,6 +92,7 @@ impl Display for TokenType {
|
||||||
TokenType::Illegal => "illegal",
|
TokenType::Illegal => "illegal",
|
||||||
TokenType::Ident => "ident",
|
TokenType::Ident => "ident",
|
||||||
TokenType::Int => "int",
|
TokenType::Int => "int",
|
||||||
|
TokenType::String => "string",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,6 +201,19 @@ impl<'a> Lexer<'a> {
|
||||||
}
|
}
|
||||||
number.into_iter().collect()
|
number.into_iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_string(&mut self) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
while let Some(c) = self.read_char() {
|
||||||
|
if c == '"' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for Lexer<'a> {
|
impl<'a> Iterator for Lexer<'a> {
|
||||||
|
@ -210,10 +225,7 @@ impl<'a> Iterator for Lexer<'a> {
|
||||||
|
|
||||||
match ch {
|
match ch {
|
||||||
Some('=') => {
|
Some('=') => {
|
||||||
let is_e = match self.input.peek() {
|
let is_e = matches!(self.input.peek(), Some(v) if *v == '=');
|
||||||
Some(v) if *v == '=' => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
if is_e {
|
if is_e {
|
||||||
self.read_char();
|
self.read_char();
|
||||||
Some(token!(TokenType::Equals))
|
Some(token!(TokenType::Equals))
|
||||||
|
@ -232,10 +244,7 @@ impl<'a> Iterator for Lexer<'a> {
|
||||||
Some('{') => Some(token!(TokenType::LBrace)),
|
Some('{') => Some(token!(TokenType::LBrace)),
|
||||||
Some('}') => Some(token!(TokenType::RBrace)),
|
Some('}') => Some(token!(TokenType::RBrace)),
|
||||||
Some('!') => {
|
Some('!') => {
|
||||||
let is_ne = match self.input.peek() {
|
let is_ne = matches!(self.input.peek(), Some(v) if *v == '=');
|
||||||
Some(v) if *v == '=' => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
if is_ne {
|
if is_ne {
|
||||||
self.read_char();
|
self.read_char();
|
||||||
Some(token!(TokenType::NotEquals))
|
Some(token!(TokenType::NotEquals))
|
||||||
|
@ -245,6 +254,10 @@ impl<'a> Iterator for Lexer<'a> {
|
||||||
}
|
}
|
||||||
Some('>') => Some(token!(TokenType::GreaterThan)),
|
Some('>') => Some(token!(TokenType::GreaterThan)),
|
||||||
Some('<') => Some(token!(TokenType::LessThan)),
|
Some('<') => Some(token!(TokenType::LessThan)),
|
||||||
|
Some('"') => {
|
||||||
|
let str = self.read_string();
|
||||||
|
Some(token!(TokenType::String, &str))
|
||||||
|
}
|
||||||
Some(ch) if is_letter(ch) => {
|
Some(ch) if is_letter(ch) => {
|
||||||
let ident = self.read_identifier(ch);
|
let ident = self.read_identifier(ch);
|
||||||
Some(lookup_ident(&ident))
|
Some(lookup_ident(&ident))
|
||||||
|
@ -364,6 +377,9 @@ mod tests {
|
||||||
10 == 10;
|
10 == 10;
|
||||||
9 != 10;
|
9 != 10;
|
||||||
|
|
||||||
|
\"foobar\"
|
||||||
|
\"foo bar\"
|
||||||
|
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
.collect::<Vec<Token>>(),
|
.collect::<Vec<Token>>(),
|
||||||
|
@ -415,6 +431,8 @@ mod tests {
|
||||||
token!(TokenType::NotEquals),
|
token!(TokenType::NotEquals),
|
||||||
token!(TokenType::Int, "10"),
|
token!(TokenType::Int, "10"),
|
||||||
token!(TokenType::Semicolon),
|
token!(TokenType::Semicolon),
|
||||||
|
token!(TokenType::String, "foobar"),
|
||||||
|
token!(TokenType::String, "foo bar"),
|
||||||
token!(TokenType::EOF),
|
token!(TokenType::EOF),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![feature(assert_matches)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ impl LetStatement {
|
||||||
|
|
||||||
impl Display for LetStatement {
|
impl Display for LetStatement {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
let mut out = format!("{} {} = ", TokenType::Let.to_string(), self.name.value);
|
let mut out = format!("{} {} = ", TokenType::Let, self.name.value);
|
||||||
|
|
||||||
if let Some(v) = &self.value {
|
if let Some(v) = &self.value {
|
||||||
let a: String = v.into();
|
let a: String = v.into();
|
||||||
|
@ -188,6 +188,7 @@ pub enum ExpressionPriority {
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Identifier(Identifier),
|
Identifier(Identifier),
|
||||||
IntegerLiteral(IntegerLiteral),
|
IntegerLiteral(IntegerLiteral),
|
||||||
|
StringLiteral(StringLiteral),
|
||||||
PrefixExpression(PrefixExpression),
|
PrefixExpression(PrefixExpression),
|
||||||
InfixExpression(InfixExpression),
|
InfixExpression(InfixExpression),
|
||||||
BooleanExpression(BooleanExpression),
|
BooleanExpression(BooleanExpression),
|
||||||
|
@ -242,6 +243,7 @@ impl Display for Expression {
|
||||||
let value = match self {
|
let value = match self {
|
||||||
Expression::Identifier(v) => v.to_string(),
|
Expression::Identifier(v) => v.to_string(),
|
||||||
Expression::IntegerLiteral(v) => v.value.to_string(),
|
Expression::IntegerLiteral(v) => v.value.to_string(),
|
||||||
|
Expression::StringLiteral(v) => v.value.to_string(),
|
||||||
Expression::PrefixExpression(v) => v.to_string(),
|
Expression::PrefixExpression(v) => v.to_string(),
|
||||||
Expression::InfixExpression(v) => v.to_string(),
|
Expression::InfixExpression(v) => v.to_string(),
|
||||||
Expression::BooleanExpression(v) => v.to_string(),
|
Expression::BooleanExpression(v) => v.to_string(),
|
||||||
|
@ -313,6 +315,24 @@ impl IntegerLiteral {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct StringLiteral {
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringLiteral {
|
||||||
|
pub fn new(v: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
value: v.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn parse(_parser: &mut Parser, token: Token) -> Option<Expression> {
|
||||||
|
Some(Expression::StringLiteral(StringLiteral::new(
|
||||||
|
&token.literal?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct PrefixExpression {
|
pub struct PrefixExpression {
|
||||||
pub operator: TokenType,
|
pub operator: TokenType,
|
||||||
|
@ -339,11 +359,7 @@ impl PrefixExpression {
|
||||||
|
|
||||||
impl Display for PrefixExpression {
|
impl Display for PrefixExpression {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!("({}{})", self.operator, self.right))
|
||||||
"({}{})",
|
|
||||||
self.operator,
|
|
||||||
self.right.to_string()
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,9 +392,7 @@ impl Display for InfixExpression {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!(
|
||||||
"({} {} {})",
|
"({} {} {})",
|
||||||
self.left.to_string(),
|
self.left, self.operator, self.right,
|
||||||
self.operator,
|
|
||||||
self.right.to_string(),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -449,13 +463,9 @@ impl IfExpression {
|
||||||
|
|
||||||
impl Display for IfExpression {
|
impl Display for IfExpression {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
let mut out = format!(
|
let mut out = format!("if {} {{ {} }}", self.condition, self.consequence);
|
||||||
"if {} {{ {} }}",
|
|
||||||
self.condition.to_string(),
|
|
||||||
self.consequence.to_string()
|
|
||||||
);
|
|
||||||
if let Some(alternative) = &self.alternative {
|
if let Some(alternative) = &self.alternative {
|
||||||
out += &format!(" else {{ {} }}", alternative.to_string());
|
out += &format!(" else {{ {} }}", alternative);
|
||||||
}
|
}
|
||||||
f.write_str(&out)
|
f.write_str(&out)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ impl<'a> Parser<'a> {
|
||||||
infix_parse_fns: HashMap::new(),
|
infix_parse_fns: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
parser.register_prefix(TokenType::String, StringLiteral::parse);
|
||||||
parser.register_prefix(TokenType::Ident, Identifier::parse);
|
parser.register_prefix(TokenType::Ident, Identifier::parse);
|
||||||
parser.register_prefix(TokenType::Int, IntegerLiteral::parse);
|
parser.register_prefix(TokenType::Int, IntegerLiteral::parse);
|
||||||
parser.register_prefix(TokenType::Bang, PrefixExpression::parse);
|
parser.register_prefix(TokenType::Bang, PrefixExpression::parse);
|
||||||
|
@ -101,10 +102,7 @@ impl<'a> Parser<'a> {
|
||||||
if self.peek_token_is(token) {
|
if self.peek_token_is(token) {
|
||||||
self.lexer.next()
|
self.lexer.next()
|
||||||
} else {
|
} else {
|
||||||
let got_token = match self.lexer.peek() {
|
let got_token = self.lexer.peek().map(|v| v.name);
|
||||||
Some(v) => Some(v.name),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
self.peek_error(token, got_token);
|
self.peek_error(token, got_token);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -113,10 +111,7 @@ impl<'a> Parser<'a> {
|
||||||
fn peek_error(&mut self, et: TokenType, gt: Option<TokenType>) {
|
fn peek_error(&mut self, et: TokenType, gt: Option<TokenType>) {
|
||||||
let msg = match gt {
|
let msg = match gt {
|
||||||
Some(v) => format!("expected next token to be {:?}, Got {:?} instead", et, v),
|
Some(v) => format!("expected next token to be {:?}, Got {:?} instead", et, v),
|
||||||
None => format!(
|
None => format!("expected next token to be {}, Got None instead", et),
|
||||||
"expected next token to be {}, Got None instead",
|
|
||||||
et.to_string()
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
self.errors.push(Error { reason: msg });
|
self.errors.push(Error { reason: msg });
|
||||||
}
|
}
|
||||||
|
@ -131,7 +126,7 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
fn no_prefix_parse_fn_error(&mut self, token: TokenType) {
|
fn no_prefix_parse_fn_error(&mut self, token: TokenType) {
|
||||||
self.errors.push(Error {
|
self.errors.push(Error {
|
||||||
reason: format!("no prefix parse function for {} found", token.to_string()),
|
reason: format!("no prefix parse function for {} found", token),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,6 +283,24 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_literal_expression() {
|
||||||
|
let lexer = Lexer::new("\"hello world\";");
|
||||||
|
let mut parser = Parser::new(lexer);
|
||||||
|
let program = parser.parse_program();
|
||||||
|
check_parser_errors(&parser);
|
||||||
|
assert_eq!(parser.errors.len(), 0);
|
||||||
|
assert!(program.is_some());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
program.unwrap().statements,
|
||||||
|
vec![Statement::ExpressionStatement(ExpressionStatement::new(
|
||||||
|
token!(TokenType::String, "hello world"),
|
||||||
|
Expression::StringLiteral(StringLiteral::new("hello world"))
|
||||||
|
))]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prefix_expressions() {
|
fn prefix_expressions() {
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
|
Loading…
Reference in New Issue
Block a user