diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ast/mod.rs | 7 | ||||
| -rw-r--r-- | src/ast/parser.rs | 40 | ||||
| -rw-r--r-- | src/interpreter.rs | 47 | ||||
| -rw-r--r-- | src/lexer.rs | 22 | ||||
| -rw-r--r-- | src/main.rs | 143 |
5 files changed, 201 insertions, 58 deletions
diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 04b1d26..d006737 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -39,9 +39,6 @@ pub enum Stmt { Return { value: Expr, }, - Print { - value: Expr, - }, } #[derive(Debug, Eq, PartialEq)] @@ -55,6 +52,10 @@ pub enum Expr { Literal(Literal), Variable(String), Grouping(Box<Expr>), + Call { + ident: String, + arguments: Vec<Expr>, + }, Binary { operator: TokenType, lhs: Box<Expr>, diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 64ed352..842ad09 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -102,10 +102,6 @@ impl<'a> AstParser<'a> { return Stmt::Block(self.block()); } - if self.advance_if_eq(&TokenType::Print) { - return self.print_statement(); - } - if self.advance_if_eq(&TokenType::Var) { return self.var_statement(); } @@ -126,12 +122,6 @@ impl<'a> AstParser<'a> { self.expression_statement() } - fn print_statement(&mut self) -> Stmt { - let value = self.expression(); - self.consume(TokenType::SemiColon, "Expected ';' at end of statement"); - Stmt::Print { value } - } - fn var_statement(&mut self) -> Stmt { let TokenType::Identifier(ident) = self.peek().tt.clone() else { panic!("Identifier expected after 'var'"); @@ -240,7 +230,35 @@ impl<'a> AstParser<'a> { }; } - self.primary() + self.call() + } + + fn call(&mut self) -> Expr { + let mut expr = self.primary(); + + if self.advance_if_eq(&TokenType::LeftParen) { + let mut arguments = Vec::<Expr>::new(); + + if self.peek().tt != TokenType::RightParen { + loop { + arguments.push(self.expression()); + if !self.advance_if_eq(&TokenType::Comma) { + break; + } + } + } + + self.consume( + TokenType::RightParen, + "Expected ')' to close off function call", + ); + + let Expr::Variable(ident) = expr else { panic!("uh oh spaghettio"); }; + + expr = Expr::Call { ident, arguments } + } + + expr } fn primary(&mut self) -> Expr { diff --git a/src/interpreter.rs b/src/interpreter.rs index aa9d441..b548d9e 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,11 +1,14 @@ use std::collections::HashMap; -use std::fmt::Display; +use std::fmt::{Debug, Display}; + +use itertools::Itertools; use crate::ast::{AstVisitor, Expr, Stmt}; use crate::lexer::{Literal, TokenType}; #[derive(Default)] pub struct AstInterpreter { + pub callables: HashMap<String, Box<dyn SlothCallable>>, memory: HashMap<String, (Value, bool)>, } @@ -54,11 +57,19 @@ impl AstVisitor<Value> for AstInterpreter { binding, range, body, - } => todo!(), - Stmt::Return { value } => todo!(), - Stmt::Print { value } => { - println!("{}", self.visit_expr(value)); + } => { + let Value::Number(lower_range) = self.visit_expr(&range.0) else { panic!("Lower range must be number") }; + let Value::Number(upper_range) = self.visit_expr(&range.1) else { panic!("Upper range must be number") }; + + for i in lower_range..upper_range { + self.memory + .insert(binding.clone(), (Value::Number(i), false)); + self.interpret(body); + } + + self.memory.remove(binding); } + Stmt::Return { value } => todo!(), }; // FIXME: Honestly should probably abandon this "visitor" pattern. 2 functions @@ -142,6 +153,16 @@ impl AstVisitor<Value> for AstInterpreter { _ => panic!(), } } + Expr::Call { ident, arguments } => { + let argument_values = arguments.iter().map(|it| self.visit_expr(it)).collect_vec(); + let Some(callable) = self.callables.remove(ident) else { + panic!("Unkown callable '{ident}'"); + }; + + let result = callable.call(self, &argument_values); + self.callables.insert(ident.clone(), callable); + result + } _ => unimplemented!("{:?}", expr), } } @@ -155,7 +176,7 @@ impl AstInterpreter { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] pub enum Value { Number(i32), String(String), @@ -163,6 +184,20 @@ pub enum Value { Nil, } +pub trait SlothCallable { + fn call(&self, interpreter: &mut AstInterpreter, args: &[Value]) -> Value; +} + +pub struct InternalFunction<'a>(pub &'a dyn Fn(&[Value]) -> Value); + +impl<'a> SlothCallable for InternalFunction<'a> { + fn call(&self, interpreter: &mut AstInterpreter, args: &[Value]) -> Value { + self.0(args) + } +} + +// pub struct SlothFunction(Vec<Stmt>); + impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/lexer.rs b/src/lexer.rs index 2155e31..2d65afc 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -70,8 +70,6 @@ pub enum TokenType { Loop, Break, Continue, - - Print, // TODO: Change to std library function } #[derive(Debug, Clone, Eq, PartialEq)] @@ -251,6 +249,7 @@ impl<'a> Iterator for Lexer<'a> { match self.advance().unwrap() { '\\' => value.push('\\'), '"' => value.push('"'), + 'n' => value.push('\n'), _ => panic!(), } continue; @@ -284,7 +283,6 @@ impl<'a> Iterator for Lexer<'a> { "loop" => TokenType::Loop, "break" => TokenType::Break, "continue" => TokenType::Continue, - "print" => TokenType::Print, _ => TokenType::Identifier(value), } } @@ -323,16 +321,16 @@ mod tests { val variable = 5; if variable >= 7 { - print "Hello World"; + print("Hello World"); } if variable < 52 { variable += 1; - print "Hello ${variable}"; + print("Hello ${variable}"); } for person in ["Cody", "Johnny"] { - print "Hello ${person}"; + print("Hello ${person}"); } "#; @@ -351,8 +349,10 @@ for person in ["Cody", "Johnny"] { TokenType::GtEq, TokenType::Literal(Literal::Number(7)), TokenType::LeftBrace, - TokenType::Print, + TokenType::Identifier("print".to_owned()), + TokenType::LeftParen, TokenType::Literal(Literal::String("Hello World".to_owned())), + TokenType::RightParen, TokenType::SemiColon, TokenType::RightBrace, // 2nd block @@ -365,8 +365,10 @@ for person in ["Cody", "Johnny"] { TokenType::PlusEq, TokenType::Literal(Literal::Number(1)), TokenType::SemiColon, - TokenType::Print, + TokenType::Identifier("print".to_owned()), + TokenType::LeftParen, TokenType::Literal(Literal::String("Hello ${variable}".to_owned())), + TokenType::RightParen, TokenType::SemiColon, TokenType::RightBrace, // 3rd block @@ -379,8 +381,10 @@ for person in ["Cody", "Johnny"] { TokenType::Literal(Literal::String("Johnny".to_owned())), TokenType::RightBracket, TokenType::LeftBrace, - TokenType::Print, + TokenType::Identifier("print".to_owned()), + TokenType::LeftParen, TokenType::Literal(Literal::String("Hello ${person}".to_owned())), + TokenType::RightParen, TokenType::SemiColon, TokenType::RightBrace, ]; diff --git a/src/main.rs b/src/main.rs index 4a78e9e..52fd2e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,52 +12,137 @@ pub mod ast; pub mod interpreter; pub mod lexer; +use std::io::{self, BufRead, Read, Write}; +use std::{env, fs}; + use itertools::Itertools; +use rand::Rng; use crate::ast::parser::AstParser; use crate::ast::AstVisitor; -use crate::interpreter::AstInterpreter; +use crate::interpreter::{AstInterpreter, InternalFunction, Value}; use crate::lexer::Lexer; -const SOURCE: &str = r#" - -val variable = 5 + 6 * 2; - -if variable == 17 { - print "Hello World"; -} +fn main() { + let args = env::args().collect_vec(); -fn fib(n: i32) -> i32 { - if n == 0 || n == 1 { - return n; + if args.len() < 2 { + println!("Sloth programming language interpreter\n"); + println!("Usage: sloth <file>"); + return; } - var grandparent = 0; - var parent = 1; - var me = 0; - - for i in 0..n-1 { - me = parent + grandparent; - grandparent = parent; - parent = me; - } + let source_path = &args[1]; + let Ok(source) = fs::read_to_string(source_path) else { + println!("Error while reading '{source_path}'"); + return; + }; - return me; -} - -print fib(5); - -"#; - -fn main() { - let lexer = Lexer::new("for x in 0..5 {}"); + let lexer = Lexer::new(&source); let tokens = lexer.collect_vec(); let mut parser = AstParser::new(tokens); let ast = parser.parse(); + println!("--- Abstract Syntax Tree ---"); println!("{ast:#?}"); + println!("--- Program Output ---"); let mut interpreter = AstInterpreter::default(); + + // Defining some builtin callables for our interpreter + interpreter.callables.insert( + "print".to_owned(), + Box::new(InternalFunction(&|args| { + use std::fmt::Write; + + let mut buffer = String::new(); + for arg in args { + write!(&mut buffer, "{}", arg); + } + + let mut stdout = io::stdout(); + stdout.lock().write_all(buffer.as_bytes()); + stdout.flush(); + + Value::Nil + })), + ); + + interpreter.callables.insert( + "println".to_owned(), + Box::new(InternalFunction(&|args| { + use std::fmt::Write; + + let mut buffer = String::new(); + for arg in args { + write!(&mut buffer, "{}", arg); + } + writeln!(&mut buffer); + + let mut stdout = io::stdout(); + stdout.lock().write_all(buffer.as_bytes()); + stdout.flush(); + + Value::Nil + })), + ); + + interpreter.callables.insert( + "readln".to_owned(), + Box::new(InternalFunction(&|args| { + let stdin = io::stdin(); + let mut line = String::new(); + stdin + .lock() + .read_line(&mut line) + .expect("Failed to read line from stdin"); + line.pop(); + + Value::String(line) + })), + ); + + interpreter.callables.insert( + "random".to_owned(), + Box::new(InternalFunction(&|args| { + let result = match args { + [] => rand::thread_rng().gen_range(1..=100), + [Value::Number(max)] => rand::thread_rng().gen_range(0..=*max), + [Value::Number(min), Value::Number(max)] => { + rand::thread_rng().gen_range(*min..=*max) + } + _ => panic!("Invalid usage of 'random' function"), + }; + + Value::Number(result) + })), + ); + + interpreter.callables.insert( + "len".to_owned(), + Box::new(InternalFunction(&|args| { + let result = match &args[0] { + Value::String(value) => value.len() as i32, + _ => panic!("Invalid usage of 'len' function"), + }; + + Value::Number(result) + })), + ); + + interpreter.callables.insert( + "parse_int".to_owned(), + Box::new(InternalFunction(&|args| { + let result = match &args[0] { + Value::String(value) => value.parse::<i32>(), + _ => panic!("Invalid usage of 'parse_int' function"), + } + .expect("Provided string was not an intenger"); + + Value::Number(result) + })), + ); + interpreter.interpret(&ast); } |
