From 17110bf563c2f57ab4e9e25e977b74df06b8c1ab Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 27 Feb 2023 11:55:17 -0600 Subject: Functions beginnings --- Cargo.lock | 66 ++++++++++++++++++++++++ Cargo.toml | 6 +++ docs/precedence.md | 1 + examples/test.sloth | 16 ++++++ src/ast/mod.rs | 7 +-- src/ast/parser.rs | 40 +++++++++++---- src/interpreter.rs | 47 ++++++++++++++--- src/lexer.rs | 22 ++++---- src/main.rs | 143 +++++++++++++++++++++++++++++++++++++++++----------- 9 files changed, 290 insertions(+), 58 deletions(-) create mode 100644 examples/test.sloth diff --git a/Cargo.lock b/Cargo.lock index 9c9041b..9109fd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,29 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "itertools" version = "0.10.5" @@ -17,9 +34,58 @@ dependencies = [ "either", ] +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "sloth" version = "0.1.0" dependencies = [ "itertools", + "rand", ] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml index 6ce9f7a..ec54670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,9 @@ edition = "2021" [dependencies] itertools = "0.10.5" +rand = "0.8.5" + +[profile.release] +strip = "debuginfo" +lto = "thin" +opt-level = "z" diff --git a/docs/precedence.md b/docs/precedence.md index 417fa45..32cc5de 100644 --- a/docs/precedence.md +++ b/docs/precedence.md @@ -5,6 +5,7 @@ Operating precedence in sloth from highest to lowest. | parentheses | () | Left | | member access | . ! !! ?. | Left | | defaulting | ?: | Right | +| function call | () | Left | | unary | ! + - | Right | | multiplicative | \* / % | Left | | additive | + - | Left | diff --git a/examples/test.sloth b/examples/test.sloth new file mode 100644 index 0000000..7104593 --- /dev/null +++ b/examples/test.sloth @@ -0,0 +1,16 @@ +print("Pick a number between 1 and 5: "); + +val human = parse_int(readln()); +val computer = random(1, 5); + +if human == computer { + println("You guessed the same number as me!"); +} + +if human > computer { + println("Your guess was too high."); +} + +if human < computer { + println("Your guess was too low."); +} 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), + Call { + ident: String, + arguments: Vec, + }, Binary { operator: TokenType, lhs: Box, 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::::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>, memory: HashMap, } @@ -54,11 +57,19 @@ impl AstVisitor 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 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); + 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 "); + 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::(), + _ => panic!("Invalid usage of 'parse_int' function"), + } + .expect("Provided string was not an intenger"); + + Value::Number(result) + })), + ); + interpreter.interpret(&ast); } -- cgit v1.2.3