aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ast/mod.rs7
-rw-r--r--src/ast/parser.rs40
-rw-r--r--src/interpreter.rs47
-rw-r--r--src/lexer.rs22
-rw-r--r--src/main.rs143
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);
}