aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock66
-rw-r--r--Cargo.toml6
-rw-r--r--docs/precedence.md1
-rw-r--r--examples/test.sloth16
-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
9 files changed, 290 insertions, 58 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9c9041b..9109fd7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,12 +3,29 @@
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -18,8 +35,57 @@ dependencies = [
]
[[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<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);
}